summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel@debian.org>2024-11-21 15:51:37 +0100
committerDaniel Baumann <daniel@debian.org>2024-11-21 15:51:37 +0100
commitebb64aabedd789b5affbf30f03e43fcf3a0561f4 (patch)
treeec4dd9937434be85039f900efcc48c75c182d81d /src
parentInitial commit. (diff)
downloadpacketq-upstream.tar.xz
packetq-upstream.zip
Adding upstream version 1.7.3+dfsg.upstream/1.7.3+dfsgupstream
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to '')
-rw-r--r--src/Makefile.am47
-rw-r--r--src/Makefile.in837
-rw-r--r--src/Murmur/MurmurHash3.cpp335
-rw-r--r--src/Murmur/MurmurHash3.h37
-rw-r--r--src/config.h.in137
-rw-r--r--src/dns.cpp456
-rw-r--r--src/dns.h541
-rw-r--r--src/icmp.cpp238
-rw-r--r--src/icmp.h76
-rw-r--r--src/output.h415
-rw-r--r--src/packet_handler.cpp566
-rw-r--r--src/packet_handler.h226
-rw-r--r--src/packetq.cpp346
-rw-r--r--src/packetq.h70
-rw-r--r--src/pcap.cpp91
-rw-r--r--src/pcap.h209
-rw-r--r--src/reader.cpp84
-rw-r--r--src/reader.h62
-rw-r--r--src/refcountstring.h122
-rwxr-xr-xsrc/regression-test.sh92
-rw-r--r--src/segzip.h121
-rw-r--r--src/server.cpp1183
-rw-r--r--src/server.h31
-rw-r--r--src/sql.cpp2696
-rw-r--r--src/sql.h1938
-rw-r--r--src/tcp.cpp268
-rw-r--r--src/tcp.h62
-rw-r--r--src/test/Makefile.am32
-rw-r--r--src/test/Makefile.in885
-rw-r--r--src/test/dns.pcapbin0 -> 20228 bytes
-rw-r--r--src/test/dns6.pcapbin0 -> 274 bytes
-rw-r--r--src/test/sql.txt26
-rw-r--r--src/test/test1.gold145
-rwxr-xr-xsrc/test/test1.sh27
-rw-r--r--src/test/test2.gold9
-rwxr-xr-xsrc/test/test2.sh23
-rw-r--r--src/test/test3.gold9
-rwxr-xr-xsrc/test/test3.sh23
-rw-r--r--src/test/test4.gold26
-rwxr-xr-xsrc/test/test4.sh23
-rw-r--r--src/test/test5.gold387
-rwxr-xr-xsrc/test/test5.sh27
-rw-r--r--src/test/test6.gold12
-rwxr-xr-xsrc/test/test6.sh23
-rw-r--r--src/test/test7.gold71
-rwxr-xr-xsrc/test/test7.sh24
-rw-r--r--src/test/test8.gold60
-rwxr-xr-xsrc/test/test8.sh26
-rw-r--r--src/variant.h323
49 files changed, 13467 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..4f70623
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,47 @@
+# Copyright (c) 2017-2024 OARC, Inc.
+# Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+# All rights reserved.
+#
+# This file is part of PacketQ.
+#
+# PacketQ is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# PacketQ is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
+CLEANFILES = *.gcda *.gcno *.gcov
+
+SUBDIRS = test
+
+AM_CXXFLAGS = -I$(srcdir) \
+ -I$(srcdir)/Murmur \
+ -I$(top_srcdir) \
+ $(libmaxminddb_CFLAGS)
+
+bin_PROGRAMS = packetq
+
+packetq_SOURCES = dns.cpp dns.h icmp.cpp icmp.h output.h packet_handler.cpp \
+ packet_handler.h packetq.cpp packetq.h pcap.cpp pcap.h reader.cpp \
+ reader.h refcountstring.h segzip.h server.cpp server.h sql.cpp sql.h \
+ tcp.cpp tcp.h variant.h
+packetq_LDADD = $(libmaxminddb_LIBS)
+
+dist_packetq_SOURCES = Murmur/MurmurHash3.cpp Murmur/MurmurHash3.h
+
+EXTRA_DIST = regression-test.sh
+
+if ENABLE_GCOV
+gcov-local:
+ for src in $(packetq_SOURCES) $(dist_packetq_SOURCES); do \
+ gcov -l -r -s "$(srcdir)" "$$src"; \
+ done
+endif
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 0000000..7e67255
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,837 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 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@
+
+# Copyright (c) 2017-2024 OARC, Inc.
+# Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+# All rights reserved.
+#
+# This file is part of PacketQ.
+#
+# PacketQ is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# PacketQ is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+
+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@
+bin_PROGRAMS = packetq$(EXEEXT)
+subdir = src
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cflags_warn_all.m4 \
+ $(top_srcdir)/m4/ax_compiler_vendor.m4 \
+ $(top_srcdir)/m4/ax_prepend_flag.m4 \
+ $(top_srcdir)/m4/ax_require_defined.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 = config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+am_packetq_OBJECTS = dns.$(OBJEXT) icmp.$(OBJEXT) \
+ packet_handler.$(OBJEXT) packetq.$(OBJEXT) pcap.$(OBJEXT) \
+ reader.$(OBJEXT) server.$(OBJEXT) sql.$(OBJEXT) tcp.$(OBJEXT)
+am__dirstamp = $(am__leading_dot)dirstamp
+dist_packetq_OBJECTS = Murmur/MurmurHash3.$(OBJEXT)
+packetq_OBJECTS = $(am_packetq_OBJECTS) $(dist_packetq_OBJECTS)
+am__DEPENDENCIES_1 =
+packetq_DEPENDENCIES = $(am__DEPENDENCIES_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@
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/dns.Po ./$(DEPDIR)/icmp.Po \
+ ./$(DEPDIR)/packet_handler.Po ./$(DEPDIR)/packetq.Po \
+ ./$(DEPDIR)/pcap.Po ./$(DEPDIR)/reader.Po \
+ ./$(DEPDIR)/server.Po ./$(DEPDIR)/sql.Po ./$(DEPDIR)/tcp.Po \
+ Murmur/$(DEPDIR)/MurmurHash3.Po
+am__mv = mv -f
+CXXCOMPILE = $(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 = $(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)
+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 = $(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 = $(packetq_SOURCES) $(dist_packetq_SOURCES)
+DIST_SOURCES = $(packetq_SOURCES) $(dist_packetq_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__extra_recursive_targets = gcov-recursive
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) \
+ config.h.in
+# 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)`
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/config.h.in \
+ $(top_srcdir)/depcomp
+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_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CXX = @CXX@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+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@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+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@
+libmaxminddb_CFLAGS = @libmaxminddb_CFLAGS@
+libmaxminddb_LIBS = @libmaxminddb_LIBS@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
+CLEANFILES = *.gcda *.gcno *.gcov
+SUBDIRS = test
+AM_CXXFLAGS = -I$(srcdir) \
+ -I$(srcdir)/Murmur \
+ -I$(top_srcdir) \
+ $(libmaxminddb_CFLAGS)
+
+packetq_SOURCES = dns.cpp dns.h icmp.cpp icmp.h output.h packet_handler.cpp \
+ packet_handler.h packetq.cpp packetq.h pcap.cpp pcap.h reader.cpp \
+ reader.h refcountstring.h segzip.h server.cpp server.h sql.cpp sql.h \
+ tcp.cpp tcp.h variant.h
+
+packetq_LDADD = $(libmaxminddb_LIBS)
+dist_packetq_SOURCES = Murmur/MurmurHash3.cpp Murmur/MurmurHash3.h
+EXTRA_DIST = regression-test.sh
+all: config.h
+ $(MAKE) $(AM_MAKEFLAGS) all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .cpp .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 src/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/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):
+
+config.h: stamp-h1
+ @test -f $@ || rm -f stamp-h1
+ @test -f $@ || $(MAKE) $(AM_MAKEFLAGS) stamp-h1
+
+stamp-h1: $(srcdir)/config.h.in $(top_builddir)/config.status
+ @rm -f stamp-h1
+ cd $(top_builddir) && $(SHELL) ./config.status src/config.h
+$(srcdir)/config.h.in: $(am__configure_deps)
+ ($(am__cd) $(top_srcdir) && $(AUTOHEADER))
+ rm -f stamp-h1
+ touch $@
+
+distclean-hdr:
+ -rm -f config.h stamp-h1
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
+Murmur/$(am__dirstamp):
+ @$(MKDIR_P) Murmur
+ @: > Murmur/$(am__dirstamp)
+Murmur/$(DEPDIR)/$(am__dirstamp):
+ @$(MKDIR_P) Murmur/$(DEPDIR)
+ @: > Murmur/$(DEPDIR)/$(am__dirstamp)
+Murmur/MurmurHash3.$(OBJEXT): Murmur/$(am__dirstamp) \
+ Murmur/$(DEPDIR)/$(am__dirstamp)
+
+packetq$(EXEEXT): $(packetq_OBJECTS) $(packetq_DEPENDENCIES) $(EXTRA_packetq_DEPENDENCIES)
+ @rm -f packetq$(EXEEXT)
+ $(AM_V_CXXLD)$(CXXLINK) $(packetq_OBJECTS) $(packetq_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+ -rm -f Murmur/*.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dns.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/icmp.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/packet_handler.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/packetq.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pcap.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/reader.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/server.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sql.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tcp.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@Murmur/$(DEPDIR)/MurmurHash3.Po@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) '$<'`
+
+# 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"
+gcov-local:
+
+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 $(PROGRAMS) config.h
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(bindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+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)
+ -rm -f Murmur/$(DEPDIR)/$(am__dirstamp)
+ -rm -f Murmur/$(am__dirstamp)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+ -test -z "$(MAINTAINERCLEANFILES)" || rm -f $(MAINTAINERCLEANFILES)
+@ENABLE_GCOV_FALSE@gcov-local:
+clean: clean-recursive
+
+clean-am: clean-binPROGRAMS clean-generic mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/dns.Po
+ -rm -f ./$(DEPDIR)/icmp.Po
+ -rm -f ./$(DEPDIR)/packet_handler.Po
+ -rm -f ./$(DEPDIR)/packetq.Po
+ -rm -f ./$(DEPDIR)/pcap.Po
+ -rm -f ./$(DEPDIR)/reader.Po
+ -rm -f ./$(DEPDIR)/server.Po
+ -rm -f ./$(DEPDIR)/sql.Po
+ -rm -f ./$(DEPDIR)/tcp.Po
+ -rm -f Murmur/$(DEPDIR)/MurmurHash3.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-hdr distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+gcov: gcov-recursive
+
+gcov-am: gcov-local
+
+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-binPROGRAMS
+
+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 ./$(DEPDIR)/dns.Po
+ -rm -f ./$(DEPDIR)/icmp.Po
+ -rm -f ./$(DEPDIR)/packet_handler.Po
+ -rm -f ./$(DEPDIR)/packetq.Po
+ -rm -f ./$(DEPDIR)/pcap.Po
+ -rm -f ./$(DEPDIR)/reader.Po
+ -rm -f ./$(DEPDIR)/server.Po
+ -rm -f ./$(DEPDIR)/sql.Po
+ -rm -f ./$(DEPDIR)/tcp.Po
+ -rm -f Murmur/$(DEPDIR)/MurmurHash3.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: $(am__recursive_targets) all install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-binPROGRAMS \
+ clean-generic cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-hdr \
+ distclean-tags distdir dvi dvi-am gcov-am gcov-local html \
+ html-am info info-am install install-am install-binPROGRAMS \
+ 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-compile mostlyclean-generic pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-binPROGRAMS
+
+.PRECIOUS: Makefile
+
+
+@ENABLE_GCOV_TRUE@gcov-local:
+@ENABLE_GCOV_TRUE@ for src in $(packetq_SOURCES) $(dist_packetq_SOURCES); do \
+@ENABLE_GCOV_TRUE@ gcov -l -r -s "$(srcdir)" "$$src"; \
+@ENABLE_GCOV_TRUE@ done
+
+# 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/src/Murmur/MurmurHash3.cpp b/src/Murmur/MurmurHash3.cpp
new file mode 100644
index 0000000..aa7982d
--- /dev/null
+++ b/src/Murmur/MurmurHash3.cpp
@@ -0,0 +1,335 @@
+//-----------------------------------------------------------------------------
+// MurmurHash3 was written by Austin Appleby, and is placed in the public
+// domain. The author hereby disclaims copyright to this source code.
+
+// Note - The x86 and x64 versions do _not_ produce the same results, as the
+// algorithms are optimized for their respective platforms. You can still
+// compile and run any of them on any platform, but your performance with the
+// non-native version will be less than optimal.
+
+#include "MurmurHash3.h"
+
+//-----------------------------------------------------------------------------
+// Platform-specific functions and macros
+
+// Microsoft Visual Studio
+
+#if defined(_MSC_VER)
+
+#define FORCE_INLINE __forceinline
+
+#include <stdlib.h>
+
+#define ROTL32(x,y) _rotl(x,y)
+#define ROTL64(x,y) _rotl64(x,y)
+
+#define BIG_CONSTANT(x) (x)
+
+// Other compilers
+
+#else // defined(_MSC_VER)
+
+#define FORCE_INLINE inline __attribute__((always_inline))
+
+inline uint32_t rotl32 ( uint32_t x, int8_t r )
+{
+ return (x << r) | (x >> (32 - r));
+}
+
+inline uint64_t rotl64 ( uint64_t x, int8_t r )
+{
+ return (x << r) | (x >> (64 - r));
+}
+
+#define ROTL32(x,y) rotl32(x,y)
+#define ROTL64(x,y) rotl64(x,y)
+
+#define BIG_CONSTANT(x) (x##LLU)
+
+#endif // !defined(_MSC_VER)
+
+//-----------------------------------------------------------------------------
+// Block read - if your platform needs to do endian-swapping or can only
+// handle aligned reads, do the conversion here
+
+FORCE_INLINE uint32_t getblock32 ( const uint32_t * p, int i )
+{
+ return p[i];
+}
+
+FORCE_INLINE uint64_t getblock64 ( const uint64_t * p, int i )
+{
+ return p[i];
+}
+
+//-----------------------------------------------------------------------------
+// Finalization mix - force all bits of a hash block to avalanche
+
+FORCE_INLINE uint32_t fmix32 ( uint32_t h )
+{
+ h ^= h >> 16;
+ h *= 0x85ebca6b;
+ h ^= h >> 13;
+ h *= 0xc2b2ae35;
+ h ^= h >> 16;
+
+ return h;
+}
+
+//----------
+
+FORCE_INLINE uint64_t fmix64 ( uint64_t k )
+{
+ k ^= k >> 33;
+ k *= BIG_CONSTANT(0xff51afd7ed558ccd);
+ k ^= k >> 33;
+ k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53);
+ k ^= k >> 33;
+
+ return k;
+}
+
+//-----------------------------------------------------------------------------
+
+void MurmurHash3_x86_32 ( const void * key, int len,
+ uint32_t seed, void * out )
+{
+ const uint8_t * data = (const uint8_t*)key;
+ const int nblocks = len / 4;
+
+ uint32_t h1 = seed;
+
+ const uint32_t c1 = 0xcc9e2d51;
+ const uint32_t c2 = 0x1b873593;
+
+ //----------
+ // body
+
+ const uint32_t * blocks = (const uint32_t *)(data + nblocks*4);
+
+ for(int i = -nblocks; i; i++)
+ {
+ uint32_t k1 = getblock32(blocks,i);
+
+ k1 *= c1;
+ k1 = ROTL32(k1,15);
+ k1 *= c2;
+
+ h1 ^= k1;
+ h1 = ROTL32(h1,13);
+ h1 = h1*5+0xe6546b64;
+ }
+
+ //----------
+ // tail
+
+ const uint8_t * tail = (const uint8_t*)(data + nblocks*4);
+
+ uint32_t k1 = 0;
+
+ switch(len & 3)
+ {
+ case 3: k1 ^= tail[2] << 16;
+ case 2: k1 ^= tail[1] << 8;
+ case 1: k1 ^= tail[0];
+ k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1;
+ };
+
+ //----------
+ // finalization
+
+ h1 ^= len;
+
+ h1 = fmix32(h1);
+
+ *(uint32_t*)out = h1;
+}
+
+//-----------------------------------------------------------------------------
+
+void MurmurHash3_x86_128 ( const void * key, const int len,
+ uint32_t seed, void * out )
+{
+ const uint8_t * data = (const uint8_t*)key;
+ const int nblocks = len / 16;
+
+ uint32_t h1 = seed;
+ uint32_t h2 = seed;
+ uint32_t h3 = seed;
+ uint32_t h4 = seed;
+
+ const uint32_t c1 = 0x239b961b;
+ const uint32_t c2 = 0xab0e9789;
+ const uint32_t c3 = 0x38b34ae5;
+ const uint32_t c4 = 0xa1e38b93;
+
+ //----------
+ // body
+
+ const uint32_t * blocks = (const uint32_t *)(data + nblocks*16);
+
+ for(int i = -nblocks; i; i++)
+ {
+ uint32_t k1 = getblock32(blocks,i*4+0);
+ uint32_t k2 = getblock32(blocks,i*4+1);
+ uint32_t k3 = getblock32(blocks,i*4+2);
+ uint32_t k4 = getblock32(blocks,i*4+3);
+
+ k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1;
+
+ h1 = ROTL32(h1,19); h1 += h2; h1 = h1*5+0x561ccd1b;
+
+ k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2;
+
+ h2 = ROTL32(h2,17); h2 += h3; h2 = h2*5+0x0bcaa747;
+
+ k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3;
+
+ h3 = ROTL32(h3,15); h3 += h4; h3 = h3*5+0x96cd1c35;
+
+ k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4;
+
+ h4 = ROTL32(h4,13); h4 += h1; h4 = h4*5+0x32ac3b17;
+ }
+
+ //----------
+ // tail
+
+ const uint8_t * tail = (const uint8_t*)(data + nblocks*16);
+
+ uint32_t k1 = 0;
+ uint32_t k2 = 0;
+ uint32_t k3 = 0;
+ uint32_t k4 = 0;
+
+ switch(len & 15)
+ {
+ case 15: k4 ^= tail[14] << 16;
+ case 14: k4 ^= tail[13] << 8;
+ case 13: k4 ^= tail[12] << 0;
+ k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4;
+
+ case 12: k3 ^= tail[11] << 24;
+ case 11: k3 ^= tail[10] << 16;
+ case 10: k3 ^= tail[ 9] << 8;
+ case 9: k3 ^= tail[ 8] << 0;
+ k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3;
+
+ case 8: k2 ^= tail[ 7] << 24;
+ case 7: k2 ^= tail[ 6] << 16;
+ case 6: k2 ^= tail[ 5] << 8;
+ case 5: k2 ^= tail[ 4] << 0;
+ k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2;
+
+ case 4: k1 ^= tail[ 3] << 24;
+ case 3: k1 ^= tail[ 2] << 16;
+ case 2: k1 ^= tail[ 1] << 8;
+ case 1: k1 ^= tail[ 0] << 0;
+ k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1;
+ };
+
+ //----------
+ // finalization
+
+ h1 ^= len; h2 ^= len; h3 ^= len; h4 ^= len;
+
+ h1 += h2; h1 += h3; h1 += h4;
+ h2 += h1; h3 += h1; h4 += h1;
+
+ h1 = fmix32(h1);
+ h2 = fmix32(h2);
+ h3 = fmix32(h3);
+ h4 = fmix32(h4);
+
+ h1 += h2; h1 += h3; h1 += h4;
+ h2 += h1; h3 += h1; h4 += h1;
+
+ ((uint32_t*)out)[0] = h1;
+ ((uint32_t*)out)[1] = h2;
+ ((uint32_t*)out)[2] = h3;
+ ((uint32_t*)out)[3] = h4;
+}
+
+//-----------------------------------------------------------------------------
+
+void MurmurHash3_x64_128 ( const void * key, const int len,
+ const uint32_t seed, void * out )
+{
+ const uint8_t * data = (const uint8_t*)key;
+ const int nblocks = len / 16;
+
+ uint64_t h1 = seed;
+ uint64_t h2 = seed;
+
+ const uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5);
+ const uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f);
+
+ //----------
+ // body
+
+ const uint64_t * blocks = (const uint64_t *)(data);
+
+ for(int i = 0; i < nblocks; i++)
+ {
+ uint64_t k1 = getblock64(blocks,i*2+0);
+ uint64_t k2 = getblock64(blocks,i*2+1);
+
+ k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1;
+
+ h1 = ROTL64(h1,27); h1 += h2; h1 = h1*5+0x52dce729;
+
+ k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2;
+
+ h2 = ROTL64(h2,31); h2 += h1; h2 = h2*5+0x38495ab5;
+ }
+
+ //----------
+ // tail
+
+ const uint8_t * tail = (const uint8_t*)(data + nblocks*16);
+
+ uint64_t k1 = 0;
+ uint64_t k2 = 0;
+
+ switch(len & 15)
+ {
+ case 15: k2 ^= ((uint64_t)tail[14]) << 48;
+ case 14: k2 ^= ((uint64_t)tail[13]) << 40;
+ case 13: k2 ^= ((uint64_t)tail[12]) << 32;
+ case 12: k2 ^= ((uint64_t)tail[11]) << 24;
+ case 11: k2 ^= ((uint64_t)tail[10]) << 16;
+ case 10: k2 ^= ((uint64_t)tail[ 9]) << 8;
+ case 9: k2 ^= ((uint64_t)tail[ 8]) << 0;
+ k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2;
+
+ case 8: k1 ^= ((uint64_t)tail[ 7]) << 56;
+ case 7: k1 ^= ((uint64_t)tail[ 6]) << 48;
+ case 6: k1 ^= ((uint64_t)tail[ 5]) << 40;
+ case 5: k1 ^= ((uint64_t)tail[ 4]) << 32;
+ case 4: k1 ^= ((uint64_t)tail[ 3]) << 24;
+ case 3: k1 ^= ((uint64_t)tail[ 2]) << 16;
+ case 2: k1 ^= ((uint64_t)tail[ 1]) << 8;
+ case 1: k1 ^= ((uint64_t)tail[ 0]) << 0;
+ k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1;
+ };
+
+ //----------
+ // finalization
+
+ h1 ^= len; h2 ^= len;
+
+ h1 += h2;
+ h2 += h1;
+
+ h1 = fmix64(h1);
+ h2 = fmix64(h2);
+
+ h1 += h2;
+ h2 += h1;
+
+ ((uint64_t*)out)[0] = h1;
+ ((uint64_t*)out)[1] = h2;
+}
+
+//-----------------------------------------------------------------------------
+
diff --git a/src/Murmur/MurmurHash3.h b/src/Murmur/MurmurHash3.h
new file mode 100644
index 0000000..54e9d3f
--- /dev/null
+++ b/src/Murmur/MurmurHash3.h
@@ -0,0 +1,37 @@
+//-----------------------------------------------------------------------------
+// MurmurHash3 was written by Austin Appleby, and is placed in the public
+// domain. The author hereby disclaims copyright to this source code.
+
+#ifndef _MURMURHASH3_H_
+#define _MURMURHASH3_H_
+
+//-----------------------------------------------------------------------------
+// Platform-specific functions and macros
+
+// Microsoft Visual Studio
+
+#if defined(_MSC_VER)
+
+typedef unsigned char uint8_t;
+typedef unsigned long uint32_t;
+typedef unsigned __int64 uint64_t;
+
+// Other compilers
+
+#else // defined(_MSC_VER)
+
+#include <stdint.h>
+
+#endif // !defined(_MSC_VER)
+
+//-----------------------------------------------------------------------------
+
+void MurmurHash3_x86_32 ( const void * key, int len, uint32_t seed, void * out );
+
+void MurmurHash3_x86_128 ( const void * key, int len, uint32_t seed, void * out );
+
+void MurmurHash3_x64_128 ( const void * key, int len, uint32_t seed, void * out );
+
+//-----------------------------------------------------------------------------
+
+#endif // _MURMURHASH3_H_
diff --git a/src/config.h.in b/src/config.h.in
new file mode 100644
index 0000000..8c828dd
--- /dev/null
+++ b/src/config.h.in
@@ -0,0 +1,137 @@
+/* src/config.h.in. Generated from configure.ac by autoheader. */
+
+/* Define to 1 if you have the <arpa/inet.h> header file. */
+#undef HAVE_ARPA_INET_H
+
+/* Define to 1 if you have the `bzero' function. */
+#undef HAVE_BZERO
+
+/* Define to 1 if you have the `dup2' function. */
+#undef HAVE_DUP2
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#undef HAVE_FCNTL_H
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Define to 1 if you have libmaxminddb. */
+#undef HAVE_LIBMAXMINDDB
+
+/* Define to 1 if you have the `nsl' library (-lnsl). */
+#undef HAVE_LIBNSL
+
+/* Define to 1 if you have the `socket' library (-lsocket). */
+#undef HAVE_LIBSOCKET
+
+/* Define to 1 if you have the `z' library (-lz). */
+#undef HAVE_LIBZ
+
+/* Define to 1 if you have the <limits.h> header file. */
+#undef HAVE_LIMITS_H
+
+/* Define to 1 if you have the `mkdir' function. */
+#undef HAVE_MKDIR
+
+/* Define to 1 if you have the <netinet/in.h> header file. */
+#undef HAVE_NETINET_IN_H
+
+/* has net/ethernet.h header */
+#undef HAVE_NET_ETHERNET_H
+
+/* has net/ethertypes.h header */
+#undef HAVE_NET_ETHERTYPES_H
+
+/* Define to 1 if your system has a GNU libc compatible `realloc' function,
+ and to 0 otherwise. */
+#undef HAVE_REALLOC
+
+/* Define to 1 if you have the `regcomp' function. */
+#undef HAVE_REGCOMP
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#undef HAVE_STDINT_H
+
+/* Define to 1 if you have the <stdio.h> header file. */
+#undef HAVE_STDIO_H
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define to 1 if you have the `strdup' function. */
+#undef HAVE_STRDUP
+
+/* Define to 1 if you have the `strerror' function. */
+#undef HAVE_STRERROR
+
+/* Define to 1 if you have the `strftime' function. */
+#undef HAVE_STRFTIME
+
+/* Define to 1 if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define to 1 if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define to 1 if you have the `strtoul' function. */
+#undef HAVE_STRTOUL
+
+/* Define to 1 if you have the <syslog.h> header file. */
+#undef HAVE_SYSLOG_H
+
+/* Define to 1 if you have the <sys/socket.h> header file. */
+#undef HAVE_SYS_SOCKET_H
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#undef HAVE_SYS_TIME_H
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#undef HAVE_SYS_TYPES_H
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* Name of package */
+#undef PACKAGE
+
+/* Define to the address where bug reports for this package should be sent. */
+#undef PACKAGE_BUGREPORT
+
+/* Define to the full name of this package. */
+#undef PACKAGE_NAME
+
+/* Define to the full name and version of this package. */
+#undef PACKAGE_STRING
+
+/* Define to the one symbol short name of this package. */
+#undef PACKAGE_TARNAME
+
+/* Define to the home page for this package. */
+#undef PACKAGE_URL
+
+/* Define to the version of this package. */
+#undef PACKAGE_VERSION
+
+/* Define to 1 if all of the C90 standard headers exist (not just the ones
+ required in a freestanding environment). This macro is provided for
+ backward compatibility; new code need not use it. */
+#undef STDC_HEADERS
+
+/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. This
+ macro is obsolete. */
+#undef TIME_WITH_SYS_TIME
+
+/* Version number of package */
+#undef VERSION
+
+/* Define to empty if `const' does not conform to ANSI C. */
+#undef const
+
+/* Define to rpl_realloc if the replacement function should be used. */
+#undef realloc
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+#undef size_t
diff --git a/src/dns.cpp b/src/dns.cpp
new file mode 100644
index 0000000..24ebca1
--- /dev/null
+++ b/src/dns.cpp
@@ -0,0 +1,456 @@
+/*
+ * Copyright (c) 2017-2024 OARC, Inc.
+ * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+ * All rights reserved.
+ *
+ * This file is part of PacketQ.
+ *
+ * PacketQ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PacketQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "dns.h"
+#include "output.h"
+#include "packet_handler.h"
+#include "packetq.h"
+#include "tcp.h"
+
+#include <cctype>
+#include <stdio.h>
+#include <stdlib.h>
+
+namespace packetq {
+
+char visible_char_map[256];
+
+void fill_in_visible_char_map()
+{
+ for (int i = 0; i < 256; ++i) {
+ visible_char_map[i] = isgraph(i) ? i : '$';
+ }
+}
+
+void fill_in_visible_char_map_rfc1035()
+{
+ for (int i = 0; i < 256; ++i) {
+ if ((i >= 'a' && i <= 'z')
+ || (i >= 'A' && i <= 'Z')
+ || (i >= '0' && i <= '9')
+ || (i == '-' || i == '_')) {
+ visible_char_map[i] = i;
+ } else { // espaping needed
+ visible_char_map[i] = 0;
+ }
+ }
+}
+
+Parse_dns::Parse_dns(bool escape_dnsnames)
+{
+ if (escape_dnsnames) {
+ fill_in_visible_char_map_rfc1035();
+ } else {
+ fill_in_visible_char_map();
+ }
+
+ table_name = "dns";
+
+ add_packet_columns();
+ add_lookup_tables();
+}
+
+void Parse_dns::add_packet_columns()
+{
+ m_ip_helper.add_packet_columns(*this);
+
+ add_packet_column("qname", "", Coltype::_text, COLUMN_QNAME);
+ add_packet_column("aname", "", Coltype::_text, COLUMN_ANAME);
+ add_packet_column("msg_id", "", Coltype::_int, COLUMN_MSG_ID);
+ add_packet_column("msg_size", "", Coltype::_int, COLUMN_MSG_SIZE);
+ add_packet_column("opcode", "", Coltype::_int, COLUMN_OPCODE);
+ add_packet_column("rcode", "", Coltype::_int, COLUMN_RCODE);
+ add_packet_column("extended_rcode", "", Coltype::_int, COLUMN_EXTENDED_RCODE);
+ add_packet_column("edns_version", "", Coltype::_int, COLUMN_EDNS_VERSION);
+ add_packet_column("z", "", Coltype::_int, COLUMN_Z);
+ add_packet_column("udp_size", "", Coltype::_int, COLUMN_UDP_SIZE);
+ add_packet_column("qd_count", "", Coltype::_int, COLUMN_QD_COUNT);
+ add_packet_column("an_count", "", Coltype::_int, COLUMN_AN_COUNT);
+ add_packet_column("ns_count", "", Coltype::_int, COLUMN_NS_COUNT);
+ add_packet_column("ar_count", "", Coltype::_int, COLUMN_AR_COUNT);
+ add_packet_column("qtype", "", Coltype::_int, COLUMN_QTYPE);
+ add_packet_column("qclass", "", Coltype::_int, COLUMN_QCLASS);
+ add_packet_column("qlabels", "", Coltype::_int, COLUMN_QLABELS);
+ add_packet_column("atype", "", Coltype::_int, COLUMN_ATYPE);
+ add_packet_column("aclass", "", Coltype::_int, COLUMN_ACLASS);
+ add_packet_column("attl", "", Coltype::_int, COLUMN_ATTL);
+ add_packet_column("alabels", "", Coltype::_int, COLUMN_ALABELS);
+ add_packet_column("aa", "", Coltype::_bool, COLUMN_AA);
+ add_packet_column("tc", "", Coltype::_bool, COLUMN_TC);
+ add_packet_column("rd", "", Coltype::_bool, COLUMN_RD);
+ add_packet_column("cd", "", Coltype::_bool, COLUMN_CD);
+ add_packet_column("ra", "", Coltype::_bool, COLUMN_RA);
+ add_packet_column("ad", "", Coltype::_bool, COLUMN_AD);
+ add_packet_column("do", "", Coltype::_bool, COLUMN_DO);
+ add_packet_column("edns0", "", Coltype::_bool, COLUMN_EDNS0);
+ add_packet_column("qr", "", Coltype::_bool, COLUMN_QR);
+
+ add_packet_column("edns0_ecs", "", Coltype::_bool, COLUMN_EDNS0_ECS);
+ add_packet_column("edns0_ecs_family", "", Coltype::_int, COLUMN_EDNS0_ECS_FAMILY);
+ add_packet_column("edns0_ecs_source", "", Coltype::_int, COLUMN_EDNS0_ECS_SOURCE);
+ add_packet_column("edns0_ecs_scope", "", Coltype::_int, COLUMN_EDNS0_ECS_SCOPE);
+ add_packet_column("edns0_ecs_address", "", Coltype::_text, COLUMN_EDNS0_ECS_ADDRESS);
+}
+
+void Parse_dns::add_lookup_tables()
+{
+ g_db.add_lut("qtype", 1, "A");
+ g_db.add_lut("qtype", 2, "NS");
+ g_db.add_lut("qtype", 3, "MD");
+ g_db.add_lut("qtype", 4, "MF");
+ g_db.add_lut("qtype", 5, "CNAME");
+ g_db.add_lut("qtype", 6, "SOA");
+ g_db.add_lut("qtype", 7, "MB");
+ g_db.add_lut("qtype", 8, "MG");
+ g_db.add_lut("qtype", 9, "MR");
+ g_db.add_lut("qtype", 10, "NULL");
+ g_db.add_lut("qtype", 11, "WKS");
+ g_db.add_lut("qtype", 12, "PTR");
+ g_db.add_lut("qtype", 13, "HINFO");
+ g_db.add_lut("qtype", 14, "MINFO");
+ g_db.add_lut("qtype", 15, "MX");
+ g_db.add_lut("qtype", 16, "TXT");
+ g_db.add_lut("qtype", 17, "RP");
+ g_db.add_lut("qtype", 18, "AFSDB");
+ g_db.add_lut("qtype", 19, "X25");
+ g_db.add_lut("qtype", 20, "ISDN");
+ g_db.add_lut("qtype", 21, "RT");
+ g_db.add_lut("qtype", 22, "NSAP");
+ g_db.add_lut("qtype", 23, "NSAP-PTR");
+ g_db.add_lut("qtype", 24, "SIG");
+ g_db.add_lut("qtype", 25, "KEY");
+ g_db.add_lut("qtype", 26, "PX");
+ g_db.add_lut("qtype", 27, "GPOS");
+ g_db.add_lut("qtype", 28, "AAAA");
+ g_db.add_lut("qtype", 29, "LOC");
+ g_db.add_lut("qtype", 30, "NXT");
+ g_db.add_lut("qtype", 31, "EID");
+ g_db.add_lut("qtype", 32, "NIMLOC");
+ g_db.add_lut("qtype", 33, "SRV");
+ g_db.add_lut("qtype", 34, "ATMA");
+ g_db.add_lut("qtype", 35, "NAPTR");
+ g_db.add_lut("qtype", 36, "KX");
+ g_db.add_lut("qtype", 37, "CERT");
+ g_db.add_lut("qtype", 38, "A6");
+ g_db.add_lut("qtype", 39, "DNAME");
+ g_db.add_lut("qtype", 40, "SINK");
+ g_db.add_lut("qtype", 41, "OPT");
+ g_db.add_lut("qtype", 42, "APL");
+ g_db.add_lut("qtype", 43, "DS");
+ g_db.add_lut("qtype", 44, "SSHFP");
+ g_db.add_lut("qtype", 45, "IPSECKEY");
+ g_db.add_lut("qtype", 46, "RRSIG");
+ g_db.add_lut("qtype", 47, "NSEC");
+ g_db.add_lut("qtype", 48, "DNSKEY");
+ g_db.add_lut("qtype", 49, "DHCID");
+ g_db.add_lut("qtype", 50, "NSEC3");
+ g_db.add_lut("qtype", 51, "NSEC3PARAM");
+ g_db.add_lut("qtype", 52, "TLSA");
+ g_db.add_lut("qtype", 53, "SMIMEA");
+ g_db.add_lut("qtype", 55, "HIP");
+ g_db.add_lut("qtype", 56, "NINFO");
+ g_db.add_lut("qtype", 57, "RKEY");
+ g_db.add_lut("qtype", 58, "TALINK");
+ g_db.add_lut("qtype", 59, "CDS");
+ g_db.add_lut("qtype", 60, "CDNSKEY");
+ g_db.add_lut("qtype", 61, "OPENPGPKEY");
+ g_db.add_lut("qtype", 62, "CSYNC");
+ g_db.add_lut("qtype", 63, "ZONEMD");
+ g_db.add_lut("qtype", 64, "SVCB");
+ g_db.add_lut("qtype", 65, "HTTPS");
+ g_db.add_lut("qtype", 99, "SPF");
+ g_db.add_lut("qtype", 100, "UINFO");
+ g_db.add_lut("qtype", 101, "UID");
+ g_db.add_lut("qtype", 102, "GID");
+ g_db.add_lut("qtype", 103, "UNSPEC");
+ g_db.add_lut("qtype", 104, "NID");
+ g_db.add_lut("qtype", 105, "L32");
+ g_db.add_lut("qtype", 106, "L64");
+ g_db.add_lut("qtype", 107, "LP");
+ g_db.add_lut("qtype", 108, "EUI48");
+ g_db.add_lut("qtype", 109, "EUI64");
+ g_db.add_lut("qtype", 249, "TKEY");
+ g_db.add_lut("qtype", 250, "TSIG");
+ g_db.add_lut("qtype", 251, "IXFR");
+ g_db.add_lut("qtype", 252, "AXFR");
+ g_db.add_lut("qtype", 253, "MAILB");
+ g_db.add_lut("qtype", 254, "MAILA");
+ g_db.add_lut("qtype", 255, "*");
+ g_db.add_lut("qtype", 256, "URI");
+ g_db.add_lut("qtype", 257, "CAA");
+ g_db.add_lut("qtype", 258, "AVC");
+ g_db.add_lut("qtype", 259, "DOA");
+ g_db.add_lut("qtype", 260, "AMTRELAY");
+ g_db.add_lut("qtype", 32768, "TA");
+ g_db.add_lut("qtype", 32769, "DLV");
+
+ g_db.add_lut("rcode", 0, "NoError");
+ g_db.add_lut("rcode", 1, "FormErr");
+ g_db.add_lut("rcode", 2, "ServFail");
+ g_db.add_lut("rcode", 3, "NXDomain");
+ g_db.add_lut("rcode", 4, "NotImp");
+ g_db.add_lut("rcode", 5, "Refused");
+ g_db.add_lut("rcode", 6, "YXDomain");
+ g_db.add_lut("rcode", 7, "YXRRSet");
+ g_db.add_lut("rcode", 8, "NXRRSet");
+ g_db.add_lut("rcode", 9, "NotAuth");
+ g_db.add_lut("rcode", 10, "NotZone");
+ g_db.add_lut("rcode", 16, "BADVERS");
+ g_db.add_lut("rcode", 16, "BADSIG");
+ g_db.add_lut("rcode", 17, "BADKEY");
+ g_db.add_lut("rcode", 18, "BADTIME");
+ g_db.add_lut("rcode", 19, "BADMODE");
+ g_db.add_lut("rcode", 20, "BADNAME");
+ g_db.add_lut("rcode", 21, "BADALG");
+ g_db.add_lut("rcode", 22, "BADTRUNC");
+}
+
+void Parse_dns::on_table_created(Table* table, const std::vector<int>& columns)
+{
+ m_ip_helper.on_table_created(table, columns);
+
+ acc_msg_id = table->get_accessor<int_column>("msg_id");
+ acc_msg_size = table->get_accessor<int_column>("msg_size");
+ acc_opcode = table->get_accessor<int_column>("opcode");
+ acc_rcode = table->get_accessor<int_column>("rcode");
+ acc_extended_rcode = table->get_accessor<int_column>("extended_rcode");
+ acc_edns_version = table->get_accessor<int_column>("edns_version");
+ acc_z = table->get_accessor<int_column>("z");
+ acc_udp_size = table->get_accessor<int_column>("udp_size");
+ acc_qd_count = table->get_accessor<int_column>("qd_count");
+ acc_an_count = table->get_accessor<int_column>("an_count");
+ acc_ns_count = table->get_accessor<int_column>("ns_count");
+ acc_ar_count = table->get_accessor<int_column>("ar_count");
+ acc_qtype = table->get_accessor<int_column>("qtype");
+ acc_qclass = table->get_accessor<int_column>("qclass");
+ acc_qlabels = table->get_accessor<int_column>("qlabels");
+ acc_atype = table->get_accessor<int_column>("atype");
+ acc_aclass = table->get_accessor<int_column>("aclass");
+ acc_attl = table->get_accessor<int_column>("attl");
+ acc_alabels = table->get_accessor<int_column>("alabels");
+
+ acc_qr = table->get_accessor<bool_column>("qr");
+ acc_aa = table->get_accessor<bool_column>("aa");
+ acc_tc = table->get_accessor<bool_column>("tc");
+ acc_rd = table->get_accessor<bool_column>("rd");
+ acc_cd = table->get_accessor<bool_column>("cd");
+ acc_ra = table->get_accessor<bool_column>("ra");
+ acc_ad = table->get_accessor<bool_column>("ad");
+ acc_do = table->get_accessor<bool_column>("do");
+ acc_edns0 = table->get_accessor<bool_column>("edns0");
+
+ acc_qname = table->get_accessor<text_column>("qname");
+ acc_aname = table->get_accessor<text_column>("aname");
+
+ acc_edns0_ecs = table->get_accessor<bool_column>("edns0_ecs");
+ acc_edns0_ecs_family = table->get_accessor<int_column>("edns0_ecs_family");
+ acc_edns0_ecs_source = table->get_accessor<int_column>("edns0_ecs_source");
+ acc_edns0_ecs_scope = table->get_accessor<int_column>("edns0_ecs_scope");
+ acc_edns0_ecs_address = table->get_accessor<text_column>("edns0_ecs_address");
+}
+
+Packet::ParseResult Parse_dns::parse(Packet& packet, const std::vector<int>& columns, Row& destination_row, bool sample)
+{
+ if (not(packet.m_len >= 12 && (packet.m_ip_header.proto == IPPROTO_UDP || packet.m_ip_header.proto == IPPROTO_TCP)))
+ return Packet::ERROR;
+
+ if (!sample)
+ return Packet::NOT_SAMPLED;
+
+ unsigned char* ddata = packet.m_data;
+ int dlength = packet.m_len;
+
+ if (packet.m_ip_header.proto == IPPROTO_TCP) {
+ int dns_size = (int(ddata[0]) << 8) | ddata[1];
+ ddata += 2;
+ dlength -= 2;
+ if (dns_size != dlength)
+ return Packet::ERROR;
+ }
+
+ DNSMessage message(ddata, dlength, packet.m_ip_header);
+
+ DNSMessage::Header& header = message.m_header;
+ IP_header& ip_header = message.m_ip_header;
+
+ if (message.m_error != 0)
+ return Packet::ERROR;
+
+ if (!header.qr and header.qdcount == 0)
+ return Packet::ERROR;
+
+ Row* r = &destination_row;
+
+ m_ip_helper.assign(r, &ip_header, columns);
+
+ for (auto i = columns.begin(), end = columns.end(); i != end; ++i) {
+ switch (*i) {
+ case COLUMN_MSG_ID:
+ acc_msg_id.value(r) = header.id;
+ break;
+
+ case COLUMN_MSG_SIZE:
+ acc_msg_size.value(r) = message.m_length;
+ break;
+
+ case COLUMN_QR:
+ acc_qr.value(r) = header.qr;
+ break;
+
+ case COLUMN_AA:
+ acc_aa.value(r) = header.aa;
+ break;
+
+ case COLUMN_TC:
+ acc_tc.value(r) = header.tc;
+ break;
+
+ case COLUMN_RD:
+ acc_rd.value(r) = header.rd;
+ break;
+
+ case COLUMN_CD:
+ acc_cd.value(r) = header.cd;
+ break;
+
+ case COLUMN_RA:
+ acc_ra.value(r) = header.ra;
+ break;
+
+ case COLUMN_AD:
+ acc_ad.value(r) = header.ad;
+ break;
+
+ case COLUMN_OPCODE:
+ acc_opcode.value(r) = header.opcode;
+ break;
+
+ case COLUMN_RCODE:
+ acc_rcode.value(r) = header.rcode;
+ break;
+
+ case COLUMN_QD_COUNT:
+ acc_qd_count.value(r) = header.qdcount;
+ break;
+
+ case COLUMN_AN_COUNT:
+ acc_an_count.value(r) = header.ancount;
+ break;
+
+ case COLUMN_NS_COUNT:
+ acc_ns_count.value(r) = header.nscount;
+ break;
+
+ case COLUMN_AR_COUNT:
+ acc_ar_count.value(r) = header.arcount;
+ break;
+
+ case COLUMN_QTYPE:
+ acc_qtype.value(r) = message.m_questions[0].qtype;
+ break;
+
+ case COLUMN_QCLASS:
+ acc_qclass.value(r) = message.m_questions[0].qclass;
+ break;
+
+ case COLUMN_QLABELS:
+ acc_qlabels.value(r) = message.m_questions[0].qname.labels;
+ break;
+
+ case COLUMN_QNAME:
+ acc_qname.value(r) = RefCountString::construct(message.m_questions[0].qname.fqdn);
+ break;
+
+ case COLUMN_EDNS0:
+ acc_edns0.value(r) = message.m_edns0 ? 1 : 0;
+ break;
+
+ case COLUMN_DO:
+ acc_do.value(r) = message.m_edns0 ? message.m_do : 0;
+ break;
+
+ case COLUMN_EXTENDED_RCODE:
+ acc_extended_rcode.value(r) = message.m_edns0 ? message.m_extended_rcode : 0;
+ break;
+
+ case COLUMN_EDNS_VERSION:
+ acc_edns_version.value(r) = message.m_edns0 ? message.m_edns_version : 0;
+ break;
+
+ case COLUMN_Z:
+ acc_z.value(r) = message.m_edns0 ? message.m_z : 0;
+ break;
+
+ case COLUMN_UDP_SIZE:
+ acc_udp_size.value(r) = message.m_edns0 ? message.m_udp_size : 0;
+ break;
+
+ case COLUMN_ANAME:
+ acc_aname.value(r) = header.ancount ? RefCountString::construct(message.m_answer[0].name.fqdn) : RefCountString::construct("");
+ break;
+
+ case COLUMN_ATYPE:
+ acc_atype.value(r) = header.ancount ? message.m_answer[0].type : 0;
+ break;
+
+ case COLUMN_ACLASS:
+ acc_aclass.value(r) = header.ancount ? message.m_answer[0].rr_class : 0;
+ break;
+
+ case COLUMN_ATTL:
+ acc_attl.value(r) = header.ancount ? message.m_answer[0].ttl : 0;
+ break;
+
+ case COLUMN_ALABELS:
+ acc_alabels.value(r) = header.ancount ? message.m_answer[0].name.labels : 0;
+ break;
+
+ case COLUMN_EDNS0_ECS:
+ acc_edns0_ecs.value(r) = message.m_edns0_ecs ? 1 : 0;
+ break;
+
+ case COLUMN_EDNS0_ECS_FAMILY:
+ acc_edns0_ecs_family.value(r) = message.m_edns0_ecs_family;
+ break;
+
+ case COLUMN_EDNS0_ECS_SOURCE:
+ acc_edns0_ecs_source.value(r) = message.m_edns0_ecs_source;
+ break;
+
+ case COLUMN_EDNS0_ECS_SCOPE:
+ acc_edns0_ecs_scope.value(r) = message.m_edns0_ecs_scope;
+ break;
+
+ case COLUMN_EDNS0_ECS_ADDRESS:
+ if (message.m_edns0_ecs_addr_set && message.m_edns0_ecs_family == 1)
+ acc_edns0_ecs_address.value(r) = v4_addr2str(message.m_edns0_ecs_addr);
+ else if (message.m_edns0_ecs_addr_set && message.m_edns0_ecs_family == 2)
+ acc_edns0_ecs_address.value(r) = v6_addr2str(message.m_edns0_ecs_addr);
+ else
+ acc_edns0_ecs_address.value(r) = RefCountString::construct("");
+ break;
+ }
+ }
+
+ return Packet::OK;
+}
+
+} // namespace packetq
diff --git a/src/dns.h b/src/dns.h
new file mode 100644
index 0000000..0afd281
--- /dev/null
+++ b/src/dns.h
@@ -0,0 +1,541 @@
+/*
+ * Copyright (c) 2017-2024 OARC, Inc.
+ * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+ * All rights reserved.
+ *
+ * This file is part of PacketQ.
+ *
+ * PacketQ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PacketQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __packetq_dns_h
+#define __packetq_dns_h
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "output.h"
+#include "packet_handler.h"
+#include "tcp.h"
+
+namespace packetq {
+
+extern char visible_char_map[256];
+
+class DNSMessage {
+public:
+ class Header {
+ public:
+ int id;
+ int z;
+ bool qr;
+ int opcode;
+ bool aa;
+ bool tc;
+ bool rd;
+ bool ra;
+ bool ad;
+ bool cd;
+ int rcode;
+ int qdcount;
+ int ancount;
+ int nscount;
+ int arcount;
+ Header()
+ : z(0)
+ {
+ id = 0;
+ qr = 0;
+ opcode = 0;
+ aa = 0;
+ tc = 0;
+ rd = 0;
+ ra = 0;
+ ad = 0;
+ cd = 0;
+ rcode = 0;
+ qdcount = 0;
+ ancount = 0;
+ nscount = 0;
+ arcount = 0;
+ }
+ void parse(DNSMessage& p)
+ {
+ /*
+ From rfc 2929
+ 1 1 1 1 1 1
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | ID |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ |QR| Opcode |AA|TC|RD|RA| Z|AD|CD| RCODE |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | QDCOUNT/ZOCOUNT |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | ANCOUNT/PRCOUNT |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | NSCOUNT/UPCOUNT |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | ARCOUNT |
+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ */
+
+ id = p.get_ushort(0);
+ qr = p.get_bit(2, 0);
+ opcode = p.get_bits(2, 1, 4);
+ aa = p.get_bit(2, 5);
+ tc = p.get_bit(2, 6);
+ rd = p.get_bit(2, 7);
+ ra = p.get_bit(2, 8);
+ ad = p.get_bit(2, 10);
+ cd = p.get_bit(2, 11);
+ rcode = p.get_bits(2, 12, 4);
+ qdcount = p.get_ushort(4);
+ ancount = p.get_ushort(6);
+ nscount = p.get_ushort(8);
+ arcount = p.get_ushort(10);
+ }
+ };
+
+ class Name {
+ public:
+ char fqdn[2048]; // escaping needs *4 the space
+ int labels;
+
+ Name()
+ : fqdn { 0 }
+ , labels(0)
+ {
+ }
+
+ void reset(void)
+ {
+ fqdn[0] = 0;
+ labels = 0;
+ }
+ };
+
+ class Question {
+ public:
+ Name qname;
+ int qtype;
+ int qclass;
+
+ Question()
+ {
+ qtype = 0;
+ qclass = 0;
+ }
+
+ int parse(DNSMessage& m, int offs)
+ {
+ offs = m.parse_dname(qname, offs);
+ qtype = m.get_ushort(offs);
+ offs += 2;
+ qclass = m.get_ushort(offs);
+ offs += 2;
+ return offs;
+ }
+ };
+
+ class RR {
+ public:
+ Name name;
+ int type;
+ int rr_class;
+ unsigned int ttl;
+ int rdlength;
+ int doffs;
+
+ RR()
+ {
+ type = 0;
+ rr_class = 0;
+ ttl = 0;
+ rdlength = 0;
+ doffs = 0;
+ }
+
+ int parse(DNSMessage& m, int offs)
+ {
+ offs = m.parse_dname(name, offs);
+ type = m.get_ushort(offs);
+ if (type == 41) {
+ m.m_opt_rr = this;
+ m.m_new_opt_rr = true;
+ }
+ offs += 2;
+ rr_class = m.get_ushort(offs);
+ offs += 2;
+ ttl = m.get_ushort(offs) << 16;
+ ttl |= m.get_ushort(offs + 2);
+ offs += 4;
+ rdlength = m.get_ushort(offs);
+ offs += 2;
+ doffs = offs;
+ offs += rdlength;
+ return offs;
+ }
+ };
+
+ IP_header& m_ip_header;
+ unsigned char* m_data;
+ int m_length;
+ Header m_header;
+ Question m_questions[2];
+ RR m_answer[2];
+ RR m_authority[2];
+ RR m_additional[2];
+ RR* m_opt_rr;
+ bool m_new_opt_rr;
+ int m_error;
+ bool m_edns0;
+ bool m_do;
+ int m_extended_rcode;
+ int m_edns_version;
+ int m_z;
+ int m_udp_size;
+ bool m_edns0_ecs;
+ int m_edns0_ecs_family;
+ int m_edns0_ecs_source;
+ int m_edns0_ecs_scope;
+ in6addr_t m_edns0_ecs_addr;
+ bool m_edns0_ecs_addr_set;
+
+ DNSMessage(unsigned char* data, int len, IP_header& head)
+ : m_ip_header(head)
+ {
+ m_opt_rr = 0;
+ m_new_opt_rr = false;
+ m_error = 0;
+ m_data = data;
+ m_length = len;
+ m_edns0 = false;
+ m_do = false;
+ m_extended_rcode = 0;
+ m_edns_version = 0;
+ m_z = 0;
+ m_udp_size = 0;
+ m_edns0_ecs = false;
+ m_edns0_ecs_family = 0;
+ m_edns0_ecs_source = 0;
+ m_edns0_ecs_scope = 0;
+ m_edns0_ecs_addr_set = false;
+
+ parse();
+ }
+ int parse_dname(Name& name, int offs)
+ {
+ int p = 0;
+ int savedoffs = 0;
+ int n = get_ubyte(offs++);
+ char* out = &name.fqdn[0];
+ if (n == 0)
+ out[p++] = '.';
+
+ while (n > 0) {
+ name.labels++;
+ while (n >= 192) {
+ if (savedoffs) {
+ out[p++] = 0;
+ return savedoffs;
+ }
+ savedoffs = offs + 1;
+ int n2 = get_ubyte(offs++);
+ int ptr = (n & 63) * 0x100 + n2;
+ offs = ptr;
+ n = get_ubyte(offs++);
+ }
+
+ // if the string is too long restart and mess it up
+ // check if we can fit a fully escaped label + . and reserve for zeroing it later
+ if (p + (n * 4) + 1 > sizeof(name.fqdn) - 1)
+ p = 0;
+
+ while (n-- > 0) {
+ const unsigned int byte = get_ubyte(offs++);
+ if (visible_char_map[byte]) {
+ out[p++] = visible_char_map[byte];
+ } else {
+ out[p++] = '\\';
+ out[p++] = '0' + byte / 100;
+ out[p++] = '0' + byte / 10 % 10;
+ out[p++] = '0' + byte % 10;
+ }
+ }
+ out[p++] = '.';
+ n = get_ubyte(offs++);
+ }
+ if (savedoffs)
+ offs = savedoffs;
+ out[p++] = 0;
+ return offs;
+ }
+ void parse_opt_rr()
+ {
+ if (m_opt_rr) {
+ if (!m_edns0) {
+ m_edns0 = true;
+ unsigned long ttl = m_opt_rr->ttl;
+ m_do = (ttl >> 15) & 1;
+ m_extended_rcode = ttl >> 24;
+ m_edns_version = (ttl >> 16) & 0xff;
+ m_z = ttl & 0x7fff;
+ m_udp_size = m_opt_rr->rr_class;
+ }
+ if (((m_opt_rr->ttl >> 16) & 0xff) == 0 && m_opt_rr->rdlength > 0) {
+ // Parse this OPT RR that is EDNS0
+ int rdlen = m_opt_rr->rdlength,
+ offs = m_opt_rr->doffs,
+ opcode = 0,
+ oplen = 0;
+
+ while (rdlen > 3) {
+ // Minimum op code and length
+ opcode = get_ushort(offs);
+ oplen = get_ushort(offs + 2);
+ offs += 4;
+ rdlen -= 4;
+
+ if (rdlen < oplen)
+ break;
+
+ if (opcode == 8 && !m_edns0_ecs && oplen > 3) {
+ // ECS - Client Subnet - RFC7871
+ m_edns0_ecs = true;
+ m_edns0_ecs_family = get_ushort(offs);
+ m_edns0_ecs_source = get_ubyte(offs + 2);
+ m_edns0_ecs_scope = get_ubyte(offs + 3);
+
+ int addrlen = (m_edns0_ecs_source / 8) + (m_edns0_ecs_source % 8 ? 1 : 0);
+ int fill = 0;
+
+ if (addrlen <= (oplen - 4)) {
+ switch (m_edns0_ecs_family) {
+ case 1:
+ fill = 4;
+ m_edns0_ecs_addr_set = true;
+ break;
+ case 2:
+ fill = 16;
+ m_edns0_ecs_addr_set = true;
+ break;
+ }
+
+ int a = 0, b;
+ for (b = 15; fill && b > -1; fill--, b--) {
+ if (a < addrlen) {
+ m_edns0_ecs_addr.__in6_u.__u6_addr8[b] = get_ubyte(offs + 4 + a);
+ a++;
+ } else {
+ m_edns0_ecs_addr.__in6_u.__u6_addr8[b] = 0;
+ }
+ }
+ }
+ }
+
+ rdlen -= oplen;
+ offs += oplen;
+ }
+ }
+ }
+ }
+ void parse()
+ {
+ m_header.parse(*this);
+ int offs = 12;
+ int q = 0;
+ int cnt = m_header.qdcount;
+ while (cnt-- > 0) {
+ offs = m_questions[q].parse(*this, offs);
+ if (offs > m_length) {
+ m_questions[q].qname.reset();
+ m_error = offs;
+ return;
+ }
+ q = 1; // not ++ ignore further Q's
+ }
+ q = 0;
+ cnt = m_header.ancount;
+ while (cnt-- > 0) {
+ offs = m_answer[q].parse(*this, offs);
+ q = 1; // not ++ ignore further Q's
+ if (offs > m_length) {
+ m_error = offs;
+ return;
+ }
+ }
+ q = 0;
+ cnt = m_header.nscount;
+ while (cnt-- > 0) {
+ offs = m_authority[q].parse(*this, offs);
+ q = 1; // not ++ ignore further Q's
+ if (offs > m_length) {
+ m_error = offs;
+ return;
+ }
+ }
+ q = 0;
+ cnt = m_header.arcount;
+ while (cnt-- > 0) {
+ offs = m_additional[q].parse(*this, offs);
+ q = 1; // not ++ ignore further Q's
+ if (offs > m_length) {
+ m_error = offs;
+ return;
+ }
+ if (m_new_opt_rr) {
+ parse_opt_rr();
+ m_new_opt_rr = false;
+ }
+ }
+ if (offs > m_length)
+ m_error = offs;
+ }
+
+ unsigned int get_ubyte(int offs)
+ {
+ if (offs >= m_length)
+ return 0;
+ return int(m_data[offs]);
+ }
+ // returns 16 bit number at byte offset offs
+ unsigned int get_ushort(int offs)
+ {
+ if ((offs + 1) >= m_length)
+ return 0;
+ return (int(m_data[offs]) << 8) | int(m_data[offs + 1]);
+ }
+ uint32_t get_uint32(int offs)
+ {
+ if ((offs + 3) >= m_length)
+ return 0;
+ return (uint32_t(m_data[offs]) << 24) | (uint32_t(m_data[offs + 1]) << 16) | (uint32_t(m_data[offs + 2]) << 8) | uint32_t(m_data[offs + 3]);
+ }
+ bool get_bit(int offs, int bit)
+ {
+ if (offs >= m_length)
+ return 0;
+ return ((get_ushort(offs) << bit) & 0x8000) == 0x8000;
+ }
+ unsigned int get_bits(int offs, int bit, int bits)
+ {
+ if (offs >= m_length)
+ return 0;
+ return ((get_ushort(offs) << bit) & 0xffff) >> (16 - bits);
+ }
+};
+
+class Parse_dns : public Packet_handler {
+public:
+ enum {
+ COLUMN_QNAME = IP_header_to_table::COLUMN_FRAGMENTS + 1,
+ COLUMN_ANAME,
+ COLUMN_MSG_ID,
+ COLUMN_MSG_SIZE,
+ COLUMN_OPCODE,
+ COLUMN_RCODE,
+ COLUMN_EXTENDED_RCODE,
+ COLUMN_EDNS_VERSION,
+ COLUMN_Z,
+ COLUMN_UDP_SIZE,
+ COLUMN_QD_COUNT,
+ COLUMN_AN_COUNT,
+ COLUMN_NS_COUNT,
+ COLUMN_AR_COUNT,
+ COLUMN_QTYPE,
+ COLUMN_QCLASS,
+ COLUMN_QLABELS,
+ COLUMN_ATYPE,
+ COLUMN_ACLASS,
+ COLUMN_ALABELS,
+ COLUMN_ATTL,
+ COLUMN_AA,
+ COLUMN_TC,
+ COLUMN_RD,
+ COLUMN_CD,
+ COLUMN_RA,
+ COLUMN_AD,
+ COLUMN_DO,
+ COLUMN_EDNS0,
+ COLUMN_QR,
+ COLUMN_EDNS0_ECS,
+ COLUMN_EDNS0_ECS_FAMILY,
+ COLUMN_EDNS0_ECS_SOURCE,
+ COLUMN_EDNS0_ECS_SCOPE,
+ COLUMN_EDNS0_ECS_ADDRESS,
+ };
+
+ Parse_dns(bool escape_dnsnames);
+
+ virtual void on_table_created(Table* table, const std::vector<int>& columns);
+ virtual Packet::ParseResult parse(Packet& packet, const std::vector<int>& columns, Row& destination_row, bool sample);
+
+ void add_packet_columns();
+ void add_lookup_tables();
+
+private:
+ Str_conv converter;
+
+ IP_header_to_table m_ip_helper;
+
+ Int_accessor acc_s;
+ Int_accessor acc_us;
+ Int_accessor acc_ether_type;
+ Int_accessor acc_protocol;
+ Int_accessor acc_src_port;
+ Int_accessor acc_msg_id;
+ Int_accessor acc_msg_size;
+ Int_accessor acc_opcode;
+ Int_accessor acc_rcode;
+ Int_accessor acc_extended_rcode;
+ Int_accessor acc_edns_version;
+ Int_accessor acc_z;
+ Int_accessor acc_udp_size;
+ Int_accessor acc_qd_count;
+ Int_accessor acc_an_count;
+ Int_accessor acc_ns_count;
+ Int_accessor acc_ar_count;
+ Int_accessor acc_qtype;
+ Int_accessor acc_qclass;
+ Int_accessor acc_qlabels;
+ Int_accessor acc_atype;
+ Int_accessor acc_aclass;
+ Int_accessor acc_attl;
+ Int_accessor acc_alabels;
+ Int_accessor acc_edns0_ecs_family;
+ Int_accessor acc_edns0_ecs_source;
+ Int_accessor acc_edns0_ecs_scope;
+ Bool_accessor acc_qr;
+ Bool_accessor acc_aa;
+ Bool_accessor acc_tc;
+ Bool_accessor acc_rd;
+ Bool_accessor acc_cd;
+ Bool_accessor acc_ra;
+ Bool_accessor acc_ad;
+ Bool_accessor acc_do;
+ Bool_accessor acc_edns0;
+ Bool_accessor acc_edns0_ecs;
+ Text_accessor acc_qname;
+ Text_accessor acc_aname;
+ Text_accessor acc_src_addr;
+ Text_accessor acc_dst_addr;
+ Text_accessor acc_edns0_ecs_address;
+};
+
+} // namespace packetq
+
+#endif // __packetq_dns_h
diff --git a/src/icmp.cpp b/src/icmp.cpp
new file mode 100644
index 0000000..108d66d
--- /dev/null
+++ b/src/icmp.cpp
@@ -0,0 +1,238 @@
+/*
+ * Copyright (c) 2017-2024 OARC, Inc.
+ * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+ * All rights reserved.
+ *
+ * This file is part of PacketQ.
+ *
+ * PacketQ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PacketQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "icmp.h"
+#include "output.h"
+#include "packet_handler.h"
+#include "packetq.h"
+#include "tcp.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+namespace packetq {
+
+Parse_icmp::Parse_icmp()
+{
+ table_name = "icmp";
+
+ add_packet_columns();
+ add_lookup_tables();
+}
+
+void Parse_icmp::add_packet_columns()
+{
+ m_ip_helper.add_packet_columns(*this);
+
+ add_packet_column("type", "", Coltype::_int, COLUMN_TYPE);
+ add_packet_column("code", "", Coltype::_int, COLUMN_CODE);
+ add_packet_column("echo_identifier", "", Coltype::_int, COLUMN_ECHO_IDENTIFIER);
+ add_packet_column("echo_sequence", "", Coltype::_int, COLUMN_ECHO_SEQUENCE);
+ add_packet_column("du_protocol", "", Coltype::_int, COLUMN_DU_PROTOCOL);
+ add_packet_column("du_src_addr", "", Coltype::_text, COLUMN_DU_SRC_ADDR);
+ add_packet_column("du_dst_addr", "", Coltype::_text, COLUMN_DU_DST_ADDR);
+ add_packet_column("desc", "", Coltype::_text, COLUMN_DESC);
+}
+
+void Parse_icmp::add_lookup_tables()
+{
+}
+
+Packet::ParseResult Parse_icmp::parse(Packet& packet, const std::vector<int>& columns, Row& destination_row, bool sample)
+{
+ if (packet.m_ip_header.proto != IPPROTO_ICMP)
+ return Packet::ERROR;
+ if (packet.m_ip_header.ethertype != 2048) // we dont support ICMPv6 yet
+ return Packet::ERROR;
+
+ if (!sample)
+ return Packet::NOT_SAMPLED;
+
+ if (packet.m_len < 2)
+ return Packet::ERROR;
+
+ Row* r = &destination_row;
+
+ m_ip_helper.assign(r, &packet.m_ip_header, columns);
+
+ unsigned char* raw = packet.m_data;
+ int type = raw[0];
+ int code = raw[1];
+ int identifier = 0, sequence = 0, protocol = 0;
+ RefCountString *src_addr = 0, *dst_addr = 0;
+ bool src_addr_used = false, dst_addr_used = false;
+
+ switch (type) {
+ case 0:
+ if (packet.m_len < 8)
+ return Packet::ERROR;
+ identifier = get_short(&raw[4]);
+ sequence = get_short(&raw[6]);
+ break;
+ case 3: {
+ IP_header head;
+ if (packet.m_len < 8 + 20)
+ return Packet::ERROR;
+ head.decode(&raw[8], packet.m_ip_header.ethertype, 0);
+ protocol = head.proto;
+ src_addr = v4_addr2str(head.src_ip);
+ dst_addr = v4_addr2str(head.dst_ip);
+ } break;
+ case 8:
+ if (packet.m_len < 8)
+ return Packet::ERROR;
+ identifier = get_short(&raw[4]);
+ sequence = get_short(&raw[6]);
+ break;
+ }
+
+ for (auto i = columns.begin(), end = columns.end(); i != end; ++i) {
+ switch (*i) {
+ case COLUMN_TYPE:
+ acc_type.value(r) = type;
+ break;
+
+ case COLUMN_CODE:
+ acc_code.value(r) = code;
+ break;
+
+ case COLUMN_ECHO_IDENTIFIER:
+ acc_echo_identifier.value(r) = identifier;
+ break;
+
+ case COLUMN_ECHO_SEQUENCE:
+ acc_echo_sequence.value(r) = sequence;
+ break;
+
+ case COLUMN_DU_PROTOCOL:
+ acc_du_protocol.value(r) = protocol;
+ break;
+
+ case COLUMN_DU_SRC_ADDR:
+ acc_du_src_addr.value(r) = src_addr ? src_addr : RefCountString::construct("");
+ src_addr_used = true;
+ break;
+
+ case COLUMN_DU_DST_ADDR:
+ acc_du_dst_addr.value(r) = dst_addr ? dst_addr : RefCountString::construct("");
+ dst_addr_used = true;
+ break;
+
+ case COLUMN_DESC:
+ switch (type) {
+ case 0:
+ acc_desc.value(r) = RefCountString::construct("Echo Reply");
+ break;
+ case 3:
+ switch (code) {
+ case 0:
+ acc_desc.value(r) = RefCountString::construct("Destination network unreachable");
+ break;
+ case 1:
+ acc_desc.value(r) = RefCountString::construct("Destination host unreachable");
+ break;
+ case 2:
+ acc_desc.value(r) = RefCountString::construct("Destination protocol unreachable");
+ break;
+ case 3:
+ acc_desc.value(r) = RefCountString::construct("Destination port unreachable");
+ break;
+ default:
+ acc_desc.value(r) = RefCountString::construct("Destination unreachable");
+ break;
+ }
+ break;
+ case 4:
+ acc_desc.value(r) = RefCountString::construct("Source quench");
+ break;
+ case 5:
+ acc_desc.value(r) = RefCountString::construct("Redirect Message");
+ break;
+ case 6:
+ acc_desc.value(r) = RefCountString::construct("Alternate Host Address");
+ break;
+ case 8:
+ acc_desc.value(r) = RefCountString::construct("Echo Request");
+ break;
+ case 9:
+ acc_desc.value(r) = RefCountString::construct("Router Advertisement");
+ break;
+ case 10:
+ acc_desc.value(r) = RefCountString::construct("Router Solicitation");
+ break;
+ case 11:
+ acc_desc.value(r) = RefCountString::construct("Time Exceeded");
+ break;
+ case 12:
+ acc_desc.value(r) = RefCountString::construct("Bad IP header");
+ break;
+ case 13:
+ acc_desc.value(r) = RefCountString::construct("Timestamp");
+ break;
+ case 14:
+ acc_desc.value(r) = RefCountString::construct("Timestamp Reply");
+ break;
+ case 15:
+ acc_desc.value(r) = RefCountString::construct("Information Request");
+ break;
+ case 16:
+ acc_desc.value(r) = RefCountString::construct("Information Reply");
+ break;
+ case 17:
+ acc_desc.value(r) = RefCountString::construct("Address Mask Request");
+ break;
+ case 18:
+ acc_desc.value(r) = RefCountString::construct("Address Mask Reply");
+ break;
+ case 30:
+ acc_desc.value(r) = RefCountString::construct("Traceroute");
+ break;
+ default:
+ acc_desc.value(r) = RefCountString::construct("UNKNOWN TYPE");
+ }
+ break;
+ }
+ }
+
+ if (src_addr && !src_addr_used)
+ src_addr->dec_refcount();
+ if (dst_addr && !dst_addr_used)
+ dst_addr->dec_refcount();
+
+ return Packet::OK;
+}
+
+void Parse_icmp::on_table_created(Table* table, const std::vector<int>& columns)
+{
+ m_ip_helper.on_table_created(table, columns);
+
+ acc_type = table->get_accessor<int_column>("type");
+ acc_code = table->get_accessor<int_column>("code");
+ acc_echo_identifier = table->get_accessor<int_column>("echo_identifier");
+ acc_echo_sequence = table->get_accessor<int_column>("echo_sequence");
+ acc_du_protocol = table->get_accessor<int_column>("du_protocol");
+ acc_du_src_addr = table->get_accessor<text_column>("du_src_addr");
+ acc_du_dst_addr = table->get_accessor<text_column>("du_dst_addr");
+ acc_desc = table->get_accessor<text_column>("desc");
+}
+
+} // namespace packetq
diff --git a/src/icmp.h b/src/icmp.h
new file mode 100644
index 0000000..0528849
--- /dev/null
+++ b/src/icmp.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2017-2024 OARC, Inc.
+ * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+ * All rights reserved.
+ *
+ * This file is part of PacketQ.
+ *
+ * PacketQ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PacketQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __packetq_icmp_h
+#define __packetq_icmp_h
+
+#include <assert.h>
+#include <cctype>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "output.h"
+#include "packet_handler.h"
+#include "sql.h"
+#include "tcp.h"
+
+namespace packetq {
+
+class Parse_icmp : public Packet_handler {
+public:
+ enum {
+ COLUMN_TYPE = IP_header_to_table::COLUMN_FRAGMENTS + 1,
+ COLUMN_CODE,
+ COLUMN_ECHO_IDENTIFIER,
+ COLUMN_ECHO_SEQUENCE,
+ COLUMN_DU_PROTOCOL,
+ COLUMN_DU_SRC_ADDR,
+ COLUMN_DU_DST_ADDR,
+ COLUMN_DESC
+ };
+
+ Parse_icmp();
+
+ virtual void on_table_created(Table* table, const std::vector<int>& columns);
+ virtual Packet::ParseResult parse(Packet& packet, const std::vector<int>& columns, Row& destination_row, bool sample);
+
+ void add_packet_columns();
+ void add_lookup_tables();
+
+private:
+ Str_conv converter;
+
+ IP_header_to_table m_ip_helper;
+
+ Int_accessor acc_type;
+ Int_accessor acc_code;
+ Int_accessor acc_echo_identifier;
+ Int_accessor acc_echo_sequence;
+ Int_accessor acc_du_protocol;
+ Text_accessor acc_du_src_addr;
+ Text_accessor acc_du_dst_addr;
+ Text_accessor acc_desc;
+};
+
+} // namespace packetq
+
+#endif // __packetq_icmp_h
diff --git a/src/output.h b/src/output.h
new file mode 100644
index 0000000..ca526de
--- /dev/null
+++ b/src/output.h
@@ -0,0 +1,415 @@
+/*
+ * Copyright (c) 2017-2024 OARC, Inc.
+ * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+ * All rights reserved.
+ *
+ * This file is part of PacketQ.
+ *
+ * PacketQ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PacketQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __packetq_output_h
+#define __packetq_output_h
+
+#include <stdio.h>
+#include <stdlib.h>
+
+namespace packetq {
+
+class Output {
+ char m_buffer[0x10000];
+ char m_diglut[0x100][4];
+ int m_len;
+ int m_tot;
+
+public:
+ Output()
+ {
+ for (int i = 0; i < 256; i++)
+ snprintf(m_diglut[i], 4, "%d", i);
+
+ m_len = 0;
+ m_tot = 0;
+ }
+ ~Output()
+ {
+ // print();
+ }
+ void reset() { m_len = 0; }
+ void print()
+ {
+ if (!m_len)
+ return;
+ fwrite(m_buffer, m_len, 1, stdout);
+ m_tot += m_len;
+ m_len = 0;
+ }
+ void flush()
+ {
+ print();
+ fflush(stdout);
+ }
+ inline void add_q_string(const char* p)
+ {
+ m_buffer[m_len++] = '"';
+ add_string_esc_json(p);
+ m_buffer[m_len++] = '"';
+ }
+ inline void add_string_esc_json(const char* p)
+ {
+ static const char lut[] = "0123456789ABCDEF";
+ if (m_len > sizeof(m_buffer) / 2)
+ print();
+ char c;
+ while ((c = *p++)) {
+ if (c == '\\') {
+ m_buffer[m_len++] = '\\';
+ c = '\\';
+ } else if (c == '"') {
+ m_buffer[m_len++] = '\\';
+ } else if (c < 0x20) {
+ m_buffer[m_len++] = '\\';
+ m_buffer[m_len++] = 'u';
+ m_buffer[m_len++] = '0';
+ m_buffer[m_len++] = '0';
+ m_buffer[m_len++] = lut[(c >> 4) & 0xf];
+ m_buffer[m_len++] = lut[c & 0xf];
+ continue;
+ }
+ m_buffer[m_len++] = c;
+ }
+ }
+ inline void add_string_esc_xml(const char* p)
+ {
+ if (m_len > sizeof(m_buffer) / 2)
+ print();
+ char c;
+ while ((c = *p++) > 'A') {
+ m_buffer[m_len++] = c;
+ }
+ if (c == 0)
+ return;
+ p--;
+ while ((c = *p++)) {
+ if (c == '>') {
+ m_buffer[m_len++] = '&';
+ add_string("gt");
+ c = ';';
+ }
+ if (c == '<') {
+ m_buffer[m_len++] = '&';
+ add_string("lt");
+ c = ';';
+ }
+ if (c == '\'') {
+ m_buffer[m_len++] = '&';
+ add_string("apos");
+ c = ';';
+ }
+ if (c == '"') {
+ m_buffer[m_len++] = '&';
+ add_string("quot");
+ c = ';';
+ }
+ m_buffer[m_len++] = c;
+ if (c == '&') {
+ add_string("amp;");
+ }
+ }
+ }
+ inline void check()
+ {
+ if (m_len > sizeof(m_buffer) / 2)
+ print();
+ }
+ inline void add_string(const char* p)
+ {
+ check();
+ char c;
+ while ((c = *p++)) {
+ m_buffer[m_len++] = c;
+ }
+ }
+
+ inline void add_hex_ushort(unsigned short v)
+ {
+ static const char lut[] = "0123456789abcdef";
+ if (v & 0xf000) {
+ m_buffer[m_len++] = lut[v >> 12];
+ m_buffer[m_len++] = lut[(v >> 8) & 0xf];
+ m_buffer[m_len++] = lut[(v >> 4) & 0xf];
+ m_buffer[m_len++] = lut[v & 0xf];
+ return;
+ }
+ if (v & 0xf00) {
+ m_buffer[m_len++] = lut[(v >> 8) & 0xf];
+ m_buffer[m_len++] = lut[(v >> 4) & 0xf];
+ m_buffer[m_len++] = lut[v & 0xf];
+ return;
+ }
+ if (v & 0xf0) {
+ m_buffer[m_len++] = lut[(v >> 4) & 0xf];
+ m_buffer[m_len++] = lut[v & 0xf];
+ return;
+ }
+ m_buffer[m_len++] = lut[v & 0xf];
+ }
+
+ inline void add_attr_ipv6(const char* name, unsigned char* addr)
+ {
+ check();
+ add_string_q(name);
+ m_buffer[m_len++] = '=';
+ m_buffer[m_len++] = '"';
+
+ unsigned short digs[8];
+ unsigned char* p = addr;
+ int longest_run = 0;
+ int longest_p = 9;
+ int cur_run = 0;
+ for (int i = 0; i < 8; i++) {
+ digs[i] = ((unsigned short)(p[0]) << 8) | (unsigned short)(p[1]);
+ if (digs[i] == 0) {
+ cur_run++;
+ if ((cur_run > 1) && (cur_run > longest_run)) {
+ longest_run = cur_run;
+ longest_p = i + 1 - cur_run;
+ }
+ } else
+ cur_run = 0;
+
+ p += 2;
+ }
+
+ for (int i = 0; i < 8; i++) {
+ if (i >= longest_p && i < longest_p + longest_run) {
+ if (i == longest_p) {
+ if (i == 0)
+ m_buffer[m_len++] = ':';
+ m_buffer[m_len++] = ':';
+ }
+ } else {
+ add_hex_ushort(digs[i]);
+ if (i != 7)
+ m_buffer[m_len++] = ':';
+ }
+ }
+
+ m_buffer[m_len++] = '"';
+ m_buffer[m_len++] = ' ';
+ }
+
+ inline void add_attr_ipv4(const char* p, unsigned int i)
+ {
+ check();
+ add_string_q(p);
+ m_buffer[m_len++] = '=';
+ m_buffer[m_len++] = '"';
+ add_string_q(m_diglut[i & 255]);
+ m_buffer[m_len++] = '.';
+ add_string_q(m_diglut[(i >> 8) & 255]);
+ m_buffer[m_len++] = '.';
+ add_string_q(m_diglut[(i >> 16) & 255]);
+ m_buffer[m_len++] = '.';
+ add_string_q(m_diglut[(i >> 24)]);
+ m_buffer[m_len++] = '"';
+ m_buffer[m_len++] = ' ';
+ }
+
+ inline void add_attr_bool(const char* p, bool i)
+ {
+ if (!i)
+ return;
+ check();
+ add_string_q(p);
+ m_buffer[m_len++] = '=';
+ m_buffer[m_len++] = '"';
+ m_buffer[m_len++] = i ? '1' : '0';
+ m_buffer[m_len++] = '"';
+ m_buffer[m_len++] = ' ';
+ }
+
+ inline void add_int(unsigned int i)
+ {
+ check();
+ if (i < 256) {
+ add_string_q(m_diglut[i & 255]);
+ } else {
+ unsigned char d[64];
+
+ unsigned char* cd = d;
+ while (i > 0 && cd < (&d[0] + sizeof(d))) {
+ unsigned int n = i;
+ i = i / 100;
+ n = n - (i * 100);
+ *cd++ = n;
+ }
+ if (cd != d) {
+ unsigned char t = *--cd;
+ add_string_q(m_diglut[t]);
+ }
+ while (cd != d) {
+ unsigned char t = *--cd;
+ if (t >= 10)
+ add_string_q(m_diglut[t]);
+ else {
+ m_buffer[m_len++] = '0';
+ m_buffer[m_len++] = '0' + t;
+ }
+ }
+ }
+ }
+ inline void add_attr_int(const char* p, unsigned int i)
+ {
+ check();
+ if (i == 0)
+ return;
+ add_string_q(p);
+ m_buffer[m_len++] = '=';
+ m_buffer[m_len++] = '"';
+ add_int(i);
+ m_buffer[m_len++] = '"';
+ m_buffer[m_len++] = ' ';
+ }
+
+ inline void add_attr_str(const char* p, const char* t)
+ {
+ add_string(p);
+ m_buffer[m_len++] = '=';
+ m_buffer[m_len++] = '"';
+ add_string_esc_json(t);
+ m_buffer[m_len++] = '"';
+ m_buffer[m_len++] = ' ';
+ }
+
+private:
+ inline void add_string_q(const char* p)
+ {
+ char c;
+ while ((c = *p++)) {
+ m_buffer[m_len++] = c;
+ }
+ }
+};
+
+class Str_conv {
+ char m_buffer[0x10000];
+ char m_diglut[0x100][4];
+ int m_len;
+ int m_tot;
+
+public:
+ Str_conv()
+ {
+ for (int i = 0; i < 256; i++)
+ snprintf(m_diglut[i], 4, "%d", i);
+
+ m_len = 0;
+ m_tot = 0;
+ }
+ ~Str_conv()
+ {
+ // print();
+ }
+ const char* get()
+ {
+ m_buffer[m_len] = 0;
+ return m_buffer;
+ }
+ int get_len() { return m_len; }
+ void reset() { m_len = 0; }
+ inline void add_attr_ipv6(unsigned char* addr)
+ {
+ unsigned short digs[8];
+ unsigned char* p = &addr[14];
+ int longest_run = 0;
+ int longest_p = 9;
+ int cur_run = 0;
+ for (int i = 0; i < 8; i++) {
+ digs[i] = ((unsigned short)(p[1]) << 8) | (unsigned short)(p[0]);
+ if (digs[i] == 0) {
+ cur_run++;
+ if ((cur_run > 1) && (cur_run > longest_run)) {
+ longest_run = cur_run;
+ longest_p = i + 1 - cur_run;
+ }
+ } else
+ cur_run = 0;
+
+ p -= 2;
+ }
+
+ for (int i = 0; i < 8; i++) {
+ if (i >= longest_p && i < longest_p + longest_run) {
+ if (i == longest_p) {
+ if (i == 0)
+ m_buffer[m_len++] = ':';
+ m_buffer[m_len++] = ':';
+ }
+ } else {
+ add_hex_ushort(digs[i]);
+ if (i != 7)
+ m_buffer[m_len++] = ':';
+ }
+ }
+ }
+
+ inline void add_string_q(const char* p)
+ {
+ char c;
+ while ((c = *p++)) {
+ m_buffer[m_len++] = c;
+ }
+ }
+
+ inline void add_attr_ipv4(unsigned int i)
+ {
+ add_string_q(m_diglut[(i >> 24)]);
+ m_buffer[m_len++] = '.';
+ add_string_q(m_diglut[(i >> 16) & 255]);
+ m_buffer[m_len++] = '.';
+ add_string_q(m_diglut[(i >> 8) & 255]);
+ m_buffer[m_len++] = '.';
+ add_string_q(m_diglut[(i >> 0) & 255]);
+ }
+ inline void add_hex_ushort(unsigned short v)
+ {
+ static const char lut[] = "0123456789abcdef";
+ if (v & 0xf000) {
+ m_buffer[m_len++] = lut[v >> 12];
+ m_buffer[m_len++] = lut[(v >> 8) & 0xf];
+ m_buffer[m_len++] = lut[(v >> 4) & 0xf];
+ m_buffer[m_len++] = lut[v & 0xf];
+ return;
+ }
+ if (v & 0xf00) {
+ m_buffer[m_len++] = lut[(v >> 8) & 0xf];
+ m_buffer[m_len++] = lut[(v >> 4) & 0xf];
+ m_buffer[m_len++] = lut[v & 0xf];
+ return;
+ }
+ if (v & 0xf0) {
+ m_buffer[m_len++] = lut[(v >> 4) & 0xf];
+ m_buffer[m_len++] = lut[v & 0xf];
+ return;
+ }
+ m_buffer[m_len++] = lut[v & 0xf];
+ }
+};
+
+extern Output g_output;
+
+} // namespace packetq
+
+#endif // __packetq_output_h
diff --git a/src/packet_handler.cpp b/src/packet_handler.cpp
new file mode 100644
index 0000000..df21e9f
--- /dev/null
+++ b/src/packet_handler.cpp
@@ -0,0 +1,566 @@
+/*
+ * Copyright (c) 2017-2024 OARC, Inc.
+ * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+ * All rights reserved.
+ *
+ * This file is part of PacketQ.
+ *
+ * PacketQ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PacketQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "packet_handler.h"
+
+#include "dns.h"
+#include "icmp.h"
+#include "output.h"
+#include "packetq.h"
+#include "sql.h"
+#include "tcp.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+namespace packetq {
+
+Payload g_payload;
+Output g_output;
+Str_conv converter;
+
+class Fragments {
+private:
+ Fragments& operator=(const Fragments& other);
+ Fragments(Fragments&& other) noexcept;
+ Fragments const& operator=(Fragments&& other);
+
+public:
+ class Range {
+ public:
+ bool operator<(const Range& r) const
+ {
+ if (begin < r.begin)
+ return true;
+ return false;
+ }
+ Range(int s, int e1)
+ {
+ begin = s;
+ endp1 = e1;
+ }
+ int begin;
+ int endp1;
+ };
+ Fragments(const Fragments& f)
+ {
+ // printf( "copy Fragments\n" );
+ m_first = f.m_first;
+ m_complete = f.m_complete;
+ m_frags = f.m_frags;
+ }
+ Fragments()
+ {
+ m_complete = 0;
+ m_frags = 0;
+ }
+ ~Fragments()
+ {
+ // printf( "delete Fragments\n", m_first.offset );
+ }
+ bool add(IP_header& head, unsigned char* data, int len)
+ {
+ if (head.offset == 0)
+ m_first = head;
+ if (head.offset + len > 0x10000 || len < 0)
+ return false;
+ m_frags++;
+ if (head.fragments == 0)
+ m_complete = head.offset + len;
+ bool complete = add_range(head.offset, head.offset + len);
+ memcpy((void*)&m_buffer[head.offset], data, len);
+ if (complete) {
+ m_complete = head.offset + len;
+ m_first.fragments = m_frags;
+ return true;
+ }
+ return false;
+ }
+ bool add_range(int start, int end)
+ {
+
+ m_ranges.push_back(Range(start, end));
+ m_ranges.sort();
+ bool merged = true;
+ // this is algorithmically horrid (hope there wont be tonnes of fragments)
+ while (merged) {
+ merged = false;
+ auto it = m_ranges.begin();
+ auto last = it;
+ if (last == m_ranges.end())
+ break;
+ it++;
+ for (; it != m_ranges.end(); it++) {
+ if (last->endp1 == it->begin) {
+ merged = true;
+ last->endp1 = it->endp1;
+ m_ranges.erase(it);
+ break;
+ }
+ }
+ }
+ if (m_ranges.size() == 1 && m_ranges.begin()->endp1 == m_complete && m_ranges.begin()->begin == 0)
+ return true;
+ return false;
+ }
+
+ std::list<Range> m_ranges;
+ int m_complete;
+ int m_frags;
+ IP_header m_first;
+ unsigned char m_buffer[0x10000];
+};
+
+class Ident {
+public:
+ bool operator<(const Ident& rhs) const
+ {
+ if (m_ident < rhs.m_ident)
+ return true;
+ if (m_ident > rhs.m_ident)
+ return false;
+ for (int i = 0; i < 4; i++) {
+ if (m_src_ip.__in6_u.__u6_addr32[i] < rhs.m_src_ip.__in6_u.__u6_addr32[i])
+ return true;
+ if (m_src_ip.__in6_u.__u6_addr32[i] > rhs.m_src_ip.__in6_u.__u6_addr32[i])
+ return false;
+ }
+ if (m_protocol < rhs.m_protocol)
+ return true;
+ if (m_protocol > rhs.m_protocol)
+ return false;
+ for (int i = 0; i < 4; i++) {
+ if (m_dst_ip.__in6_u.__u6_addr32[i] < rhs.m_dst_ip.__in6_u.__u6_addr32[i])
+ return true;
+ if (m_dst_ip.__in6_u.__u6_addr32[i] > rhs.m_dst_ip.__in6_u.__u6_addr32[i])
+ return false;
+ }
+ return false;
+ }
+ in6addr_t m_dst_ip;
+ in6addr_t m_src_ip;
+ int m_ident;
+ int m_protocol;
+};
+
+class FragmentHandler {
+public:
+ void add_fragment(IP_header& head, unsigned char* data, int len, Packet& p)
+ {
+ Ident i;
+ i.m_src_ip = head.src_ip;
+ i.m_dst_ip = head.dst_ip;
+ i.m_protocol = head.proto;
+ i.m_ident = head.ident;
+ Fragments& frag = m_fragments[i];
+ if (frag.add(head, data, len)) {
+ p.m_ip_header = frag.m_first;
+ p.parse_transport(frag.m_buffer, frag.m_complete);
+ m_fragments.erase(i);
+ }
+ }
+ std::map<Ident, Fragments> m_fragments;
+};
+
+FragmentHandler m_fraghandler;
+
+void IP_header::reset()
+{
+ memset(&src_ip, 0, sizeof(in6addr_t));
+ memset(&dst_ip, 0, sizeof(in6addr_t));
+ fragments = 0;
+ offset = 0;
+ ident = 0;
+ s = 0;
+ us = 0;
+ ethertype = 0;
+ src_port = 0;
+ dst_port = 0;
+ proto = 0;
+ ip_ttl = 0;
+ ip_version = 0;
+ id = 0;
+ length = 0;
+}
+
+int IP_header::decode(unsigned char* data, int itype, int i_id)
+{
+ reset();
+ ethertype = itype;
+ id = i_id;
+ int len = 0;
+ // ether frame done (ignored mac's)
+ // ip
+
+ ip_version = data[0] >> 4;
+ proto = 0;
+ if (ip_version == 4) {
+ if (ethertype == 0)
+ ethertype = 0x800;
+
+ int header_len = (data[0] & 0xf) * 4;
+ proto = data[9];
+ ip_ttl = data[8];
+
+ src_ip.__in6_u.__u6_addr32[3] = get_int(&data[12]);
+ dst_ip.__in6_u.__u6_addr32[3] = get_int(&data[16]);
+
+ int totallen = get_short(&data[2]);
+ length = totallen - header_len;
+ int flags = get_short(&data[6]);
+ offset = (flags & 0x1fff) << 3;
+ flags >>= 13;
+ if (flags & 1)
+ fragments = 1;
+ data += header_len;
+ len += header_len;
+ } else if (ip_version == 6) {
+ if (ethertype == 0)
+ ethertype = 0x86DD;
+
+ proto = data[6];
+ ip_ttl = data[7];
+
+ src_ip.__in6_u.__u6_addr32[3] = get_int(&data[8]);
+ src_ip.__in6_u.__u6_addr32[2] = get_int(&data[12]);
+ src_ip.__in6_u.__u6_addr32[1] = get_int(&data[16]);
+ src_ip.__in6_u.__u6_addr32[0] = get_int(&data[20]);
+
+ dst_ip.__in6_u.__u6_addr32[3] = get_int(&data[24]);
+ dst_ip.__in6_u.__u6_addr32[2] = get_int(&data[28]);
+ dst_ip.__in6_u.__u6_addr32[1] = get_int(&data[32]);
+ dst_ip.__in6_u.__u6_addr32[0] = get_int(&data[36]);
+
+ data += 40;
+ len += 40;
+
+ // process next headers - NOTE: there are 6 not 4
+ while (proto == 0 || proto == 43 || proto == 44 || proto == 60) {
+ if (proto == 44) {
+ offset = get_short(&data[2]) & 0xfff8;
+ fragments = get_short(&data[2]) & 1;
+ }
+ proto = data[0];
+ int hdr_len = data[1] + 8;
+ data += hdr_len;
+ len += hdr_len;
+ }
+ } else {
+ return 0;
+ }
+
+ return len;
+}
+
+std::vector<Packet_handler*> packet_handlers;
+
+Packet::ParseResult Packet::parse(Packet_handler* handler, const std::vector<int>& columns, Row& destination_row, bool sample)
+{
+ bool base_layers_parsed;
+ if (m_link_layer_type == 1)
+ base_layers_parsed = parse_ethernet();
+ else if (m_link_layer_type == 113)
+ base_layers_parsed = parse_sll();
+ else
+ base_layers_parsed = parse_ip(m_data, m_len, 0);
+
+ if (!base_layers_parsed)
+ return ERROR;
+
+ // do the application layer
+ return handler->parse(*this, columns, destination_row, sample);
+}
+
+bool Packet::parse_ethernet()
+{
+ unsigned char* data = m_data;
+ int len = m_len;
+ if (len < 14)
+ return false; // check for etherframe size
+
+ int ethertype = data[13] | (data[12] << 8);
+ if (ethertype == 0x8100) {
+ if (len < 18)
+ return false; // check for etherframe size + VLAN tag
+ ethertype = data[17] | (data[16] << 8);
+ data += 18;
+ len -= 18;
+ } else {
+ data += 14;
+ len -= 14;
+ }
+
+ return parse_ip(data, len, ethertype);
+}
+
+bool Packet::parse_sll()
+{
+ unsigned char* data = m_data;
+ int len = m_len;
+ if (len < 16)
+ return false; // check for LINUX_SLL size
+
+ int ethertype = data[15] | (data[14] << 8);
+ if (ethertype == 0x8100) {
+ if (len < 20)
+ return false; // check for etherframe size + VLAN tag
+ ethertype = data[19] | (data[18] << 8);
+ data += 20;
+ len -= 20;
+ } else {
+ data += 16;
+ len -= 16;
+ }
+
+ return parse_ip(data, len, ethertype);
+}
+
+bool Packet::parse_ip(unsigned char* data, int len, int ethertype)
+{
+ if (len < 5 * 4)
+ return false; // check for etherframe size + ipv4 header
+
+ int consumed = m_ip_header.decode(data, ethertype, m_id);
+ m_ip_header.s = m_s;
+ m_ip_header.us = m_us;
+ data += consumed;
+ len -= consumed;
+ if (m_ip_header.fragments > 0 || m_ip_header.offset > 0) {
+ m_fraghandler.add_fragment(m_ip_header, data, len, *this);
+ return false;
+ }
+
+ return parse_transport(data, len);
+}
+
+bool Packet::parse_transport(unsigned char* data, int len)
+{
+ // tcp/udp
+ if (m_ip_header.proto == IPPROTO_TCP) {
+ if (len < 14)
+ return false;
+
+ m_ip_header.src_port = get_short(data);
+ m_ip_header.dst_port = get_short(&data[2]);
+
+ int seq = get_int(&data[4]);
+ int ack = get_int(&data[8]);
+
+ int dataoffs = 4 * (data[12] >> 4);
+
+ unsigned char bits = data[13];
+ char syn = (bits >> 1) & 1;
+ char fin = (bits >> 0) & 1;
+ char rst = (bits >> 2) & 1;
+
+ // get the assembled TCP packet and remove the individual segments.
+ data += dataoffs;
+ len -= dataoffs;
+ if (len < 0) {
+ fprintf(stderr, "Warning: Found TCP packet with bad length\n");
+ return false;
+ }
+
+ unsigned int rest = len;
+ data = assemble_tcp(g_payload, &m_ip_header.src_ip, &m_ip_header.dst_ip, m_ip_header.src_port, m_ip_header.dst_port, &rest, seq, data, rest, syn, fin, rst, ack);
+ len = rest;
+ } else if (m_ip_header.proto == IPPROTO_UDP) {
+ if (len < 4)
+ return false;
+
+ m_ip_header.src_port = get_short(data);
+ m_ip_header.dst_port = get_short(&data[2]);
+
+ data += 8;
+ len -= 8;
+
+ if (len < 0) {
+ fprintf(stderr, "Warning: Found UDP packet with bad length\n");
+ return false;
+ }
+ }
+
+ if (data) {
+ m_data = data;
+ m_len = len;
+ return true;
+ }
+
+ return false;
+}
+
+Table* Packet_handler::create_table(const std::vector<int>& columns)
+{
+ Table* table = g_db.create_table(table_name);
+
+ for (auto i = packet_columns.begin(); i != packet_columns.end(); ++i)
+ if (std::find(columns.begin(), columns.end(), i->id) != columns.end())
+ table->add_column(i->name, i->type, i->id);
+
+ on_table_created(table, columns);
+
+ return table;
+}
+
+void Packet_handler::add_packet_column(const char* name, const char* description, Coltype::Type type, int id)
+{
+ Packet_column c;
+ c.name = name;
+ c.description = description;
+ c.id = id;
+ c.type = type;
+ packet_columns.push_back(c);
+}
+
+void init_packet_handlers(bool escape_dnsnames)
+{
+ packet_handlers.push_back(new Parse_dns(escape_dnsnames));
+ packet_handlers.push_back(new Parse_icmp());
+}
+
+void destroy_packet_handlers()
+{
+ for (auto i = packet_handlers.begin(); i != packet_handlers.end(); ++i)
+ delete *i;
+ packet_handlers.clear();
+}
+
+Packet_handler* get_packet_handler(std::string table_name)
+{
+ for (auto i = packet_handlers.begin(); i != packet_handlers.end(); ++i) {
+ if (table_name == (*i)->table_name)
+ return *i;
+ }
+
+ return 0;
+}
+
+void IP_header_to_table::add_packet_columns(Packet_handler& packet_handler)
+{
+ packet_handler.add_packet_column("id", "ID", Coltype::_int, COLUMN_ID);
+ packet_handler.add_packet_column("s", "Seconds", Coltype::_int, COLUMN_S);
+ packet_handler.add_packet_column("us", "Milliseconds", Coltype::_int, COLUMN_US);
+ packet_handler.add_packet_column("ether_type", "", Coltype::_int, COLUMN_ETHER_TYPE);
+ packet_handler.add_packet_column("src_port", "", Coltype::_int, COLUMN_SRC_PORT); // this is really tcp/udp but accidents do happen
+ packet_handler.add_packet_column("dst_port", "", Coltype::_int, COLUMN_DST_PORT);
+ packet_handler.add_packet_column("src_addr", "", Coltype::_text, COLUMN_SRC_ADDR);
+ packet_handler.add_packet_column("dst_addr", "", Coltype::_text, COLUMN_DST_ADDR);
+ packet_handler.add_packet_column("protocol", "", Coltype::_int, COLUMN_PROTOCOL);
+ packet_handler.add_packet_column("ip_ttl", "", Coltype::_int, COLUMN_IP_TTL);
+ packet_handler.add_packet_column("ip_version", "", Coltype::_int, COLUMN_IP_VERSION);
+ packet_handler.add_packet_column("fragments", "", Coltype::_int, COLUMN_FRAGMENTS);
+}
+
+void IP_header_to_table::on_table_created(Table* table, const std::vector<int>& columns)
+{
+ acc_src_addr = table->get_accessor<text_column>("src_addr");
+ acc_dst_addr = table->get_accessor<text_column>("dst_addr");
+ acc_ether_type = table->get_accessor<int_column>("ether_type");
+ acc_protocol = table->get_accessor<int_column>("protocol");
+ acc_ip_ttl = table->get_accessor<int_column>("ip_ttl");
+ acc_ip_version = table->get_accessor<int_column>("ip_version");
+ acc_src_port = table->get_accessor<int_column>("src_port");
+ acc_dst_port = table->get_accessor<int_column>("dst_port");
+ acc_s = table->get_accessor<int_column>("s");
+ acc_us = table->get_accessor<int_column>("us");
+ acc_id = table->get_accessor<int_column>("id");
+ acc_fragments = table->get_accessor<int_column>("fragments");
+}
+
+void IP_header_to_table::assign(Row* row, IP_header* head, const std::vector<int>& columns)
+{
+ if (!head)
+ return;
+
+ for (auto i = columns.begin(), end = columns.end(); i != end; ++i) {
+ switch (*i) {
+ case COLUMN_ID:
+ acc_id.value(row) = head->id;
+ break;
+
+ case COLUMN_S:
+ acc_s.value(row) = head->s;
+ break;
+
+ case COLUMN_US:
+ acc_us.value(row) = head->us;
+ break;
+
+ case COLUMN_ETHER_TYPE:
+ acc_ether_type.value(row) = head->ethertype;
+ break;
+
+ case COLUMN_PROTOCOL:
+ acc_protocol.value(row) = head->proto;
+ break;
+
+ case COLUMN_IP_TTL:
+ acc_ip_ttl.value(row) = head->ip_ttl;
+ break;
+
+ case COLUMN_IP_VERSION:
+ acc_ip_version.value(row) = head->ip_version;
+ break;
+
+ case COLUMN_SRC_PORT:
+ acc_src_port.value(row) = head->src_port;
+ break;
+
+ case COLUMN_DST_PORT:
+ acc_dst_port.value(row) = head->dst_port;
+ break;
+
+ case COLUMN_FRAGMENTS:
+ acc_fragments.value(row) = head->fragments;
+ break;
+
+ case COLUMN_SRC_ADDR:
+ if (head->ethertype == 2048)
+ acc_src_addr.value(row) = v4_addr2str(head->src_ip);
+ else
+ acc_src_addr.value(row) = v6_addr2str(head->src_ip);
+ break;
+
+ case COLUMN_DST_ADDR:
+ if (head->ethertype == 2048)
+ acc_dst_addr.value(row) = v4_addr2str(head->dst_ip);
+ else
+ acc_dst_addr.value(row) = v6_addr2str(head->dst_ip);
+ break;
+ }
+ }
+}
+
+RefCountString* v4_addr2str(in6addr_t& addr)
+{
+ converter.reset();
+ converter.add_attr_ipv4(addr.__in6_u.__u6_addr32[3]);
+ return RefCountString::construct(converter.get());
+}
+
+RefCountString* v6_addr2str(in6addr_t& addr)
+{
+ converter.reset();
+ converter.add_attr_ipv6(&addr.__in6_u.__u6_addr8[0]);
+ return RefCountString::construct(converter.get());
+}
+
+} // namespace packetq
diff --git a/src/packet_handler.h b/src/packet_handler.h
new file mode 100644
index 0000000..7e966d4
--- /dev/null
+++ b/src/packet_handler.h
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2017-2024 OARC, Inc.
+ * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+ * All rights reserved.
+ *
+ * This file is part of PacketQ.
+ *
+ * PacketQ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PacketQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __packetq_packet_handler_h
+#define __packetq_packet_handler_h
+
+#include <assert.h>
+#include <cctype>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+#include <vector>
+
+#include "sql.h"
+#include "tcp.h"
+
+namespace packetq {
+
+class Table;
+class Row;
+
+inline int get_int_h(unsigned char* data)
+{
+ return data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
+}
+
+inline int get_short_h(unsigned char* data)
+{
+ return data[0] | (data[1] << 8);
+}
+
+inline int get_int(unsigned char* data)
+{
+ return data[3] | (data[2] << 8) | (data[1] << 16) | (data[0] << 24);
+}
+
+inline int get_short(unsigned char* data)
+{
+ return data[1] | (data[0] << 8);
+}
+
+RefCountString* v4_addr2str(in6addr_t& addr);
+RefCountString* v6_addr2str(in6addr_t& addr);
+
+class Payload {
+public:
+ char m_p[0x10000];
+ int m_size;
+ Payload()
+ {
+ m_size = sizeof(m_p);
+ }
+ inline char* alloc(int size)
+ {
+ if (size > m_size)
+ return 0;
+ return m_p;
+ }
+};
+
+class IP_header {
+public:
+ IP_header()
+ : s(0)
+ , us(0)
+ , ethertype(0)
+ , src_port(0)
+ , dst_port(0)
+ , proto(0)
+ , ip_ttl(0)
+ , ip_version(0)
+ , id(0)
+ , length(0)
+ , fragments(0)
+ , ident(0)
+ , offset(0)
+ {
+ memset(&src_ip, 0, sizeof(src_ip));
+ memset(&dst_ip, 0, sizeof(dst_ip));
+ }
+
+ void reset();
+ int decode(unsigned char* data, int ether_type, int id);
+ unsigned int s;
+ unsigned int us;
+ unsigned short ethertype;
+ in6addr_t src_ip;
+ in6addr_t dst_ip;
+ unsigned short src_port;
+ unsigned short dst_port;
+ unsigned short proto;
+ unsigned short ip_ttl;
+ unsigned short ip_version;
+ unsigned int id;
+ unsigned int length;
+ unsigned int fragments;
+ unsigned int ident;
+ unsigned int offset;
+};
+
+class Packet_handler;
+
+class IP_header_to_table {
+public:
+ enum {
+ COLUMN_ID,
+ COLUMN_S,
+ COLUMN_US,
+ COLUMN_ETHER_TYPE,
+ COLUMN_PROTOCOL,
+ COLUMN_IP_TTL,
+ COLUMN_IP_VERSION,
+ COLUMN_SRC_PORT,
+ COLUMN_DST_PORT,
+ COLUMN_SRC_ADDR,
+ COLUMN_DST_ADDR,
+ COLUMN_FRAGMENTS
+ };
+
+ void add_packet_columns(Packet_handler& packet_handler);
+ void on_table_created(Table* table, const std::vector<int>& columns);
+ void assign(Row* row, IP_header* head, const std::vector<int>& columns);
+
+private:
+ Int_accessor acc_id;
+ Int_accessor acc_s;
+ Int_accessor acc_us;
+ Int_accessor acc_ether_type;
+ Int_accessor acc_protocol;
+ Int_accessor acc_ip_ttl;
+ Int_accessor acc_ip_version;
+ Int_accessor acc_src_port;
+ Int_accessor acc_dst_port;
+ Int_accessor acc_fragments;
+ Text_accessor acc_src_addr;
+ Text_accessor acc_dst_addr;
+};
+
+class Packet {
+public:
+ enum ParseResult {
+ ERROR,
+ OK,
+ NOT_SAMPLED
+ };
+
+ Packet(unsigned char* data, int len, int s, int us, int id, int link_layer_type)
+ {
+ m_s = s;
+ m_us = us;
+ m_data = data;
+ m_len = len;
+ m_id = id;
+ m_link_layer_type = link_layer_type;
+ }
+
+ ParseResult parse(Packet_handler* handler, const std::vector<int>& columns, Row& destination_row, bool sample);
+ bool parse_ethernet();
+ bool parse_sll();
+ bool parse_ip(unsigned char* data, int len, int ether_type);
+ bool parse_transport(unsigned char* data, int len);
+
+ IP_header m_ip_header;
+ unsigned char* m_data;
+ int m_len;
+ int m_s;
+ int m_us;
+ int m_id;
+ int m_link_layer_type;
+};
+
+struct Packet_column {
+ const char* name;
+ const char* description;
+ int id;
+ Coltype::Type type;
+};
+
+class Packet_handler {
+public:
+ Packet_handler()
+ : table_name(0)
+ {
+ }
+ virtual ~Packet_handler()
+ {
+ }
+
+ Table* create_table(const std::vector<int>& columns);
+
+ // for actual packet handlers to fill in
+ virtual void on_table_created(Table* table, const std::vector<int>& columns) = 0;
+ virtual Packet::ParseResult parse(Packet& packet, const std::vector<int>& columns, Row& destination_row, bool sample) = 0;
+
+ const char* table_name;
+ std::vector<Packet_column> packet_columns;
+
+ void add_packet_column(const char* name, const char* description, Coltype::Type type, int id);
+};
+
+void init_packet_handlers(bool escape_dnsnames);
+void destroy_packet_handlers();
+Packet_handler* get_packet_handler(std::string table_name);
+
+} // namespace packetq
+
+#endif // __packetq_packet_handler_h
diff --git a/src/packetq.cpp b/src/packetq.cpp
new file mode 100644
index 0000000..3a56fd5
--- /dev/null
+++ b/src/packetq.cpp
@@ -0,0 +1,346 @@
+/*
+ * Copyright (c) 2017-2024 OARC, Inc.
+ * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+ * All rights reserved.
+ *
+ * This file is part of PacketQ.
+ *
+ * PacketQ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PacketQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "packet_handler.h"
+#include "packetq.h"
+#include "pcap.h"
+#include "reader.h"
+#include "server.h"
+#include "sql.h"
+
+#include <algorithm>
+#include <list>
+#include <stack>
+#include <stdexcept>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
+#ifndef WIN32
+#include <getopt.h>
+#include <signal.h>
+#endif
+
+#define NUM_QUERIES 32
+
+namespace packetq {
+
+static void usage(char* argv0, bool longversion)
+{
+ if (!longversion) {
+ fprintf(stdout,
+ "usage: %s [-vhjctxd] [-s stmt] [-l pkts] [-p port] [-w dir] [-r dir] [-m num] <pcapfile ...>\n",
+ argv0);
+ return;
+ }
+
+ fprintf(stdout,
+ "usage: %s [options] pcapfile(s)...\n"
+ /* -o description .*/
+ " --select statements |\n"
+ " -s statement Set the SQL statement, can be given multiple times.\n"
+ " --limit packets |\n"
+ " -l packets Set maximum number of packets to process, from all\n"
+ " files and not per file.\n"
+ " --version | -v Display version and exit.\n"
+ " --help | -h Display this help.\n"
+ "\n"
+ "Output:\n"
+ " --json | -j JSON (default)\n"
+ " --csv | -c CSV\n"
+ " --table | -t Text table\n"
+ " --xml | -x XML\n"
+ " --rfc1035 Output DNS names escaped using RFC1035 format:\n"
+ " All characters outsize [a-zA-Z0-9_-] are escaped\n"
+ " like \\012. (Octet value in decimal.)\n"
+ "\n"
+ "Web Server:\n"
+ " --daemon | -d Run web server in daemon mode.\n"
+ " --port number |\n"
+ " -p number Set the port number to listen on.\n"
+ " --webroot dir |\n"
+ " -w dir Set the root directory for the web content.\n"
+ " --pcaproot dir |\n"
+ " -r dir Set the root for the PCAP files to make available.\n"
+ " --maxconn number |\n"
+ " -m number Set the maximum number of concurrent connections.\n"
+ "\n"
+ "example> packetq --csv -s \"select count(*) as mycount, protocol from dns group by protocol;\" myfile.pcap\n"
+ "\n"
+ "Packet fields (available in all tables):\n"
+ " id, s, us, ether_type, src_addr, src_port, dst_addr, dst_port, protocol,\n"
+ " ip_ttl, ip_version, fragments\n"
+ "\"dns\" table fields:\n"
+ " qname, aname, msg_id, msg_size, opcode, rcode, extended_rcode,\n"
+ " edns_version, z, udp_size, qd_count, an_count, ns_count, ar_count,\n"
+ " qtype, qclass, atype, aclass, attl, aa, tc, rd, cd, ra, ad, do, edns0, qr,\n"
+ " edns0_ecs, edns0_ecs_family, edns0_ecs_source, edns0_ecs_scope,\n"
+ " edns0_ecs_address\n"
+ "\"icmp\" table fields:\n"
+ " type, code, echo_identifier, echo_sequence, du_protocol, du_src_addr,\n"
+ " du_dst_addr, desc\n",
+ argv0);
+}
+
+#ifdef WIN32
+// windows support is merely for development purposes atm
+#define PACKAGE_STRING "packetq"
+struct option {
+ char* s;
+ int args;
+ int b;
+ char c;
+};
+
+char* optarg = 0;
+int optind = 1;
+
+int getopt_long(int argc, char* argv[], const char* str, option* opt, int* option_index)
+{
+ while (optind < argc) {
+ if (argv[optind][0] != '-')
+ return -1;
+ if (argv[optind][1] != '-') {
+ int i = 0;
+ while (opt[i].s != NULL) {
+ if (opt[i].c == argv[optind][1]) {
+ optarg = argv[optind + opt[i].args];
+ optind += 1 + opt[i].args;
+ return opt[i].c;
+ }
+ i++;
+ }
+ }
+ optind++;
+ }
+ return -1;
+}
+
+#endif
+
+void sigproc(int sig)
+{
+ // ignore sig pipe
+ signal(SIGPIPE, sigproc);
+}
+
+PacketQ* g_app = new PacketQ();
+
+} // namespace packetq
+
+using namespace packetq;
+
+// The main funtion
+int main(int argc, char* argv[])
+{
+ signal(SIGPIPE, sigproc);
+ int port = 0;
+ int limit = 0;
+ int max_conn = 7;
+ bool daemon = false;
+
+ std::string webroot = "", pcaproot = "";
+ std::string queries[NUM_QUERIES] = {
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ };
+ int qcount = 0;
+
+ while (1) {
+ int option_index;
+ struct option long_options[] = {
+ { "select", 1, 0, 's' },
+ { "limit", 1, 0, 'l' },
+ { "maxconn", 1, 0, 'm' },
+ { "webroot", 1, 0, 'w' },
+ { "pcaproot", 1, 0, 'r' },
+ { "port", 1, 0, 'p' },
+ { "daemon", 0, 0, 'd' },
+ { "csv", 0, 0, 'c' },
+ { "json", 0, 0, 'j' },
+ { "table", 0, 0, 't' },
+ { "xml", 0, 0, 'x' },
+ { "rfc1035", 0, 0, 10000 },
+ { "help", 0, 0, 'h' },
+ { "version", 0, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+
+ int c = getopt_long(argc, argv, "w:r:s:l:p:hHdvcxtjm:", long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'v':
+ fprintf(stdout, "%s\n", PACKAGE_STRING);
+ exit(0);
+ case 's':
+ if (qcount < NUM_QUERIES) {
+ queries[qcount++] = optarg;
+ } else {
+ fprintf(stderr, "Warning: can't handle more than %d separate query strings; discarding '%s'\n", NUM_QUERIES, optarg);
+ }
+ break;
+ case 'c':
+ g_app->set_output(PacketQ::csv);
+ break;
+ case 't':
+ g_app->set_output(PacketQ::csv_format);
+ break;
+ case 'x':
+ g_app->set_output(PacketQ::xml);
+ break;
+ case 'j':
+ g_app->set_output(PacketQ::json);
+ break;
+ case 'd':
+ daemon = true;
+ break;
+ case 'w':
+ webroot = optarg;
+ break;
+ case 'r':
+ pcaproot = optarg;
+ break;
+ case 'm':
+ max_conn = atoi(optarg) + 1;
+ if (max_conn < 2)
+ max_conn = 2;
+ break;
+ case 'l':
+ limit = atoi(optarg);
+ break;
+ case 'p':
+ port = atoi(optarg);
+ break;
+ case 10000: // rfc1035
+ g_app->set_escape(true);
+ break;
+ default:
+ fprintf(stderr, "Unknown option: %c\n", c);
+ usage(argv[0], false);
+ return 1;
+ case 'h':
+ usage(argv[0], true);
+ return 1;
+ }
+ }
+ init_packet_handlers(g_app->get_escape()); // set up tables
+ g_app->set_limit(limit);
+ if (port > 0) {
+ start_server(port, daemon, pcaproot, webroot, max_conn);
+ }
+
+ if (optind >= argc) {
+ fprintf(stderr, "Missing input uri\n");
+ usage(argv[0], false);
+ return 1;
+ }
+
+ std::vector<std::string> in_files;
+
+ while (optind < argc) {
+ in_files.push_back(argv[optind]);
+ optind++;
+ }
+
+ Reader reader(in_files, g_app->get_limit());
+
+ if (g_app->get_output() == PacketQ::json) {
+ printf("[\n");
+ }
+ for (int i = 0; i < qcount; i++) {
+ char tablename[32];
+ snprintf(tablename, 32, "result-%d", i);
+ try {
+ Query query(tablename, queries[i].c_str());
+ query.parse();
+ query.execute(reader);
+ Table* result = query.m_result;
+
+ switch (g_app->get_output()) {
+ case (PacketQ::csv_format):
+ if (result)
+ result->csv(true);
+ break;
+ case (PacketQ::csv):
+ if (result)
+ result->csv();
+ break;
+ case (PacketQ::xml):
+ if (result)
+ result->xml();
+ break;
+ case (PacketQ::json):
+ if (result)
+ result->json(i < (qcount - 1));
+ break;
+ }
+ } catch (Error& e) {
+ printf("Error: %s\n", e.m_err.c_str());
+ fflush(stdout);
+ exit(1);
+ } catch (...) {
+ printf("Error: an unknown error has occured !\n");
+ fflush(stdout);
+ }
+ }
+ if (g_app->get_output() == PacketQ::json) {
+ printf("]\n");
+ }
+
+ delete g_app;
+
+ destroy_packet_handlers();
+
+ return 0;
+}
diff --git a/src/packetq.h b/src/packetq.h
new file mode 100644
index 0000000..8ee9841
--- /dev/null
+++ b/src/packetq.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2017-2024 OARC, Inc.
+ * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+ * All rights reserved.
+ *
+ * This file is part of PacketQ.
+ *
+ * PacketQ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PacketQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __packetq_packetq_h
+#define __packetq_packetq_h
+
+#include "sql.h"
+
+namespace packetq {
+
+// App class
+class PacketQ {
+public:
+ enum OutputOpts {
+ json,
+ csv,
+ csv_format,
+ xml
+ };
+ PacketQ()
+ {
+ m_escape = false;
+ m_limit = 0;
+ m_output = json;
+ }
+ void set_escape(bool escape)
+ {
+ m_escape = escape;
+ }
+ void set_limit(int limit)
+ {
+ m_limit = limit;
+ }
+ void set_output(OutputOpts opt)
+ {
+ m_output = opt;
+ }
+ int get_escape() { return m_escape; }
+ OutputOpts get_output() { return m_output; }
+ int get_limit() { return m_limit; }
+
+private:
+ bool m_escape;
+ int m_limit;
+ OutputOpts m_output;
+};
+
+extern PacketQ* g_app;
+
+} // namespace packetq
+
+#endif // __packetq_packetq_h
diff --git a/src/pcap.cpp b/src/pcap.cpp
new file mode 100644
index 0000000..0a39487
--- /dev/null
+++ b/src/pcap.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2017-2024 OARC, Inc.
+ * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+ * All rights reserved.
+ *
+ * This file is part of PacketQ.
+ *
+ * PacketQ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PacketQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pcap.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+namespace packetq {
+
+bool Pcap_file::get_header()
+{
+ // establish: byte order and file format
+ int res = get_int32();
+ if (res != 0xa1b2c3d4) {
+ res = flip32(res);
+ if (res != 0xa1b2c3d4) {
+ if (!m_gzipped) {
+ set_gzipped();
+ return get_header();
+ }
+ return false;
+ }
+ m_reverse_order = true;
+ }
+ // establish version
+ int major_version = get_int16();
+ int minor_version = get_int16();
+ if (major_version != 2 || minor_version != 4) {
+ printf("maj:%d min:%d\n", major_version, minor_version);
+ return false;
+ }
+ // check for 0 timezone offset and accuracy
+ if (!(get_int32() == 0)) {
+ printf("timezone offset != 0");
+ return false;
+ }
+ if (!(get_int32() == 0)) {
+ printf("timezone offset != 0");
+ return false;
+ }
+
+ m_snapshot_length = get_int32();
+ // check for ethernet packets
+ m_link_layer_type = get_int32();
+ if (m_link_layer_type != 1 && m_link_layer_type != 101 && m_link_layer_type != 113) {
+ fprintf(stderr, "PCAP file unsupported linklayer (%d)\n", m_link_layer_type);
+ return false;
+ }
+ return true;
+}
+
+unsigned char* Pcap_file::get_packet(int& len, int& s, int& us)
+{
+ s = 0;
+ us = 0;
+ len = 0;
+ s = get_int32();
+ us = get_int32();
+ len = get_int32();
+
+ // skip past reallen
+ get_int32();
+
+ if (get_eof() || len < 0)
+ return 0;
+
+ unsigned char* buf = get_bytes(len);
+
+ return buf;
+}
+
+} // namespace packetq
diff --git a/src/pcap.h b/src/pcap.h
new file mode 100644
index 0000000..26bef6a
--- /dev/null
+++ b/src/pcap.h
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2017-2024 OARC, Inc.
+ * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+ * All rights reserved.
+ *
+ * This file is part of PacketQ.
+ *
+ * PacketQ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PacketQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __packetq_pcap_h
+#define __packetq_pcap_h
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <zlib.h>
+
+#include "segzip.h"
+#include "sql.h"
+
+namespace packetq {
+
+class Pcap_file {
+private:
+ Pcap_file& operator=(const Pcap_file& other);
+ Pcap_file(Pcap_file&& other) noexcept;
+ Pcap_file const& operator=(Pcap_file&& other);
+
+public:
+ static const bool TAKE_OVER_FP = true;
+
+ Pcap_file(FILE* fp, bool take_over_fp = false)
+ {
+ m_fp_owned = take_over_fp;
+ m_fp = fp;
+ m_reverse_order = false;
+ m_packetbuffer = 0;
+ m_packetbuffer_len = 0;
+ m_eof = false;
+ m_snapshot_length = 0;
+ m_link_layer_type = 0;
+ m_gzipped = false;
+ }
+ ~Pcap_file()
+ {
+ if (m_packetbuffer)
+ delete[] m_packetbuffer;
+ if (m_fp_owned)
+ fclose(m_fp);
+ }
+
+ bool get_header();
+
+ unsigned char* get_packet(int& len, int& s, int& us);
+
+ int get_int32()
+ {
+ int res = 0;
+ get_bytes((unsigned char*)&res, 4);
+ if (m_reverse_order) {
+ return flip32(res);
+ }
+ return res;
+ }
+ int get_int16()
+ {
+ short res = 0;
+ get_bytes((unsigned char*)&res, 2);
+ if (m_reverse_order) {
+ return flip16(res);
+ }
+ return res;
+ }
+ unsigned char* get_bytes(int count)
+ {
+ Buffer& buf = m_gzipped ? m_zipbuffer : m_filebuffer;
+ if (count < buf.m_buffer_len - buf.m_buffer_pos) {
+ unsigned char* ptr = &buf.m_buffer[buf.m_buffer_pos];
+ buf.m_buffer_pos += count;
+ return ptr;
+ }
+
+ unsigned char* bufp = get_pbuffer(count + 400);
+ int r = get_bytes(bufp, count);
+
+ if (r == count)
+ return bufp;
+ return 0;
+ }
+ int get_bytes(unsigned char* dst, int count)
+ {
+ Buffer& buf = m_gzipped ? m_zipbuffer : m_filebuffer;
+ if (count == 0)
+ return 0;
+
+ int bytes = 0;
+ while (count > 0) {
+ if (buf.m_buffer_len == buf.m_buffer_pos) {
+ buffread();
+ if (buf.m_buffer_len == 0) {
+ m_eof = true;
+ return bytes;
+ }
+ }
+ int n = (buf.m_buffer_len - buf.m_buffer_pos) > count ? count : buf.m_buffer_len - buf.m_buffer_pos;
+ for (int i = 0; i < n; i++) {
+ *dst++ = buf.m_buffer[buf.m_buffer_pos++];
+ }
+ bytes += n;
+ count -= n;
+ }
+ return bytes;
+ }
+ void buffread()
+ {
+ if (!m_fp)
+ throw Error("No file");
+
+ Buffer& buf = m_filebuffer;
+ if (buf.m_buffer_len == buf.m_buffer_pos) {
+ buf.m_buffer_len = (int)fread(buf.m_buffer, 1, buf.m_nextread, m_fp);
+ buf.m_buffer_pos = 0;
+ buf.m_nextread = sizeof(buf.m_buffer);
+ }
+ if (m_gzipped) {
+ if (m_zip.m_error || buf.m_buffer_len == buf.m_buffer_pos) {
+ m_zipbuffer.m_buffer_pos = m_zipbuffer.m_buffer_len = 0;
+ return;
+ }
+ if (m_zipbuffer.m_buffer_len == m_zipbuffer.m_buffer_pos)
+ m_zip.inflate(m_filebuffer, m_zipbuffer);
+ }
+ }
+ void set_gzipped()
+ {
+ m_gzipped = true;
+ m_filebuffer.m_buffer_pos = 0;
+ }
+ int flip16(unsigned short i)
+ {
+ unsigned int r = i & 0xff;
+ r <<= 8;
+ i >>= 8;
+ r |= i & 0xff;
+ return int(r);
+ }
+ int flip32(unsigned int i)
+ {
+ unsigned int r = i & 0xff;
+ r <<= 8;
+ i >>= 8;
+ r |= i & 0xff;
+ r <<= 8;
+ i >>= 8;
+ r |= i & 0xff;
+ r <<= 8;
+ i >>= 8;
+ r |= i & 0xff;
+ return int(r);
+ }
+
+ unsigned char* get_pbuffer(int len)
+ {
+ if (!m_packetbuffer || len >= m_packetbuffer_len) {
+ if (m_packetbuffer) {
+ delete[] m_packetbuffer;
+ m_packetbuffer = 0;
+ }
+ m_packetbuffer_len = len + 4096;
+ m_packetbuffer = new (std::nothrow) unsigned char[m_packetbuffer_len];
+ if (!m_packetbuffer)
+ m_packetbuffer_len = 0;
+ }
+ return m_packetbuffer;
+ }
+ bool get_eof() { return m_eof; }
+ int get_link_layer_type() { return m_link_layer_type; }
+
+private:
+ int m_snapshot_length;
+ int m_link_layer_type;
+ bool m_reverse_order;
+ bool m_eof;
+ bool m_gzipped;
+ FILE* m_fp;
+ bool m_fp_owned;
+
+ unsigned char* m_packetbuffer;
+ int m_packetbuffer_len;
+
+ Buffer m_filebuffer;
+ Buffer m_zipbuffer;
+ Zip m_zip;
+};
+
+} // namespace packetq
+
+#endif // __packetq_pcap_h
diff --git a/src/reader.cpp b/src/reader.cpp
new file mode 100644
index 0000000..dae5376
--- /dev/null
+++ b/src/reader.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2017-2024 OARC, Inc.
+ * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+ * All rights reserved.
+ *
+ * This file is part of PacketQ.
+ *
+ * PacketQ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PacketQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "reader.h"
+#include "packet_handler.h"
+
+namespace packetq {
+
+void Reader::seek_to_start()
+{
+ currently_reading = filenames.begin();
+ pcap.reset();
+ packets_read = 0;
+}
+
+bool Reader::done()
+{
+ return (!pcap && currently_reading == filenames.end()) || (max_packets > 0 && packets_read >= max_packets);
+}
+
+bool Reader::read_next(Packet_handler* handler, const std::vector<int>& columns, Row& destination_row, int skip_packets)
+{
+ bool filled_in_row = false;
+
+ while (!filled_in_row and !done()) {
+ // try opening pcap file
+ if (!pcap && currently_reading != filenames.end()) {
+ FILE* fp = fopen(currently_reading->c_str(), "rb");
+ if (fp) {
+ pcap.reset(new Pcap_file(fp, Pcap_file::TAKE_OVER_FP));
+
+ if (!pcap->get_header())
+ pcap.reset();
+ }
+
+ if (!pcap)
+ ++currently_reading;
+ }
+
+ // try reading a row
+ if (pcap) {
+ int len, s, us;
+ unsigned char* data = pcap->get_packet(len, s, us);
+ bool read_success = len && data;
+ ++packets_read; // we count all packets
+ if (read_success) {
+ Packet packet(data, len, s, us, packets_read, pcap->get_link_layer_type());
+ Packet::ParseResult res = packet.parse(handler, columns, destination_row, skip_packets == 0);
+
+ if (res == Packet::NOT_SAMPLED)
+ --skip_packets;
+
+ filled_in_row = res == Packet::OK;
+ } else {
+ // last row in file
+ pcap.reset();
+ if (currently_reading != filenames.end())
+ ++currently_reading;
+ }
+ }
+ }
+
+ return filled_in_row;
+}
+
+} // namespace packetq
diff --git a/src/reader.h b/src/reader.h
new file mode 100644
index 0000000..fb65833
--- /dev/null
+++ b/src/reader.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2017-2024 OARC, Inc.
+ * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+ * All rights reserved.
+ *
+ * This file is part of PacketQ.
+ *
+ * PacketQ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PacketQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __packetq_reader_h
+#define __packetq_reader_h
+
+#include <memory>
+#include <stdio.h>
+#include <vector>
+
+#include "pcap.h"
+#include "sql.h"
+
+namespace packetq {
+
+class Packet_handler;
+
+// reading packet rows out of a list of files
+class Reader {
+public:
+ Reader(std::vector<std::string> filenames, int max_packets)
+ : packets_read(0)
+ {
+ this->filenames = filenames;
+ this->currently_reading = filenames.end();
+ this->max_packets = max_packets;
+ }
+
+ void seek_to_start();
+
+ bool done();
+ bool read_next(Packet_handler* handler, const std::vector<int>& columns, Row& destination_row, int skip_packets);
+
+private:
+ std::vector<std::string>::iterator currently_reading;
+
+ std::vector<std::string> filenames;
+ int max_packets, packets_read;
+ std::unique_ptr<Pcap_file> pcap;
+};
+
+} // namespace packetq
+
+#endif // __packetq_reader_h
diff --git a/src/refcountstring.h b/src/refcountstring.h
new file mode 100644
index 0000000..f91a4a0
--- /dev/null
+++ b/src/refcountstring.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2017-2024 OARC, Inc.
+ * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+ * All rights reserved.
+ *
+ * This file is part of PacketQ.
+ *
+ * PacketQ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PacketQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __packetq_refcountstring_h
+#define __packetq_refcountstring_h
+
+#include <cstdlib>
+#include <cstring>
+
+// A simple reference-counted C string, intended to be used through a pointer
+// as RefCountString * with manual management of the reference count in order
+// to stay a POD (for use in unions). For the same reason, constructors are
+// static. The wrapper RefCountStringHandle can be used where automatic
+// reference handling is possible.
+struct RefCountString {
+ // data
+ int count;
+ char data[];
+
+ // implementation
+ void inc_refcount()
+ {
+ count += 1;
+ }
+
+ void dec_refcount()
+ {
+ count -= 1;
+ if (count == 0)
+ std::free(this);
+ }
+
+ static RefCountString* allocate(int data_length)
+ {
+ void* chunk = std::calloc(1, sizeof(RefCountString) + data_length);
+ if (!chunk)
+ throw std::bad_alloc();
+
+ RefCountString* new_str = static_cast<RefCountString*>(chunk);
+ new_str->count = 1;
+ return new_str;
+ }
+
+ static RefCountString* construct(const char* c_string)
+ {
+ std::size_t length = std::strlen(c_string);
+ RefCountString* str = RefCountString::allocate(length + 1);
+ std::memcpy(str->data, c_string, length + 1);
+ return str;
+ }
+
+ static RefCountString* construct(const char* data, int from, int to)
+ {
+ int length = to - from;
+ if (length < 0)
+ length = 0;
+ RefCountString* str = RefCountString::allocate(length + 1);
+ std::memcpy(str->data, data + from, length);
+ str->data[length - 1 + 1] = '\0';
+ return str;
+ }
+};
+
+class RefCountStringHandle {
+private:
+ RefCountStringHandle& operator=(const RefCountStringHandle& other);
+ RefCountStringHandle(RefCountStringHandle&& other) noexcept;
+ RefCountStringHandle const& operator=(RefCountStringHandle&& other);
+
+public:
+ RefCountStringHandle()
+ {
+ value = 0;
+ }
+
+ RefCountStringHandle(RefCountString* str)
+ {
+ value = str;
+ }
+
+ ~RefCountStringHandle()
+ {
+ if (value)
+ value->dec_refcount();
+ }
+
+ RefCountString* operator*()
+ {
+ return value;
+ }
+
+ void set(RefCountString* str)
+ {
+ if (value != str) {
+ if (value)
+ value->dec_refcount();
+ value = str;
+ }
+ }
+
+ RefCountString* value;
+};
+
+#endif // __packetq_refcountstring_h
diff --git a/src/regression-test.sh b/src/regression-test.sh
new file mode 100755
index 0000000..9d25736
--- /dev/null
+++ b/src/regression-test.sh
@@ -0,0 +1,92 @@
+# Copyright (c) 2017-2024 OARC, Inc.
+# Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+# All rights reserved.
+#
+# This file is part of PacketQ.
+#
+# PacketQ is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# PacketQ is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+
+set -e
+
+# Simple regression-testing tool that exercises all operators of the
+# SQL evaluator and outputs the results.
+#
+# Usage: regression-test.sh pcap-dump-file
+#
+# If the script finds a binary called packetq-before, it will execute
+# the query on that too and compare the results with diff. So copy the
+# packetq binary to packetq-before before you make a change to see the
+# effect on the output.
+
+#set -e
+DIR=/tmp/test/packetq
+mkdir -p $DIR
+typeset -i test
+test=0
+cd $(dirname $0)
+for SQL in \
+ "select qname as CertainQnames, qtype as Qtype, count(1) as count from dns where (qname='localhost' or qname like '%.root-servers.net') and qr==0 group by CertainQnames,Qtype order by count desc ;" \
+ 'select qtype as Qtype, qname as Qname, count(1) as count from dns where qclass==3 and qr==0 group by Qtype,Qname order by count desc ;' \
+ 'select rcode as Rcode, if(qr==1,dst_addr,src_addr) as ClientAddr, count(1) as count from dns where qr==1 group by Rcode,ClientAddr order by count desc limit 50;' \
+ "select 'ALL' as All, if(ether_type==34525,rsplit(src_addr,7,':')||':'||rsplit(src_addr,6,':')||':'||rsplit(src_addr,5,':')||':'||rsplit(src_addr,4,':')||':'||rsplit(src_addr,3,':')||'::',rsplit(src_addr,3)||'.'||rsplit(src_addr,2)||'.'||rsplit(src_addr,1)||'.0') as ClientSubnet, count(1) as count from dns where qr==0 group by All,ClientSubnet order by count,ClientSubnet desc limit 200;;" \
+ "select 'ALL' as All, subnet(src_addr,24,96) as ClientSubnet, count(1) as count from dns where qr==0 group by All,ClientSubnet order by count desc,ClientSubnet limit 200;;" \
+ "select if(rsplit(qname,1)='de','ok','non-auth-tld') as Class, if(ether_type==34525,rsplit(src_addr,7,':')||':'||rsplit(src_addr,6,':')||':'||rsplit(src_addr,5,':')||':'||rsplit(src_addr,4,':')||':'||rsplit(src_addr,3,':')||'::',rsplit(src_addr,3)||'.'||rsplit(src_addr,2)||'.'||rsplit(src_addr,1)||'.0') as ClientSubnet, count(1) as count from dns where qr==0 group by Class,ClientSubnet order by count,ClientSubnet,Class desc limit 200;;" \
+ "select if(qr==1,'sent','recv') as Direction, if(protocol==6,'tcp',if(protocol==17,'udp',if(protocol==1,'icmp',if(protocol==58,'ipv6-icmp',protocol)))) as IPProto, count(1) as count from dns group by Direction,IPProto order by count,Direction desc ;" \
+ "select if(ether_type==34525,'IPv6','IPv4') as IPVersion, qtype as Qtype, count(1) as count from dns where qr==0 group by IPVersion,Qtype order by count desc ;" \
+ "select 'ALL' as All, do, edns0, edns_version, extended_rcode, z, if(do==1,'set','clr') as D0, count(1) as count from dns where qr==0 group by All,do,D0,edns0,edns_version,extended_rcode,z order by count desc ;" \
+ "select 'ALL' as All, if(edns0,edns_version,'none') as EDNSVersion, count(1) as count from dns where qr==0 group by All,EDNSVersion order by count desc ;" \
+ "select 'ALL' as All, if(qname like 'xn--%','idn','normal') as IDNQname, count(1) as count from dns where qr==0 group by All,IDNQname order by count desc ;" \
+ "select 'ALL' as All, lower(rsplit(qname,1)) as TLD, count(1) as count from dns where qr==0 and (qname like 'xn--%') group by All,TLD order by count,TLD desc ;" \
+ "select 'ALL' as All, if(qr==1,dst_addr,src_addr) as ClientAddr, count(1) as count from dns where qr==0 and (qtype=28 or qtype=38) and (qname like '%.root-servers.net') group by All,ClientAddr order by count desc limit 50;;" \
+ "select 'ALL' as All, opcode as Opcode, count(1) as count from dns where qr==0 group by All,Opcode order by count desc ;" \
+ "select 'ALL' as All, qtype as Qtype, count(1) as count from dns where qr==0 group by All,Qtype order by count desc ;" \
+ 'select qtype as Qtype, len(qname) as QnameLen, count(1) as count from dns where qr==0 group by Qtype,QnameLen order by count,QnameLen,Qtype desc ;' \
+ 'select qtype as Qtype, lower(rsplit(qname,1)) as TLD, count(1) as count from dns where qr==0 and (qtype=1 or qtype=2 or qtype=5 or qtype=6 or qtype=12 or qtype=15 or qtype=28 or qtype=38 or qtype=255) group by Qtype,TLD order by count,TLD,Qtype desc limit 200;;' \
+ "select 'ALL' as All, rcode as Rcode, count(1) as count from dns where qr==1 group by All,Rcode order by count desc ;" \
+ 'select rcode as Rcode, msg_size as ReplyLen, count(1) as count from dns where qr==1 group by Rcode,ReplyLen order by count desc ;' \
+ "select 'ALL' as All, rd as RD, count(1) as count from dns where qr==0 group by All,RD order by count desc ;" \
+ "select if(protocol==6,'tcp',if(protocol==17,'udp',protocol)) as Transport, qtype as Qtype, count(1) as count from dns where qr==0 group by Transport,Qtype order by Transport,Qtype,count desc ;" \
+ "select s, dst_addr as Dst_addr, qtype as questiontype, lower(src_addr) as lower_src, if(1 and s < 1 or s <= 1 or s > 1 or s >= 1, 't', 'f'), trim(trim('foofoo' || rsplit(src_addr, 1) || 'foofoo', 'foo'), 'bar'), count(*), len(src_addr), sum(msg_size + -1 - 2 % 4 << 3 >> 2 | 3 & ~4) + 1, min(msg_size), max(msg_size), truncate(1.1) as integer, 1.1 as float, sum(src_port + 1.0 - 2.0 / 1.5 * -2.5) + 1.0, max(src_port + 1.0), min(src_port + 1.0), avg(src_port), stdev(src_port), name('rcode', 0) from dns where src_addr like '%' and (qr or not qr) group by src_addr, s having s >= 0 order by s, dst_addr, lower_src, integer, float" \
+ "select name( 'qtype' , qtype ) as qt, count(*) as count from dns group by qtype order by count desc;"\
+ "select count(*) as count, lower(rsplit(qname,1)) as tld, istld(tld) as flag from dns group by tld order by count desc limit 50;" \
+ ;
+do
+ test=$test+1
+ new=$(./packetq --version | tr " " "_")
+ echo ""
+ if [ ${#SQL} -gt 200 ]; then ellipsis="..."; else ellipsis=""; fi
+ echo "Test $test: '${SQL:0:200}$ellipsis'"
+ t_new=$(/usr/bin/time -f "%e" ./packetq --tlds /usr/share/packetq/tlds -s "$SQL" $1 2>&1 > $DIR/$new.test$test.result)
+ e_new=$?
+ echo " Comparing $new against available binaries:"
+ for prev in $(ls ../../packetq*/src/packetq); do
+ old=$($prev --version | cut -d " " -f 2)
+ ver=$(printf "%-20s" $old)
+ bin=$(printf "%-48s" $prev)
+ t_old=$(/usr/bin/time -f "%e" $prev -s "$SQL" $1 2>&1 > $DIR/$old.test$test.result)
+ e_old=$?
+ if [ $e_new = 0 -a $e_old = 0 -a "$t_old" != "0.00" ]; then
+ echo -e " $bin $ver: $t_old --> $t_new ($(python -c "print '%5.2f %d%%' % ($t_new - $t_old, ($t_new-$t_old)*100/$t_old)"))"
+ else
+ echo -e " $bin $ver: $t_old --> $t_new ($ver Failed)"
+ fi
+ diff -u $DIR/$old.test$test.result $DIR/$new.test$test.result > $DIR/$new.test$test.diff
+ if [ $? = 0 ]; then
+ #echo "Test $test: No changes in output"
+ true
+ else
+ head -n 20 $DIR/$new.test$test.diff
+ fi
+ done
+done
diff --git a/src/segzip.h b/src/segzip.h
new file mode 100644
index 0000000..14b28e4
--- /dev/null
+++ b/src/segzip.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2017-2024 OARC, Inc.
+ * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+ * All rights reserved.
+ *
+ * This file is part of PacketQ.
+ *
+ * PacketQ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PacketQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __packetq_segzip_h
+#define __packetq_segzip_h
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <zlib.h>
+
+namespace packetq {
+
+class Buffer {
+public:
+ Buffer()
+ {
+ m_buffer_len = 0;
+ m_buffer_pos = 0;
+ m_nextread = 1024;
+ }
+ int size() { return sizeof(m_buffer); }
+ unsigned char m_buffer[0x40000];
+ int m_nextread;
+ int m_buffer_len;
+ int m_buffer_pos;
+};
+
+class Zip {
+private:
+ Zip& operator=(const Zip& other);
+ Zip(Zip&& other) noexcept;
+ Zip const& operator=(Zip&& other);
+
+public:
+ Zip()
+ : m_stream()
+ {
+ m_init = true;
+ m_error = false;
+ m_run_end = false;
+ m_stream.next_out = 0;
+ m_stream.avail_out = 0;
+ }
+ ~Zip()
+ {
+ if (m_run_end)
+ ::inflateEnd(&m_stream);
+ }
+ bool inflate(Buffer& in, Buffer& out)
+ {
+ if (m_error) {
+ in.m_buffer_pos = in.m_buffer_len;
+ out.m_buffer_len = 0;
+ return false;
+ }
+ out.m_buffer_pos = 0;
+ out.m_buffer_len = sizeof(out.m_buffer);
+ m_stream.next_out = &out.m_buffer[out.m_buffer_pos];
+ m_stream.avail_out = out.m_buffer_len - out.m_buffer_pos;
+ if (m_init) {
+ m_stream.next_in = 0;
+ m_stream.avail_in = 0;
+ m_stream.zalloc = 0;
+ m_stream.zfree = 0;
+ m_stream.opaque = 0;
+ m_init = false;
+ if (inflateInit2(&m_stream, 15 + 32) != Z_OK) {
+ m_error = true;
+ out.m_buffer_len = 0;
+ in.m_buffer_pos = in.m_buffer_len;
+ return false;
+ }
+ }
+ m_stream.next_in = &in.m_buffer[in.m_buffer_pos];
+ m_stream.avail_in = in.m_buffer_len - in.m_buffer_pos;
+
+ int ret = ::inflate(&m_stream, Z_NO_FLUSH);
+
+ if (ret != Z_OK)
+ ::inflateEnd(&m_stream);
+ else
+ m_run_end = true;
+ if (ret != Z_OK && ret != Z_STREAM_END) {
+ m_error = true;
+ out.m_buffer_len = 0;
+ in.m_buffer_pos = in.m_buffer_len = 0;
+ return false;
+ }
+
+ in.m_buffer_pos = in.m_buffer_len - m_stream.avail_in;
+ out.m_buffer_len = sizeof(out.m_buffer) - m_stream.avail_out;
+ out.m_buffer_pos = 0;
+ return true;
+ }
+ bool m_init;
+ bool m_run_end;
+ bool m_error;
+ z_stream m_stream;
+};
+
+} // namespace packetq
+
+#endif // __packetq_segzip_h
diff --git a/src/server.cpp b/src/server.cpp
new file mode 100644
index 0000000..96e14d3
--- /dev/null
+++ b/src/server.cpp
@@ -0,0 +1,1183 @@
+/*
+ * Copyright (c) 2017-2024 OARC, Inc.
+ * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+ * All rights reserved.
+ *
+ * This file is part of PacketQ.
+ *
+ * PacketQ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PacketQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "packetq.h"
+#include "pcap.h"
+#include "reader.h"
+
+#include <arpa/inet.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <list>
+#include <map>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+
+#define MAXHOSTNAME 256
+
+namespace packetq {
+namespace httpd {
+ class Socket;
+ class Server;
+
+ static char redirect[] = "HTTP/1.1 307 Temporary Redirect\r\n"
+ "Location: /\r\n"
+ "Connection: close\r\n"
+ "Content-Type: text/html\r\n"
+ "\r\n<html><head><title>moved</title></head><body><h1>moved</h1>this page has moved to /</body></html>";
+
+ static char header[] = "HTTP/1.1 200 OK\r\n"
+ "Server: PacketQ builtin\r\n"
+ "Connection: close\r\n"
+ "Access-Control-Allow-Origin: *\r\n"
+ "Content-Type: %s\r\n"
+ "\r\n";
+
+ static Server* g_server = 0;
+
+ class SocketPool {
+ public:
+ SocketPool()
+ {
+ m_free = 0;
+ for (int i = 0; i < FD_SETSIZE; i++) {
+ m_sockets[i] = 0;
+ }
+ m_socket_count = 0;
+ }
+ int add(Socket* s)
+ {
+ if (m_free < FD_SETSIZE)
+ m_sockets[m_free] = s;
+ else
+ return -1;
+ int idx = m_free;
+ while (m_free < FD_SETSIZE && m_sockets[m_free])
+ m_free++;
+ m_socket_count++;
+ return idx;
+ }
+ void remove(int s)
+ {
+ m_sockets[s] = 0;
+ if (s < m_free)
+ m_free = s;
+ m_socket_count--;
+ }
+
+ int get_sockets() { return m_socket_count; }
+ void select();
+
+ fd_set m_readset;
+ fd_set m_writeset;
+ int m_free;
+ int m_socket_count;
+ Socket* m_sockets[FD_SETSIZE];
+ };
+
+ SocketPool g_pool;
+
+ class Stream {
+ public:
+ class Buffer {
+ private:
+ Buffer& operator=(const Buffer& other);
+ Buffer const& operator=(Buffer&& other);
+
+ public:
+ Buffer(const unsigned char* buf, int len)
+ {
+ m_buf = new unsigned char[len];
+ memcpy(m_buf, buf, len);
+ m_len = len;
+ m_pos = 0;
+ }
+ Buffer(Buffer&& other) noexcept
+ {
+ m_buf = other.m_buf;
+ m_len = other.m_len;
+ m_pos = other.m_pos;
+ other.m_buf = 0;
+ other.m_len = 0;
+ other.m_pos = 0;
+ }
+ ~Buffer()
+ {
+ m_len = 0;
+ delete[] m_buf;
+ }
+ unsigned char* m_buf;
+ int m_len;
+ int m_pos;
+ };
+ Stream()
+ {
+ m_len = 0;
+ }
+ void push_front(unsigned char* data, int len)
+ {
+ m_stream.push_front(Buffer(data, len));
+ m_len += len;
+ }
+ void write(unsigned char* data, int len)
+ {
+ m_stream.push_back(Buffer(data, len));
+ m_len += len;
+ }
+ int read(unsigned char* data, int maxlen)
+ {
+ int p = 0;
+ while (p < maxlen && m_len > 0) {
+ Buffer& buf = m_stream.front();
+ int l = maxlen - p;
+ if (l > buf.m_len - buf.m_pos)
+ l = buf.m_len - buf.m_pos;
+ for (int i = 0; i < l; i++) {
+ data[p++] = buf.m_buf[buf.m_pos++];
+ }
+ m_len -= l;
+ if (l == 0) {
+ m_stream.pop_front();
+ }
+ }
+ return p;
+ }
+ int len()
+ {
+ return m_len;
+ }
+ int get()
+ {
+ unsigned char c = '@';
+ int r = read(&c, 1);
+ if (r == 0)
+ return -1;
+ return c;
+ }
+
+ private:
+ int m_len;
+ std::list<Stream::Buffer> m_stream;
+ };
+
+ class Socket {
+ private:
+ Socket& operator=(const Socket& other);
+ Socket(Socket&& other) noexcept;
+ Socket const& operator=(Socket&& other);
+
+ public:
+ Socket(int s, bool serv)
+ : m_want_write(false)
+ {
+ int flags;
+
+ // Add non-blocking flag
+ if ((flags = fcntl(s, F_GETFL)) == -1) {
+ syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_GETFL) failed: %d\n", s, errno);
+ exit(-1);
+ }
+ if (fcntl(s, F_SETFL, flags | O_NONBLOCK)) {
+ syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_SETFL, 0x%x) failed: %d\n", s, errno, flags | O_NONBLOCK);
+ exit(-1);
+ }
+
+ m_bytes_written = 0;
+ m_bytes_read = 0;
+ m_serv = serv;
+ m_socket = s;
+ m_sidx = g_pool.add(this);
+ m_close_when_empty = false;
+ m_file = 0;
+ }
+ virtual ~Socket()
+ {
+ g_pool.remove(m_sidx);
+ close(m_socket);
+ }
+ void process(bool read)
+ {
+ // m_serv means this is a listening socket
+ if (m_serv)
+ return;
+ if (!read) {
+ on_write();
+ unsigned char ptr[4096];
+ int len;
+
+ while ((len = m_write.read(ptr, sizeof(ptr)))) {
+ int res = write(m_socket, ptr, len);
+#if EAGAIN != EWOULDBLOCK
+ if (res == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+#else
+ if (res == -1 && (errno == EAGAIN)) {
+#endif
+ m_write.push_front(ptr, len);
+ set_want_write();
+ return;
+ }
+ if (res < 0) {
+ delete this;
+ return;
+ }
+ m_bytes_written += res;
+ if (res < len) {
+ m_write.push_front(&ptr[res], len - res);
+ set_want_write();
+ return;
+ }
+ }
+ if (m_close_when_empty) {
+ shutdown(m_socket, SHUT_WR);
+ // fflush(m_socket);
+ delete this;
+ }
+ }
+ if (read) {
+ int avail = read_sock();
+
+ if (avail)
+ on_read();
+ }
+ }
+ virtual void file_write()
+ {
+ }
+ virtual void file_read()
+ {
+ }
+ void set_delete()
+ {
+ m_close_when_empty = true;
+ m_want_write = false;
+ }
+ virtual void on_write()
+ {
+ }
+ virtual void on_read()
+ {
+ }
+ int read_sock()
+ {
+ unsigned char buf[2000];
+ int len = 0;
+ if ((len = recv(m_socket, buf, sizeof(buf) - 1, 0)) > 0) {
+ m_bytes_read += len;
+ buf[len] = 0;
+ m_read.write(buf, len);
+ }
+ if (len < 0) {
+ delete this;
+ return 0;
+ }
+ return len;
+ }
+ bool failed()
+ {
+ return m_sidx == -1;
+ }
+ bool has_data()
+ {
+ return m_want_write || m_write.len() > 0;
+ }
+ void set_want_write(bool set = true)
+ {
+ m_want_write = set;
+ }
+
+ int m_bytes_written;
+ int m_bytes_read;
+ int m_socket;
+ int m_file;
+ int m_sidx;
+ bool m_serv;
+ bool m_want_write;
+ bool m_close_when_empty;
+ char m_buffer[4096];
+
+ Stream m_read, m_write;
+ };
+
+ void SocketPool::select()
+ {
+ FD_ZERO(&m_readset);
+ FD_ZERO(&m_writeset);
+ int max = 0;
+ for (int i = 0; i < FD_SETSIZE; i++) {
+ if (m_sockets[i]) {
+ if (max < m_sockets[i]->m_socket)
+ max = m_sockets[i]->m_socket;
+ FD_SET(m_sockets[i]->m_socket, &m_readset);
+ if (m_sockets[i]->has_data())
+ FD_SET(m_sockets[i]->m_socket, &m_writeset);
+ if (max < m_sockets[i]->m_file)
+ max = m_sockets[i]->m_file;
+ FD_SET(m_sockets[i]->m_file, &m_readset);
+ }
+ }
+ timeval timeout;
+ timeout.tv_sec = 30;
+ timeout.tv_usec = 0;
+
+ int sel = ::select(max + 1, &m_readset, &m_writeset, 0, &timeout);
+ if (sel < 0) {
+ syslog(LOG_ERR | LOG_USER, "sel -1 errno = %d %s max:%d", errno, strerror(errno), max);
+ exit(-1);
+ }
+ if (sel) {
+ for (int i = 0; i < FD_SETSIZE; i++) {
+ if (m_sockets[i] && m_sockets[i]->m_file) {
+ if (FD_ISSET(m_sockets[i]->m_file, &m_readset)) {
+ m_sockets[i]->file_read();
+ }
+ if (m_sockets[i] && FD_ISSET(m_sockets[i]->m_file, &m_writeset)) {
+ m_sockets[i]->file_write();
+ }
+ }
+ if (m_sockets[i] && FD_ISSET(m_sockets[i]->m_socket, &m_readset)) {
+ m_sockets[i]->process(true);
+ }
+ if (m_sockets[i] && FD_ISSET(m_sockets[i]->m_socket, &m_writeset)) {
+ m_sockets[i]->process(false);
+ }
+ }
+ }
+ }
+
+ class Server {
+ public:
+ Socket m_socket;
+
+ Server(int port, const std::string& pcaproot, const std::string& webroot)
+ : m_socket(establish(port), true)
+ , m_pcaproot(pcaproot)
+ , m_webroot(webroot)
+ {
+ if (m_socket.m_socket < 0) {
+ if (m_socket.m_socket == EADDRINUSE)
+ syslog(LOG_ERR | LOG_USER, "Fail EADDRINUSE (%d)\n", m_socket.m_socket);
+ else
+ syslog(LOG_ERR | LOG_USER, "Fail %d port:%d\n", m_socket.m_socket, port);
+ exit(-1);
+ }
+ }
+
+ ~Server()
+ {
+ }
+
+ int get_connection()
+ {
+ int s = m_socket.m_socket;
+ int t; /* socket of connection */
+ if ((t = accept(s, NULL, NULL)) < 0) /* accept connection if there is one */
+ return (-1);
+
+ return (t);
+ }
+
+ int establish(unsigned short portnum)
+ {
+ int s, res;
+ sockaddr_in sa;
+ memset(&sa, 0, sizeof(struct sockaddr_in));
+
+ sa.sin_family = AF_INET;
+ sa.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ sa.sin_port = htons(portnum);
+
+ if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
+ return (-2);
+ int on = 1;
+
+ res = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+ if ((res = bind(s, (const sockaddr*)&sa, sizeof(struct sockaddr_in))) < 0) {
+ close(s);
+ return (res); /* bind address to socket */
+ }
+
+ // Add non-blocking flag
+ int flags;
+ if ((flags = fcntl(s, F_GETFL)) == -1) {
+ syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_GETFL) failed: %d\n", s, errno);
+ exit(-1);
+ }
+ if (fcntl(s, F_SETFL, flags | O_NONBLOCK)) {
+ syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_SETFL, 0x%x) failed: %d\n", s, errno, flags | O_NONBLOCK);
+ exit(-1);
+ }
+
+ listen(s, 511); /* max # of queued connects */
+ return (s);
+ }
+
+ std::string m_pcaproot;
+ std::string m_webroot;
+ };
+
+ class Url {
+ public:
+ Url(const char* url)
+ : m_full(url)
+ {
+ int i = 0;
+ for (; i < m_full.length();) {
+ char c = m_full.c_str()[i];
+ if (c == '?') {
+ i++;
+ break;
+ }
+ m_path += c;
+ i++;
+ }
+ m_path = decode(m_path);
+ if (m_path == "")
+ m_path = "/";
+ decode_params(m_full.substr(i).c_str());
+ }
+ void decode_params(const char* params)
+ {
+ if (!params)
+ return;
+ std::string str = params;
+ for (int i = 0; i < str.length();) {
+ std::string param = "", value = "";
+ for (; i < str.length();) {
+ char c = str.c_str()[i];
+ if (c == '=' || c == '&') {
+ i++;
+ break;
+ }
+ param += c;
+ i++;
+ }
+ for (; i < str.length();) {
+ char c = str.c_str()[i];
+ if (c == '&') {
+ i++;
+ break;
+ }
+ value += c;
+ i++;
+ }
+ add_param(decode(param), decode(value));
+ }
+ }
+ const char* get_param(const char* param)
+ {
+ auto it = m_params.find(std::string(param));
+ if (it == m_params.end())
+ return 0;
+ return it->second.c_str();
+ }
+ void add_param(std::string key, std::string val)
+ {
+ auto it = m_params.find(key);
+ if (it == m_params.end()) {
+ m_params[key] = val;
+ m_counts[key] = 1;
+ return;
+ }
+ char cnt[100];
+ int n = m_counts[key];
+ m_counts[key] = n + 1;
+
+ snprintf(cnt, sizeof(cnt) - 1, "%d", n);
+ cnt[99] = 0;
+ std::string keyn = key;
+ keyn += cnt;
+ m_params[keyn] = val;
+ }
+
+ std::string decode(std::string str)
+ {
+ std::string dst;
+ int percent_state = 0;
+ int code = 0;
+ for (int i = 0; i < str.length(); i++) {
+ char c = str.c_str()[i];
+ if (percent_state) {
+ int n = 0;
+ if (c >= '0' && c <= '9')
+ n = c - '0';
+ if (c >= 'a' && c <= 'f')
+ n = c - 'a' + 10;
+ if (c >= 'A' && c <= 'F')
+ n = c - 'A' + 10;
+ code = (code << 4) | n;
+ percent_state--;
+ if (!percent_state) {
+ dst += char(code);
+ code = 0;
+ }
+ } else {
+ if (c == '%') {
+ percent_state = 2;
+ } else
+ dst += c;
+ }
+ }
+ return dst;
+ }
+
+ std::string get_full()
+ {
+ return m_full;
+ }
+ std::string get_path()
+ {
+ return m_path;
+ }
+
+ std::string m_full;
+ std::string m_path;
+ std::map<std::string, std::string> m_params;
+ std::map<std::string, int> m_counts;
+ };
+
+ class Page {
+ public:
+ Page(const char* url, const char* body)
+ : m_url(url)
+ {
+ m_url.decode_params(body);
+ }
+
+ void process()
+ {
+
+ if (m_url.get_path().compare("/query") == 0) {
+ if (!m_url.get_param("file")) {
+ printf(header, "text/plain");
+ printf("no file selected\n");
+ return;
+ }
+
+ printf(header, "text/plain");
+ if (m_url.get_param("sql"))
+ query(m_url.get_param("sql"));
+ else
+ printf("no query defined \n");
+ } else if (m_url.get_path().substr(0, 8).compare("/resolve") == 0) {
+ resolve();
+ } else if (m_url.get_path().substr(0, 5).compare("/list") == 0) {
+ serve_dir();
+ } else {
+ serve_static();
+ }
+
+ delete g_app;
+ }
+ static std::string join_path(const std::string& a, const std::string& b)
+ {
+ if (b.find("..") != std::string::npos)
+ return a;
+ if (a.length() == 0)
+ return b;
+ if (b.length() == 0)
+ return a;
+ if (a[a.length() - 1] != '/' && b[0] != '/')
+ return a + std::string("/") + b;
+ return a + b;
+ }
+ const char* get_mimetype(const std::string& file)
+ {
+ int p = file.find_last_of('.');
+ if (p == std::string::npos || p + 1 >= file.length())
+ return 0;
+ std::string suff = file.substr(p + 1);
+ if (suff.compare("js") == 0)
+ return "application/x-javascript";
+ if (suff.compare("jpg") == 0)
+ return "image/jpeg";
+ if (suff.compare("html") == 0)
+ return "text/html";
+ if (suff.compare("htm") == 0)
+ return "text/html";
+ if (suff.compare("txt") == 0)
+ return "text/plain";
+ if (suff.compare("png") == 0)
+ return "image/png";
+ if (suff.compare("gif") == 0)
+ return "image/gif";
+ if (suff.compare("ico") == 0)
+ return "image/x-icon";
+ if (suff.compare("json") == 0)
+ return "application/json";
+ if (suff.compare("css") == 0)
+ return "text/css";
+
+ return 0;
+ }
+ bool serve_file(const std::string& file)
+ {
+ const char* mimetype = get_mimetype(file);
+
+ if (mimetype) {
+ FILE* fp = fopen(file.c_str(), "rb");
+ if (fp) {
+ printf(header, mimetype);
+ char buffer[8192];
+ int len;
+ while ((len = fread(buffer, 1, 200, fp)) > 0) {
+ fwrite(buffer, 1, len, stdout);
+ }
+ fclose(fp);
+ return true;
+ }
+ }
+ return false;
+ }
+ void serve_static()
+ {
+ if (g_server->m_webroot == "") {
+ printf(header, "text/html");
+ printf("<h2>This server is not configured to serve static pages</h2>");
+ printf("Start using the -w option to set a html directory");
+ return;
+ }
+ std::string file = join_path(g_server->m_webroot, m_url.get_path());
+
+ if (serve_file(file))
+ return;
+ if (serve_file(join_path(file, "index.html")))
+ return;
+
+ if (m_url.get_path().compare("/") != 0) {
+ printf("%s", redirect);
+ return;
+ }
+
+ printf(header, "text/html");
+ printf("<h2>It works !</h2><br>\n");
+ printf("%s", "<a href=\"/query?file=sample.pcap&sql=select%20qr,qname,protocol%20from%20dns%20limit%2018;\">Test query</a><br/>\n");
+ printf("%s", "<a href=\"/list\">list available files</a><br/>\n");
+ }
+
+ void resolve()
+ {
+ const char* ip = m_url.get_param("ip");
+ const char* name = m_url.get_param("name");
+
+ if (ip) {
+
+ printf(header, "application/json");
+
+ printf("[");
+
+ struct addrinfo* result;
+ struct addrinfo* res;
+ int error;
+
+ error = getaddrinfo(ip, NULL, NULL, &result);
+ if (error == 0) {
+ for (res = result; res != NULL; res = res->ai_next) {
+ char hostname[NI_MAXHOST] = "";
+
+ error = getnameinfo(res->ai_addr, res->ai_addrlen, hostname, NI_MAXHOST, NULL, 0, 0);
+ if (error != 0) {
+ continue;
+ }
+ if (*hostname != '\0') {
+ printf("\"%s\"", hostname);
+ break;
+ }
+ }
+ freeaddrinfo(result);
+ }
+ printf("]\n");
+ } else if (name) {
+ char tmp[100];
+ printf(header, "application/json");
+
+ printf("[");
+
+ struct addrinfo* result;
+ struct addrinfo* res;
+ int error;
+
+ error = getaddrinfo(name, NULL, NULL, &result);
+ char empty[] = "", line[] = ",\n";
+ char* sep = empty;
+ if (error == 0) {
+ for (res = result; res != NULL; res = res->ai_next) {
+ void* ptr = &((struct sockaddr_in*)res->ai_addr)->sin_addr;
+ if (res->ai_family == AF_INET6)
+ ptr = &((struct sockaddr_in6*)res->ai_addr)->sin6_addr;
+ tmp[0] = 0;
+ inet_ntop(res->ai_family, ptr, tmp, sizeof(tmp));
+ printf("%s\"%s\"", sep, tmp);
+ sep = line;
+ }
+ freeaddrinfo(result);
+ }
+ printf("]\n");
+ } else
+ printf("[]\n");
+ }
+
+ void serve_dir()
+ {
+ if (g_server->m_pcaproot == "") {
+ printf(header, "text/html");
+ printf("<h2>This server is not configured to list pcapfiles</h2>");
+ printf("Start using the -r option to set a pcap directory");
+ return;
+ }
+ std::string directory = join_path(g_server->m_pcaproot, m_url.get_path().substr(5));
+
+ DIR* dir = opendir(directory.c_str());
+ if (!dir) {
+ printf("%s", redirect);
+ return;
+ }
+
+ printf(header, "application/json");
+
+ printf("[\n");
+ struct dirent* d;
+ struct stat statbuf;
+
+ char comma = ' ';
+
+ while ((d = readdir(dir)) != 0) {
+ std::string subject = join_path(directory, d->d_name);
+ int fd = open(subject.c_str(), O_RDONLY);
+
+ if (fd < 0)
+ continue;
+ if (fstat(fd, &statbuf) == -1) {
+ close(fd);
+ continue;
+ }
+ if (S_ISDIR(statbuf.st_mode)) {
+ if ((strcmp(d->d_name, ".") != 0) && (strcmp(d->d_name, "..") != 0)) {
+ printf(" %c{\n \"data\" : \"%s\",\n \"attr\" : { \"id\": \"%s\" },\n \"children\" : [], \"state\" : \"closed\" }\n",
+ comma, d->d_name, join_path(m_url.get_path(), d->d_name).substr(5).c_str());
+ comma = ',';
+ }
+ } else {
+ bool found = false;
+ FILE* fp = fdopen(fd, "rb");
+ if (fp) {
+ Pcap_file pfile(fp);
+ if (pfile.get_header()) {
+ unsigned char* data = 0;
+ int s = 0, us, len;
+ data = pfile.get_packet(len, s, us);
+ if (data) {
+ printf(" %c{\n \"data\" : \"%s\",\n \"attr\" : { \"id\" : \"%s\", \"size\": %d, \"time\": %d,\"type\": \"pcap\" },\n \"children\" : [] }\n",
+ comma, d->d_name, join_path(m_url.get_path(), d->d_name).substr(5).c_str(), int(statbuf.st_size), s);
+ comma = ',';
+ found = true;
+ }
+ }
+ fclose(fp);
+ }
+ if (!found) {
+ std::string str = subject;
+ transform(str.begin(), str.end(), str.begin(), tolower);
+ if (str.rfind(".json") == str.length() - 5) {
+ printf(" %c{\n \"data\" : \"%s\",\n \"attr\" : { \"id\" : \"%s\", \"size\": %d, \"type\": \"json\" },\n \"children\" : [] }\n",
+ comma, d->d_name, join_path(m_url.get_path(), d->d_name).substr(5).c_str(), int(statbuf.st_size));
+ comma = ',';
+ }
+ }
+ }
+ close(fd);
+ }
+
+ printf("]\n");
+
+ closedir(dir);
+ }
+
+ void query(const char* sql)
+ {
+ Query query("result", sql);
+
+ query.parse();
+
+ std::vector<std::string> in_files;
+
+ int i = 0;
+ while (true) {
+ char param[50] = "file";
+
+ std::string par = "file";
+ if (i > 0) {
+ snprintf(param, sizeof(param) - 1, "file%d", i);
+ param[49] = 0;
+ }
+ i++;
+ const char* f = m_url.get_param(param);
+ if (!f)
+ break;
+ std::string file = join_path(g_server->m_pcaproot, f);
+ in_files.push_back(file);
+ }
+
+ Reader reader(in_files, g_app->get_limit());
+
+ query.execute(reader);
+ query.m_result->json(false);
+ }
+ Url m_url;
+ };
+
+ class Http_socket : public Socket {
+ private:
+ Http_socket& operator=(const Http_socket& other);
+ Http_socket(Http_socket&& other) noexcept;
+ Http_socket const& operator=(Http_socket&& other);
+
+ public:
+ enum State {
+ get_post,
+ header,
+ body,
+ error,
+ wait_child,
+ done
+ };
+ Http_socket(int socket)
+ : Socket(socket, false)
+ , m_http_version(0)
+ , m_emptyline(0)
+ {
+ m_state = get_post;
+ m_nextc = -1;
+ m_cr = false;
+ m_line = "";
+ m_url = "";
+ m_child_fd = 0;
+ m_child_pid = 0;
+ m_child_read = 0;
+ m_body_cnt = -1;
+ m_content_len = 0;
+ }
+ ~Http_socket()
+ {
+ if (m_child_pid) {
+ kill(m_child_pid, SIGHUP);
+ int status;
+ waitpid(m_child_pid, &status, 0);
+ }
+ if (m_child_fd)
+ close(m_child_fd);
+ m_file = 0;
+ }
+ inline void print(const char* fmt, ...)
+ {
+ char string[4096];
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(string, sizeof(string), fmt, ap);
+ va_end(ap);
+
+ m_write.write((unsigned char*)string, strlen(string));
+ }
+
+ int peek()
+ {
+ if (m_nextc >= 0)
+ return m_nextc;
+ m_nextc = m_read.get();
+ return m_nextc;
+ }
+ int getc()
+ {
+ int c = peek();
+ m_nextc = -1;
+ return c;
+ }
+
+ void on_read()
+ {
+ while (true) {
+ int c = peek();
+ if (c == -1)
+ return;
+ if (m_body_cnt >= 0) {
+ c = getc();
+ if (!(m_body_cnt == 0 && c == 10)) {
+ m_line += char(c);
+ m_content_len--;
+ }
+ m_body_cnt++;
+ if (m_content_len == 0)
+ parseline();
+ continue;
+ }
+ c = getc();
+ if (c != 13 && c != 10) {
+ m_line += char(c);
+ m_cr = false;
+ } else {
+ bool cr = m_cr;
+ m_cr = false;
+ if (c == 10 && cr) {
+ continue;
+ }
+ if (c == 13)
+ m_cr = true;
+ parseline();
+ }
+ }
+ }
+ virtual void file_read()
+ {
+ set_want_write();
+ }
+ virtual void file_write()
+ {
+ }
+ void on_write()
+ {
+ set_want_write(false);
+ if (m_state == wait_child) {
+ unsigned char buffer[4096];
+ int status;
+ bool done = true;
+ if (0 == waitpid(m_child_pid, &status, WNOHANG)) {
+ done = false;
+ }
+ if (m_child_fd) {
+ // Add non-blocking flag
+ int flags;
+ if ((flags = fcntl(m_child_fd, F_GETFL)) == -1) {
+ syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_GETFL) failed: %d\n", m_child_fd, errno);
+ exit(-1);
+ }
+ if (fcntl(m_child_fd, F_SETFL, flags | O_NONBLOCK)) {
+ syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_SETFL, 0x%x) failed: %d\n", m_child_fd, errno, flags | O_NONBLOCK);
+ exit(-1);
+ }
+
+ size_t res;
+ pollfd pfd;
+ pfd.fd = m_child_fd;
+ pfd.events = POLLIN;
+ pfd.revents = 0;
+ if (1 == poll(&pfd, 1, 0) && (pfd.revents & POLLIN) != 0) {
+ if ((res = read(m_child_fd, buffer, (int)sizeof(buffer))) > 0) {
+ done = false;
+ m_child_read += res;
+ m_write.write(buffer, res);
+ }
+ }
+ }
+ if (done) {
+ m_child_pid = 0;
+ if (m_child_fd) {
+ close(m_child_fd);
+ m_child_fd = 0;
+ m_file = 0;
+ }
+ set_delete();
+ }
+ }
+ }
+ void parseline()
+ {
+ switch (m_state) {
+ case (get_post): {
+ m_state = error;
+ syslog(LOG_INFO | LOG_USER, "%s\n", m_line.c_str());
+ int p = 0;
+ if (m_line.find("GET ") != -1) {
+ if ((p = m_line.find(" HTTP/1.1")) != -1) {
+ m_http_version = 1;
+ } else if ((p = m_line.find(" HTTP/1.0")) != -1) {
+ m_http_version = 0;
+ } else {
+ return;
+ }
+ m_url = m_line.substr(4, p - 4);
+ m_state = header;
+ } else if (m_line.find("POST ") != -1) {
+ if ((p = m_line.find(" HTTP/1.1")) != -1) {
+ m_http_version = 1;
+ } else if ((p = m_line.find(" HTTP/1.0")) != -1) {
+ m_http_version = 0;
+ } else {
+ return;
+ }
+ m_url = m_line.substr(5, p - 5);
+ m_state = header;
+ }
+ } break;
+ case (header):
+ if (m_line.length() == 0) {
+ m_body_cnt = 0;
+ m_state = body;
+ } else {
+ int colon = m_line.find(": ");
+ std::string key = m_line.substr(0, colon);
+ std::string val = m_line.substr(colon + 2);
+ if (key == "Content-Length") {
+ if (val.length() > 0)
+ m_content_len = atoi(val.c_str());
+ }
+ }
+ break;
+ case (body):
+ m_body = m_line;
+ header_done();
+ break;
+ default:
+ printf("error line: %s !\n", m_line.c_str());
+ break;
+ }
+ m_line = "";
+ }
+ void header_done()
+ {
+ fflush(stdout); // required before fork or any unflushed output will go to the client
+ int fd[2];
+ if (pipe(fd) < 0)
+ return;
+
+ // Add non-blocking flag
+ int flags;
+ if ((flags = fcntl(fd[0], F_GETFL)) == -1) {
+ syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_GETFL) failed: %d\n", fd[0], errno);
+ exit(-1);
+ }
+ if (fcntl(fd[0], F_SETFL, flags | O_NONBLOCK)) {
+ syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_SETFL, 0x%x) failed: %d\n", fd[0], errno, flags | O_NONBLOCK);
+ exit(-1);
+ }
+
+ m_child_pid = fork();
+ if (m_child_pid < 0) {
+ print("Internal error");
+ set_delete();
+ return;
+ }
+ if (m_child_pid == 0) {
+ ////////// child code /////////
+ dup2(fd[1], fileno(stdout));
+ dup2(fd[1], fileno(stderr));
+ close(fd[1]);
+
+ Page page(m_url.c_str(), m_body.c_str());
+ page.process();
+ fflush(stdout);
+ exit(0);
+ ///////////// child exit() ///////////////
+ } else {
+ close(fd[1]);
+ m_child_fd = fd[0];
+ m_file = m_child_fd;
+ m_state = wait_child;
+ }
+ set_want_write();
+ }
+ State m_state;
+ bool m_cr;
+ int m_body_cnt;
+ int m_content_len;
+ int m_nextc;
+ int m_child_pid;
+ int m_child_fd;
+
+ int m_child_read;
+
+ int m_http_version; // 0 = HTTP/1.0 1 = HTTP/1.1
+
+ int m_emptyline;
+ std::string m_line;
+ std::string m_url;
+ std::string m_body;
+ };
+
+} // namespace httpd
+
+using namespace httpd;
+
+void start_server(int port, bool fork_me, const std::string& pcaproot, const std::string& webroot, int max_conn)
+{
+ pid_t pid, sid;
+ bool fg = !fork_me;
+
+ printf("listening on port %d\n", port);
+
+ if (!fg) {
+ pid = fork();
+
+ if (pid < 0) {
+ exit(EXIT_FAILURE);
+ } else if (pid > 0) {
+ exit(EXIT_SUCCESS);
+ }
+
+ sid = setsid();
+
+ if (sid < 0) {
+ exit(EXIT_FAILURE);
+ }
+ }
+ openlog("packetq", LOG_PID, LOG_USER);
+
+ httpd::Server server(port, pcaproot, webroot);
+ g_server = &server;
+
+ while (true) {
+ httpd::g_pool.select();
+ int cnt = g_pool.get_sockets();
+ if (cnt < max_conn) {
+ int c = server.get_connection();
+ if (c > -1) {
+ Http_socket* s = new (std::nothrow) Http_socket(c);
+ if (s && s->failed()) {
+ syslog(LOG_ERR | LOG_USER, "failed to create socket");
+ delete s;
+ }
+ }
+ }
+ usleep(1000);
+ }
+ // loop will never break
+ // g_server = 0;
+ // syslog(LOG_INFO | LOG_USER, "exiting");
+ // exit(EXIT_SUCCESS);
+}
+
+} // namespace packetq
diff --git a/src/server.h b/src/server.h
new file mode 100644
index 0000000..ed14fee
--- /dev/null
+++ b/src/server.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2017-2024 OARC, Inc.
+ * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+ * All rights reserved.
+ *
+ * This file is part of PacketQ.
+ *
+ * PacketQ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PacketQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __packetq_server_h
+#define __packetq_server_h
+
+namespace packetq {
+
+void start_server(int port, bool fork, const std::string& pcaproot, const std::string& webroot, int max_conn);
+
+} // namespace packetq
+
+#endif // __packetq_server_h
diff --git a/src/sql.cpp b/src/sql.cpp
new file mode 100644
index 0000000..94ad7ed
--- /dev/null
+++ b/src/sql.cpp
@@ -0,0 +1,2696 @@
+/*
+ * Copyright (c) 2017-2024 OARC, Inc.
+ * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+ * All rights reserved.
+ *
+ * This file is part of PacketQ.
+ *
+ * PacketQ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PacketQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "sql.h"
+#include "output.h"
+#include "packet_handler.h"
+#include "packetq.h"
+#include "reader.h"
+
+#include <unordered_map>
+#include <utility>
+#include <vector>
+#ifdef WIN32
+#include <windows.h>
+#endif
+#ifdef HAVE_LIBMAXMINDDB
+#include <maxminddb.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+static MMDB_s* __cc_mmdb = 0;
+static MMDB_s* __asn_mmdb = 0;
+#endif
+
+namespace packetq {
+
+bool verbose = false;
+
+int g_allocs = 0;
+
+Column* Table::add_column(const char* name, const char* type, int id, bool hidden)
+{
+ if (!type)
+ return add_column(name, Coltype::_text, id, hidden);
+ else if (strcmp(type, "bool") == 0)
+ return add_column(name, Coltype::_bool, id, hidden);
+ else if (strcmp(type, "int") == 0)
+ return add_column(name, Coltype::_int, id, hidden);
+ else if (strcmp(type, "float") == 0)
+ return add_column(name, Coltype::_float, id, hidden);
+ else
+ return add_column(name, Coltype::_text, id, hidden);
+}
+
+Column* Table::add_column(const char* name, Coltype::Type type, int id, bool hidden)
+{
+ Column* col = new Column(name, type, id, hidden);
+ col->m_offset = Table::align(m_curpos, col->m_def.m_align);
+ m_curpos = col->m_offset + col->m_def.m_size;
+ if (type == Coltype::_text)
+ m_text_column_offsets.push_back(col->m_offset);
+ m_cols.push_back(col);
+ return col;
+}
+
+void Table::delete_row(Row* row)
+{
+ row->decref_text_columns(m_text_column_offsets);
+ m_row_allocator->deallocate(row);
+}
+
+Row* Table::create_row()
+{
+ if (!m_row_allocator) {
+ m_rsize = sizeof(Row) - ROW_DUMMY_SIZE; // exclude the dummy
+ m_dsize = m_curpos;
+ m_row_allocator = new Allocator<Row>(m_rsize + m_dsize, 10000);
+ }
+
+ Row* r = m_row_allocator->allocate();
+ r->zero_text_columns(m_text_column_offsets);
+ return r;
+}
+
+void Table::add_row(Row* row)
+{
+ m_rows.push_back(row);
+}
+
+int g_comp = 0;
+
+void Ordering_terms::compile(const std::vector<Table*>& tables, const std::vector<int>& search_order, Query& q)
+{
+ for (auto it = m_terms.begin(); it != m_terms.end(); it++) {
+ OP* op = it->m_op;
+ it->m_op = op->compile(tables, search_order, q);
+ }
+}
+
+class Sorter {
+public:
+ Sorter(Ordering_terms& order)
+ : m_order(order)
+ {
+ }
+ bool operator()(Row* ia, Row* ib)
+ {
+ // this works under the assumption that the ordering terms have
+ // been compiled with only one table so the row index i is 0
+ Row** ia_rows = &ia;
+ Row** ib_rows = &ib;
+
+ for (auto it = m_order.m_terms.begin(); it != m_order.m_terms.end(); it++) {
+ g_comp++;
+
+ OP* op = it->m_op;
+ op->evaluate(it->m_asc ? ia_rows : ib_rows, m_a);
+ op->evaluate(it->m_asc ? ib_rows : ia_rows, m_b);
+ int res = m_a.cmp(m_b);
+ if (res < 0)
+ return true;
+ if (res > 0)
+ return false;
+ }
+ return false;
+ }
+ bool eq(Row* ia, Row* ib)
+ {
+ Row** ia_rows = &ia;
+ Row** ib_rows = &ib;
+
+ for (auto it = m_order.m_terms.begin(); it != m_order.m_terms.end(); it++) {
+ g_comp++;
+
+ OP* op = it->m_op;
+ op->evaluate(it->m_asc ? ia_rows : ib_rows, m_a);
+ op->evaluate(it->m_asc ? ib_rows : ia_rows, m_b);
+ int res = m_a.cmp(m_b);
+ if (res != 0)
+ return false;
+ }
+ return true;
+ }
+ int cmp(Row* ia, Row* ib)
+ {
+ Row** ia_rows = &ia;
+ Row** ib_rows = &ib;
+
+ for (auto it = m_order.m_terms.begin(); it != m_order.m_terms.end(); it++) {
+ g_comp++;
+
+ OP* op = it->m_op;
+ op->evaluate(it->m_asc ? ia_rows : ib_rows, m_a);
+ op->evaluate(it->m_asc ? ib_rows : ia_rows, m_b);
+ int res = m_a.cmp(m_b);
+ if (res != 0)
+ return res;
+ }
+ return 0;
+ }
+ Ordering_terms& m_order;
+ Variant m_a, m_b;
+};
+
+struct Stki {
+ int s;
+ int l;
+ int b;
+};
+
+struct Spkt {
+ Variant cache;
+ int row;
+};
+
+class Per_sort {
+public:
+ struct Tlink {
+ Tlink()
+ : m_next(0)
+ , row(0)
+ , m_eq(0)
+ {
+ }
+
+ Tlink* get_eq() { return m_eq; }
+ void reset()
+ {
+ m_next = 0;
+ row = 0;
+ m_eq = 0;
+ }
+ void add_eq(Tlink* o)
+ {
+ if (!o->m_eq) {
+ // add single
+ if (!m_eq) {
+ // as first
+ m_eq = o;
+ o->m_eq_last = o;
+ return;
+ }
+ // to list
+ m_eq->m_eq_last->m_eq = o;
+ m_eq->m_eq_last = o;
+ return;
+ } else {
+ // add list
+ if (!m_eq) {
+ // as first
+ m_eq = o;
+ o->m_eq_last = o->m_eq->m_eq_last;
+ return;
+ }
+ // to list
+ m_eq->m_eq_last->m_eq = o;
+ m_eq->m_eq_last = o->m_eq->m_eq_last;
+ }
+ }
+ union {
+ Tlink* m_next;
+ Tlink* m_eq_last;
+ };
+ Row* row;
+ Variant cache;
+
+ private:
+ Tlink* m_eq;
+ };
+ struct List {
+ public:
+ void reset()
+ {
+ m_size = 0;
+ m_fl[0] = 0;
+ m_fl[1] = 0;
+ }
+ int m_size;
+ Tlink* m_fl[2];
+ };
+ Per_sort(Table& table, Ordering_terms& order)
+ : m_sorter(order)
+ , m_table(table)
+ {
+ m_escalate_sort = order.m_terms.size() > 1;
+ m_asc = order.m_terms.begin()->m_asc ? 1 : -1;
+ m_op = order.m_terms.begin()->m_op;
+ memset(m_groups, 0, sizeof(m_groups));
+ m_current.reset();
+ }
+ inline bool add_to_list(List& list, Tlink* t)
+ {
+ if (list.m_fl[0] == 0) {
+ list.m_fl[0] = list.m_fl[1] = t;
+ list.m_size = 1;
+ return true;
+ }
+ list.m_fl[1]->m_next = t;
+ list.m_fl[1] = t;
+ list.m_size++;
+ return true;
+ }
+ inline bool insert_into_list(List& list, Tlink* t)
+ {
+ if (list.m_fl[0] == 0) {
+ list.m_fl[0] = list.m_fl[1] = t;
+ list.m_size = 1;
+ return true;
+ }
+ int cmp0 = cmp(t, list.m_fl[0]);
+ if (cmp0 == 0) {
+ list.m_fl[0]->add_eq(t);
+ return true;
+ }
+ if (cmp0 < 0) {
+ t->m_next = list.m_fl[0];
+ list.m_fl[0] = t;
+ list.m_size++;
+ return true;
+ }
+ int cmp1 = cmp(t, list.m_fl[1]);
+ if (cmp1 == 0) {
+ list.m_fl[1]->add_eq(t);
+ return true;
+ }
+ if (cmp1 > 0) {
+ list.m_fl[1]->m_next = t;
+ list.m_fl[1] = t;
+ list.m_size++;
+ return true;
+ }
+ return false;
+ }
+ int cmp(Tlink* a, Tlink* b)
+ {
+ int cmp = a->cache.cmp(b->cache) * m_asc;
+ if (cmp != 0 || !m_escalate_sort)
+ return cmp;
+ return m_sorter.cmp(a->row, b->row);
+ }
+ inline void add(Tlink* t)
+ {
+ if (!insert_into_list(m_current, t)) {
+ insert_list(m_current);
+ m_current.reset();
+ insert_into_list(m_current, t);
+ }
+ }
+ inline void insert_list(List& l)
+ {
+ unsigned int size = l.m_size;
+ int offs = 0;
+ size >>= 1;
+ while (size != 0) {
+ offs++;
+ size >>= 1;
+ }
+ if (m_groups[offs].m_size != 0) {
+ List m = merge(m_groups[offs], l);
+ m_groups[offs].reset();
+ insert_list(m);
+ } else
+ m_groups[offs] = l;
+ }
+ List merge(List& l1, List& l2)
+ {
+ List r;
+ r.reset();
+ Tlink* a = l1.m_fl[0];
+ Tlink* b = l2.m_fl[0];
+ if (!a)
+ return l2;
+ if (!b)
+ return l1;
+
+ int size = l1.m_size + l2.m_size;
+
+ while (a && b) {
+ int c = cmp(a, b);
+ if (c == 0) {
+ Tlink* a2 = a;
+ Tlink* b2 = b;
+ a = a->m_next;
+ b = b->m_next;
+ a2->m_next = 0;
+ b2->m_next = 0;
+ a2->add_eq(b2);
+ add_to_list(r, a2);
+ size--;
+ } else if (c < 0) {
+ Tlink* a2 = a;
+ a = a->m_next;
+ a2->m_next = 0;
+ add_to_list(r, a2);
+ } else {
+ Tlink* b2 = b;
+ b = b->m_next;
+ b2->m_next = 0;
+ add_to_list(r, b2);
+ }
+ }
+ if (a) {
+ r.m_fl[1]->m_next = a;
+ r.m_fl[1] = l1.m_fl[1];
+ }
+ if (b) {
+ r.m_fl[1]->m_next = b;
+ r.m_fl[1] = l2.m_fl[1];
+ }
+
+ l1.reset();
+ l2.reset();
+ r.m_size = size;
+ return r;
+ }
+
+ void sort()
+ {
+ int table_size = (int)m_table.m_rows.size();
+ if (table_size <= 1)
+ return;
+ auto it = m_table.m_rows.begin();
+ Tlink* links = new Tlink[table_size];
+
+ int i;
+ for (i = 0; i < table_size; i++) {
+ Tlink& r = links[i];
+ r.reset();
+ r.row = *it;
+ // &row works under the assumption that m_op has been compiled with
+ // this table only so row index is 0
+ m_op->evaluate(&r.row, r.cache);
+ it++;
+ add(&r);
+ }
+ if (m_current.m_size)
+ insert_list(m_current);
+ List result;
+ result.reset();
+ for (i = 0; i < sizeof(m_groups) / sizeof(List); i++) {
+ if (m_groups[i].m_size > 0) {
+ result = merge(result, m_groups[i]);
+ m_groups[i].reset();
+ }
+ }
+ Tlink* p = result.m_fl[0];
+ it = m_table.m_rows.begin();
+ while (p) {
+ *it++ = p->row;
+ Tlink* e = p->get_eq();
+ while (e) {
+ *it++ = e->row;
+ e = e->get_eq();
+ }
+ p = p->m_next;
+ };
+
+ delete[] links;
+ }
+
+ OP* m_op;
+ Sorter m_sorter;
+ bool m_escalate_sort;
+ int m_asc;
+ Table& m_table;
+ List m_groups[32];
+ List m_current;
+};
+
+void Table::per_sort(Ordering_terms& order)
+{
+ Per_sort sort(*this, order);
+
+ sort.sort();
+
+ return;
+}
+
+void Table::merge_sort(Ordering_terms& order)
+{
+ Sorter sorter(order);
+
+ bool escalate_sort = order.m_terms.size() > 1;
+ int asc = order.m_terms.begin()->m_asc ? 1 : -1;
+ OP* op = order.m_terms.begin()->m_op;
+
+ int table_size = (int)m_rows.size();
+ if (table_size <= 1)
+ return;
+ Row** row_ptrs = new Row*[table_size];
+ Spkt* spktpool = new Spkt[table_size * 2];
+ Spkt* rows[2];
+ rows[0] = spktpool;
+ rows[1] = &spktpool[table_size];
+ auto it = m_rows.begin();
+
+ int i = 0;
+ Spkt* r = rows[0];
+ for (i = 0; i < table_size; i += 2) {
+
+ row_ptrs[i] = *it++;
+ r[i].row = i;
+ // &row works under the assumption that m_op has been compiled with
+ // this table only so row index is 0
+ op->evaluate(&row_ptrs[r[i].row], r[i].cache);
+ if (i + 1 < table_size) {
+ row_ptrs[i + 1] = *it++;
+ r[i + 1].row = i + 1;
+ op->evaluate(&row_ptrs[r[i + 1].row], r[i + 1].cache);
+ }
+ }
+ int swap = 0;
+
+ Stki stack[64];
+ Stki* sp = stack;
+ sp->s = 0;
+ sp->l = 2;
+ sp->b = 1;
+ rows[1][0] = rows[0][1];
+ rows[1][1] = rows[0][0];
+
+ sp--;
+
+ int npos = 0;
+ while (true) {
+ int start, len;
+ if (sp > stack && sp->l == sp[-1].l) {
+ // two equal size -> merge
+
+ len = sp->l <<= 1;
+ start = sp[-1].s;
+ swap = sp[-1].b;
+ sp--;
+ sp--;
+ } else {
+ start = npos;
+ npos += 2;
+ swap = 0;
+ len = 2;
+ }
+
+ int cnt = start + len > table_size ? table_size - start : len;
+ Spkt* s = rows[swap];
+ Spkt* d = rows[1 - swap];
+ if (cnt > 0) {
+ int p1 = start;
+ int p2 = start + (len >> 1);
+ int l1 = len >> 1;
+ int l2 = l1;
+ if (p1 + l1 > table_size)
+ l1 = table_size - p1;
+ if (p2 + l2 > table_size)
+ l2 = table_size - p2;
+
+ i = start;
+ while (cnt-- > 0) {
+ if (l1 <= 0) {
+ d[i++] = s[p2++];
+ } else if (l2 <= 0) {
+ d[i++] = s[p1++];
+ } else {
+ int cmp = s[p1].cache.cmp(s[p2].cache) * asc;
+ if (cmp < 0 || (cmp == 0 && escalate_sort && sorter(row_ptrs[s[p1].row], row_ptrs[s[p2].row]))) {
+ l1--;
+ d[i++] = s[p1++];
+ } else {
+ l2--;
+ d[i++] = s[p2++];
+ }
+ }
+ }
+ }
+ ++sp;
+ sp->l = len;
+ sp->s = start;
+ sp->b = 1 - swap;
+ if (len > table_size) {
+ for (it = m_rows.begin(); it != m_rows.end(); it++) {
+ *it = row_ptrs[(d++)->row];
+ }
+
+ break;
+ }
+ }
+ delete[] row_ptrs;
+ delete[] spktpool;
+ return;
+}
+
+void Table::limit(int limit, int offset)
+{
+ int count = 0;
+ auto e = m_rows.end();
+ for (auto it = m_rows.begin(); it != m_rows.end(); it++) {
+ if (e != m_rows.end()) {
+ delete_row(*e);
+ m_rows.erase(e);
+ e = m_rows.end();
+ }
+
+ int l = count++;
+ if (l < offset || l >= offset + limit) {
+ e = it;
+ }
+ }
+ if (e != m_rows.end()) {
+ delete_row(*e);
+ m_rows.erase(e);
+ }
+}
+
+void printrep(int n, char c)
+{
+ if (n >= 3000)
+ return;
+ char buf[3000];
+ int i;
+ for (i = 0; i < n; i++)
+ buf[i] = c;
+ buf[i] = 0;
+ printf("%s", buf);
+}
+
+void Table::xml()
+{
+ g_output.reset();
+ int cols = (int)m_cols.size();
+
+ g_output.add_string("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ g_output.add_string("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n");
+ g_output.add_string("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n<head>\n <title>");
+ g_output.add_string(m_name.c_str());
+ g_output.add_string("</title>\n");
+ g_output.add_string("<style type=\"text/css\">\n");
+ g_output.add_string(" th.int { color: #0F0C00; }\n");
+ g_output.add_string(" th.float { color: #0F0900; }\n");
+ g_output.add_string(" th.text { color: #0F0600; }\n");
+ g_output.add_string(" th.bool { color: #0C0900; }\n");
+ g_output.add_string("</style>\n");
+ g_output.add_string("</head>\n");
+ g_output.add_string("<body>\n");
+ g_output.add_string("<table>\n");
+
+ g_output.add_string("<tr>");
+
+ for (int i = 0; i < cols; i++) {
+ if (m_cols[i]->m_hidden)
+ continue;
+
+ const char* t = "";
+ switch (m_cols[i]->m_type) {
+ case (Coltype::_float):
+ t = "float";
+ break;
+ case (Coltype::_int):
+ t = "int";
+ break;
+ case (Coltype::_text):
+ t = "text";
+ break;
+ case (Coltype::_bool):
+ t = "bool";
+ break;
+ }
+ g_output.add_string("<th class=\"");
+ g_output.add_string(t);
+ g_output.add_string("\">");
+ g_output.add_string(m_cols[i]->m_name.c_str());
+ g_output.add_string("</th>");
+ }
+ g_output.add_string("</tr>\n");
+ for (auto it = m_rows.begin(); it != m_rows.end(); it++) {
+ g_output.add_string("<tr>");
+ Row* r = *it;
+
+ Variant v;
+ for (int i = 0; i < cols; i++) {
+ Column* c = m_cols[i];
+
+ if (c->m_hidden)
+ continue;
+
+ int offset = c->m_offset;
+
+ static const int bufsize = 100;
+ char buf[bufsize];
+
+ g_output.add_string("<td>");
+ switch (c->m_type) {
+ case Coltype::_bool:
+ g_output.add_string(r->access_column<bool_column>(offset) ? "1" : "0");
+ break;
+ case Coltype::_int:
+ snprintf(buf, bufsize, "%i", r->access_column<int_column>(offset));
+ g_output.add_string(buf);
+ break;
+ case Coltype::_float:
+ snprintf(buf, bufsize, "%g", r->access_column<float_column>(offset));
+ g_output.add_string(buf);
+ break;
+ case Coltype::_text:
+ g_output.add_string(r->access_column<text_column>(offset)->data);
+ break;
+ }
+ g_output.add_string("</td> ");
+ }
+
+ g_output.add_string("</tr>\n");
+ }
+ g_output.add_string("</table>\n");
+ g_output.add_string("</body>\n");
+ g_output.add_string("</html>\n");
+ g_output.print();
+}
+
+void Table::json(bool trailing_comma)
+{
+ g_output.reset();
+ int cols = (int)m_cols.size();
+
+ g_output.add_string(" {\n ");
+
+ g_output.add_q_string("table_name");
+ g_output.add_string(": ");
+ g_output.add_q_string(m_name.c_str());
+ g_output.add_string(",\n ");
+
+ g_output.add_q_string("query");
+ g_output.add_string(": ");
+ g_output.add_q_string(m_qstring.c_str());
+ g_output.add_string(",\n ");
+
+ g_output.add_q_string("head");
+ g_output.add_string(": [");
+
+ bool append_comma = false;
+ for (int i = 0; i < cols; i++) {
+ if (m_cols[i]->m_hidden)
+ continue;
+
+ if (append_comma)
+ g_output.add_string(",\n");
+ else
+ g_output.add_string("\n");
+ append_comma = true;
+ g_output.add_string(" { ");
+ g_output.add_q_string("name");
+ g_output.add_string(": ");
+ g_output.add_q_string(m_cols[i]->m_name.c_str());
+ g_output.add_string(",");
+ g_output.add_q_string("type");
+ g_output.add_string(": ");
+ const char* t = "";
+ switch (m_cols[i]->m_type) {
+ case (Coltype::_float):
+ t = "float";
+ break;
+ case (Coltype::_int):
+ t = "int";
+ break;
+ case (Coltype::_text):
+ t = "text";
+ break;
+ case (Coltype::_bool):
+ t = "bool";
+ break;
+ }
+ g_output.add_q_string(t);
+ g_output.add_string(" }");
+ }
+ g_output.add_string("\n ],\n ");
+ g_output.add_q_string("data");
+ g_output.add_string(": [");
+ bool outer_comma = false;
+ for (auto it = m_rows.begin(); it != m_rows.end(); it++) {
+ if (outer_comma)
+ g_output.add_string(",\n [");
+ else
+ g_output.add_string("\n [");
+ outer_comma = true;
+ bool comma = false;
+ Row* r = *it;
+
+ for (int i = 0; i < cols; i++) {
+ Column* c = m_cols[i];
+
+ if (c->m_hidden)
+ continue;
+
+ if (comma)
+ g_output.add_string(",");
+
+ comma = true;
+
+ int offset = c->m_offset;
+ static const int bufsize = 100;
+ char buf[bufsize];
+
+ switch (c->m_type) {
+ case Coltype::_bool:
+ g_output.add_string(r->access_column<bool_column>(offset) ? "1" : "0");
+ break;
+ case Coltype::_int:
+ snprintf(buf, bufsize, "%i", r->access_column<int_column>(offset));
+ g_output.add_string(buf);
+ break;
+ case Coltype::_float:
+ snprintf(buf, bufsize, "%g", r->access_column<float_column>(offset));
+ g_output.add_string(buf);
+ break;
+ case Coltype::_text:
+ g_output.add_q_string(r->access_column<text_column>(offset)->data);
+ break;
+ }
+ }
+
+ g_output.add_string("]");
+ }
+ g_output.add_string("\n ]\n");
+ if (trailing_comma) {
+ g_output.add_string(" },\n");
+ } else {
+ g_output.add_string(" }\n");
+ }
+ g_output.print();
+}
+
+std::string csv_qoute_string(const std::string& s)
+{
+ std::string r = "\"";
+ int len = s.length();
+ for (int i = 0; i < len; i++) {
+ if (s[i] == '"') {
+ r += '"';
+ }
+ r += s[i];
+ }
+ r += '"';
+ return r;
+}
+
+void Table::csv(bool format)
+{
+ int cols = (int)m_cols.size();
+ std::vector<int> col_len(cols);
+
+ for (int i = 0; i < cols; i++)
+ col_len[i] = 0;
+ int max = 0;
+ char* tmp = 0;
+ if (format) {
+ for (auto it = m_rows.begin(); it != m_rows.end(); it++) {
+ Row* r = *it;
+
+ for (int i = 0; i < cols; i++) {
+ Column* c = m_cols[i];
+
+ if (c->m_hidden)
+ continue;
+
+ int len = 0;
+
+ int offset = c->m_offset;
+ static const int bufsize = 100;
+ char buf[bufsize];
+
+ switch (c->m_type) {
+ case Coltype::_bool:
+ len = 1;
+ break;
+ case Coltype::_int:
+ snprintf(buf, bufsize, "%i", r->access_column<int_column>(offset));
+ len = strlen(buf);
+ break;
+ case Coltype::_float:
+ snprintf(buf, bufsize, "%g", r->access_column<float_column>(offset));
+ len = strlen(buf);
+ break;
+ case Coltype::_text:
+ len = csv_qoute_string(r->access_column<text_column>(offset)->data).length();
+ break;
+ }
+ len++;
+ if (len > col_len[i])
+ col_len[i] = len;
+ if (len > max)
+ max = len;
+ }
+ }
+ for (int i = 0; i < cols; i++) {
+ if (m_cols[i]->m_hidden)
+ continue;
+
+ int l = csv_qoute_string(m_cols[i]->m_name).length();
+ l++;
+ if (l > col_len[i])
+ col_len[i] = l;
+ if (l > max)
+ max = l;
+ }
+
+ tmp = new char[max + 1];
+ for (int i = 0; i < max; i++)
+ tmp[i] = 32;
+ tmp[max] = 0;
+ }
+
+ for (int i = 0; i < cols; i++) {
+ if (m_cols[i]->m_hidden)
+ continue;
+
+ printf("%s", csv_qoute_string(m_cols[i]->m_name).c_str());
+ if (i < cols - 1) {
+ size_t pos = csv_qoute_string(m_cols[i]->m_name).length() + max - col_len[i] + 1;
+ if (format && pos < max) {
+ printf("%s,", &tmp[pos]);
+ } else {
+ printf(",");
+ }
+ }
+ }
+ printf("\n");
+ for (auto it = m_rows.begin(); it != m_rows.end(); it++) {
+ Row* r = *it;
+
+ for (int i = 0; i < cols; i++) {
+ Column* c = m_cols[i];
+
+ if (c->m_hidden)
+ continue;
+
+ int offset = c->m_offset;
+ static const int bufsize = 100;
+ char buf[bufsize];
+
+ std::string out;
+
+ switch (c->m_type) {
+ case Coltype::_bool:
+ out = r->access_column<bool_column>(offset) ? "1" : "0";
+ break;
+ case Coltype::_int:
+ snprintf(buf, bufsize, "%i", r->access_column<int_column>(offset));
+ out = buf;
+ break;
+ case Coltype::_float:
+ snprintf(buf, bufsize, "%g", r->access_column<float_column>(offset));
+ out = buf;
+ break;
+ case Coltype::_text:
+ out = csv_qoute_string(r->access_column<text_column>(offset)->data);
+ break;
+ }
+
+ fputs(out.c_str(), stdout);
+ if (i < cols - 1) {
+ if (format) {
+ printf("%s,", &tmp[out.length() + max - col_len[i] + 1]);
+ } else {
+ printf(",");
+ }
+ }
+ }
+
+ printf("\n");
+ }
+ delete[] tmp;
+}
+
+void Table::dump()
+{
+ int cols = (int)m_cols.size();
+ int width = 25;
+ char fmti[40];
+ snprintf(fmti, sizeof(fmti) - 1, "%%%dd |", width);
+ fmti[39] = 0;
+ char fmtd[40];
+ snprintf(fmtd, sizeof(fmtd) - 1, "%%%dg |", width);
+ fmtd[39] = 0;
+ char fmts[40];
+ snprintf(fmts, sizeof(fmts) - 1, "%%%ds |", width);
+ fmts[39] = 0;
+
+ printf("Table::dump() table:%s cols:%d\n", m_name.c_str(), cols);
+ printrep((width + 2) * cols + 1, '-');
+ printf("\n");
+ printf("|");
+ for (int i = 0; i < cols; i++)
+ printf(fmti, m_cols[i]->m_type);
+ printf("\n");
+ printf("|");
+ for (int i = 0; i < cols; i++)
+ printf(fmts, m_cols[i]->m_name.c_str());
+ printf("\n");
+ printrep((width + 2) * cols + 1, '*');
+ printf("\n");
+ for (auto it = m_rows.begin(); it != m_rows.end(); it++) {
+ printf("|");
+ Row* r = *it;
+
+ for (int i = 0; i < cols; i++) {
+ Column* c = m_cols[i];
+ int offset = c->m_offset;
+
+ switch (c->m_type) {
+ case Coltype::_bool:
+ printf(fmts, r->access_column<bool_column>(offset) ? "1" : "0");
+ break;
+ case Coltype::_int:
+ printf(fmti, r->access_column<int_column>(offset));
+ break;
+ case Coltype::_float:
+ printf(fmtd, r->access_column<float_column>(offset));
+ break;
+ case Coltype::_text:
+ printf(fmts, r->access_column<text_column>(offset));
+ break;
+ }
+ }
+
+ printf("\n");
+ }
+ printrep((width + 2) * cols + 1, '-');
+ printf("\n");
+}
+
+class Parser {
+private:
+ Token::Type m_last;
+
+public:
+ std::list<Token> m_tokens;
+ typedef std::list<Token>::iterator Lit;
+
+ Parser()
+ {
+ m_last = Token::_invalid;
+ }
+
+ void push(Token::Type type, const char* string)
+ {
+ if (!(type == Token::_semicolon && m_last == Token::_semicolon))
+ m_tokens.push_back(Token(type, string));
+ m_last = type;
+ }
+
+ void dump()
+ {
+ for (auto it = m_tokens.begin(); it != m_tokens.end(); it++) {
+ printf("Type %d: %s\n", it->get_type(), it->get_token());
+ }
+ }
+
+ bool analyze(Query& q)
+ {
+ auto it = m_tokens.begin();
+ bool ok = true;
+ while (ok) {
+ ok = false;
+ if (get_sample_stmt(q, it))
+ ok = true;
+
+ if (get_select_stmt(q, it))
+ ok = true;
+ }
+
+ return true;
+ }
+
+ bool get_sample_stmt(Query& q, Lit& i_iter)
+ {
+ Lit it = i_iter;
+ if (!is(it, Token::_label, "sample"))
+ return false;
+ it++;
+ if (!is(it, Token::_number))
+ return false;
+ int sample = atoi(it->get_token());
+ it++;
+ if (!is(it, Token::_semicolon))
+ return false;
+
+ it++;
+ q.m_sample = sample;
+ i_iter = it;
+ return true;
+ }
+
+ bool get_select_stmt(Query& q, Lit& i_iter)
+ {
+ Lit it = i_iter;
+ if (!get_select_core(q, it)) {
+ return false;
+ }
+
+ get_from(q, it);
+ get_where(q, it);
+ get_group_by(q, it);
+ get_order_by(q, it);
+ get_limit(q, it);
+ get_as(q, it);
+ if (!is(it, Token::_semicolon)) {
+ throw Error("Expected ';' but found '%s' !", it->get_token());
+ }
+ it++;
+ i_iter = it;
+ return true;
+ }
+
+ bool is(Lit& it, Token::Type type, const char* str = 0)
+ {
+ if (!str)
+ return (it->get_type() == type);
+
+ return (it->get_type() == type && cmpi(it->get_token(), str));
+ }
+
+ OP* get_result_column(std::list<Token>::iterator& it)
+ {
+ OP* res = 0;
+ Lit save = it;
+ if (is(it, Token::_op, "*")) {
+ it++;
+ return new OP(Token(Token::_column, "*"));
+ }
+ if (it->get_type() == Token::_label) {
+ std::string table = it->get_token();
+ it++;
+ if (is(it, Token::_op, ".")) {
+ it++;
+ if (is(it, Token::_op, "*")) {
+ it++;
+ std::string c = table + ".*";
+ return new OP(Token(Token::_column, c.c_str()));
+ }
+ it = save;
+ return 0;
+ }
+ }
+ it = save;
+ if ((res = get_expr(it, 0))) {
+ save = it;
+
+ if (is(it, Token::_label, "as")) {
+ it++;
+ if (is(it, Token::_label)) {
+ res->m_name = it->get_token();
+ it++;
+ } else
+ it = save;
+ }
+ }
+ return res;
+
+ // check for valid table
+ }
+
+ bool get_select_core(Query& q, Lit& it)
+ {
+ Lit save = it;
+ if (!is(it, Token::_label, "select"))
+ return false;
+ it++;
+ bool again = true;
+ bool success = true;
+ while (again) {
+ OP* op;
+ if ((op = get_result_column(it))) {
+ q.m_select.push_back(op);
+ } else {
+ success = false;
+ break;
+ }
+
+ if (is(it, Token::_op, ","))
+ it++;
+ else
+ again = false;
+ }
+ if (success)
+ return true;
+
+ it = save;
+ return false;
+ }
+
+ bool get_ordering_terms(Ordering_terms& ordering, std::list<Token>::iterator& it)
+ {
+ OP* op;
+ while ((op = get_expr(it, 0))) {
+ bool asc = true;
+ if (it->get_type() == Token::_label) {
+ if (cmpi(it->get_token(), "asc")) {
+ // default
+ } else if (cmpi(it->get_token(), "desc")) {
+ asc = false;
+ } else if (cmpi(it->get_token(), "collate")) {
+ throw Error("unhandled option:collate");
+ } else {
+ ordering.m_terms.push_back(Ordering_terms::OP_dir(op, asc));
+ break;
+ }
+
+ it++;
+ }
+
+ ordering.m_terms.push_back(Ordering_terms::OP_dir(op, asc));
+
+ if (!is(it, Token::_op, ","))
+ break;
+ it++;
+ }
+ return true;
+ }
+ bool get_group_by(Query& q, Lit& it)
+ {
+ if (!is(it, Token::_label, "group")) {
+ return true;
+ }
+ it++;
+ if (!is(it, Token::_label, "by"))
+ return false;
+ it++;
+ bool res = get_ordering_terms(q.m_group_by, it);
+ get_having(q, it);
+ return res;
+ }
+ bool get_as(Query& q, Lit& it)
+ {
+ if (!is(it, Token::_label, "as")) {
+ return true;
+ }
+ it++;
+ if (!is(it, Token::_label))
+ return false;
+ q.m_result->m_name = it->get_token();
+ it++;
+ return true;
+ }
+
+ bool get_order_by(Query& q, Lit& it)
+ {
+ if (!is(it, Token::_label, "order")) {
+ return true;
+ }
+ it++;
+ if (!is(it, Token::_label, "by"))
+ return false;
+ it++;
+ return get_ordering_terms(q.m_order_by, it);
+ }
+
+ bool get_limit(Query& q, Lit& it)
+ {
+ Lit save = it;
+ if (!is(it, Token::_label, "limit")) {
+ return true;
+ }
+ it++;
+ if (!is(it, Token::_number)) {
+ it = save;
+ throw Error("non numeric operand to limit");
+ }
+ q.m_limit = atoi(it->get_token());
+ it++;
+ save = it;
+
+ if (!is(it, Token::_label, "offset")) {
+ return true;
+ }
+ it++;
+ if (!is(it, Token::_number)) {
+ it = save;
+ throw Error("non numeric operand to offset");
+ }
+ q.m_offset = atoi(it->get_token());
+ it++;
+ return true;
+ }
+
+ bool get_having(Query& q, Lit& it)
+ {
+ Lit save = it;
+ if (!is(it, Token::_label, "having"))
+ return true;
+ it++;
+ OP* res = 0;
+ if ((res = get_expr(it, 0))) {
+ q.m_having = res;
+ return true;
+ }
+ it = save;
+ return false;
+ }
+
+ bool get_where(Query& q, Lit& it)
+ {
+ Lit save = it;
+ if (!is(it, Token::_label, "where"))
+ return true;
+ it++;
+ OP* res = 0;
+ if ((res = get_expr(it, 0))) {
+ q.m_where = res;
+ return true;
+ }
+ it = save;
+ return false;
+ }
+
+ bool get_from(Query& q, Lit& it)
+ {
+ if (!is(it, Token::_label, "from"))
+ return false;
+ it++;
+ if (it->get_type() == Token::_label) {
+ const char* name = it->get_token();
+ if (get_packet_handler(name)) {
+ q.m_from_name = name;
+ it++;
+ return true;
+ } else
+ throw Error("Error in from statement, table '%s' not found", name);
+ }
+ throw Error("Error in from statement");
+ }
+
+ int get_stack_precedence(std::stack<OP*>& operator_stack)
+ {
+ int pre = 0;
+ if (!operator_stack.empty())
+ pre = operator_stack.top()->m_precedence;
+ return pre;
+ }
+
+ // using The shunting yard algorithm
+ OP* get_expr(Lit& it, int rec)
+ {
+ std::stack<OP*> operator_stack;
+ std::stack<OP*> operand_stack;
+ bool success = true;
+ bool expect_expr = true;
+ while (success) {
+ success = false;
+ Lit save = it;
+ Lit next = it;
+ next++;
+
+ // match literal
+ if (expect_expr && is_literal(it)) {
+ OP* op = new OP(*it);
+ it++;
+ operand_stack.push(op);
+ success = true;
+ expect_expr = false;
+ continue;
+ }
+ // match literal
+ if (expect_expr && is_unary_op(it)) {
+ OP* op = new OP(*it);
+ op->set_type(Token::_uop);
+ it++;
+ if (!(op->m_right = get_expr(it, rec + 1)))
+ throw Error("Got unary '%s' but could not parse following expression", op->get_token());
+
+ operand_stack.push(op);
+ success = true;
+ expect_expr = false;
+ continue;
+ }
+ // match function-name (
+ if (expect_expr && is(it, Token::_label) && is(next, Token::_paren, "(")) {
+ OP* func = new OP(*it);
+ func->set_type(Token::_function);
+ it++;
+ it++;
+ if (is(it, Token::_op, "*")) {
+ it->set_type(Token::_number);
+ it->set_token("1");
+ }
+ func->m_param[0] = get_expr(it, rec + 1);
+ if (!func->m_param[0])
+ throw Error("Missing operand to function");
+ operand_stack.push(func);
+ int n = 1;
+ while (n < OP::max_param() && is(it, Token::_op, ",")) {
+
+ it++;
+ func->m_param[n++] = get_expr(it, rec + 1);
+ }
+ if (!is(it, Token::_paren, ")"))
+ throw Error("Expected ) after %s", func->get_token());
+ it++;
+ expect_expr = false;
+ success = true;
+ continue;
+ }
+ // match [[databasename .] table-name . ] column name
+ if (expect_expr && is(it, Token::_label)) {
+ OP* op = new OP(*it);
+ it++;
+ success = true;
+ op->set_type(Token::_column);
+ operand_stack.push(op);
+ expect_expr = false;
+ continue;
+ }
+ // match ( expr )
+ if (!expect_expr && is(it, Token::_paren, ",")) {
+ break;
+ }
+ if (!expect_expr && is(it, Token::_paren, ")")) {
+ break;
+ }
+ if (expect_expr && is(it, Token::_paren, "(")) {
+ it++;
+ OP* op = 0;
+ if ((op = get_expr(it, rec + 1))) {
+ if (is(it, Token::_paren, ")")) {
+ it++;
+ operand_stack.push(op);
+ expect_expr = false;
+ success = true;
+ continue;
+ }
+ throw Error("Error in expression no )");
+ }
+ it = save;
+ throw Error("Error in expression");
+ }
+ // bin op
+ if (!expect_expr && is(it, Token::_op, "is") && is(next, Token::_op, "not")) {
+ it++;
+ it->set_token("is not");
+ }
+ // bin op
+ if (!expect_expr && is(it, Token::_op, "not") && is(next, Token::_op, "like")) {
+ it++;
+ it->set_token("not like");
+ }
+ if (!expect_expr && is(it, Token::_op) && OP::is_binary(it->get_token())) {
+ OP* bop = new OP(*it);
+ while (bop->m_precedence <= get_stack_precedence(operator_stack)) {
+
+ OP* stack_op = operator_stack.top();
+ operator_stack.pop();
+ if (operand_stack.size() >= 2) {
+
+ OP* stk1 = operand_stack.top();
+ stack_op->m_right = stk1;
+ operand_stack.pop();
+ OP* stk2 = operand_stack.top();
+ stack_op->m_left = stk2;
+ operand_stack.pop();
+ operand_stack.push(stack_op);
+ }
+ }
+
+ operator_stack.push(bop);
+ it++;
+ success = true;
+ expect_expr = true;
+ continue;
+ }
+ }
+ while (!operator_stack.empty()) {
+ OP* bop = operator_stack.top();
+ operator_stack.pop();
+ if (bop) {
+ if (operand_stack.size() >= 2) {
+ OP* stk = operand_stack.top();
+ bop->m_right = stk;
+ operand_stack.pop();
+ OP* stk2 = operand_stack.top();
+ bop->m_left = stk2;
+ operand_stack.pop();
+ operand_stack.push(bop);
+ }
+ }
+ }
+ if (operand_stack.size() == 0)
+ return 0;
+ return operand_stack.top();
+ }
+
+ bool is_unary_op(Lit& it)
+ {
+ if (is(it, Token::_op, "-"))
+ return true;
+ if (is(it, Token::_op, "+"))
+ return true;
+ if (is(it, Token::_op, "~"))
+ return true;
+ if (is(it, Token::_op, "not"))
+ return true;
+ return false;
+ }
+ bool is_literal(Lit& it)
+ {
+ if (it->get_type() == Token::_number || it->get_type() == Token::_string)
+ return true;
+ if (it->get_type() == Token::_label && cmpi(it->get_token(), "null"))
+ return true;
+ return false;
+ }
+};
+
+class Lexer {
+public:
+ Lexer(Parser& p)
+ : m_parser(p)
+ , num_state(_nan)
+ {
+ }
+
+ Parser& m_parser;
+
+ enum State {
+ _unknown,
+ _white,
+ _label,
+ _number,
+ _op,
+ _paren,
+ _string
+ };
+ bool is_white(const char c) { return (c == ' ' || c == 9 || c == 10 || c == 13); }
+ bool is_char(const char c) { return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); }
+ bool is_num(const char c) { return ((c >= '0' && c <= '9')); }
+ bool is_op(const char c) { return (c == '*' || c == ',' || c == '=' || c == '<' || c == '>' || c == '/' || c == '|' || c == '%' || c == '+' || c == '-' || c == '&' || c == '~' || c == '!'); }
+ bool is_paren(const char c) { return (c == '(' || c == ')'); }
+ bool is_termination(const char c) { return (c == 0 || c == ';'); }
+ bool is_quote(const char c) { return (c == '\''); }
+ bool is_label_start(const char c) { return (is_char(c)); }
+ bool is_label_part(const char c) { return (is_num(c) || is_char(c) || c == '_'); }
+
+ bool lex(const char* i_str)
+ {
+ const char* p = i_str;
+ State state = _white;
+ const char* strstart = 0;
+ bool is_escaped = false;
+ std::string str = "";
+
+ while (true) {
+ switch (state) {
+ case (_unknown): {
+ if (is_white(*p))
+ state = _white;
+ else if (is_label_start(*p))
+ state = _label;
+ else if (is_num(*p) || (*p == '.' && is_num(p[1])))
+ state = _number;
+ else if (is_quote(*p))
+ state = _string;
+ else if (is_paren(*p))
+ state = _paren;
+ else if (is_op(*p))
+ state = _op;
+ else if (is_termination(*p)) {
+ m_parser.push(Token::_semicolon, ";");
+ if (*p++ == 0) {
+ m_parser.push(Token::_end, "END");
+ return true;
+ }
+ } else {
+ m_parser.push(Token::_end, "END");
+ printf("Unknown char %c (%d) at %d! in statement %s\n", *p, *p, int(p - i_str), i_str);
+ return false;
+ }
+ } break;
+ case (_white): {
+ if (is_white(*p))
+ p++;
+ else {
+ state = _unknown;
+ }
+ } break;
+ case (_string): {
+ if ((!is_escaped) && is_quote(*p)) {
+ if (!strstart) {
+ strstart = p;
+ str = "";
+ } else {
+ m_parser.push(Token::_string, str.c_str());
+
+ str = "";
+ strstart = 0;
+ state = _unknown;
+ is_escaped = false;
+ }
+ p++;
+ } else {
+ if (is_escaped) {
+ if (!is_quote(*p))
+ str += '\\';
+ is_escaped = false;
+ }
+
+ if (*p == 0) {
+ printf("Unexpected end of string in statement %s\n", strstart);
+ m_parser.push(Token::_end, "END");
+ return false;
+ }
+ if (*p == '\\')
+ is_escaped = true;
+ else
+ str += (*p);
+ p++;
+ }
+
+ } break;
+ case (_number): {
+ if (parse_num(*p)) {
+ if (!strstart)
+ strstart = p;
+ p++;
+ } else {
+ if (!strstart)
+ throw Error("Numeric problem");
+ std::string label = strstart;
+ label = label.substr(0, p - strstart);
+ m_parser.push(Token::_number, label.c_str());
+
+ strstart = 0;
+ state = _unknown;
+ }
+ } break;
+ case (_label): {
+ if (!strstart) {
+ strstart = p++;
+ } else if (is_label_part(*p)) {
+ p++;
+ } else {
+ std::string label = strstart;
+ label = label.substr(0, p - strstart);
+
+ Token::Type type = Token::_label;
+ if (cmpi(label, "is"))
+ type = Token::_op;
+ if (cmpi(label, "not"))
+ type = Token::_op;
+ if (cmpi(label, "in"))
+ type = Token::_op;
+ if (cmpi(label, "like"))
+ type = Token::_op;
+ if (cmpi(label, "glob"))
+ type = Token::_op;
+ if (cmpi(label, "match"))
+ type = Token::_op;
+ if (cmpi(label, "regexp"))
+ type = Token::_op;
+ if (cmpi(label, "and"))
+ type = Token::_op;
+ if (cmpi(label, "or"))
+ type = Token::_op;
+
+ m_parser.push(type, label.c_str());
+
+ strstart = 0;
+ state = _unknown;
+ }
+
+ } break;
+ case (_paren): {
+ std::string s;
+ s = *p;
+ m_parser.push(Token::_paren, s.c_str());
+ p++;
+ state = _unknown;
+ } break;
+ case (_op): {
+ std::string s;
+ s = *p;
+ char n = p[1];
+ switch (*p) {
+ case ('|'):
+ if (n == '|') {
+ p++;
+ s += *p;
+ }
+ break;
+ case ('>'):
+ if (n == '=') {
+ p++;
+ s += *p;
+ }
+ if (n == '>') {
+ p++;
+ s += *p;
+ }
+ break;
+ case ('<'):
+ if (n == '<') {
+ p++;
+ s += *p;
+ }
+ if (n == '=') {
+ p++;
+ s += *p;
+ }
+ if (n == '>') {
+ p++;
+ s += *p;
+ }
+ break;
+ case ('='):
+ if (n == '=') {
+ p++;
+ s += *p;
+ }
+ break;
+ case ('!'):
+ if (n == '=') {
+ p++;
+ s += *p;
+ }
+ break;
+ }
+ m_parser.push(Token::_op, s.c_str());
+ p++;
+ state = _unknown;
+ } break;
+ default:
+ printf("Missing impl char %c at %d! in statement %s\n", *p, int(p - i_str), i_str);
+ m_parser.push(Token::_end, "END");
+ return false;
+ }
+ }
+ }
+ bool parse_num(const char p)
+ {
+ switch (num_state) {
+ case (_nan):
+ num_state = _int;
+ if (p == '.')
+ num_state = _dot;
+ break;
+ case (_int):
+ if (p == '.')
+ num_state = _dot;
+ else if (!is_num(p)) {
+ num_state = _nan;
+ return false;
+ }
+ break;
+ case (_dot):
+ if (p == 'E' || p == 'e')
+ num_state = _e;
+ else if (!is_num(p)) {
+ num_state = _nan;
+ return false;
+ }
+ break;
+ case (_dec):
+ if (p == 'E' || p == 'e')
+ num_state = _e;
+ else if (!is_num(p)) {
+ num_state = _nan;
+ return false;
+ }
+ break;
+ case (_e):
+ if (p == '+' || p == '-')
+ num_state = _sign;
+ else if (is_num(p))
+ num_state = _exp;
+ else {
+ throw Error("expected number digit after E");
+ }
+ break;
+ case (_sign):
+ if (!is_num(p))
+ throw Error("expected number digit after E");
+ num_state = _exp;
+ break;
+ case (_exp):
+ if (!is_num(p)) {
+ num_state = _nan;
+ return false;
+ }
+ break;
+ }
+ return true;
+ }
+ enum Num_state {
+ _nan,
+ _int,
+ _dot,
+ _dec,
+ _e,
+ _sign,
+ _exp,
+ };
+
+ Num_state num_state;
+};
+
+void Query::parse()
+{
+ Parser p;
+ Lexer l(p);
+ l.lex(m_sql.c_str());
+ // p.dump();
+ if (!p.analyze(*this))
+ throw Error("error parsing select statement");
+}
+
+// return column and index in tables, or 0 for column if column isn't found
+std::pair<Column*, int> lookup_column_in_tables(const std::vector<Table*>& tables,
+ const std::vector<int>& search_order,
+ const char* name)
+{
+ if (strcmp(name, "*") == 0)
+ return std::pair<Column*, int>((Column*)0, 0);
+
+ for (auto i = search_order.begin(); i != search_order.end(); ++i) {
+ Table* table = tables[*i];
+ int col_index = table->get_col_index(name);
+ if (col_index >= 0)
+ return std::pair<Column*, int>(table->m_cols[col_index], *i);
+ }
+
+ return std::pair<Column*, int>((Column*)0, 0);
+}
+
+OP* OP::compile(const std::vector<Table*>& tables, const std::vector<int>& search_order, Query& q)
+{
+ OP* ret = 0;
+ for (int i = 0; i < max_param(); i++) {
+ if (m_param[i]) {
+ m_param[i] = m_param[i]->compile(tables, search_order, q);
+ if (m_param[i]->m_has_aggregate_function)
+ m_has_aggregate_function = true;
+ }
+ }
+
+ if (m_left) {
+ m_left = m_left->compile(tables, search_order, q);
+ if (m_left->m_has_aggregate_function)
+ m_has_aggregate_function = true;
+ }
+
+ if (m_right) {
+ m_right = m_right->compile(tables, search_order, q);
+ if (m_right->m_has_aggregate_function)
+ m_has_aggregate_function = true;
+ }
+
+ // default to destination row
+ m_row_index = tables.size() - 1;
+
+ if (get_type() == _column) {
+ auto lookup = lookup_column_in_tables(tables, search_order, get_token());
+ Column* column = lookup.first;
+ m_row_index = lookup.second;
+
+ if (!column)
+ throw Error("Column '%s' not found", get_token());
+
+ int offset = column->m_offset;
+
+ m_t = column->m_type;
+
+ switch (m_t) {
+ case Coltype::_int:
+ ret = new Column_access_int(*this, offset);
+ break;
+ case Coltype::_bool:
+ ret = new Column_access_bool(*this, offset);
+ break;
+ case Coltype::_float:
+ ret = new Column_access_float(*this, offset);
+ break;
+ case Coltype::_text:
+ ret = new Column_access_text(*this, offset);
+ break;
+ }
+ } else if (get_type() == _number) {
+ const char* p = get_token();
+ bool integer = true;
+ while (*p != 0) {
+ if (*p < '0' || *p > '9')
+ integer = false;
+ p++;
+ }
+ if (integer) {
+ m_t = Coltype::_int;
+ ret = new Static_int(*this);
+ } else {
+ m_t = Coltype::_float;
+ ret = new Static_float(*this);
+ }
+ } else if (get_type() == _string) {
+ m_t = Coltype::_text;
+ ret = new Static_text(*this);
+ } else if ((get_type() == _function) && m_param[0]) {
+ Table* dest_table = tables[m_row_index];
+
+ m_t = Coltype::_int;
+ if (cmpi(get_token(), "if") && m_param[1] && m_param[2]) {
+ m_t = m_param[1]->m_t;
+ if (m_param[2]->m_t > m_t)
+ m_t = m_param[2]->m_t;
+ ret = new If_func(*this);
+ } else if (cmpi(get_token(), "name") && m_param[1]) {
+ m_t = Coltype::_text;
+ ret = new Name_func(*this);
+ } else if (cmpi(get_token(), "trim")) {
+ m_t = Coltype::_text;
+ ret = new Trim_func(*this);
+ } else if (cmpi(get_token(), "rsplit") && m_param[1]) {
+ m_t = Coltype::_text;
+ ret = new Rsplit_func(*this);
+ } else if (cmpi(get_token(), "netmask")) {
+ m_t = Coltype::_text;
+ ret = new Netmask_func(*this);
+ } else if (cmpi(get_token(), "cc")) {
+ m_t = Coltype::_text;
+ ret = new Cc_func(*this);
+ } else if (cmpi(get_token(), "asn")) {
+ m_t = Coltype::_int;
+ ret = new Asn_func(*this);
+ } else if (cmpi(get_token(), "count")) {
+ m_t = Coltype::_int;
+ ret = new Count_func(*this, dest_table);
+ } else if (m_param[0]->ret_type() == Coltype::_float && cmpi(get_token(), "min")) {
+ m_t = Coltype::_float;
+ ret = new Min_func_float(*this, dest_table);
+ } else if (m_param[0]->ret_type() == Coltype::_float && cmpi(get_token(), "max")) {
+ m_t = Coltype::_float;
+ ret = new Max_func_float(*this, dest_table);
+ } else if (m_param[0]->ret_type() == Coltype::_float && cmpi(get_token(), "sum")) {
+ m_t = Coltype::_float;
+ ret = new Sum_func_float(*this, dest_table);
+ } else if (cmpi(get_token(), "min")) {
+ m_t = Coltype::_int;
+ ret = new Min_func_int(*this, dest_table);
+ } else if (cmpi(get_token(), "max")) {
+ m_t = Coltype::_int;
+ ret = new Max_func_int(*this, dest_table);
+ } else if (cmpi(get_token(), "sum")) {
+ m_t = Coltype::_int;
+ ret = new Sum_func_int(*this, dest_table);
+ } else if (cmpi(get_token(), "lower")) {
+ m_t = Coltype::_text;
+ ret = new Lower_func(*this);
+ } else if (cmpi(get_token(), "len")) {
+ m_t = Coltype::_int;
+ ret = new Len_func(*this);
+ } else if (cmpi(get_token(), "truncate")) {
+ m_t = Coltype::_int;
+ ret = new Truncate_func(*this);
+ } else if (cmpi(get_token(), "stdev")) {
+ m_t = Coltype::_float;
+ ret = new Stdev_func(*this, dest_table);
+ } else if (cmpi(get_token(), "avg")) {
+ m_t = Coltype::_float;
+ ret = new Avg_func(*this, dest_table);
+ }
+ } else if ((get_type() == _op) && m_left && m_right) {
+ if (cmpi(get_token(), "||")) {
+ m_t = Coltype::_text;
+ ret = new Bin_op_concatenate(*this);
+ } else if (cmpi(get_token(), "*")) {
+ if (m_left->ret_type() == Coltype::_float || m_right->ret_type() == Coltype::_float) {
+ m_t = Coltype::_float;
+ ret = new Bin_op_mul_float(*this);
+ } else {
+ m_t = Coltype::_int;
+ ret = new Bin_op_mul(*this);
+ }
+ } else if (cmpi(get_token(), "/")) {
+ m_t = Coltype::_float;
+ ret = new Bin_op_div(*this);
+ } else if (cmpi(get_token(), "%")) {
+ m_t = Coltype::_float;
+ ret = new Bin_op_modulo(*this);
+ } else if (cmpi(get_token(), "+")) {
+ if (m_left->ret_type() == Coltype::_float || m_right->ret_type() == Coltype::_float) {
+ m_t = Coltype::_float;
+ ret = new Bin_op_add_float(*this);
+ } else {
+ m_t = Coltype::_int;
+ ret = new Bin_op_add(*this);
+ }
+ } else if (cmpi(get_token(), "-")) {
+ if (m_left->ret_type() == Coltype::_float || m_right->ret_type() == Coltype::_float) {
+ m_t = Coltype::_float;
+ ret = new Bin_op_sub_float(*this);
+ } else {
+ m_t = Coltype::_int;
+ ret = new Bin_op_sub(*this);
+ }
+ } else if (cmpi(get_token(), "<<")) {
+ m_t = Coltype::_int;
+ ret = new Bin_op_arithmetic_shift_left(*this);
+ } else if (cmpi(get_token(), ">>")) {
+ m_t = Coltype::_int;
+ ret = new Bin_op_arithmetic_shift_right(*this);
+ } else if (cmpi(get_token(), "&")) {
+ m_t = Coltype::_int;
+ ret = new Bin_op_bitwise_and(*this);
+ } else if (cmpi(get_token(), "|")) {
+ m_t = Coltype::_int;
+ ret = new Bin_op_bitwise_or(*this);
+ } else if (cmpi(get_token(), "<")) {
+ m_t = Coltype::_bool;
+ ret = new Bin_op_lt(*this);
+ } else if (cmpi(get_token(), "<=")) {
+ m_t = Coltype::_bool;
+ ret = new Bin_op_lteq(*this);
+ } else if (cmpi(get_token(), ">")) {
+ m_t = Coltype::_bool;
+ ret = new Bin_op_gt(*this);
+ } else if (cmpi(get_token(), ">=")) {
+ m_t = Coltype::_bool;
+ ret = new Bin_op_gteq(*this);
+ } else if (cmpi(get_token(), "=")) {
+ m_t = Coltype::_bool;
+ ret = new Bin_op_eq(*this);
+ } else if (cmpi(get_token(), "==")) {
+ m_t = Coltype::_bool;
+ ret = new Bin_op_eq(*this);
+ } else if (cmpi(get_token(), "like")) {
+ m_t = Coltype::_bool;
+ ret = new Bin_op_like(*this);
+ } else if (cmpi(get_token(), "not like")) {
+ m_t = Coltype::_bool;
+ ret = new Bin_op_not_like(*this);
+ } else if (cmpi(get_token(), "!=")) {
+ m_t = Coltype::_bool;
+ ret = new Bin_op_not_eq(*this);
+ } else if (cmpi(get_token(), "<>")) {
+ m_t = Coltype::_bool;
+ ret = new Bin_op_not_eq(*this);
+ } else if (cmpi(get_token(), "is")) {
+ m_t = Coltype::_bool;
+ ret = new Bin_op_eq(*this);
+ } else if (cmpi(get_token(), "is not")) {
+ m_t = Coltype::_bool;
+ ret = new Bin_op_not_eq(*this);
+ } else if (cmpi(get_token(), "and")) {
+ m_t = Coltype::_bool;
+ ret = new Bin_op_and(*this);
+ } else if (cmpi(get_token(), "or")) {
+ m_t = Coltype::_bool;
+ ret = new Bin_op_or(*this);
+ }
+ } else if ((get_type() == _uop) && m_right) {
+ if (cmpi(get_token(), "not")) {
+ m_t = Coltype::_bool;
+ ret = new Un_op_not(*this);
+ } else if (cmpi(get_token(), "+")) {
+ ret = m_right;
+ } else if (cmpi(get_token(), "-")) {
+ if (m_right->ret_type() == Coltype::_float) {
+ m_t = Coltype::_float;
+ ret = new Un_op_neg_float(*this);
+ } else {
+ m_t = Coltype::_int;
+ ret = new Un_op_neg(*this);
+ }
+ } else if (cmpi(get_token(), "~")) {
+ m_t = Coltype::_int;
+ ret = new Un_op_ones_complement(*this);
+ }
+ }
+ clear_ptr();
+ if (!ret)
+ throw Error("Unknown operator error '%s' !", get_token());
+
+ delete this;
+
+ return ret;
+}
+
+void OP::evaluate_aggregate_operands(Row** rows)
+{
+ if (m_left)
+ m_left->evaluate_aggregate_operands(rows);
+ if (m_right)
+ m_right->evaluate_aggregate_operands(rows);
+ for (int i = 0; i < max_param(); ++i)
+ if (m_param[i])
+ m_param[i]->evaluate_aggregate_operands(rows);
+}
+
+void OP::combine_aggregate(Row* base_row, Row* other_row)
+{
+ if (m_left)
+ m_left->combine_aggregate(base_row, other_row);
+ if (m_right)
+ m_right->combine_aggregate(base_row, other_row);
+ for (int i = 0; i < max_param(); ++i)
+ if (m_param[i])
+ m_param[i]->combine_aggregate(base_row, other_row);
+}
+
+// return any column access ops found in given list of op trees - they don't
+// have to be compiled beforehand; duplicate column tokens are skipped
+std::vector<OP*> find_unique_column_ops(std::vector<OP*> ops)
+{
+ std::vector<OP*> res;
+
+ while (!ops.empty()) {
+ OP* op = ops.back();
+ ops.pop_back();
+
+ if (op->m_left)
+ ops.push_back(op->m_left);
+ if (op->m_right)
+ ops.push_back(op->m_right);
+ for (int i = 0; i < op->max_param(); ++i)
+ if (op->m_param[i])
+ ops.push_back(op->m_param[i]);
+
+ if (op->get_type() == Token::_column) {
+ bool found = false;
+
+ for (auto i = res.begin(); i != res.end(); ++i) {
+ if (cmpii((*i)->get_token(), op->get_token())) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ res.push_back(op);
+ }
+ }
+
+ return res;
+}
+
+void Query::replace_star_column_with_all_columns()
+{
+ bool found_star = false;
+ for (auto i = m_select.begin(); i != m_select.end(); ++i) {
+ if (strcmp((*i)->get_token(), "*") == 0) {
+ found_star = true;
+ break;
+ }
+ }
+
+ if (found_star) {
+ for (auto i = m_select.begin(); i != m_select.end(); ++i)
+ delete *i;
+ m_select.clear();
+
+ if (!m_from_name.empty()) {
+ Packet_handler* handler = get_packet_handler(m_from_name);
+
+ for (auto i = handler->packet_columns.begin(); i != handler->packet_columns.end(); ++i)
+ m_select.push_back(new OP(Token(Token::_column, i->name)));
+ }
+ }
+}
+
+void Query::process_from()
+{
+ replace_star_column_with_all_columns();
+
+ if (m_from_name.empty())
+ return;
+
+ std::vector<OP*> all_ops;
+ all_ops.insert(all_ops.end(), m_select.begin(), m_select.end());
+ if (m_where)
+ all_ops.push_back(m_where);
+ // skip m_having, it can't access source columns
+ for (auto i = m_order_by.m_terms.begin(); i != m_order_by.m_terms.end(); ++i)
+ all_ops.push_back(i->m_op);
+ for (auto i = m_group_by.m_terms.begin(); i != m_group_by.m_terms.end(); ++i)
+ all_ops.push_back(i->m_op);
+
+ auto used_columns = find_unique_column_ops(all_ops);
+
+ // add from table with used columns
+ Packet_handler* handler = get_packet_handler(m_from_name);
+ for (auto j = handler->packet_columns.begin(); j != handler->packet_columns.end(); ++j)
+ for (auto i = used_columns.begin(); i != used_columns.end(); ++i)
+ if (cmpii(j->name, (*i)->get_token()))
+ m_used_from_column_ids.push_back(j->id);
+
+ m_from = handler->create_table(m_used_from_column_ids);
+}
+
+void Query::process_select(Row** rows, Row* dest, GenericAccessor* dest_accessors)
+{
+ for (unsigned int i = 0, size = m_select.size(); i < size; ++i) {
+ OP* op = m_select[i];
+ if (!op)
+ continue;
+
+ if (op->m_has_aggregate_function) {
+ // defer evaluating aggregate functions, just eval their operands
+ op->evaluate_aggregate_operands(rows);
+ } else {
+ Variant v;
+ op->evaluate(rows, v);
+ dest_accessors[i].set(dest, v);
+ }
+ }
+}
+
+void Query::combine_aggregates_in_select(Row* base_row, Row* other_row)
+{
+ for (unsigned int i = 0; i < m_select.size(); ++i) {
+ OP* op = m_select[i];
+ if (op && op->m_has_aggregate_function)
+ op->combine_aggregate(base_row, other_row);
+ }
+}
+
+void Query::process_aggregates_in_select(Row** rows, Row* dest, GenericAccessor dest_accessors[])
+{
+ for (unsigned int i = 0; i < m_select.size(); ++i) {
+ OP* op = m_select[i];
+ if (op && op->m_has_aggregate_function) {
+ Variant v;
+ op->evaluate(rows, v);
+ dest_accessors[i].set(dest, v);
+ }
+ }
+}
+
+bool Query::process_where(Row** rows)
+{
+ if (!m_where)
+ return true;
+
+ Variant v;
+ m_where->evaluate(rows, v);
+ return v.get_bool();
+}
+
+bool Query::process_having(Row** rows)
+{
+ if (!m_having)
+ return true;
+
+ Variant v;
+ m_having->evaluate(rows, v);
+ return v.get_bool();
+}
+
+std::vector<Variant> process_group_by_key(Ordering_terms& group_by, Row** rows)
+{
+ int size = group_by.m_terms.size();
+
+ std::vector<Variant> res(size);
+
+ for (int i = 0; i < size; ++i)
+ group_by.m_terms[i].m_op->evaluate(rows, res[i]);
+
+ return res;
+}
+
+bool Query::has_aggregate_functions()
+{
+ // this assumes the ops have been compiled
+ for (auto it = m_select.begin(); it != m_select.end(); it++)
+ if ((*it)->m_has_aggregate_function)
+ return true;
+
+ return false;
+}
+
+void Query::execute(Reader& reader)
+{
+ std::vector<Table*> tables;
+ std::vector<int> search_results_last, search_results_first, search_results_only;
+
+ // set up tables
+ process_from();
+
+ if (m_from)
+ tables.push_back(m_from);
+ tables.push_back(m_result);
+
+ for (int i = 0; i < int(tables.size()); ++i)
+ search_results_last.push_back(i);
+ for (int i = int(tables.size()) - 1; i >= 0; --i)
+ search_results_first.push_back(i);
+
+ search_results_only.push_back(tables.size() - 1);
+
+ std::vector<Row*> row_ptrs(tables.size());
+ Row** rows = &row_ptrs[0];
+
+ std::vector<GenericAccessor> result_accessors_vector;
+
+ // compile
+ for (auto i = m_select.begin(); i != m_select.end(); ++i) {
+ *i = (*i)->compile(tables, search_results_last, *this);
+ Column* col = m_result->add_column((*i)->get_name(), (*i)->ret_type());
+ GenericAccessor a;
+ a.m_offset = col->m_offset;
+ a.m_type = col->m_type;
+ result_accessors_vector.push_back(a);
+ }
+
+ if (m_where)
+ m_where = m_where->compile(tables, search_results_last, *this);
+
+ if (m_having)
+ m_having = m_having->compile(tables, search_results_only, *this);
+
+ if (m_group_by.exist())
+ m_group_by.compile(tables, search_results_last, *this);
+
+ if (m_order_by.exist()) {
+ // copy any missing columns to result table as hidden so we can
+ // order by them
+ std::vector<OP*> ops;
+ for (auto i = m_order_by.m_terms.begin(); i != m_order_by.m_terms.end(); ++i)
+ ops.push_back(i->m_op);
+
+ std::vector<OP*> column_ops = find_unique_column_ops(ops);
+
+ for (auto i = column_ops.begin(); i != column_ops.end(); ++i) {
+ const char* name = (*i)->get_token();
+ auto lookup = lookup_column_in_tables(tables, search_results_first, name);
+ if (lookup.first and lookup.second < int(tables.size()) - 1) {
+ // found, but not in result table
+ OP* copying_op = new OP(**i);
+ copying_op = copying_op->compile(tables, search_results_last, *this);
+ m_select.push_back(copying_op);
+ Column* col = m_result->add_column(copying_op->get_name(), copying_op->ret_type(), -1, Column::HIDDEN);
+ GenericAccessor a;
+ a.m_offset = col->m_offset;
+ a.m_type = col->m_type;
+ result_accessors_vector.push_back(a);
+ }
+ }
+
+ // we only provide access to result table for "order by"; in order
+ // to make the sort thing work correctly the result table currently
+ // has to be at index 0
+ std::vector<Table*> tables_result_only = { m_result };
+ std::vector<int> tables_result_only_search = { 0 };
+ m_order_by.compile(tables_result_only, tables_result_only_search, *this);
+ }
+
+ // execute
+ GenericAccessor* result_accessors = &result_accessors_vector[0];
+ bool aggregate_functions = has_aggregate_functions();
+
+ int count = 0;
+ bool limiter = !m_order_by.exist() && !m_group_by.exist() && !aggregate_functions && m_limit >= 0;
+
+ if (m_from) {
+ bool first_row = true;
+ Packet_handler* handler = get_packet_handler(m_from_name);
+
+ reader.seek_to_start();
+
+ const int src_i = 0, dest_i = tables.size() - 1;
+
+ rows[src_i] = m_from->create_row();
+
+ if (m_group_by.exist() || aggregate_functions) {
+ std::unordered_map<std::vector<Variant>, Row*> groups;
+
+ rows[dest_i] = 0;
+ while (reader.read_next(handler, m_used_from_column_ids, *rows[src_i], first_row or m_sample == 0 ? 0 : m_sample - 1)) {
+ // fill in groups
+ if (rows[dest_i])
+ rows[dest_i]->reset_text_columns(m_result->m_text_column_offsets);
+ else
+ rows[dest_i] = m_result->create_row();
+
+ process_select(rows, rows[dest_i], result_accessors);
+ if (process_where(rows)) {
+ auto key = process_group_by_key(m_group_by, rows);
+ Row*& entry = groups[key];
+ if (entry) {
+ combine_aggregates_in_select(entry, rows[dest_i]);
+ } else {
+ entry = rows[dest_i];
+ rows[dest_i] = 0;
+ }
+ }
+
+ first_row = false;
+ rows[src_i]->reset_text_columns(m_from->m_text_column_offsets);
+ }
+ if (rows[dest_i])
+ m_result->delete_row(rows[dest_i]);
+
+ // put groups into result
+ for (auto i = groups.begin(); i != groups.end(); ++i) {
+ rows[dest_i] = i->second;
+ // propagate the aggregate results through the evaluation tree
+ process_aggregates_in_select(rows, rows[dest_i], result_accessors);
+ if (process_having(rows))
+ m_result->add_row(rows[dest_i]);
+ else
+ m_result->delete_row(rows[dest_i]);
+ }
+ } else {
+ rows[dest_i] = m_result->create_row();
+ while (reader.read_next(handler, m_used_from_column_ids, *rows[src_i], first_row or m_sample == 0 ? 0 : m_sample - 1)) {
+ // fill in result
+ process_select(rows, rows[dest_i], result_accessors);
+ if (process_where(rows)) {
+ bool commit = true;
+ if (limiter) {
+ int l = count++;
+ if (m_offset > 0)
+ l -= m_offset;
+ if (m_limit >= 0 && l >= m_limit)
+ break;
+
+ if (l < 0)
+ commit = false;
+ }
+
+ if (commit) {
+ m_result->add_row(rows[dest_i]);
+ rows[dest_i] = m_result->create_row();
+ }
+ }
+
+ first_row = false;
+ rows[src_i]->reset_text_columns(m_from->m_text_column_offsets);
+ }
+ m_result->delete_row(rows[dest_i]);
+ }
+
+ m_from->delete_row(rows[src_i]);
+ } else {
+ const int dest_i = tables.size() - 1;
+ rows[dest_i] = m_result->create_row();
+ process_select(rows, rows[dest_i], result_accessors);
+ if (process_where(rows))
+ m_result->add_row(rows[dest_i]);
+ else
+ m_result->delete_row(rows[dest_i]);
+ }
+
+ if (m_order_by.exist())
+ m_result->per_sort(m_order_by);
+
+ if (m_limit >= 0 && !limiter)
+ m_result->limit(m_limit, m_offset);
+}
+
+DB::DB()
+{
+ Column::init_defs();
+}
+
+DB::~DB()
+{
+}
+
+bool DB::query(const char* q)
+{
+ return false;
+}
+
+Table* DB::get_table(const char* i_name)
+{
+ std::string name = lower(i_name);
+ Table* t = 0;
+ auto it = m_tables.find(name);
+ if (it != m_tables.end())
+ t = it->second;
+
+ return t;
+}
+Table* DB::create_or_use_table(const char* i_name)
+{
+ std::string name = lower(i_name);
+ Table* t = get_table(name.c_str());
+ if (!t)
+ t = create_table(name.c_str());
+
+ return t;
+}
+Table* DB::create_table(const char* i_name)
+{
+ std::string name = lower(i_name);
+ Table* t = new Table(name.c_str());
+ m_tables[std::string(name.c_str())] = t;
+
+ return t;
+}
+Column::Column(const char* name, Coltype::Type type, int id, bool hidden)
+ : m_name(name)
+ , m_type(type)
+ , m_def(Column::m_coldefs[type])
+ , m_id(id)
+ , m_offset(0)
+{
+ m_hidden = hidden;
+}
+
+void Trim_func::evaluate(Row** rows, Variant& v)
+{
+ Variant str;
+ m_param[0]->evaluate(rows, str);
+ RefCountStringHandle str_handle(str.get_text());
+ const char* s = (*str_handle)->data;
+
+ const char* t;
+ RefCountStringHandle trim_handle;
+ if (m_param[1]) {
+ Variant trim;
+ m_param[1]->evaluate(rows, trim);
+ trim_handle.set(trim.get_text());
+ t = (*trim_handle)->data;
+ } else
+ t = " ";
+
+ int l = strlen(t);
+ if (l <= 0) {
+ v = *str_handle;
+ return;
+ }
+
+ int slen = strlen(s);
+ int start = 0, end = slen;
+
+ // left trim
+ while (end - start >= l && memcmp(s + start, t, l) == 0)
+ start += l;
+
+ // right trim
+ while (end - start >= l && memcmp(s + end - l, t, l) == 0)
+ end -= l;
+
+ if (start == 0 && end == slen)
+ v = *str_handle;
+ else {
+ RefCountStringHandle res(RefCountString::construct(s, start, end));
+ v = *res;
+ }
+}
+
+Cc_func::Cc_func(const OP& op)
+ : OP(op)
+{
+#ifdef HAVE_LIBMAXMINDDB
+ if (__cc_mmdb) {
+ return;
+ }
+
+ std::string db;
+ char* env = getenv("PACKETQ_MAXMIND_CC_DB");
+ if (env) {
+ db = env;
+ }
+
+ if (db.empty()) {
+ std::list<std::string> paths = {
+ "/var/lib/GeoIP", "/usr/share/GeoIP", "/usr/local/share/GeoIP"
+ };
+
+ if ((env = getenv("PACKETQ_MAXMIND_PATH"))) {
+ paths.push_front(std::string(env));
+ }
+
+ auto i = paths.begin();
+ for (; i != paths.end(); i++) {
+ db = (*i) + "/GeoLite2-Country.mmdb";
+ struct stat s;
+ if (!stat(db.c_str(), &s)) {
+ break;
+ }
+ }
+ if (i == paths.end()) {
+ return;
+ }
+ }
+
+ MMDB_s* mmdb = new (std::nothrow) MMDB_s;
+ if (!mmdb) {
+ return;
+ }
+
+ int ret = MMDB_open(db.c_str(), 0, mmdb);
+ if (ret != MMDB_SUCCESS) {
+ fprintf(stderr, "Warning: cannot open MaxMind CC database \"%s\": %s\n", db.c_str(), MMDB_strerror(ret));
+ delete mmdb;
+ return;
+ }
+
+ __cc_mmdb = mmdb;
+#endif
+}
+
+void Cc_func::evaluate(Row** rows, Variant& v)
+{
+#ifdef HAVE_LIBMAXMINDDB
+ if (!__cc_mmdb) {
+ RefCountStringHandle res(RefCountString::construct(""));
+ v = *res;
+ return;
+ }
+
+ Variant str;
+ m_param[0]->evaluate(rows, str);
+ RefCountStringHandle str_handle(str.get_text());
+
+ int gai_error, ret;
+
+ MMDB_lookup_result_s mmdb_result = MMDB_lookup_string(__cc_mmdb, (*str_handle)->data, &gai_error, &ret);
+
+ if (gai_error || ret != MMDB_SUCCESS || !mmdb_result.found_entry) {
+ RefCountStringHandle res(RefCountString::construct(""));
+ v = *res;
+ return;
+ }
+
+ MMDB_entry_data_s entry_data;
+ ret = MMDB_get_value(&mmdb_result.entry, &entry_data, "country", "iso_code", NULL);
+
+ if (ret != MMDB_SUCCESS || !entry_data.has_data || entry_data.type != MMDB_DATA_TYPE_UTF8_STRING) {
+ RefCountStringHandle res(RefCountString::construct(""));
+ v = *res;
+ return;
+ }
+
+ RefCountStringHandle res(RefCountString::construct(entry_data.utf8_string, 0, entry_data.data_size));
+ v = *res;
+#else
+ RefCountStringHandle res(RefCountString::construct(""));
+ v = *res;
+#endif
+}
+
+Asn_func::Asn_func(const OP& op)
+ : OP(op)
+{
+#ifdef HAVE_LIBMAXMINDDB
+ if (__asn_mmdb) {
+ return;
+ }
+
+ std::string db;
+ char* env = getenv("PACKETQ_MAXMIND_ASN_DB");
+ if (env) {
+ db = env;
+ }
+
+ if (db.empty()) {
+ std::list<std::string> paths = {
+ "/var/lib/GeoIP", "/usr/share/GeoIP", "/usr/local/share/GeoIP"
+ };
+
+ if ((env = getenv("PACKETQ_MAXMIND_PATH"))) {
+ paths.push_front(std::string(env));
+ }
+
+ auto i = paths.begin();
+ for (; i != paths.end(); i++) {
+ db = (*i) + "/GeoLite2-ASN.mmdb";
+ struct stat s;
+ if (!stat(db.c_str(), &s)) {
+ break;
+ }
+ }
+ if (i == paths.end()) {
+ return;
+ }
+ }
+
+ MMDB_s* mmdb = new (std::nothrow) MMDB_s;
+ if (!mmdb) {
+ return;
+ }
+
+ int ret = MMDB_open(db.c_str(), 0, mmdb);
+ if (ret != MMDB_SUCCESS) {
+ fprintf(stderr, "Warning: cannot open MaxMind ASN database \"%s\": %s\n", db.c_str(), MMDB_strerror(ret));
+ delete mmdb;
+ return;
+ }
+
+ __asn_mmdb = mmdb;
+#endif
+}
+
+void Asn_func::evaluate(Row** rows, Variant& v)
+{
+#ifdef HAVE_LIBMAXMINDDB
+ if (!__asn_mmdb) {
+ v = -1;
+ return;
+ }
+
+ Variant str;
+ m_param[0]->evaluate(rows, str);
+ RefCountStringHandle str_handle(str.get_text());
+
+ int gai_error, ret;
+
+ MMDB_lookup_result_s mmdb_result = MMDB_lookup_string(__asn_mmdb, (*str_handle)->data, &gai_error, &ret);
+
+ if (gai_error || ret != MMDB_SUCCESS || !mmdb_result.found_entry) {
+ v = -1;
+ return;
+ }
+
+ MMDB_entry_data_s entry_data;
+ ret = MMDB_get_value(&mmdb_result.entry, &entry_data, "autonomous_system_number", NULL);
+
+ if (ret != MMDB_SUCCESS || !entry_data.has_data || entry_data.type != MMDB_DATA_TYPE_UINT32) {
+ v = -1;
+ return;
+ }
+
+ v = (int_column)entry_data.uint32;
+#else
+ v = -1;
+#endif
+}
+
+DB g_db;
+
+Coldef Column::m_coldefs[COLTYPE_MAX];
+
+} // namespace packetq
diff --git a/src/sql.h b/src/sql.h
new file mode 100644
index 0000000..0c68e5f
--- /dev/null
+++ b/src/sql.h
@@ -0,0 +1,1938 @@
+/*
+ * Copyright (c) 2017-2024 OARC, Inc.
+ * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+ * All rights reserved.
+ *
+ * This file is part of PacketQ.
+ *
+ * PacketQ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PacketQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __packetq_sql_h
+#define __packetq_sql_h
+
+#include <algorithm>
+#include <functional>
+#include <list>
+#include <map>
+#include <math.h>
+#include <regex.h>
+#include <set>
+#include <stack>
+#include <stdarg.h>
+#include <stdexcept>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+#include <sys/types.h>
+#include <vector>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#ifndef s6_addr32 // For *BSD
+#define s6_addr32 __u6_addr.__u6_addr32
+#endif
+#include <sys/socket.h>
+
+#include "refcountstring.h"
+#include "variant.h"
+
+#ifdef WIN32
+#define snprintf _snprintf
+#endif
+
+#define RE_LEN 64
+
+namespace packetq {
+
+extern int g_allocs;
+static const int max_func_param = 4;
+extern bool verbose;
+
+inline void vlog(const char* fmt, ...)
+{
+ if (!verbose)
+ return;
+
+ char string[1024];
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(string, sizeof(string), fmt, ap);
+ va_end(ap);
+
+ fprintf(stderr, "%s", string);
+}
+
+class Error {
+public:
+ Error(const char* fmt, ...)
+ {
+ char string[1024];
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(string, sizeof(string), fmt, ap);
+ va_end(ap);
+
+ m_err = string;
+ }
+ std::string m_err;
+};
+
+class Query;
+class Table;
+class Parser;
+class Row;
+class Ordering_terms;
+
+inline std::string lower(const char* s)
+{
+ std::string str = s;
+ transform(str.begin(), str.end(), str.begin(), tolower);
+ return str;
+}
+
+inline bool cmpi(const std::string& i_a, const char* b)
+{
+ std::string str = i_a;
+ transform(str.begin(), str.end(), str.begin(), tolower);
+ return (str.compare(b) == 0);
+}
+
+inline bool cmpii(const std::string& i_a, const char* b)
+{
+ std::string stra = i_a;
+ transform(stra.begin(), stra.end(), stra.begin(), tolower);
+ std::string strb = b;
+ transform(strb.begin(), strb.end(), strb.begin(), tolower);
+ return (stra.compare(strb) == 0);
+}
+
+inline const char* ind(int in)
+{
+ static const char spc[] = " ";
+ if (in < 0)
+ in = 0;
+ if (in > sizeof(spc) - 2)
+ in = sizeof(spc) - 2;
+ return &spc[sizeof(spc) - 1 - in];
+}
+
+class DB {
+private:
+ class Item {
+ public:
+ std::string m_function;
+ int m_key;
+ bool operator<(const Item& r) const
+ {
+ if (m_key < r.m_key)
+ return true;
+ if (m_key > r.m_key)
+ return false;
+ if (m_function < r.m_function)
+ return true;
+ return false;
+ }
+ };
+
+public:
+ DB();
+ ~DB();
+
+ bool query(const char* q);
+
+ Table* create_table(const char* name);
+ Table* create_or_use_table(const char* name);
+ Table* get_table(const char* name);
+ void add_lut(const char* table, int key, const char* value)
+ {
+ Item i;
+ i.m_function = table;
+ i.m_key = key;
+ m_lut[i] = value;
+ }
+
+ RefCountString* get_value(const char* table, int key)
+ {
+ Item i;
+ i.m_function = table;
+ i.m_key = key;
+ auto it = m_lut.find(i);
+ if (it != m_lut.end())
+ return RefCountString::construct(it->second.c_str());
+
+ return 0;
+ }
+
+private:
+ std::map<std::string, Table*> m_tables;
+ std::map<Item, std::string> m_lut;
+};
+
+extern DB g_db;
+
+class Coldef {
+public:
+ int m_size;
+ int m_align;
+};
+
+template <typename T>
+class Allocator {
+private:
+ Allocator& operator=(const Allocator& other);
+ Allocator(Allocator&& other) noexcept;
+ Allocator const& operator=(Allocator&& other);
+
+public:
+ Allocator(int size, int buffersize)
+ : m_buffersize(buffersize)
+ , m_size(size)
+ {
+ add_buffer();
+ }
+ ~Allocator()
+ {
+ auto it = m_buffers.begin();
+ while (it != m_buffers.end()) {
+ delete *it;
+ m_buffers.erase(it);
+ it = m_buffers.begin();
+ }
+ }
+
+ void add_buffer()
+ {
+ m_curr_buffer = new _Buffer(*this);
+ m_buffers.push_back(m_curr_buffer);
+ }
+ T* allocate()
+ {
+
+ T* obj = m_curr_buffer->allocate();
+ if (!obj) {
+ for (auto it = m_buffers.begin();
+ it != m_buffers.end();
+ it++) {
+ if ((*it)->m_has_space)
+ obj = (*it)->allocate();
+ if (obj) {
+ m_curr_buffer = *it;
+ break;
+ }
+ }
+ }
+ if (!obj) {
+ add_buffer();
+ obj = m_curr_buffer->allocate();
+ }
+ return obj;
+ }
+ void deallocate(T* item)
+ {
+ _Buffer** buffptr = (_Buffer**)item;
+ buffptr[-1]->deallocate(item);
+ }
+
+private:
+ class _Buffer {
+ private:
+ _Buffer& operator=(const _Buffer& other);
+ _Buffer(_Buffer&& other) noexcept;
+ _Buffer const& operator=(_Buffer&& other);
+
+ public:
+ friend class Allocator;
+ _Buffer(Allocator& allocator)
+ : m_allocator(allocator)
+ {
+ m_has_space = true;
+ m_used = 0;
+ m_stride = (sizeof(_Buffer*) + m_allocator.m_size);
+ // align size of m_stride to that of a pointer
+ m_stride = ((m_stride / sizeof(void*)) + 1) * sizeof(void*);
+ m_memory = (char*)calloc(m_stride, m_allocator.m_buffersize);
+ }
+ ~_Buffer()
+ {
+ free(m_memory);
+ }
+
+ T* allocate()
+ {
+ T* obj = 0;
+ if (m_free_list.size() > 0) {
+ obj = m_free_list.top();
+ m_free_list.pop();
+ }
+ if (!obj && m_used < m_allocator.m_buffersize) {
+ char* ptr = &m_memory[m_stride * m_used++];
+ _Buffer** b = (_Buffer**)ptr;
+ *b = this;
+ obj = (T*)(&b[1]);
+ }
+ m_has_space = true;
+ if (!obj)
+ m_has_space = false;
+ return obj;
+ }
+ void deallocate(T* item)
+ {
+ m_has_space = true;
+ memset(item, 0, m_allocator.m_size);
+ m_free_list.push(item);
+ }
+
+ bool m_has_space;
+ int m_stride;
+ std::stack<T*> m_free_list;
+ Allocator& m_allocator;
+ int m_used;
+ char* m_memory;
+ };
+
+ _Buffer* m_curr_buffer;
+ std::list<_Buffer*> m_buffers;
+
+ int m_buffersize;
+ int m_size;
+};
+
+class Column {
+public:
+ static const bool HIDDEN = true;
+
+ static Coldef m_coldefs[COLTYPE_MAX];
+ Column(const char* name, Coltype::Type type, int id, bool hidden);
+ // called at startup by DB
+ static void init_defs()
+ {
+ m_coldefs[Coltype::_bool].m_size = bool_size;
+ m_coldefs[Coltype::_bool].m_align = bool_align;
+ m_coldefs[Coltype::_int].m_size = int_size;
+ m_coldefs[Coltype::_int].m_align = int_align;
+ m_coldefs[Coltype::_float].m_size = float_size;
+ m_coldefs[Coltype::_float].m_align = float_align;
+ m_coldefs[Coltype::_text].m_size = text_size;
+ m_coldefs[Coltype::_text].m_align = text_align;
+ }
+ std::string m_name;
+ Coltype::Type m_type;
+ Coldef& m_def;
+ bool m_hidden;
+ int m_id; // numeric id used by packet parsers for speed
+ int m_offset;
+};
+
+// for accessing a field in a row
+template <typename T>
+class Accessor {
+public:
+ Accessor()
+ : m_offset(0)
+ {
+ }
+
+ T& value(Row* row);
+
+ int m_offset;
+};
+
+typedef Accessor<bool_column> Bool_accessor;
+typedef Accessor<int_column> Int_accessor;
+typedef Accessor<float_column> Float_accessor;
+typedef Accessor<text_column> Text_accessor;
+
+// for writing a variant to a field in a row
+class GenericAccessor {
+public:
+ void set(Row* row, const Variant& v);
+
+ int m_offset;
+ Coltype::Type m_type;
+};
+
+class Table {
+private:
+ Table& operator=(const Table& other);
+ Table(Table&& other) noexcept;
+ Table const& operator=(Table&& other);
+
+public:
+ Table(const char* name = 0, const char* query = 0)
+ : m_rsize(0)
+ , m_dsize(0)
+ {
+ m_row_allocator = 0;
+ m_name = name ? name : "result";
+ m_qstring = query ? query : "";
+ m_curpos = 0;
+ }
+ ~Table()
+ {
+ for (auto i = m_rows.begin(), end = m_rows.end(); i != end; ++i)
+ delete_row(*i);
+ for (auto i = m_cols.begin(), end = m_cols.end(); i != end; ++i)
+ delete *i;
+ delete m_row_allocator;
+ }
+ static int align(int pos, int align)
+ {
+ int res = pos;
+ int rem = pos % align;
+ if (rem)
+ res = pos + align - rem;
+ return res;
+ }
+ int get_col_index(const char* col)
+ {
+ int i = 0;
+ for (auto it = m_cols.begin(); it != m_cols.end(); it++) {
+ if (cmpii(m_cols[i]->m_name, col))
+ return i;
+ i++;
+ }
+ return -1;
+ }
+ int get_column_id(const char* col)
+ {
+ for (auto i = m_cols.begin(); i != m_cols.end(); ++i) {
+ if (cmpii((*i)->m_name, col))
+ return (*i)->m_id;
+ }
+ return -1;
+ }
+
+ template <typename T>
+ Accessor<T> get_accessor(const char* col)
+ {
+ Accessor<T> res;
+ res.m_offset = -1;
+
+ int i = get_col_index(col);
+ if (i >= 0)
+ res.m_offset = m_cols[i]->m_offset;
+
+ return res;
+ }
+
+ void dump();
+ void json(bool trailing_comma);
+ void csv(bool format = false);
+ void xml();
+
+ Column* add_column(const char* name, Coltype::Type type, int id = -1, bool hidden = false);
+ Column* add_column(const char* name, const char* type, int id = -1, bool hidden = false);
+ void merge_sort(Ordering_terms& order);
+ void per_sort(Ordering_terms& order);
+ Row* create_row();
+ void delete_row(Row* row);
+ void add_row(Row* row);
+ void limit(int limit, int offset = 0);
+
+ std::vector<Column*> m_cols;
+ std::list<Row*> m_rows;
+ int m_curpos;
+ std::string m_name;
+ std::string m_qstring;
+ Allocator<Row>* m_row_allocator;
+ int m_rsize;
+ int m_dsize;
+ std::vector<int> m_text_column_offsets;
+};
+
+#define ROW_DUMMY_SIZE sizeof(void*)
+
+class Row {
+public:
+ void zero_text_columns(const std::vector<int>& text_column_offsets)
+ {
+ for (auto i = text_column_offsets.begin(), end = text_column_offsets.end(); i != end; ++i)
+ access_column<text_column>(*i) = 0;
+ }
+
+ void decref_text_columns(const std::vector<int>& text_column_offsets)
+ {
+ for (auto i = text_column_offsets.begin(), end = text_column_offsets.end(); i != end; ++i) {
+ text_column& t = access_column<text_column>(*i);
+ if (t)
+ t->dec_refcount();
+ }
+ }
+
+ void reset_text_columns(const std::vector<int>& text_column_offsets)
+ {
+ for (auto i = text_column_offsets.begin(), end = text_column_offsets.end(); i != end; ++i) {
+ text_column& t = access_column<text_column>(*i);
+ if (t) {
+ t->dec_refcount();
+ t = 0;
+ }
+ }
+ }
+
+ template <typename T>
+ T& access_column(int offset)
+ {
+ void* ptr = m_data + offset;
+ return *reinterpret_cast<T*>(ptr);
+ }
+
+ char m_data[ROW_DUMMY_SIZE]; // dummy
+};
+
+template <typename T>
+inline T& Accessor<T>::value(Row* row)
+{
+ return row->access_column<T>(m_offset);
+}
+
+inline void GenericAccessor::set(Row* row, const Variant& v)
+{
+ switch (m_type) {
+ case Coltype::_bool:
+ row->access_column<bool_column>(m_offset) = v.get_bool();
+ break;
+
+ case Coltype::_int:
+ row->access_column<int_column>(m_offset) = v.get_int();
+ break;
+
+ case Coltype::_float:
+ row->access_column<float_column>(m_offset) = v.get_float();
+ break;
+
+ case Coltype::_text:
+ // reference count on string has already been incremented by get_text()
+ // so we can assign the pointer directly
+ row->access_column<text_column>(m_offset) = v.get_text();
+ break;
+ }
+}
+
+class Token {
+public:
+ enum Type {
+ _invalid = 0,
+ _label = 1,
+ _number = 2,
+ _op = 3,
+ _uop = 4,
+ _string = 5,
+ _column = 6,
+ _paren = 7,
+ _function = 8,
+ _semicolon = 9,
+ _end = 10
+ };
+
+ Token(const Type type, const char* token)
+ {
+ m_type = type;
+ m_token = token;
+ }
+ const char* get_token() const
+ {
+ return m_token.c_str();
+ }
+ void set_token(const char* istr)
+ {
+ m_token = istr;
+ }
+ Type get_type() const
+ {
+ return m_type;
+ }
+ void set_type(const Type type)
+ {
+ m_type = type;
+ }
+
+private:
+ Type m_type;
+ std::string m_token;
+};
+
+class OP : public Token {
+private:
+ OP& operator=(const OP& other);
+ OP(OP&& other)
+ noexcept;
+ OP const& operator=(OP&& other);
+
+public:
+ static int is_binary(const char* str)
+ {
+ const char* bin_ops[] = { "||", "*", "/", "%", "+", "-", "<<", ">>", "&", "|", "<", "<=", ">", ">=", "=", "==", "!=", "<>", "is", "is not", "in", "like", "not like", "or", "and" };
+ int pre_ops[] = { 8, 7, 7, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1 };
+ int len = sizeof(bin_ops) / sizeof(const char*);
+ int idx = len - 1;
+ while (idx >= 0) {
+ if (cmpi(std::string(str), bin_ops[idx])) {
+ return pre_ops[idx];
+ }
+ idx--;
+ }
+ return 0;
+ }
+
+ OP(const OP& op)
+ : Token(op.get_type(), op.get_token())
+ {
+ for (int i = 0; i < max_param(); i++) {
+ m_param[i] = op.m_param[i];
+ }
+ m_left = op.m_left;
+ m_right = op.m_right;
+ m_row_index = op.m_row_index;
+ m_t = op.m_t;
+ m_name = op.m_name;
+ m_has_aggregate_function = op.m_has_aggregate_function;
+
+ precedence();
+ }
+ OP(const Token& tok)
+ : Token(tok.get_type(), tok.get_token())
+ {
+ for (int i = 0; i < max_param(); i++) {
+ m_param[i] = 0;
+ }
+ m_left = m_right = 0;
+ m_row_index = -1;
+ m_t = Coltype::_int;
+ m_name = "";
+ m_has_aggregate_function = false;
+
+ precedence();
+ }
+ virtual ~OP()
+ {
+ for (int i = 0; i < max_param(); i++)
+ if (m_param[i])
+ delete m_param[i];
+ if (m_left)
+ delete m_left;
+ if (m_right)
+ delete m_right;
+ }
+ static int max_param()
+ {
+ return max_func_param;
+ }
+ void clear_ptr()
+ {
+ for (int i = 0; i < max_param(); i++) {
+ m_param[i] = 0;
+ }
+ m_left = m_right = 0;
+ }
+ void precedence()
+ {
+ m_precedence = is_binary(get_token());
+ }
+ const char* get_name()
+ {
+ if (m_name.length() > 0)
+ return m_name.c_str();
+
+ m_name = "";
+ if (m_left) {
+ m_name += "(";
+ m_name += m_left->get_name();
+ }
+ m_name += get_token();
+ if (m_right) {
+ if (!m_left)
+ m_name += "(";
+ m_name += m_right->get_name();
+ m_name += ")";
+ }
+ if (m_param[0]) {
+ m_name += "(";
+ for (int i = 0; i < max_param(); i++)
+ if (m_param[i]) {
+ if (i > 0)
+ m_name += ",";
+ m_name += m_param[i]->get_name();
+ }
+ m_name += ")";
+ }
+ return m_name.c_str();
+ }
+ Coltype::Type ret_type() { return m_t; }
+
+ // add a hidden column for storing intermediate results
+ template <typename T>
+ T add_intermediate_column(Table* table, std::string name_suffix, Coltype::Type type)
+ {
+ std::string name = std::string(get_name()) + name_suffix;
+ Column* column = table->add_column(name.c_str(), type, -1, Column::HIDDEN);
+ T res;
+ res.m_offset = column->m_offset;
+ return res;
+ }
+
+ virtual void evaluate(Row** rows, Variant& v) { throw Error("evaluate() called on abstract OP class"); };
+ virtual void evaluate_aggregate_operands(Row** rows);
+ virtual void combine_aggregate(Row* base_row, Row* other_row);
+ OP* compile(const std::vector<Table*>& tables,
+ const std::vector<int>& search_order, Query& q);
+ int m_row_index;
+ std::string m_name;
+ int m_precedence;
+ OP* m_param[max_func_param];
+ OP* m_left;
+ OP* m_right;
+ Coltype::Type m_t;
+ bool m_has_aggregate_function;
+};
+
+////////////////// column accessors
+
+class Column_access_int : public OP {
+public:
+ Column_access_int(const OP& op, int offset)
+ : OP(op)
+ {
+ m_accessor.m_offset = offset;
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ v = m_accessor.value(rows[m_row_index]);
+ }
+ Int_accessor m_accessor;
+};
+
+class Column_access_bool : public OP {
+public:
+ Column_access_bool(const OP& op, int offset)
+ : OP(op)
+ {
+ m_accessor.m_offset = offset;
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ v = m_accessor.value(rows[m_row_index]);
+ }
+ Bool_accessor m_accessor;
+};
+
+class Column_access_float : public OP {
+public:
+ Column_access_float(const OP& op, int offset)
+ : OP(op)
+ {
+ m_accessor.m_offset = offset;
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ v = m_accessor.value(rows[m_row_index]);
+ }
+ Float_accessor m_accessor;
+};
+
+class Column_access_text : public OP {
+public:
+ Column_access_text(const OP& op, int offset)
+ : OP(op)
+ {
+ m_accessor.m_offset = offset;
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ v = m_accessor.value(rows[m_row_index]);
+ }
+ Text_accessor m_accessor;
+};
+
+///////////////// Static numbers
+
+class Static_int : public OP {
+public:
+ Static_int(const OP& op)
+ : OP(op)
+ {
+ m_val = atoi(get_token());
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ v = m_val;
+ }
+ int m_val;
+};
+
+class Static_float : public OP {
+public:
+ Static_float(const OP& op)
+ : OP(op)
+ {
+ m_val = atof(get_token());
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ v = m_val;
+ }
+ double m_val;
+};
+
+class Static_text : public OP {
+public:
+ Static_text(const OP& op)
+ : OP(op)
+ {
+ m_val.set(RefCountString::construct(get_token()));
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ v = *m_val;
+ }
+ RefCountStringHandle m_val;
+};
+
+///////////////// Functions
+
+class Netmask_func : public OP {
+public:
+ Netmask_func(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant orig_ip;
+ m_param[0]->evaluate(rows, orig_ip);
+
+ if (!valid_masks)
+ set_masks(rows);
+
+ RefCountStringHandle src(orig_ip.get_text());
+ RefCountStringHandle dest(RefCountString::allocate(INET6_ADDRSTRLEN + 1));
+
+ if (strchr((*src)->data, ':')) {
+ struct in6_addr a6;
+ if (inet_pton(AF_INET6, (*src)->data, &a6) == 1) {
+ a6.s6_addr32[0] &= v6_mask[0];
+ a6.s6_addr32[1] &= v6_mask[1];
+ a6.s6_addr32[2] &= v6_mask[2];
+ a6.s6_addr32[3] &= v6_mask[3];
+ if (inet_ntop(AF_INET6, &a6, (*dest)->data, INET6_ADDRSTRLEN)) {
+ v = *dest;
+ return;
+ }
+ }
+ } else {
+ struct in_addr a4;
+ if (inet_pton(AF_INET, (*src)->data, &a4) == 1) {
+ a4.s_addr &= v4_mask;
+ if (inet_ntop(AF_INET, &a4, (*dest)->data, INET6_ADDRSTRLEN)) {
+ v = *dest;
+ return;
+ }
+ }
+ }
+
+ // Operation on non-IP address text
+ RefCountStringHandle empty(RefCountString::construct(""));
+ v = *empty;
+ }
+
+private:
+ void set_masks(Row** rows)
+ {
+ if (m_param[1]) {
+ Variant v4cidr;
+ m_param[1]->evaluate(rows, v4cidr);
+ int v4size = v4cidr.get_int();
+ if (v4size > -1 && v4size < 33) {
+ v4_mask = htonl(0xffffffff << (32 - v4size));
+ }
+ }
+ if (m_param[2]) {
+ Variant v6cidr;
+ m_param[2]->evaluate(rows, v6cidr);
+ int v6size = v6cidr.get_int();
+ if (v6size > -1 && v6size < 129) {
+ for (int i = 0; i < 4; i++) {
+ if (v6size >= 32) {
+ v6_mask[i] = 0xffffffff;
+ v6size -= 32;
+ } else if (v6size) {
+ v6_mask[i] = htonl(0xffffffff << (32 - v6size));
+ v6size = 0;
+ } else {
+ v6_mask[i] = 0;
+ }
+ }
+ }
+ }
+ valid_masks = true;
+ }
+
+ uint32_t v4_mask = htonl(0xffffff00);
+ uint32_t v6_mask[4] = { 0xffffffff, htonl(0xffff0000), 0, 0 };
+ bool valid_masks = false;
+};
+
+class Cc_func : public OP {
+public:
+ Cc_func(const OP& op);
+ ~Cc_func() { }
+ void evaluate(Row** rows, Variant& v);
+};
+
+class Asn_func : public OP {
+public:
+ Asn_func(const OP& op);
+ ~Asn_func() { }
+ void evaluate(Row** rows, Variant& v);
+};
+
+class Truncate_func : public OP {
+public:
+ Truncate_func(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant val;
+ m_param[0]->evaluate(rows, val);
+ v = val.get_int();
+ }
+};
+
+class Name_func : public OP {
+public:
+ Name_func(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant str, num;
+ m_param[0]->evaluate(rows, str);
+ m_param[1]->evaluate(rows, num);
+
+ int_column n = num.get_int();
+ RefCountStringHandle lookup(str.get_text());
+ RefCountStringHandle r(g_db.get_value((*lookup)->data, n));
+
+ if (!*r)
+ r.set(num.get_text());
+
+ v = *r;
+ }
+};
+
+class Lower_func : public OP {
+public:
+ Lower_func(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant str;
+ m_param[0]->evaluate(rows, str);
+ RefCountStringHandle src(str.get_text());
+
+ int l = strlen((*src)->data);
+ int p = 0;
+
+ RefCountStringHandle dest(RefCountString::allocate(l + 1));
+
+ while ((*src)->data[p]) {
+ char c = (*src)->data[p];
+ if (c >= 'A' && c <= 'Z') {
+ c = c - 'A' + 'a';
+ }
+ (*dest)->data[p] = c;
+ p++;
+ }
+ (*dest)->data[p] = 0;
+
+ v = *dest;
+ }
+};
+
+class Rsplit_func : public OP {
+public:
+ Rsplit_func(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ char sep = '.';
+ Variant str, num;
+ m_param[0]->evaluate(rows, str);
+ m_param[1]->evaluate(rows, num);
+
+ if (m_param[2]) {
+ Variant vsep;
+ m_param[2]->evaluate(rows, vsep);
+ RefCountStringHandle sep_text(vsep.get_text());
+ const char* s = (*sep_text)->data;
+ if (s)
+ sep = s[0];
+ }
+
+ int n = num.get_int();
+ RefCountStringHandle src(str.get_text());
+ const char* s = (*src)->data;
+ int l = strlen(s);
+ if (!l) {
+ RefCountStringHandle res(RefCountString::construct(""));
+ v = *res;
+ return;
+ }
+ int p = l - 1;
+ int found = 0, end = l, start = 0;
+ if (n == 0)
+ end = p + 1;
+ while (p >= 0) {
+ char c = s[p];
+ if (c == sep) {
+ found++;
+ if (found == n)
+ end = p;
+ if (found == n + 1)
+ start = p + 1;
+ }
+ p--;
+ }
+ if (found < n || start >= l) {
+ RefCountStringHandle res(RefCountString::construct(""));
+ v = *res;
+ return;
+ }
+ size_t buflen = start < end ? end - start + 1 : 1;
+ char buf[buflen];
+ p = 0;
+ while (start < end)
+ buf[p++] = s[start++];
+ buf[p] = 0;
+
+ RefCountStringHandle res(RefCountString::construct(buf));
+ v = *res;
+ }
+};
+
+class Len_func : public OP {
+public:
+ Len_func(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant str;
+ m_param[0]->evaluate(rows, str);
+ RefCountStringHandle t(str.get_text());
+ v = int(strlen((*t)->data));
+ }
+};
+
+class Trim_func : public OP {
+public:
+ Trim_func(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v);
+};
+
+class If_func : public OP {
+public:
+ If_func(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant cond;
+ m_param[0]->evaluate(rows, cond);
+ if (cond.get_bool())
+ m_param[1]->evaluate(rows, v);
+ else
+ m_param[2]->evaluate(rows, v);
+ }
+};
+
+// Aggregate functions, generally these store their calculations in hidden
+// columns
+
+class Min_func_int : public OP {
+public:
+ Min_func_int(const OP& op, Table* dest_table)
+ : OP(op)
+ {
+ m_has_aggregate_function = true;
+ acc_min = add_intermediate_column<Int_accessor>(dest_table, ".min", Coltype::_int);
+ }
+ virtual void evaluate_aggregate_operands(Row** rows)
+ {
+ Variant p;
+ m_param[0]->evaluate(rows, p);
+ acc_min.value(rows[m_row_index]) = p.get_int();
+ }
+ virtual void combine_aggregate(Row* base_row, Row* next_row)
+ {
+ int_column n = acc_min.value(next_row);
+ if (n < acc_min.value(base_row))
+ acc_min.value(base_row) = n;
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ v = acc_min.value(rows[m_row_index]);
+ }
+
+ Int_accessor acc_min;
+};
+
+class Min_func_float : public OP {
+public:
+ Min_func_float(const OP& op, Table* dest_table)
+ : OP(op)
+ {
+ m_has_aggregate_function = true;
+ acc_min = add_intermediate_column<Float_accessor>(dest_table, ".min", Coltype::_float);
+ }
+ virtual void evaluate_aggregate_operands(Row** rows)
+ {
+ Variant p;
+ m_param[0]->evaluate(rows, p);
+ acc_min.value(rows[m_row_index]) = p.get_float();
+ }
+ virtual void combine_aggregate(Row* base_row, Row* next_row)
+ {
+ float_column n = acc_min.value(next_row);
+ if (n < acc_min.value(base_row))
+ acc_min.value(base_row) = n;
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ v = acc_min.value(rows[m_row_index]);
+ }
+
+ Float_accessor acc_min;
+};
+
+class Max_func_int : public OP {
+public:
+ Max_func_int(const OP& op, Table* dest_table)
+ : OP(op)
+ {
+ m_has_aggregate_function = true;
+ acc_max = add_intermediate_column<Int_accessor>(dest_table, ".max", Coltype::_int);
+ }
+ virtual void evaluate_aggregate_operands(Row** rows)
+ {
+ Variant p;
+ m_param[0]->evaluate(rows, p);
+ acc_max.value(rows[m_row_index]) = p.get_int();
+ }
+ virtual void combine_aggregate(Row* base_row, Row* next_row)
+ {
+ int_column n = acc_max.value(next_row);
+ if (n > acc_max.value(base_row))
+ acc_max.value(base_row) = n;
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ v = acc_max.value(rows[m_row_index]);
+ }
+
+ Int_accessor acc_max;
+};
+
+class Max_func_float : public OP {
+public:
+ Max_func_float(const OP& op, Table* dest_table)
+ : OP(op)
+ {
+ m_has_aggregate_function = true;
+ acc_max = add_intermediate_column<Float_accessor>(dest_table, ".max", Coltype::_float);
+ }
+ virtual void evaluate_aggregate_operands(Row** rows)
+ {
+ Variant p;
+ m_param[0]->evaluate(rows, p);
+ acc_max.value(rows[m_row_index]) = p.get_float();
+ }
+ virtual void combine_aggregate(Row* base_row, Row* next_row)
+ {
+ float_column n = acc_max.value(next_row);
+ if (n > acc_max.value(base_row))
+ acc_max.value(base_row) = n;
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ v = acc_max.value(rows[m_row_index]);
+ }
+
+ Float_accessor acc_max;
+};
+
+class Stdev_func : public OP {
+public:
+ Stdev_func(const OP& op, Table* dest_table)
+ : OP(op)
+ {
+ m_has_aggregate_function = true;
+ acc_sum = add_intermediate_column<Float_accessor>(dest_table, ".sum", Coltype::_float);
+ acc_sum_sq = add_intermediate_column<Float_accessor>(dest_table, ".sumsquared", Coltype::_float);
+ acc_count = add_intermediate_column<Int_accessor>(dest_table, ".count", Coltype::_int);
+ }
+ virtual void evaluate_aggregate_operands(Row** rows)
+ {
+ Variant p;
+ m_param[0]->evaluate(rows, p);
+ float_column val = p.get_float();
+
+ acc_sum.value(rows[m_row_index]) = val;
+ acc_sum_sq.value(rows[m_row_index]) = val * val;
+ acc_count.value(rows[m_row_index]) = 1;
+ }
+ virtual void combine_aggregate(Row* base_row, Row* next_row)
+ {
+ acc_sum.value(base_row) = acc_sum.value(base_row) + acc_sum.value(next_row);
+ acc_sum_sq.value(base_row) = acc_sum_sq.value(base_row) + acc_sum_sq.value(next_row);
+ acc_count.value(base_row) = acc_count.value(base_row) + acc_count.value(next_row);
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Row* row = rows[m_row_index];
+ int c = acc_count.value(row);
+ if (c == 0) {
+ c = 1;
+ }
+ double mean = acc_sum.value(row) / c;
+ double variance = acc_sum_sq.value(row) / c - mean * mean;
+
+ v = sqrt(variance);
+ }
+
+ Float_accessor acc_sum, acc_sum_sq;
+ Int_accessor acc_count;
+};
+
+class Avg_func : public OP {
+public:
+ Avg_func(const OP& op, Table* dest_table)
+ : OP(op)
+ {
+ m_has_aggregate_function = true;
+ acc_sum = add_intermediate_column<Float_accessor>(dest_table, ".sum", Coltype::_float);
+ acc_count = add_intermediate_column<Int_accessor>(dest_table, ".count", Coltype::_int);
+ }
+ virtual void evaluate_aggregate_operands(Row** rows)
+ {
+ Variant p;
+ m_param[0]->evaluate(rows, p);
+ acc_sum.value(rows[m_row_index]) = p.get_float();
+ acc_count.value(rows[m_row_index]) = 1;
+ }
+ virtual void combine_aggregate(Row* base_row, Row* next_row)
+ {
+ acc_sum.value(base_row) = acc_sum.value(base_row) + acc_sum.value(next_row);
+ acc_count.value(base_row) = acc_count.value(base_row) + acc_count.value(next_row);
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ v = acc_sum.value(rows[m_row_index]) / acc_count.value(rows[m_row_index]);
+ }
+
+ Float_accessor acc_sum;
+ Int_accessor acc_count;
+};
+
+class Sum_func_int : public OP {
+public:
+ Sum_func_int(const OP& op, Table* dest_table)
+ : OP(op)
+ {
+ m_has_aggregate_function = true;
+ acc_sum = add_intermediate_column<Int_accessor>(dest_table, ".sum", Coltype::_int);
+ }
+ virtual void evaluate_aggregate_operands(Row** rows)
+ {
+ Variant p;
+ m_param[0]->evaluate(rows, p);
+ acc_sum.value(rows[m_row_index]) = p.get_int();
+ }
+ virtual void combine_aggregate(Row* base_row, Row* next_row)
+ {
+ acc_sum.value(base_row) = acc_sum.value(base_row) + acc_sum.value(next_row);
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ v = acc_sum.value(rows[m_row_index]);
+ }
+
+ Int_accessor acc_sum;
+};
+
+class Sum_func_float : public OP {
+public:
+ Sum_func_float(const OP& op, Table* dest_table)
+ : OP(op)
+ {
+ m_has_aggregate_function = true;
+ acc_sum = add_intermediate_column<Float_accessor>(dest_table, ".sum", Coltype::_float);
+ }
+ virtual void evaluate_aggregate_operands(Row** rows)
+ {
+ Variant p;
+ m_param[0]->evaluate(rows, p);
+ acc_sum.value(rows[m_row_index]) = p.get_float();
+ }
+ virtual void combine_aggregate(Row* base_row, Row* next_row)
+ {
+ acc_sum.value(base_row) = acc_sum.value(base_row) + acc_sum.value(next_row);
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ v = acc_sum.value(rows[m_row_index]);
+ }
+
+ Float_accessor acc_sum;
+};
+
+class Count_func : public OP {
+public:
+ Count_func(const OP& op, Table* dest_table)
+ : OP(op)
+ {
+ m_has_aggregate_function = true;
+ acc_count = add_intermediate_column<Int_accessor>(dest_table, ".count", Coltype::_int);
+ }
+ virtual void evaluate_aggregate_operands(Row** rows)
+ {
+ acc_count.value(rows[m_row_index]) = 1;
+ }
+ virtual void combine_aggregate(Row* base_row, Row* next_row)
+ {
+ acc_count.value(base_row) = acc_count.value(base_row) + acc_count.value(next_row);
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ v = acc_count.value(rows[m_row_index]);
+ }
+
+ Int_accessor acc_count;
+};
+
+//////////////// Binary ops
+
+class Bin_op_eq : public OP {
+public:
+ Bin_op_eq(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant lhs, rhs;
+ m_left->evaluate(rows, lhs);
+ m_right->evaluate(rows, rhs);
+ v = bool(lhs == rhs);
+ }
+};
+class Bin_op_not_eq : public OP {
+public:
+ Bin_op_not_eq(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant lhs, rhs;
+ m_left->evaluate(rows, lhs);
+ m_right->evaluate(rows, rhs);
+ v = !bool(lhs == rhs);
+ }
+};
+class Bin_op_or : public OP {
+public:
+ Bin_op_or(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ m_left->evaluate(rows, v);
+ if (v.get_bool()) {
+ v = true;
+ return;
+ }
+ m_right->evaluate(rows, v);
+ v = v.get_bool();
+ }
+};
+
+class Bin_op_and : public OP {
+public:
+ Bin_op_and(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ m_left->evaluate(rows, v);
+ if (!v.get_bool()) {
+ v = false;
+ return;
+ }
+ m_right->evaluate(rows, v);
+ v = v.get_bool();
+ }
+};
+class Bin_op_lt : public OP {
+public:
+ Bin_op_lt(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant lhs, rhs;
+ m_left->evaluate(rows, lhs);
+ m_right->evaluate(rows, rhs);
+ v = bool(lhs < rhs);
+ }
+};
+class Bin_op_gt : public OP {
+public:
+ Bin_op_gt(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant lhs, rhs;
+ m_left->evaluate(rows, lhs);
+ m_right->evaluate(rows, rhs);
+ v = bool(rhs < lhs);
+ }
+};
+class Bin_op_lteq : public OP {
+public:
+ Bin_op_lteq(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant lhs, rhs;
+ m_left->evaluate(rows, lhs);
+ m_right->evaluate(rows, rhs);
+ v = !bool(rhs < lhs);
+ }
+};
+class Bin_op_gteq : public OP {
+public:
+ Bin_op_gteq(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant lhs, rhs;
+ m_left->evaluate(rows, lhs);
+ m_right->evaluate(rows, rhs);
+ v = !bool(lhs < rhs);
+ }
+};
+class Bin_op_add : public OP {
+public:
+ Bin_op_add(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant lhs, rhs;
+ m_left->evaluate(rows, lhs);
+ m_right->evaluate(rows, rhs);
+ v = lhs.get_int() + rhs.get_int();
+ }
+};
+class Bin_op_add_float : public OP {
+public:
+ Bin_op_add_float(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant lhs, rhs;
+ m_left->evaluate(rows, lhs);
+ m_right->evaluate(rows, rhs);
+ v = lhs.get_float() + rhs.get_float();
+ }
+};
+class Bin_op_sub : public OP {
+public:
+ Bin_op_sub(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant lhs, rhs;
+ m_left->evaluate(rows, lhs);
+ m_right->evaluate(rows, rhs);
+ v = lhs.get_int() - rhs.get_int();
+ }
+};
+class Bin_op_sub_float : public OP {
+public:
+ Bin_op_sub_float(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant lhs, rhs;
+ m_left->evaluate(rows, lhs);
+ m_right->evaluate(rows, rhs);
+ v = lhs.get_float() - rhs.get_float();
+ }
+};
+
+class Bin_op_mul : public OP {
+public:
+ Bin_op_mul(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant lhs, rhs;
+ m_left->evaluate(rows, lhs);
+ m_right->evaluate(rows, rhs);
+ v = lhs.get_int() * rhs.get_int();
+ }
+};
+class Bin_op_mul_float : public OP {
+public:
+ Bin_op_mul_float(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant lhs, rhs;
+ m_left->evaluate(rows, lhs);
+ m_right->evaluate(rows, rhs);
+ v = lhs.get_float() * rhs.get_float();
+ }
+};
+class Bin_op_div : public OP {
+public:
+ Bin_op_div(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant lhs, rhs;
+ m_left->evaluate(rows, lhs);
+ m_right->evaluate(rows, rhs);
+ v = lhs.get_float() / rhs.get_float();
+ }
+};
+class Bin_op_modulo : public OP {
+public:
+ Bin_op_modulo(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant lhs, rhs;
+ m_left->evaluate(rows, lhs);
+ m_right->evaluate(rows, rhs);
+ v = fmod(lhs.get_float(), rhs.get_float());
+ }
+};
+class Bin_op_arithmetic_shift_left : public OP {
+public:
+ Bin_op_arithmetic_shift_left(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant lhs, rhs;
+ m_left->evaluate(rows, lhs);
+ m_right->evaluate(rows, rhs);
+ v = lhs.get_int() << rhs.get_int();
+ }
+};
+class Bin_op_arithmetic_shift_right : public OP {
+public:
+ Bin_op_arithmetic_shift_right(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant lhs, rhs;
+ m_left->evaluate(rows, lhs);
+ m_right->evaluate(rows, rhs);
+ v = lhs.get_int() >> rhs.get_int();
+ }
+};
+class Bin_op_bitwise_and : public OP {
+public:
+ Bin_op_bitwise_and(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant lhs, rhs;
+ m_left->evaluate(rows, lhs);
+ m_right->evaluate(rows, rhs);
+ v = lhs.get_int() & rhs.get_int();
+ }
+};
+class Bin_op_bitwise_or : public OP {
+public:
+ Bin_op_bitwise_or(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant lhs, rhs;
+ m_left->evaluate(rows, lhs);
+ m_right->evaluate(rows, rhs);
+ v = lhs.get_int() | rhs.get_int();
+ }
+};
+class Bin_op_concatenate : public OP {
+public:
+ Bin_op_concatenate(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant lhs, rhs;
+ m_left->evaluate(rows, lhs);
+ m_right->evaluate(rows, rhs);
+ RefCountStringHandle lhandle(lhs.get_text()), rhandle(rhs.get_text());
+
+ const char* lhs_str = (*lhandle)->data;
+ const char* rhs_str = (*rhandle)->data;
+
+ int l = (int)strlen(lhs_str);
+ int r = (int)strlen(rhs_str);
+
+ RefCountStringHandle res(RefCountString::allocate(l + r + 1));
+ memcpy((*res)->data, lhs_str, l);
+ memcpy((*res)->data + l, rhs_str, r + 1); // copy the zero terminator
+
+ v = *res;
+ }
+};
+class Bin_op_like : public OP {
+private:
+ Bin_op_like& operator=(const Bin_op_like& other);
+ Bin_op_like(Bin_op_like&& other) noexcept;
+ Bin_op_like const& operator=(Bin_op_like&& other);
+
+ regex_t m_re;
+ char m_re_str[RE_LEN];
+ bool m_compiled;
+ int m_err;
+
+public:
+ Bin_op_like(const OP& op)
+ : OP(op)
+ , m_re()
+ {
+ m_err = 0;
+ m_compiled = false;
+ }
+ void regex_from_like(const char* s, char* r, int l)
+ {
+ char* stop = r + l - 4;
+ if (r < stop) {
+ *r++ = '^';
+ while (r < stop and *s) {
+ // printf("s: %s\n", s);
+ if (*s == '\\') {
+ s++;
+ if (*s) {
+ *r = *s;
+ } else {
+ s--;
+ }
+ } else if (*s == '.') {
+ *r++ = '\\';
+ *r = '.';
+ } else if (*s == '*') {
+ *r++ = '\\';
+ *r = '*';
+ } else if (*s == '%') {
+ *r++ = '.';
+ *r = '*';
+ } else if (*s == '_') {
+ *r = '.';
+ } else {
+ *r = *s;
+ }
+ s++;
+ r++;
+ // printf("r: %s\n\n", start);
+ }
+ *r++ = '$';
+ *r = '\0';
+ }
+ // printf("r: %s\n\n", start);
+ // printf("Done\n\n");
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant lhs, rhs;
+ m_left->evaluate(rows, lhs);
+ m_right->evaluate(rows, rhs);
+ RefCountStringHandle lhandle(lhs.get_text()), rhandle(rhs.get_text());
+ const char* lstr = (*lhandle)->data;
+ const char* rstr = (*rhandle)->data;
+ if (!m_compiled) {
+ m_compiled = true; // Set this before we try; no need to try again if we fail
+ regex_from_like(rstr, m_re_str, RE_LEN);
+ m_err = regcomp(&m_re, m_re_str, REG_NOSUB);
+ if (m_err) {
+ char errstr[RE_LEN];
+ regerror(m_err, &m_re, errstr, RE_LEN);
+ printf("Error compiling regex: %d: %s", m_err, errstr);
+ }
+ }
+ if (m_err)
+ v = false;
+ else
+ v = regexec(&m_re, lstr, 0, 0, 0) == 0;
+ }
+ ~Bin_op_like()
+ {
+ if (m_compiled) {
+ regfree(&m_re);
+ }
+ }
+};
+class Bin_op_not_like : public Bin_op_like {
+public:
+ Bin_op_not_like(const OP& op)
+ : Bin_op_like(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Bin_op_like::evaluate(rows, v);
+ v = !v.get_bool();
+ }
+};
+////////////////// unary ops
+
+class Un_op_not : public OP {
+public:
+ Un_op_not(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant rhs;
+ m_right->evaluate(rows, rhs);
+ v = !rhs.get_bool();
+ }
+};
+
+class Un_op_neg : public OP {
+public:
+ Un_op_neg(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant rhs;
+ m_right->evaluate(rows, rhs);
+ v = -rhs.get_int();
+ }
+};
+
+class Un_op_neg_float : public OP {
+public:
+ Un_op_neg_float(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant rhs;
+ m_right->evaluate(rows, rhs);
+ v = -rhs.get_float();
+ }
+};
+
+class Un_op_ones_complement : public OP {
+public:
+ Un_op_ones_complement(const OP& op)
+ : OP(op)
+ {
+ }
+ void evaluate(Row** rows, Variant& v)
+ {
+ Variant rhs;
+ m_right->evaluate(rows, rhs);
+ v = ~rhs.get_int();
+ }
+};
+
+class Ordering_terms {
+private:
+ Ordering_terms& operator=(const Ordering_terms& other);
+ Ordering_terms(Ordering_terms&& other) noexcept;
+ Ordering_terms const& operator=(Ordering_terms&& other);
+
+public:
+ Ordering_terms()
+ {
+ m_terms.clear();
+ }
+ ~Ordering_terms()
+ {
+ auto it = m_terms.begin();
+ while (it != m_terms.end()) {
+ delete it->m_op;
+ it->m_op = 0;
+ it++;
+ }
+ }
+ class OP_dir {
+ public:
+ OP_dir(OP* op, bool asc)
+ {
+ m_op = op;
+ m_asc = asc;
+ }
+ OP* m_op;
+ bool m_asc;
+ };
+ bool exist()
+ {
+ return !m_terms.empty();
+ }
+ void compile(const std::vector<Table*>& tables, const std::vector<int>& search_order, Query& q);
+
+ std::vector<OP_dir> m_terms;
+};
+
+class Reader;
+
+class Query {
+private:
+ Query& operator=(const Query& other);
+ Query(Query&& other) noexcept;
+ Query const& operator=(Query&& other);
+
+public:
+ Query(const char* name, const char* query)
+ {
+ m_sample = 0;
+ m_where = 0;
+ m_having = 0;
+ m_from = 0;
+ m_limit = -1;
+ m_offset = 0;
+ m_result = new Table(name, query);
+ m_sql = query;
+ }
+
+ ~Query()
+ {
+ if (m_from)
+ delete m_from;
+ if (m_result)
+ delete m_result;
+ if (m_where)
+ delete m_where;
+ if (m_having)
+ delete m_having;
+ for (auto i = m_select.begin(); i != m_select.end(); ++i)
+ delete *i;
+ }
+
+ void parse();
+ void execute(Reader& reader);
+
+ std::vector<OP*> m_select;
+ OP* m_where;
+ OP* m_having;
+ Ordering_terms m_order_by;
+ Ordering_terms m_group_by;
+
+ int m_limit;
+ int m_offset;
+ int m_sample;
+
+ std::string m_from_name;
+ std::vector<int> m_used_from_column_ids;
+
+ Table* m_from;
+ Table* m_result;
+
+private:
+ void replace_star_column_with_all_columns();
+
+ void process_from();
+ void process_select(Row** rows, Row* dest, GenericAccessor dest_accessors[]);
+ void combine_aggregates_in_select(Row* base_row, Row* other_row);
+ void process_aggregates_in_select(Row** rows, Row* dest, GenericAccessor dest_accessors[]);
+ bool process_where(Row** rows);
+ bool process_having(Row** rows);
+ bool has_aggregate_functions();
+
+ std::string m_sql;
+};
+
+} // namespace packetq
+
+namespace std {
+
+// support for hashing std::vector<packetq::Variant>
+template <>
+struct hash<std::vector<packetq::Variant>> {
+ size_t operator()(const std::vector<packetq::Variant>& seq) const
+ {
+ // combination procedure from boost::hash_combine
+ std::size_t accumulator = 0;
+ for (auto i = seq.begin(), end = seq.end(); i != end; ++i)
+ accumulator ^= i->hash() + 0x9e3779b9 + (accumulator << 6) + (accumulator >> 2);
+ return accumulator;
+ }
+};
+
+} // namespace std
+
+#endif // __packetq_sql_h
diff --git a/src/tcp.cpp b/src/tcp.cpp
new file mode 100644
index 0000000..5787e84
--- /dev/null
+++ b/src/tcp.cpp
@@ -0,0 +1,268 @@
+/*
+ * Copyright (c) 2017-2024 OARC, Inc.
+ * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+ * All rights reserved.
+ *
+ * This file is part of PacketQ.
+ *
+ * PacketQ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PacketQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "tcp.h"
+#include "packet_handler.h"
+
+#include <cstring>
+#include <list>
+#include <map>
+#include <stdlib.h>
+
+namespace packetq {
+
+/// TCP Stream id class - serves as the key in the streams map
+class Stream_id {
+public:
+ /// constructor
+ Stream_id(in6addr_t& src_ip,
+ in6addr_t& dst_ip,
+ unsigned short src_port,
+ unsigned short dst_port)
+ {
+ m_src_ip = src_ip;
+ m_dst_ip = dst_ip;
+ m_src_port = src_port;
+ m_dst_port = dst_port;
+ }
+
+ /// < comparison operator for the std::map
+ bool operator<(const Stream_id& rhs) const
+ {
+ return memcmp(this, &rhs, sizeof(*this)) < 0;
+ }
+
+private:
+ in6addr_t m_src_ip, m_dst_ip;
+ unsigned short m_src_port, m_dst_port;
+};
+
+/// TCP data segment container
+/** Data_segment contains the data found in a single tcp packet
+ * Data_segment are inerted into a list in the Stream class
+ */
+class Data_segment {
+private:
+ Data_segment& operator=(const Data_segment& other);
+ Data_segment(Data_segment&& other) noexcept;
+ Data_segment const& operator=(Data_segment&& other);
+
+public:
+ /// Constructor taking a memory block with packet content
+ Data_segment(unsigned char* data, unsigned int len)
+ {
+ m_datasize = len;
+ m_data = new unsigned char[m_datasize];
+ for (unsigned int i = 0; i < m_datasize; i++) {
+ m_data[i] = data[i];
+ }
+ }
+ Data_segment(const Data_segment& other)
+ {
+ m_datasize = other.m_datasize;
+ m_data = new unsigned char[m_datasize];
+ for (unsigned int i = 0; i < m_datasize; i++) {
+ m_data[i] = other.m_data[i];
+ }
+ }
+ /// Destructor
+ ~Data_segment()
+ {
+ delete[] m_data;
+ }
+
+ /// size of the data
+ unsigned int m_datasize;
+ /// pointer to the data
+ unsigned char* m_data;
+};
+
+int g_count = 0;
+
+/// TCP Stream class
+/** The Stream class has an Stream_id and a list of Data_segemnts that make up
+ * a tcp data stream.
+ * The Streams are organized into a global map ( g_tcp_streams ) indexed by a Stream_id
+ */
+class Stream {
+public:
+ /// Constructor
+ Stream()
+ {
+ m_ser = g_count++;
+ m_content = false;
+ m_nseq = false;
+ m_seq = 0;
+ }
+ ~Stream()
+ {
+ m_segments.clear();
+ }
+ /// add a datasegment to the stream
+ /** If the segment has the expected sequence number
+ * the segment will be added to the list
+ */
+ void add(bool syn, unsigned int seq, Data_segment& s)
+ {
+ m_content = true;
+ if (!m_segments.size() || syn)
+ m_seq = seq;
+
+ if (m_seq == seq) {
+ m_content = true;
+ if ((s.m_datasize > 0 && s.m_datasize <= 65535)) {
+ m_segments.push_back(s);
+ m_seq = seq + s.m_datasize;
+ }
+ }
+ }
+ /// checka if there's any content in the stream
+ bool has_content()
+ {
+ return m_content;
+ }
+ /// Erase (and free) all segments and reset state
+ void erase()
+ {
+ m_content = false;
+ m_nseq = false;
+ m_segments.clear();
+ }
+ /// return the streams data size
+ int get_size()
+ {
+ int size = 0;
+ for (auto it = m_segments.begin();
+ it != m_segments.end(); it++) {
+ size += it->m_datasize;
+ }
+ return size;
+ }
+ /// debug functionality to dump a streams content
+ void dump()
+ {
+ int start = 2;
+ for (auto it = m_segments.begin();
+ it != m_segments.end(); it++) {
+ for (unsigned int i = start; i < it->m_datasize; i++) {
+ printf("%02x", it->m_data[i]);
+ }
+ start = 0;
+ }
+ printf("\n");
+ }
+ /// returns the data in the stream
+ /** The returned data is located in a static buffer shared by all streams
+ * the data is valid until the next call to get_buffer()
+ */
+ unsigned char* get_buffer()
+ {
+ int p = 0;
+ for (auto it = m_segments.begin();
+ it != m_segments.end(); it++) {
+ for (unsigned int i = 0; i < it->m_datasize; i++) {
+ m_buffer[p++] = it->m_data[i];
+ if (p >= 0xffff)
+ return m_buffer;
+ }
+ }
+ return m_buffer;
+ }
+
+private:
+ unsigned int m_seq;
+ int m_ser;
+ bool m_content;
+ bool m_nseq;
+ std::list<Data_segment> m_segments;
+
+ static unsigned char m_buffer[0x10000];
+};
+unsigned char Stream::m_buffer[0x10000];
+
+std::map<Stream_id, Stream> g_tcp_streams;
+
+/// assemble_tcp builds datastreams out of tcp packets
+/** TCP packets are inserted into streams. When the streams are closed
+ * the contained data is returned as a pointer the data
+ * it is up to the caller to free() the memory returned.
+ */
+unsigned char*
+assemble_tcp(
+ Payload& payload,
+ in6addr_t* src_ip,
+ in6addr_t* dst_ip,
+ unsigned short src_port,
+ unsigned short dst_port,
+ unsigned int* rest,
+ unsigned int seq,
+ unsigned char* data,
+ int len,
+ char syn,
+ char fin,
+ char rst,
+ char ack)
+{
+ Stream_id id(*src_ip, *dst_ip, src_port, dst_port);
+ Stream& str = g_tcp_streams[id];
+ bool data_avail = false;
+
+ if (!str.has_content()) {
+ Data_segment seg(data, len);
+ str.add(syn, seq, seg);
+ } else {
+ if (rst == 1) {
+ str.erase();
+ } else if (syn == 1) {
+ str.erase();
+ Data_segment seg(data, len);
+ str.add(syn, seq, seg);
+ } else {
+ Data_segment seg(data, len);
+ str.add(syn, seq, seg);
+ }
+ }
+
+ data = 0;
+ if (str.has_content()) {
+ int size = str.get_size();
+ if (size < 2) {
+ // need at least dnslen
+ return 0;
+ }
+ unsigned char* buffer = str.get_buffer();
+ int dns_size = (int(buffer[0]) << 8) | buffer[1];
+
+ data_avail = (fin == 1) && (rst == 0);
+ if (data_avail || dns_size + 2 == size) {
+ *rest = size;
+ if (*rest > 0xffff)
+ *rest = 0xffff;
+ data = (unsigned char*)payload.alloc(*rest);
+ memcpy(data, buffer, *rest);
+ str.erase();
+ g_tcp_streams.erase(id);
+ }
+ }
+ return data;
+}
+
+} // namespace packetq
diff --git a/src/tcp.h b/src/tcp.h
new file mode 100644
index 0000000..48a2a2d
--- /dev/null
+++ b/src/tcp.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2017-2024 OARC, Inc.
+ * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+ * All rights reserved.
+ *
+ * This file is part of PacketQ.
+ *
+ * PacketQ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PacketQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __packetq_tcp_h
+#define __packetq_tcp_h
+
+#include <stdio.h>
+#include <stdint.h>
+
+namespace packetq {
+
+struct _in6_addr {
+ union {
+ uint8_t __u6_addr8[16];
+ uint16_t __u6_addr16[8];
+ uint32_t __u6_addr32[4];
+ } __in6_u; /* 128-bit IP6 address */
+};
+
+typedef struct _in6_addr in6addr_t;
+
+class Payload;
+
+/** Assembles tcp packets into streams and returns data
+ when 'fin' has been recieved
+ */
+unsigned char* assemble_tcp(
+ Payload& payload,
+ in6addr_t* src_ip,
+ in6addr_t* dst_ip,
+ unsigned short src_port,
+ unsigned short dst_port,
+ unsigned int* rest,
+ unsigned int seq,
+ unsigned char* data,
+ int len,
+ char syn,
+ char fin,
+ char rst,
+ char ack);
+
+} // namespace packetq
+
+#endif // __packetq_tcp_h
diff --git a/src/test/Makefile.am b/src/test/Makefile.am
new file mode 100644
index 0000000..eb40c2c
--- /dev/null
+++ b/src/test/Makefile.am
@@ -0,0 +1,32 @@
+# Copyright (c) 2017-2024 OARC, Inc.
+# Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+# All rights reserved.
+#
+# This file is part of PacketQ.
+#
+# PacketQ is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# PacketQ is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
+
+CLEANFILES = test*.log test*.trs \
+ test1.out test2.out test3.out test4.out test5.out test6.out test7.out \
+ test8.out
+
+TESTS = test1.sh test2.sh test3.sh test4.sh test5.sh test6.sh test7.sh \
+ test8.sh
+
+EXTRA_DIST = $(TESTS) \
+ test1.gold test2.gold test3.gold test4.gold test5.gold test6.gold \
+ test7.gold sql.txt test8.gold \
+ dns.pcap dns6.pcap
diff --git a/src/test/Makefile.in b/src/test/Makefile.in
new file mode 100644
index 0000000..d4f26b2
--- /dev/null
+++ b/src/test/Makefile.in
@@ -0,0 +1,885 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 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@
+
+# Copyright (c) 2017-2024 OARC, Inc.
+# Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+# All rights reserved.
+#
+# This file is part of PacketQ.
+#
+# PacketQ is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# PacketQ is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+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 = src/test
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cflags_warn_all.m4 \
+ $(top_srcdir)/m4/ax_compiler_vendor.m4 \
+ $(top_srcdir)/m4/ax_prepend_flag.m4 \
+ $(top_srcdir)/m4/ax_require_defined.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)/src/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 =
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__extra_recursive_targets = gcov-recursive
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ fi; \
+}
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__recheck_rx = ^[ ]*:recheck:[ ]*
+am__global_test_result_rx = ^[ ]*:global-test-result:[ ]*
+am__copy_in_global_log_rx = ^[ ]*:copy-in-global-log:[ ]*
+# A command that, given a newline-separated list of test names on the
+# standard input, print the name of the tests that are to be re-run
+# upon "make recheck".
+am__list_recheck_tests = $(AWK) '{ \
+ recheck = 1; \
+ while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+ { \
+ if (rc < 0) \
+ { \
+ if ((getline line2 < ($$0 ".log")) < 0) \
+ recheck = 0; \
+ break; \
+ } \
+ else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \
+ { \
+ recheck = 0; \
+ break; \
+ } \
+ else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \
+ { \
+ break; \
+ } \
+ }; \
+ if (recheck) \
+ print $$0; \
+ close ($$0 ".trs"); \
+ close ($$0 ".log"); \
+}'
+# A command that, given a newline-separated list of test names on the
+# standard input, create the global log from their .trs and .log files.
+am__create_global_log = $(AWK) ' \
+function fatal(msg) \
+{ \
+ print "fatal: making $@: " msg | "cat >&2"; \
+ exit 1; \
+} \
+function rst_section(header) \
+{ \
+ print header; \
+ len = length(header); \
+ for (i = 1; i <= len; i = i + 1) \
+ printf "="; \
+ printf "\n\n"; \
+} \
+{ \
+ copy_in_global_log = 1; \
+ global_test_result = "RUN"; \
+ while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+ { \
+ if (rc < 0) \
+ fatal("failed to read from " $$0 ".trs"); \
+ if (line ~ /$(am__global_test_result_rx)/) \
+ { \
+ sub("$(am__global_test_result_rx)", "", line); \
+ sub("[ ]*$$", "", line); \
+ global_test_result = line; \
+ } \
+ else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \
+ copy_in_global_log = 0; \
+ }; \
+ if (copy_in_global_log) \
+ { \
+ rst_section(global_test_result ": " $$0); \
+ while ((rc = (getline line < ($$0 ".log"))) != 0) \
+ { \
+ if (rc < 0) \
+ fatal("failed to read from " $$0 ".log"); \
+ print line; \
+ }; \
+ printf "\n"; \
+ }; \
+ close ($$0 ".trs"); \
+ close ($$0 ".log"); \
+}'
+# Restructured Text title.
+am__rst_title = { sed 's/.*/ & /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; }
+# Solaris 10 'make', and several other traditional 'make' implementations,
+# pass "-e" to $(SHELL), and POSIX 2008 even requires this. Work around it
+# by disabling -e (using the XSI extension "set +e") if it's set.
+am__sh_e_setup = case $$- in *e*) set +e;; esac
+# Default flags passed to test drivers.
+am__common_driver_flags = \
+ --color-tests "$$am__color_tests" \
+ --enable-hard-errors "$$am__enable_hard_errors" \
+ --expect-failure "$$am__expect_failure"
+# To be inserted before the command running the test. Creates the
+# directory for the log if needed. Stores in $dir the directory
+# containing $f, in $tst the test, in $log the log. Executes the
+# developer- defined test setup AM_TESTS_ENVIRONMENT (if any), and
+# passes TESTS_ENVIRONMENT. Set up options for the wrapper that
+# will run the test scripts (or their associated LOG_COMPILER, if
+# thy have one).
+am__check_pre = \
+$(am__sh_e_setup); \
+$(am__vpath_adj_setup) $(am__vpath_adj) \
+$(am__tty_colors); \
+srcdir=$(srcdir); export srcdir; \
+case "$@" in \
+ */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;; \
+ *) am__odir=.;; \
+esac; \
+test "x$$am__odir" = x"." || test -d "$$am__odir" \
+ || $(MKDIR_P) "$$am__odir" || exit $$?; \
+if test -f "./$$f"; then dir=./; \
+elif test -f "$$f"; then dir=; \
+else dir="$(srcdir)/"; fi; \
+tst=$$dir$$f; log='$@'; \
+if test -n '$(DISABLE_HARD_ERRORS)'; then \
+ am__enable_hard_errors=no; \
+else \
+ am__enable_hard_errors=yes; \
+fi; \
+case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$f[\ \ ]* | *[\ \ ]$$dir$$f[\ \ ]*) \
+ am__expect_failure=yes;; \
+ *) \
+ am__expect_failure=no;; \
+esac; \
+$(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT)
+# A shell command to get the names of the tests scripts with any registered
+# extension removed (i.e., equivalently, the names of the test logs, with
+# the '.log' extension removed). The result is saved in the shell variable
+# '$bases'. This honors runtime overriding of TESTS and TEST_LOGS. Sadly,
+# we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)",
+# since that might cause problem with VPATH rewrites for suffix-less tests.
+# See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'.
+am__set_TESTS_bases = \
+ bases='$(TEST_LOGS)'; \
+ bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \
+ bases=`echo $$bases`
+AM_TESTSUITE_SUMMARY_HEADER = ' for $(PACKAGE_STRING)'
+RECHECK_LOGS = $(TEST_LOGS)
+AM_RECURSIVE_TARGETS = check recheck
+TEST_SUITE_LOG = test-suite.log
+TEST_EXTENSIONS = @EXEEXT@ .test
+LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver
+LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS)
+am__set_b = \
+ case '$@' in \
+ */*) \
+ case '$*' in \
+ */*) b='$*';; \
+ *) b=`echo '$@' | sed 's/\.log$$//'`; \
+ esac;; \
+ *) \
+ b='$*';; \
+ esac
+am__test_logs1 = $(TESTS:=.log)
+am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log)
+TEST_LOGS = $(am__test_logs2:.test.log=.log)
+TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver
+TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \
+ $(TEST_LOG_FLAGS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/test-driver
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CXX = @CXX@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+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@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+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@
+libmaxminddb_CFLAGS = @libmaxminddb_CFLAGS@
+libmaxminddb_LIBS = @libmaxminddb_LIBS@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
+CLEANFILES = test*.log test*.trs \
+ test1.out test2.out test3.out test4.out test5.out test6.out test7.out \
+ test8.out
+
+TESTS = test1.sh test2.sh test3.sh test4.sh test5.sh test6.sh test7.sh \
+ test8.sh
+
+EXTRA_DIST = $(TESTS) \
+ test1.gold test2.gold test3.gold test4.gold test5.gold test6.gold \
+ test7.gold sql.txt test8.gold \
+ dns.pcap dns6.pcap
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .log .test .test$(EXEEXT) .trs
+$(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 src/test/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/test/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):
+gcov-local:
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+
+# Recover from deleted '.trs' file; this should ensure that
+# "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create
+# both 'foo.log' and 'foo.trs'. Break the recipe in two subshells
+# to avoid problems with "make -n".
+.log.trs:
+ rm -f $< $@
+ $(MAKE) $(AM_MAKEFLAGS) $<
+
+# Leading 'am--fnord' is there to ensure the list of targets does not
+# expand to empty, as could happen e.g. with make check TESTS=''.
+am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck)
+am--force-recheck:
+ @:
+
+$(TEST_SUITE_LOG): $(TEST_LOGS)
+ @$(am__set_TESTS_bases); \
+ am__f_ok () { test -f "$$1" && test -r "$$1"; }; \
+ redo_bases=`for i in $$bases; do \
+ am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \
+ done`; \
+ if test -n "$$redo_bases"; then \
+ redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \
+ redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \
+ if $(am__make_dryrun); then :; else \
+ rm -f $$redo_logs && rm -f $$redo_results || exit 1; \
+ fi; \
+ fi; \
+ if test -n "$$am__remaking_logs"; then \
+ echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \
+ "recursion detected" >&2; \
+ elif test -n "$$redo_logs"; then \
+ am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \
+ fi; \
+ if $(am__make_dryrun); then :; else \
+ st=0; \
+ errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \
+ for i in $$redo_bases; do \
+ test -f $$i.trs && test -r $$i.trs \
+ || { echo "$$errmsg $$i.trs" >&2; st=1; }; \
+ test -f $$i.log && test -r $$i.log \
+ || { echo "$$errmsg $$i.log" >&2; st=1; }; \
+ done; \
+ test $$st -eq 0 || exit 1; \
+ fi
+ @$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \
+ ws='[ ]'; \
+ results=`for b in $$bases; do echo $$b.trs; done`; \
+ test -n "$$results" || results=/dev/null; \
+ all=` grep "^$$ws*:test-result:" $$results | wc -l`; \
+ pass=` grep "^$$ws*:test-result:$$ws*PASS" $$results | wc -l`; \
+ fail=` grep "^$$ws*:test-result:$$ws*FAIL" $$results | wc -l`; \
+ skip=` grep "^$$ws*:test-result:$$ws*SKIP" $$results | wc -l`; \
+ xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \
+ xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \
+ error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \
+ if test `expr $$fail + $$xpass + $$error` -eq 0; then \
+ success=true; \
+ else \
+ success=false; \
+ fi; \
+ br='==================='; br=$$br$$br$$br$$br; \
+ result_count () \
+ { \
+ if test x"$$1" = x"--maybe-color"; then \
+ maybe_colorize=yes; \
+ elif test x"$$1" = x"--no-color"; then \
+ maybe_colorize=no; \
+ else \
+ echo "$@: invalid 'result_count' usage" >&2; exit 4; \
+ fi; \
+ shift; \
+ desc=$$1 count=$$2; \
+ if test $$maybe_colorize = yes && test $$count -gt 0; then \
+ color_start=$$3 color_end=$$std; \
+ else \
+ color_start= color_end=; \
+ fi; \
+ echo "$${color_start}# $$desc $$count$${color_end}"; \
+ }; \
+ create_testsuite_report () \
+ { \
+ result_count $$1 "TOTAL:" $$all "$$brg"; \
+ result_count $$1 "PASS: " $$pass "$$grn"; \
+ result_count $$1 "SKIP: " $$skip "$$blu"; \
+ result_count $$1 "XFAIL:" $$xfail "$$lgn"; \
+ result_count $$1 "FAIL: " $$fail "$$red"; \
+ result_count $$1 "XPASS:" $$xpass "$$red"; \
+ result_count $$1 "ERROR:" $$error "$$mgn"; \
+ }; \
+ { \
+ echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" | \
+ $(am__rst_title); \
+ create_testsuite_report --no-color; \
+ echo; \
+ echo ".. contents:: :depth: 2"; \
+ echo; \
+ for b in $$bases; do echo $$b; done \
+ | $(am__create_global_log); \
+ } >$(TEST_SUITE_LOG).tmp || exit 1; \
+ mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG); \
+ if $$success; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG); \
+ fi; \
+ echo "$${col}$$br$${std}"; \
+ echo "$${col}Testsuite summary"$(AM_TESTSUITE_SUMMARY_HEADER)"$${std}"; \
+ echo "$${col}$$br$${std}"; \
+ create_testsuite_report --maybe-color; \
+ echo "$$col$$br$$std"; \
+ if $$success; then :; else \
+ echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}"; \
+ if test -n "$(PACKAGE_BUGREPORT)"; then \
+ echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}"; \
+ fi; \
+ echo "$$col$$br$$std"; \
+ fi; \
+ $$success || exit 1
+
+check-TESTS:
+ @list='$(RECHECK_LOGS)'; test -z "$$list" || rm -f $$list
+ @list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list
+ @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+ @set +e; $(am__set_TESTS_bases); \
+ log_list=`for i in $$bases; do echo $$i.log; done`; \
+ trs_list=`for i in $$bases; do echo $$i.trs; done`; \
+ log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \
+ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \
+ exit $$?;
+recheck: all
+ @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+ @set +e; $(am__set_TESTS_bases); \
+ bases=`for i in $$bases; do echo $$i; done \
+ | $(am__list_recheck_tests)` || exit 1; \
+ log_list=`for i in $$bases; do echo $$i.log; done`; \
+ log_list=`echo $$log_list`; \
+ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \
+ am__force_recheck=am--force-recheck \
+ TEST_LOGS="$$log_list"; \
+ exit $$?
+test1.sh.log: test1.sh
+ @p='test1.sh'; \
+ b='test1.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test2.sh.log: test2.sh
+ @p='test2.sh'; \
+ b='test2.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test3.sh.log: test3.sh
+ @p='test3.sh'; \
+ b='test3.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test4.sh.log: test4.sh
+ @p='test4.sh'; \
+ b='test4.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test5.sh.log: test5.sh
+ @p='test5.sh'; \
+ b='test5.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test6.sh.log: test6.sh
+ @p='test6.sh'; \
+ b='test6.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test7.sh.log: test7.sh
+ @p='test7.sh'; \
+ b='test7.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+test8.sh.log: test8.sh
+ @p='test8.sh'; \
+ b='test8.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+.test.log:
+ @p='$<'; \
+ $(am__set_b); \
+ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+@am__EXEEXT_TRUE@.test$(EXEEXT).log:
+@am__EXEEXT_TRUE@ @p='$<'; \
+@am__EXEEXT_TRUE@ $(am__set_b); \
+@am__EXEEXT_TRUE@ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+@am__EXEEXT_TRUE@ --log-file $$b.log --trs-file $$b.trs \
+@am__EXEEXT_TRUE@ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+@am__EXEEXT_TRUE@ "$$tst" $(AM_TESTS_FD_REDIRECT)
+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
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-am
+all-am: Makefile
+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:
+ -test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS)
+ -test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs)
+ -test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+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."
+ -test -z "$(MAINTAINERCLEANFILES)" || rm -f $(MAINTAINERCLEANFILES)
+clean: clean-am
+
+clean-am: clean-generic mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+gcov: gcov-am
+
+gcov-am: gcov-local
+
+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 Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: all all-am check check-TESTS check-am clean clean-generic \
+ cscopelist-am ctags-am distclean distclean-generic distdir dvi \
+ dvi-am gcov-am gcov-local 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-generic pdf pdf-am ps ps-am recheck 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/src/test/dns.pcap b/src/test/dns.pcap
new file mode 100644
index 0000000..a0e585c
--- /dev/null
+++ b/src/test/dns.pcap
Binary files differ
diff --git a/src/test/dns6.pcap b/src/test/dns6.pcap
new file mode 100644
index 0000000..5fa3af8
--- /dev/null
+++ b/src/test/dns6.pcap
Binary files differ
diff --git a/src/test/sql.txt b/src/test/sql.txt
new file mode 100644
index 0000000..b1d0bbb
--- /dev/null
+++ b/src/test/sql.txt
@@ -0,0 +1,26 @@
+select qname as CertainQnames, qtype as Qtype, count(1) as count from dns where (qname='localhost' or qname like '%.root-servers.net') and qr==0 group by CertainQnames,Qtype order by count desc;
+select qtype as Qtype, qname as Qname, count(1) as count from dns where qclass==3 and qr==0 group by Qtype,Qname order by count desc;
+select rcode as Rcode, if(qr==1,dst_addr,src_addr) as ClientAddr, count(1) as count from dns where qr==1 group by Rcode,ClientAddr order by count,Rcode,ClientAddr desc limit 50;
+select 'ALL' as All, if(ether_type==34525,rsplit(src_addr,7,':')||':'||rsplit(src_addr,6,':')||':'||rsplit(src_addr,5,':')||':'||rsplit(src_addr,4,':')||':'||rsplit(src_addr,3,':')||'::',rsplit(src_addr,3)||'.'||rsplit(src_addr,2)||'.'||rsplit(src_addr,1)||'.0') as ClientSubnet, count(1) as count from dns where qr==0 group by All,ClientSubnet order by count,ClientSubnet desc limit 200;
+#select 'ALL' as All, subnet(src_addr,24,96) as ClientSubnet, count(1) as count from dns where qr==0 group by All,ClientSubnet order by count desc,ClientSubnet limit 200;
+select if(rsplit(qname,1)='de','ok','non-auth-tld') as Class, if(ether_type==34525,rsplit(src_addr,7,':')||':'||rsplit(src_addr,6,':')||':'||rsplit(src_addr,5,':')||':'||rsplit(src_addr,4,':')||':'||rsplit(src_addr,3,':')||'::',rsplit(src_addr,3)||'.'||rsplit(src_addr,2)||'.'||rsplit(src_addr,1)||'.0') as ClientSubnet, count(1) as count from dns where qr==0 group by Class,ClientSubnet order by count,ClientSubnet,Class desc limit 200;
+select if(qr==1,'sent','recv') as Direction, if(protocol==6,'tcp',if(protocol==17,'udp',if(protocol==1,'icmp',if(protocol==58,'ipv6-icmp',protocol)))) as IPProto, count(1) as count from dns group by Direction,IPProto order by count,Direction,IPProto desc;
+select if(ether_type==34525,'IPv6','IPv4') as IPVersion, qtype as Qtype, count(1) as count from dns where qr==0 group by IPVersion,Qtype order by count,IPVersion,Qtype desc;
+select 'ALL' as All, do, edns0, edns_version, extended_rcode, z, if(do==1,'set','clr') as D0, count(1) as count from dns where qr==0 group by All,do,D0,edns0,edns_version,extended_rcode,z order by count desc;
+select 'ALL' as All, if(edns0,edns_version,'none') as EDNSVersion, count(1) as count from dns where qr==0 group by All,EDNSVersion order by count desc;
+select 'ALL' as All, if(qname like 'xn--%','idn','normal') as IDNQname, count(1) as count from dns where qr==0 group by All,IDNQname order by count desc;
+select 'ALL' as All, lower(rsplit(qname,1)) as TLD, count(1) as count from dns where qr==0 and (qname like 'xn--%') group by All,TLD order by count,TLD desc;
+select 'ALL' as All, if(qr==1,dst_addr,src_addr) as ClientAddr, count(1) as count from dns where qr==0 and (qtype=28 or qtype=38) and (qname like '%.root-servers.net') group by All,ClientAddr order by count desc limit 50;
+select 'ALL' as All, opcode as Opcode, count(1) as count from dns where qr==0 group by All,Opcode order by count,Opcode desc;
+select 'ALL' as All, qtype as Qtype, count(1) as count from dns where qr==0 group by All,Qtype order by count,Qtype desc;
+select qtype as Qtype, len(qname) as QnameLen, count(1) as count from dns where qr==0 group by Qtype,QnameLen order by count,QnameLen,Qtype desc;
+select qtype as Qtype, lower(rsplit(qname,1)) as TLD, count(1) as count from dns where qr==0 and (qtype=1 or qtype=2 or qtype=5 or qtype=6 or qtype=12 or qtype=15 or qtype=28 or qtype=38 or qtype=255) group by Qtype,TLD order by count,TLD,Qtype desc limit 200;
+select 'ALL' as All, rcode as Rcode, count(1) as count from dns where qr==1 group by All,Rcode order by count,Rcode desc;
+select rcode as Rcode, msg_size as ReplyLen, count(1) as count from dns where qr==1 group by Rcode,ReplyLen order by count,Rcode,ReplyLen desc;
+select 'ALL' as All, rd as RD, count(1) as count from dns where qr==0 group by All,RD order by count desc;
+select if(protocol==6,'tcp',if(protocol==17,'udp',protocol)) as Transport, qtype as Qtype, count(1) as count from dns where qr==0 group by Transport,Qtype order by Transport,Qtype,count desc;
+select s, dst_addr as Dst_addr, qtype as questiontype, lower(src_addr) as lower_src, if(1 and s < 1 or s <= 1 or s > 1 or s >= 1, 't', 'f'), trim(trim('foofoo' || rsplit(src_addr, 1) || 'foofoo', 'foo'), 'bar'), count(*), len(src_addr), sum(msg_size + -1 - 2 % 4 << 3 >> 2 | 3 & ~4) + 1, min(msg_size), max(msg_size), truncate(1.1) as integer, 1.1 as float, sum(src_port + 1.0 - 2.0 / 1.5 * -2.5) + 1.0, max(src_port + 1.0), min(src_port + 1.0), avg(src_port), stdev(src_port), name('rcode', 0) from dns where src_addr like '%' and (qr or not qr) group by src_addr, s having s >= 0 order by s, dst_addr, lower_src, integer, float;
+select name( 'qtype' , qtype ) as qt, count(*) as count from dns group by qtype order by count, qt desc;
+#select count(*) as count, lower(rsplit(qname,1)) as tld, istld(tld) as flag from dns group by tld order by count desc limit 50;
+select * from icmp;
+select count(*) from icmp;
diff --git a/src/test/test1.gold b/src/test/test1.gold
new file mode 100644
index 0000000..d0b3203
--- /dev/null
+++ b/src/test/test1.gold
@@ -0,0 +1,145 @@
+[
+ {
+ "table_name": "result-0",
+ "query": "select s, dst_addr as Dst_addr, qtype as questiontype, lower(src_addr) as lower_src, if(1 and s < 1 or s <= 1 or s > 1 or s >= 1, 't', 'f'), trim(trim('foofoo' || rsplit(src_addr, 1) || 'foofoo', 'foo'), 'bar'), count(*), len(src_addr), sum(msg_size + -1 - 2 % 4 << 3 >> 2 | 3 & ~4) + 1, min(msg_size), max(msg_size), truncate(1.1) as integer, 1.1 as float, sum(src_port + 1.0 - 2.0 / 1.5 * -2.5) + 1.0, max(src_port + 1.0), min(src_port + 1.0), avg(src_port), stdev(src_port), name('rcode', 0) from dns where src_addr like '%' and (qr or not qr) group by src_addr, s having s >= 0 order by s, lower_src, integer, float",
+ "head": [
+ { "name": "s","type": "int" },
+ { "name": "Dst_addr","type": "text" },
+ { "name": "questiontype","type": "int" },
+ { "name": "lower_src","type": "text" },
+ { "name": "if((1and((((s<1)or(s<=1))or(s>1))or(s>=1))),t,f)","type": "text" },
+ { "name": "trim(trim(((foofoo||rsplit(src_addr,1))||foofoo),foo),bar)","type": "text" },
+ { "name": "count(1)","type": "int" },
+ { "name": "len(src_addr)","type": "int" },
+ { "name": "(sum((msg_size+-((((((1-(2%4))<<3)>>2)|3)&~(4)))))+1)","type": "int" },
+ { "name": "min(msg_size)","type": "int" },
+ { "name": "max(msg_size)","type": "int" },
+ { "name": "integer","type": "int" },
+ { "name": "float","type": "float" },
+ { "name": "(sum(((src_port+1.0)-((2.0/1.5)*-(2.5))))+1.0)","type": "float" },
+ { "name": "max((src_port+1.0))","type": "float" },
+ { "name": "min((src_port+1.0))","type": "float" },
+ { "name": "avg(src_port)","type": "float" },
+ { "name": "stdev(src_port)","type": "float" },
+ { "name": "name(rcode,0)","type": "text" }
+ ],
+ "data": [
+ [1297433016,"212.247.204.2",12,"172.18.24.52","t","24",1,12,48,42,42,1,1.1,52271.3,52267,52267,52266,0,"NoError"],
+ [1297433016,"172.18.24.52",12,"212.247.204.2","t","204",1,13,125,119,119,1,1.1,58.3333,54,54,53,0,"NoError"],
+ [1297433026,"212.247.204.2",1,"172.18.24.52","t","24",1,12,36,30,30,1,1.1,54066.3,54062,54062,54061,0,"NoError"],
+ [1297433027,"172.18.24.52",1,"212.247.204.2","t","204",1,13,84,78,78,1,1.1,58.3333,54,54,53,0,"NoError"],
+ [1297433030,"212.247.204.2",1,"172.18.24.52","t","24",1,12,30,24,24,1,1.1,59489.3,59485,59485,59484,0,"NoError"],
+ [1297433030,"172.18.24.52",1,"212.247.204.2","t","204",1,13,46,40,40,1,1.1,58.3333,54,54,53,0,"NoError"],
+ [1297433038,"212.247.204.2",12,"172.18.24.52","t","24",1,12,50,44,44,1,1.1,49372.3,49368,49368,49367,0,"NoError"],
+ [1297433039,"172.18.24.52",12,"212.247.204.2","t","204",1,13,102,96,96,1,1.1,58.3333,54,54,53,0,"NoError"]
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select netmask(src_addr), netmask(dst_addr, 8, 16) from dns",
+ "head": [
+ { "name": "netmask(src_addr)","type": "text" },
+ { "name": "netmask(dst_addr,8,16)","type": "text" }
+ ],
+ "data": [
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"],
+ ["172.17.0.0","8.0.0.0"],
+ ["8.8.8.0","172.0.0.0"]
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select netmask(src_addr), netmask(dst_addr, 8, 16) from dns",
+ "head": [
+ { "name": "netmask(src_addr)","type": "text" },
+ { "name": "netmask(dst_addr,8,16)","type": "text" }
+ ],
+ "data": [
+ ["2a01:3f0::","2001::"],
+ ["2001:4860:4860::","2a01::"]
+ ]
+ }
+]
diff --git a/src/test/test1.sh b/src/test/test1.sh
new file mode 100755
index 0000000..83e6779
--- /dev/null
+++ b/src/test/test1.sh
@@ -0,0 +1,27 @@
+#!/bin/sh -e
+# Copyright (c) 2017-2024 OARC, Inc.
+# Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+# All rights reserved.
+#
+# This file is part of PacketQ.
+#
+# PacketQ is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# PacketQ is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+
+../packetq -j -s "select s, dst_addr as Dst_addr, qtype as questiontype, lower(src_addr) as lower_src, if(1 and s < 1 or s <= 1 or s > 1 or s >= 1, 't', 'f'), trim(trim('foofoo' || rsplit(src_addr, 1) || 'foofoo', 'foo'), 'bar'), count(*), len(src_addr), sum(msg_size + -1 - 2 % 4 << 3 >> 2 | 3 & ~4) + 1, min(msg_size), max(msg_size), truncate(1.1) as integer, 1.1 as float, sum(src_port + 1.0 - 2.0 / 1.5 * -2.5) + 1.0, max(src_port + 1.0), min(src_port + 1.0), avg(src_port), stdev(src_port), name('rcode', 0) from dns where src_addr like '%' and (qr or not qr) group by src_addr, s having s >= 0 order by s, lower_src, integer, float" "$srcdir/../../pcap/sample.pcap.gz" > test1.out
+
+../packetq -j -s "select netmask(src_addr), netmask(dst_addr, 8, 16) from dns" "$srcdir/dns.pcap" >>test1.out
+
+../packetq -j -s "select netmask(src_addr), netmask(dst_addr, 8, 16) from dns" "$srcdir/dns6.pcap" >>test1.out
+
+diff -uw "$srcdir/test1.gold" test1.out
diff --git a/src/test/test2.gold b/src/test/test2.gold
new file mode 100644
index 0000000..eff7e73
--- /dev/null
+++ b/src/test/test2.gold
@@ -0,0 +1,9 @@
+"s","Dst_addr","questiontype","lower_src","if((1and((((s<1)or(s<=1))or(s>1))or(s>=1))),t,f)","trim(trim(((foofoo||rsplit(src_addr,1))||foofoo),foo),bar)","count(1)","len(src_addr)","(sum((msg_size+-((((((1-(2%4))<<3)>>2)|3)&~(4)))))+1)","min(msg_size)","max(msg_size)","integer","float","(sum(((src_port+1.0)-((2.0/1.5)*-(2.5))))+1.0)","max((src_port+1.0))","min((src_port+1.0))","avg(src_port)","stdev(src_port)","name(rcode,0)"
+1297433016,"212.247.204.2",12,"172.18.24.52","t","24",1,12,48,42,42,1,1.1,52271.3,52267,52267,52266,0,"NoError"
+1297433016,"172.18.24.52",12,"212.247.204.2","t","204",1,13,125,119,119,1,1.1,58.3333,54,54,53,0,"NoError"
+1297433026,"212.247.204.2",1,"172.18.24.52","t","24",1,12,36,30,30,1,1.1,54066.3,54062,54062,54061,0,"NoError"
+1297433027,"172.18.24.52",1,"212.247.204.2","t","204",1,13,84,78,78,1,1.1,58.3333,54,54,53,0,"NoError"
+1297433030,"212.247.204.2",1,"172.18.24.52","t","24",1,12,30,24,24,1,1.1,59489.3,59485,59485,59484,0,"NoError"
+1297433030,"172.18.24.52",1,"212.247.204.2","t","204",1,13,46,40,40,1,1.1,58.3333,54,54,53,0,"NoError"
+1297433038,"212.247.204.2",12,"172.18.24.52","t","24",1,12,50,44,44,1,1.1,49372.3,49368,49368,49367,0,"NoError"
+1297433039,"172.18.24.52",12,"212.247.204.2","t","204",1,13,102,96,96,1,1.1,58.3333,54,54,53,0,"NoError"
diff --git a/src/test/test2.sh b/src/test/test2.sh
new file mode 100755
index 0000000..c443ec1
--- /dev/null
+++ b/src/test/test2.sh
@@ -0,0 +1,23 @@
+#!/bin/sh -e
+# Copyright (c) 2017-2024 OARC, Inc.
+# Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+# All rights reserved.
+#
+# This file is part of PacketQ.
+#
+# PacketQ is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# PacketQ is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+
+../packetq -c -s "select s, dst_addr as Dst_addr, qtype as questiontype, lower(src_addr) as lower_src, if(1 and s < 1 or s <= 1 or s > 1 or s >= 1, 't', 'f'), trim(trim('foofoo' || rsplit(src_addr, 1) || 'foofoo', 'foo'), 'bar'), count(*), len(src_addr), sum(msg_size + -1 - 2 % 4 << 3 >> 2 | 3 & ~4) + 1, min(msg_size), max(msg_size), truncate(1.1) as integer, 1.1 as float, sum(src_port + 1.0 - 2.0 / 1.5 * -2.5) + 1.0, max(src_port + 1.0), min(src_port + 1.0), avg(src_port), stdev(src_port), name('rcode', 0) from dns where src_addr like '%' and (qr or not qr) group by src_addr, s having s >= 0 order by s, lower_src, integer, float" "$srcdir/../../pcap/sample.pcap.gz" > test2.out
+
+diff -uw "$srcdir/test2.gold" test2.out
diff --git a/src/test/test3.gold b/src/test/test3.gold
new file mode 100644
index 0000000..c605f71
--- /dev/null
+++ b/src/test/test3.gold
@@ -0,0 +1,9 @@
+"s" ,"Dst_addr" ,"questiontype","lower_src" ,"if((1and((((s<1)or(s<=1))or(s>1))or(s>=1))),t,f)","trim(trim(((foofoo||rsplit(src_addr,1))||foofoo),foo),bar)","count(1)","len(src_addr)","(sum((msg_size+-((((((1-(2%4))<<3)>>2)|3)&~(4)))))+1)","min(msg_size)","max(msg_size)","integer","float","(sum(((src_port+1.0)-((2.0/1.5)*-(2.5))))+1.0)","max((src_port+1.0))","min((src_port+1.0))","avg(src_port)","stdev(src_port)","name(rcode,0)"
+1297433016,"212.247.204.2",12 ,"172.18.24.52" ,"t" ,"24" ,1 ,12 ,48 ,42 ,42 ,1 ,1.1 ,52271.3 ,52267 ,52267 ,52266 ,0 ,"NoError"
+1297433016,"172.18.24.52" ,12 ,"212.247.204.2","t" ,"204" ,1 ,13 ,125 ,119 ,119 ,1 ,1.1 ,58.3333 ,54 ,54 ,53 ,0 ,"NoError"
+1297433026,"212.247.204.2",1 ,"172.18.24.52" ,"t" ,"24" ,1 ,12 ,36 ,30 ,30 ,1 ,1.1 ,54066.3 ,54062 ,54062 ,54061 ,0 ,"NoError"
+1297433027,"172.18.24.52" ,1 ,"212.247.204.2","t" ,"204" ,1 ,13 ,84 ,78 ,78 ,1 ,1.1 ,58.3333 ,54 ,54 ,53 ,0 ,"NoError"
+1297433030,"212.247.204.2",1 ,"172.18.24.52" ,"t" ,"24" ,1 ,12 ,30 ,24 ,24 ,1 ,1.1 ,59489.3 ,59485 ,59485 ,59484 ,0 ,"NoError"
+1297433030,"172.18.24.52" ,1 ,"212.247.204.2","t" ,"204" ,1 ,13 ,46 ,40 ,40 ,1 ,1.1 ,58.3333 ,54 ,54 ,53 ,0 ,"NoError"
+1297433038,"212.247.204.2",12 ,"172.18.24.52" ,"t" ,"24" ,1 ,12 ,50 ,44 ,44 ,1 ,1.1 ,49372.3 ,49368 ,49368 ,49367 ,0 ,"NoError"
+1297433039,"172.18.24.52" ,12 ,"212.247.204.2","t" ,"204" ,1 ,13 ,102 ,96 ,96 ,1 ,1.1 ,58.3333 ,54 ,54 ,53 ,0 ,"NoError"
diff --git a/src/test/test3.sh b/src/test/test3.sh
new file mode 100755
index 0000000..528845d
--- /dev/null
+++ b/src/test/test3.sh
@@ -0,0 +1,23 @@
+#!/bin/sh -e
+# Copyright (c) 2017-2024 OARC, Inc.
+# Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+# All rights reserved.
+#
+# This file is part of PacketQ.
+#
+# PacketQ is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# PacketQ is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+
+../packetq -t -s "select s, dst_addr as Dst_addr, qtype as questiontype, lower(src_addr) as lower_src, if(1 and s < 1 or s <= 1 or s > 1 or s >= 1, 't', 'f'), trim(trim('foofoo' || rsplit(src_addr, 1) || 'foofoo', 'foo'), 'bar'), count(*), len(src_addr), sum(msg_size + -1 - 2 % 4 << 3 >> 2 | 3 & ~4) + 1, min(msg_size), max(msg_size), truncate(1.1) as integer, 1.1 as float, sum(src_port + 1.0 - 2.0 / 1.5 * -2.5) + 1.0, max(src_port + 1.0), min(src_port + 1.0), avg(src_port), stdev(src_port), name('rcode', 0) from dns where src_addr like '%' and (qr or not qr) group by src_addr, s having s >= 0 order by s, lower_src, integer, float" "$srcdir/../../pcap/sample.pcap.gz" > test3.out
+
+diff -uw "$srcdir/test3.gold" test3.out
diff --git a/src/test/test4.gold b/src/test/test4.gold
new file mode 100644
index 0000000..18b1cfc
--- /dev/null
+++ b/src/test/test4.gold
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>result-0</title>
+<style type="text/css">
+ th.int { color: #0F0C00; }
+ th.float { color: #0F0900; }
+ th.text { color: #0F0600; }
+ th.bool { color: #0C0900; }
+</style>
+</head>
+<body>
+<table>
+<tr><th class="int">s</th><th class="text">Dst_addr</th><th class="int">questiontype</th><th class="text">lower_src</th><th class="text">if((1and((((s<1)or(s<=1))or(s>1))or(s>=1))),t,f)</th><th class="text">trim(trim(((foofoo||rsplit(src_addr,1))||foofoo),foo),bar)</th><th class="int">count(1)</th><th class="int">len(src_addr)</th><th class="int">(sum((msg_size+-((((((1-(2%4))<<3)>>2)|3)&~(4)))))+1)</th><th class="int">min(msg_size)</th><th class="int">max(msg_size)</th><th class="int">integer</th><th class="float">float</th><th class="float">(sum(((src_port+1.0)-((2.0/1.5)*-(2.5))))+1.0)</th><th class="float">max((src_port+1.0))</th><th class="float">min((src_port+1.0))</th><th class="float">avg(src_port)</th><th class="float">stdev(src_port)</th><th class="text">name(rcode,0)</th></tr>
+<tr><td>1297433016</td> <td>212.247.204.2</td> <td>12</td> <td>172.18.24.52</td> <td>t</td> <td>24</td> <td>1</td> <td>12</td> <td>48</td> <td>42</td> <td>42</td> <td>1</td> <td>1.1</td> <td>52271.3</td> <td>52267</td> <td>52267</td> <td>52266</td> <td>0</td> <td>NoError</td> </tr>
+<tr><td>1297433016</td> <td>172.18.24.52</td> <td>12</td> <td>212.247.204.2</td> <td>t</td> <td>204</td> <td>1</td> <td>13</td> <td>125</td> <td>119</td> <td>119</td> <td>1</td> <td>1.1</td> <td>58.3333</td> <td>54</td> <td>54</td> <td>53</td> <td>0</td> <td>NoError</td> </tr>
+<tr><td>1297433026</td> <td>212.247.204.2</td> <td>1</td> <td>172.18.24.52</td> <td>t</td> <td>24</td> <td>1</td> <td>12</td> <td>36</td> <td>30</td> <td>30</td> <td>1</td> <td>1.1</td> <td>54066.3</td> <td>54062</td> <td>54062</td> <td>54061</td> <td>0</td> <td>NoError</td> </tr>
+<tr><td>1297433027</td> <td>172.18.24.52</td> <td>1</td> <td>212.247.204.2</td> <td>t</td> <td>204</td> <td>1</td> <td>13</td> <td>84</td> <td>78</td> <td>78</td> <td>1</td> <td>1.1</td> <td>58.3333</td> <td>54</td> <td>54</td> <td>53</td> <td>0</td> <td>NoError</td> </tr>
+<tr><td>1297433030</td> <td>212.247.204.2</td> <td>1</td> <td>172.18.24.52</td> <td>t</td> <td>24</td> <td>1</td> <td>12</td> <td>30</td> <td>24</td> <td>24</td> <td>1</td> <td>1.1</td> <td>59489.3</td> <td>59485</td> <td>59485</td> <td>59484</td> <td>0</td> <td>NoError</td> </tr>
+<tr><td>1297433030</td> <td>172.18.24.52</td> <td>1</td> <td>212.247.204.2</td> <td>t</td> <td>204</td> <td>1</td> <td>13</td> <td>46</td> <td>40</td> <td>40</td> <td>1</td> <td>1.1</td> <td>58.3333</td> <td>54</td> <td>54</td> <td>53</td> <td>0</td> <td>NoError</td> </tr>
+<tr><td>1297433038</td> <td>212.247.204.2</td> <td>12</td> <td>172.18.24.52</td> <td>t</td> <td>24</td> <td>1</td> <td>12</td> <td>50</td> <td>44</td> <td>44</td> <td>1</td> <td>1.1</td> <td>49372.3</td> <td>49368</td> <td>49368</td> <td>49367</td> <td>0</td> <td>NoError</td> </tr>
+<tr><td>1297433039</td> <td>172.18.24.52</td> <td>12</td> <td>212.247.204.2</td> <td>t</td> <td>204</td> <td>1</td> <td>13</td> <td>102</td> <td>96</td> <td>96</td> <td>1</td> <td>1.1</td> <td>58.3333</td> <td>54</td> <td>54</td> <td>53</td> <td>0</td> <td>NoError</td> </tr>
+</table>
+</body>
+</html>
diff --git a/src/test/test4.sh b/src/test/test4.sh
new file mode 100755
index 0000000..0747e73
--- /dev/null
+++ b/src/test/test4.sh
@@ -0,0 +1,23 @@
+#!/bin/sh -e
+# Copyright (c) 2017-2024 OARC, Inc.
+# Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+# All rights reserved.
+#
+# This file is part of PacketQ.
+#
+# PacketQ is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# PacketQ is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+
+../packetq -x -s "select s, dst_addr as Dst_addr, qtype as questiontype, lower(src_addr) as lower_src, if(1 and s < 1 or s <= 1 or s > 1 or s >= 1, 't', 'f'), trim(trim('foofoo' || rsplit(src_addr, 1) || 'foofoo', 'foo'), 'bar'), count(*), len(src_addr), sum(msg_size + -1 - 2 % 4 << 3 >> 2 | 3 & ~4) + 1, min(msg_size), max(msg_size), truncate(1.1) as integer, 1.1 as float, sum(src_port + 1.0 - 2.0 / 1.5 * -2.5) + 1.0, max(src_port + 1.0), min(src_port + 1.0), avg(src_port), stdev(src_port), name('rcode', 0) from dns where src_addr like '%' and (qr or not qr) group by src_addr, s having s >= 0 order by s, lower_src, integer, float" "$srcdir/../../pcap/sample.pcap.gz" > test4.out
+
+diff -uw "$srcdir/test4.gold" test4.out
diff --git a/src/test/test5.gold b/src/test/test5.gold
new file mode 100644
index 0000000..c1c4a82
--- /dev/null
+++ b/src/test/test5.gold
@@ -0,0 +1,387 @@
+[
+ {
+ "table_name": "result-0",
+ "query": "select qname as CertainQnames, qtype as Qtype, count(1) as count from dns where (qname='localhost' or qname like '%.root-servers.net') and qr==0 group by CertainQnames,Qtype order by count desc;",
+ "head": [
+ { "name": "CertainQnames","type": "text" },
+ { "name": "Qtype","type": "int" },
+ { "name": "count","type": "int" }
+ ],
+ "data": [
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select qtype as Qtype, qname as Qname, count(1) as count from dns where qclass==3 and qr==0 group by Qtype,Qname order by count desc;",
+ "head": [
+ { "name": "Qtype","type": "int" },
+ { "name": "Qname","type": "text" },
+ { "name": "count","type": "int" }
+ ],
+ "data": [
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select rcode as Rcode, if(qr==1,dst_addr,src_addr) as ClientAddr, count(1) as count from dns where qr==1 group by Rcode,ClientAddr order by count,Rcode,ClientAddr desc limit 50;",
+ "head": [
+ { "name": "Rcode","type": "int" },
+ { "name": "ClientAddr","type": "text" },
+ { "name": "count","type": "int" }
+ ],
+ "data": [
+ [3,"172.18.24.52",1],
+ [0,"172.18.24.52",3]
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select 'ALL' as All, if(ether_type==34525,rsplit(src_addr,7,':')||':'||rsplit(src_addr,6,':')||':'||rsplit(src_addr,5,':')||':'||rsplit(src_addr,4,':')||':'||rsplit(src_addr,3,':')||'::',rsplit(src_addr,3)||'.'||rsplit(src_addr,2)||'.'||rsplit(src_addr,1)||'.0') as ClientSubnet, count(1) as count from dns where qr==0 group by All,ClientSubnet order by count,ClientSubnet desc limit 200;",
+ "head": [
+ { "name": "All","type": "text" },
+ { "name": "ClientSubnet","type": "text" },
+ { "name": "count","type": "int" }
+ ],
+ "data": [
+ ["ALL","172.18.24.0",4]
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select if(rsplit(qname,1)='de','ok','non-auth-tld') as Class, if(ether_type==34525,rsplit(src_addr,7,':')||':'||rsplit(src_addr,6,':')||':'||rsplit(src_addr,5,':')||':'||rsplit(src_addr,4,':')||':'||rsplit(src_addr,3,':')||'::',rsplit(src_addr,3)||'.'||rsplit(src_addr,2)||'.'||rsplit(src_addr,1)||'.0') as ClientSubnet, count(1) as count from dns where qr==0 group by Class,ClientSubnet order by count,ClientSubnet,Class desc limit 200;",
+ "head": [
+ { "name": "Class","type": "text" },
+ { "name": "ClientSubnet","type": "text" },
+ { "name": "count","type": "int" }
+ ],
+ "data": [
+ ["non-auth-tld","172.18.24.0",4]
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select if(qr==1,'sent','recv') as Direction, if(protocol==6,'tcp',if(protocol==17,'udp',if(protocol==1,'icmp',if(protocol==58,'ipv6-icmp',protocol)))) as IPProto, count(1) as count from dns group by Direction,IPProto order by count,Direction,IPProto desc;",
+ "head": [
+ { "name": "Direction","type": "text" },
+ { "name": "IPProto","type": "text" },
+ { "name": "count","type": "int" }
+ ],
+ "data": [
+ ["recv","udp",4],
+ ["sent","udp",4]
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select if(ether_type==34525,'IPv6','IPv4') as IPVersion, qtype as Qtype, count(1) as count from dns where qr==0 group by IPVersion,Qtype order by count,IPVersion,Qtype desc;",
+ "head": [
+ { "name": "IPVersion","type": "text" },
+ { "name": "Qtype","type": "int" },
+ { "name": "count","type": "int" }
+ ],
+ "data": [
+ ["IPv4",12,2],
+ ["IPv4",1,2]
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select 'ALL' as All, do, edns0, edns_version, extended_rcode, z, if(do==1,'set','clr') as D0, count(1) as count from dns where qr==0 group by All,do,D0,edns0,edns_version,extended_rcode,z order by count desc;",
+ "head": [
+ { "name": "All","type": "text" },
+ { "name": "do","type": "bool" },
+ { "name": "edns0","type": "bool" },
+ { "name": "edns_version","type": "int" },
+ { "name": "extended_rcode","type": "int" },
+ { "name": "z","type": "int" },
+ { "name": "D0","type": "text" },
+ { "name": "count","type": "int" }
+ ],
+ "data": [
+ ["ALL",0,0,0,0,0,"clr",4]
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select 'ALL' as All, if(edns0,edns_version,'none') as EDNSVersion, count(1) as count from dns where qr==0 group by All,EDNSVersion order by count desc;",
+ "head": [
+ { "name": "All","type": "text" },
+ { "name": "EDNSVersion","type": "text" },
+ { "name": "count","type": "int" }
+ ],
+ "data": [
+ ["ALL","none",4]
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select 'ALL' as All, if(qname like 'xn--%','idn','normal') as IDNQname, count(1) as count from dns where qr==0 group by All,IDNQname order by count desc;",
+ "head": [
+ { "name": "All","type": "text" },
+ { "name": "IDNQname","type": "text" },
+ { "name": "count","type": "int" }
+ ],
+ "data": [
+ ["ALL","normal",4]
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select 'ALL' as All, lower(rsplit(qname,1)) as TLD, count(1) as count from dns where qr==0 and (qname like 'xn--%') group by All,TLD order by count,TLD desc;",
+ "head": [
+ { "name": "All","type": "text" },
+ { "name": "TLD","type": "text" },
+ { "name": "count","type": "int" }
+ ],
+ "data": [
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select 'ALL' as All, if(qr==1,dst_addr,src_addr) as ClientAddr, count(1) as count from dns where qr==0 and (qtype=28 or qtype=38) and (qname like '%.root-servers.net') group by All,ClientAddr order by count desc limit 50;",
+ "head": [
+ { "name": "All","type": "text" },
+ { "name": "ClientAddr","type": "text" },
+ { "name": "count","type": "int" }
+ ],
+ "data": [
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select 'ALL' as All, opcode as Opcode, count(1) as count from dns where qr==0 group by All,Opcode order by count,Opcode desc;",
+ "head": [
+ { "name": "All","type": "text" },
+ { "name": "Opcode","type": "int" },
+ { "name": "count","type": "int" }
+ ],
+ "data": [
+ ["ALL",0,4]
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select 'ALL' as All, qtype as Qtype, count(1) as count from dns where qr==0 group by All,Qtype order by count,Qtype desc;",
+ "head": [
+ { "name": "All","type": "text" },
+ { "name": "Qtype","type": "int" },
+ { "name": "count","type": "int" }
+ ],
+ "data": [
+ ["ALL",12,2],
+ ["ALL",1,2]
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select qtype as Qtype, len(qname) as QnameLen, count(1) as count from dns where qr==0 group by Qtype,QnameLen order by count,QnameLen,Qtype desc;",
+ "head": [
+ { "name": "Qtype","type": "int" },
+ { "name": "QnameLen","type": "int" },
+ { "name": "count","type": "int" }
+ ],
+ "data": [
+ [1,7,1],
+ [1,13,1],
+ [12,25,1],
+ [12,27,1]
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select qtype as Qtype, lower(rsplit(qname,1)) as TLD, count(1) as count from dns where qr==0 and (qtype=1 or qtype=2 or qtype=5 or qtype=6 or qtype=12 or qtype=15 or qtype=28 or qtype=38 or qtype=255) group by Qtype,TLD order by count,TLD,Qtype desc limit 200;",
+ "head": [
+ { "name": "Qtype","type": "int" },
+ { "name": "TLD","type": "text" },
+ { "name": "count","type": "int" }
+ ],
+ "data": [
+ [1,"org",1],
+ [1,"se",1],
+ [12,"arpa",2]
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select 'ALL' as All, rcode as Rcode, count(1) as count from dns where qr==1 group by All,Rcode order by count,Rcode desc;",
+ "head": [
+ { "name": "All","type": "text" },
+ { "name": "Rcode","type": "int" },
+ { "name": "count","type": "int" }
+ ],
+ "data": [
+ ["ALL",3,1],
+ ["ALL",0,3]
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select rcode as Rcode, msg_size as ReplyLen, count(1) as count from dns where qr==1 group by Rcode,ReplyLen order by count,Rcode,ReplyLen desc;",
+ "head": [
+ { "name": "Rcode","type": "int" },
+ { "name": "ReplyLen","type": "int" },
+ { "name": "count","type": "int" }
+ ],
+ "data": [
+ [0,96,1],
+ [0,78,1],
+ [0,40,1],
+ [3,119,1]
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select 'ALL' as All, rd as RD, count(1) as count from dns where qr==0 group by All,RD order by count desc;",
+ "head": [
+ { "name": "All","type": "text" },
+ { "name": "RD","type": "bool" },
+ { "name": "count","type": "int" }
+ ],
+ "data": [
+ ["ALL",1,4]
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select if(protocol==6,'tcp',if(protocol==17,'udp',protocol)) as Transport, qtype as Qtype, count(1) as count from dns where qr==0 group by Transport,Qtype order by Transport,Qtype,count desc;",
+ "head": [
+ { "name": "Transport","type": "text" },
+ { "name": "Qtype","type": "int" },
+ { "name": "count","type": "int" }
+ ],
+ "data": [
+ ["udp",1,2],
+ ["udp",12,2]
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select s, dst_addr as Dst_addr, qtype as questiontype, lower(src_addr) as lower_src, if(1 and s < 1 or s <= 1 or s > 1 or s >= 1, 't', 'f'), trim(trim('foofoo' || rsplit(src_addr, 1) || 'foofoo', 'foo'), 'bar'), count(*), len(src_addr), sum(msg_size + -1 - 2 % 4 << 3 >> 2 | 3 & ~4) + 1, min(msg_size), max(msg_size), truncate(1.1) as integer, 1.1 as float, sum(src_port + 1.0 - 2.0 / 1.5 * -2.5) + 1.0, max(src_port + 1.0), min(src_port + 1.0), avg(src_port), stdev(src_port), name('rcode', 0) from dns where src_addr like '%' and (qr or not qr) group by src_addr, s having s >= 0 order by s, dst_addr, lower_src, integer, float;",
+ "head": [
+ { "name": "s","type": "int" },
+ { "name": "Dst_addr","type": "text" },
+ { "name": "questiontype","type": "int" },
+ { "name": "lower_src","type": "text" },
+ { "name": "if((1and((((s<1)or(s<=1))or(s>1))or(s>=1))),t,f)","type": "text" },
+ { "name": "trim(trim(((foofoo||rsplit(src_addr,1))||foofoo),foo),bar)","type": "text" },
+ { "name": "count(1)","type": "int" },
+ { "name": "len(src_addr)","type": "int" },
+ { "name": "(sum((msg_size+-((((((1-(2%4))<<3)>>2)|3)&~(4)))))+1)","type": "int" },
+ { "name": "min(msg_size)","type": "int" },
+ { "name": "max(msg_size)","type": "int" },
+ { "name": "integer","type": "int" },
+ { "name": "float","type": "float" },
+ { "name": "(sum(((src_port+1.0)-((2.0/1.5)*-(2.5))))+1.0)","type": "float" },
+ { "name": "max((src_port+1.0))","type": "float" },
+ { "name": "min((src_port+1.0))","type": "float" },
+ { "name": "avg(src_port)","type": "float" },
+ { "name": "stdev(src_port)","type": "float" },
+ { "name": "name(rcode,0)","type": "text" }
+ ],
+ "data": [
+ [1297433016,"172.18.24.52",12,"212.247.204.2","t","204",1,13,125,119,119,1,1.1,58.3333,54,54,53,0,"NoError"],
+ [1297433016,"212.247.204.2",12,"172.18.24.52","t","24",1,12,48,42,42,1,1.1,52271.3,52267,52267,52266,0,"NoError"],
+ [1297433026,"212.247.204.2",1,"172.18.24.52","t","24",1,12,36,30,30,1,1.1,54066.3,54062,54062,54061,0,"NoError"],
+ [1297433027,"172.18.24.52",1,"212.247.204.2","t","204",1,13,84,78,78,1,1.1,58.3333,54,54,53,0,"NoError"],
+ [1297433030,"172.18.24.52",1,"212.247.204.2","t","204",1,13,46,40,40,1,1.1,58.3333,54,54,53,0,"NoError"],
+ [1297433030,"212.247.204.2",1,"172.18.24.52","t","24",1,12,30,24,24,1,1.1,59489.3,59485,59485,59484,0,"NoError"],
+ [1297433038,"212.247.204.2",12,"172.18.24.52","t","24",1,12,50,44,44,1,1.1,49372.3,49368,49368,49367,0,"NoError"],
+ [1297433039,"172.18.24.52",12,"212.247.204.2","t","204",1,13,102,96,96,1,1.1,58.3333,54,54,53,0,"NoError"]
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select name( 'qtype' , qtype ) as qt, count(*) as count from dns group by qtype order by count, qt desc;",
+ "head": [
+ { "name": "qt","type": "text" },
+ { "name": "count","type": "int" }
+ ],
+ "data": [
+ ["PTR",4],
+ ["A",4]
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select * from icmp;",
+ "head": [
+ { "name": "id","type": "int" },
+ { "name": "s","type": "int" },
+ { "name": "us","type": "int" },
+ { "name": "ether_type","type": "int" },
+ { "name": "src_port","type": "int" },
+ { "name": "dst_port","type": "int" },
+ { "name": "src_addr","type": "text" },
+ { "name": "dst_addr","type": "text" },
+ { "name": "protocol","type": "int" },
+ { "name": "ip_ttl","type": "int" },
+ { "name": "ip_version","type": "int" },
+ { "name": "fragments","type": "int" },
+ { "name": "type","type": "int" },
+ { "name": "code","type": "int" },
+ { "name": "echo_identifier","type": "int" },
+ { "name": "echo_sequence","type": "int" },
+ { "name": "du_protocol","type": "int" },
+ { "name": "du_src_addr","type": "text" },
+ { "name": "du_dst_addr","type": "text" },
+ { "name": "desc","type": "text" }
+ ],
+ "data": [
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select count(*) from icmp;",
+ "head": [
+ { "name": "count(1)","type": "int" }
+ ],
+ "data": [
+ ]
+ }
+]
diff --git a/src/test/test5.sh b/src/test/test5.sh
new file mode 100755
index 0000000..42ce265
--- /dev/null
+++ b/src/test/test5.sh
@@ -0,0 +1,27 @@
+#!/bin/sh -e
+# Copyright (c) 2017-2024 OARC, Inc.
+# Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+# All rights reserved.
+#
+# This file is part of PacketQ.
+#
+# PacketQ is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# PacketQ is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+
+rm -f test5.out
+
+cat "$srcdir/sql.txt" | grep -v '^#' | while read sql; do
+ ../packetq -s "$sql" "$srcdir/../../pcap/sample.pcap.gz" >> test5.out
+done
+
+diff -uw "$srcdir/test5.gold" test5.out
diff --git a/src/test/test6.gold b/src/test/test6.gold
new file mode 100644
index 0000000..621f41c
--- /dev/null
+++ b/src/test/test6.gold
@@ -0,0 +1,12 @@
+[
+ {
+ "table_name": "result-0",
+ "query": "select count(*) from dns",
+ "head": [
+ { "name": "count(1)","type": "int" }
+ ],
+ "data": [
+ [2]
+ ]
+ }
+]
diff --git a/src/test/test6.sh b/src/test/test6.sh
new file mode 100755
index 0000000..9846b36
--- /dev/null
+++ b/src/test/test6.sh
@@ -0,0 +1,23 @@
+#!/bin/sh -e
+# Copyright (c) 2017-2024 OARC, Inc.
+# Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+# All rights reserved.
+#
+# This file is part of PacketQ.
+#
+# PacketQ is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# PacketQ is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+
+../packetq -s "select count(*) from dns" "$srcdir/../../pcap/sample-bigendian.pcap.gz" > test6.out
+
+diff -uw "$srcdir/test6.gold" test6.out
diff --git a/src/test/test7.gold b/src/test/test7.gold
new file mode 100644
index 0000000..151c3b5
--- /dev/null
+++ b/src/test/test7.gold
@@ -0,0 +1,71 @@
+[
+ {
+ "table_name": "result-0",
+ "query": "select * from icmp",
+ "head": [
+ { "name": "id","type": "int" },
+ { "name": "s","type": "int" },
+ { "name": "us","type": "int" },
+ { "name": "ether_type","type": "int" },
+ { "name": "src_port","type": "int" },
+ { "name": "dst_port","type": "int" },
+ { "name": "src_addr","type": "text" },
+ { "name": "dst_addr","type": "text" },
+ { "name": "protocol","type": "int" },
+ { "name": "ip_ttl","type": "int" },
+ { "name": "ip_version","type": "int" },
+ { "name": "fragments","type": "int" },
+ { "name": "type","type": "int" },
+ { "name": "code","type": "int" },
+ { "name": "echo_identifier","type": "int" },
+ { "name": "echo_sequence","type": "int" },
+ { "name": "du_protocol","type": "int" },
+ { "name": "du_src_addr","type": "text" },
+ { "name": "du_dst_addr","type": "text" },
+ { "name": "desc","type": "text" }
+ ],
+ "data": [
+ [1,1297781718,401023,2048,0,0,"172.18.24.52","172.18.201.234",1,64,4,0,8,0,17563,0,0,"","","Echo Request"],
+ [2,1297781719,401120,2048,0,0,"172.18.24.52","172.18.201.234",1,64,4,0,8,0,17563,1,0,"","","Echo Request"],
+ [3,1297781720,401368,2048,0,0,"172.18.24.52","172.18.201.234",1,64,4,0,8,0,17563,2,0,"","","Echo Request"],
+ [4,1297781729,36800,2048,0,0,"172.18.24.52","172.18.24.1",1,64,4,0,8,0,17819,0,0,"","","Echo Request"],
+ [5,1297781729,37210,2048,0,0,"172.18.24.1","172.18.24.52",1,64,4,0,0,0,17819,0,0,"","","Echo Reply"],
+ [6,1297781730,37013,2048,0,0,"172.18.24.52","172.18.24.1",1,64,4,0,8,0,17819,1,0,"","","Echo Request"],
+ [7,1297781730,37634,2048,0,0,"172.18.24.1","172.18.24.52",1,64,4,0,0,0,17819,1,0,"","","Echo Reply"],
+ [8,1297781731,37342,2048,0,0,"172.18.24.52","172.18.24.1",1,64,4,0,8,0,17819,2,0,"","","Echo Request"],
+ [9,1297781731,37864,2048,0,0,"172.18.24.1","172.18.24.52",1,64,4,0,0,0,17819,2,0,"","","Echo Reply"],
+ [10,1297781733,388527,2048,0,0,"172.18.24.52","172.18.24.12",1,64,4,0,8,0,18075,0,0,"","","Echo Request"],
+ [11,1297781733,389073,2048,0,0,"172.18.24.12","172.18.24.52",1,128,4,0,0,0,18075,0,0,"","","Echo Reply"],
+ [12,1297781734,388627,2048,0,0,"172.18.24.52","172.18.24.12",1,64,4,0,8,0,18075,1,0,"","","Echo Request"],
+ [13,1297781734,388912,2048,0,0,"172.18.24.12","172.18.24.52",1,128,4,0,0,0,18075,1,0,"","","Echo Reply"],
+ [14,1297781735,388779,2048,0,0,"172.18.24.52","172.18.24.12",1,64,4,0,8,0,18075,2,0,"","","Echo Request"],
+ [15,1297781735,389060,2048,0,0,"172.18.24.12","172.18.24.52",1,128,4,0,0,0,18075,2,0,"","","Echo Reply"],
+ [16,1297781739,517764,2048,0,0,"172.18.24.52","172.18.23.12",1,64,4,0,8,0,18331,0,0,"","","Echo Request"],
+ [17,1297781740,518089,2048,0,0,"172.18.24.52","172.18.23.12",1,64,4,0,8,0,18331,1,0,"","","Echo Request"],
+ [18,1297781741,518404,2048,0,0,"172.18.24.52","172.18.23.12",1,64,4,0,8,0,18331,2,0,"","","Echo Request"],
+ [19,1297781745,4951,2048,0,0,"172.18.24.52","173.18.23.12",1,64,4,0,8,0,19355,0,0,"","","Echo Request"],
+ [20,1297781746,5090,2048,0,0,"172.18.24.52","173.18.23.12",1,64,4,0,8,0,19355,1,0,"","","Echo Request"],
+ [21,1297781747,5403,2048,0,0,"172.18.24.52","173.18.23.12",1,64,4,0,8,0,19355,2,0,"","","Echo Request"],
+ [22,1297781749,725341,2048,0,0,"172.18.24.52","174.18.23.12",1,64,4,0,8,0,19611,0,0,"","","Echo Request"],
+ [23,1297781750,725507,2048,0,0,"172.18.24.52","174.18.23.12",1,64,4,0,8,0,19611,1,0,"","","Echo Request"],
+ [24,1297781750,900759,2048,0,0,"75.160.241.10","172.18.24.52",1,234,4,0,3,1,0,0,1,"172.18.24.52","174.18.23.12","Destination host unreachable"],
+ [25,1297781751,725837,2048,0,0,"172.18.24.52","174.18.23.12",1,64,4,0,8,0,19611,2,0,"","","Echo Request"],
+ [26,1297781752,726162,2048,0,0,"172.18.24.52","174.18.23.12",1,64,4,0,8,0,19611,3,0,"","","Echo Request"],
+ [27,1297781753,726487,2048,0,0,"172.18.24.52","174.18.23.12",1,64,4,0,8,0,19611,4,0,"","","Echo Request"],
+ [28,1297781754,726804,2048,0,0,"172.18.24.52","174.18.23.12",1,64,4,0,8,0,19611,5,0,"","","Echo Request"],
+ [29,1297781754,903407,2048,0,0,"75.160.241.10","172.18.24.52",1,234,4,0,3,1,0,0,1,"172.18.24.52","174.18.23.12","Destination host unreachable"]
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select count(*) from icmp",
+ "head": [
+ { "name": "count(1)","type": "int" }
+ ],
+ "data": [
+ [29]
+ ]
+ }
+]
diff --git a/src/test/test7.sh b/src/test/test7.sh
new file mode 100755
index 0000000..6433aab
--- /dev/null
+++ b/src/test/test7.sh
@@ -0,0 +1,24 @@
+#!/bin/sh -e
+# Copyright (c) 2017-2024 OARC, Inc.
+# Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+# All rights reserved.
+#
+# This file is part of PacketQ.
+#
+# PacketQ is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# PacketQ is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+
+../packetq -s "select * from icmp" "$srcdir/../../pcap/icmp.pcap.gz" > test7.out
+../packetq -s "select count(*) from icmp" "$srcdir/../../pcap/icmp.pcap.gz" >> test7.out
+
+diff -uw "$srcdir/test7.gold" test7.out
diff --git a/src/test/test8.gold b/src/test/test8.gold
new file mode 100644
index 0000000..02a8b99
--- /dev/null
+++ b/src/test/test8.gold
@@ -0,0 +1,60 @@
+[
+ {
+ "table_name": "result-0",
+ "query": "select qname from dns",
+ "head": [
+ { "name": "qname","type": "text" }
+ ],
+ "data": [
+ ["test."],
+ ["'."],
+ ["\"."],
+ ["$."],
+ ["\\."],
+ ["$."],
+ ["$."],
+ ["_test.all.54.non-escaped.characters.abcdefghijklmnopqrstuvwxyz.ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789.test."],
+ ["dot.embeded.in.a.label.test."]
+ ]
+ }
+]
+[
+ {
+ "table_name": "result-0",
+ "query": "select qname from dns",
+ "head": [
+ { "name": "qname","type": "text" }
+ ],
+ "data": [
+ ["test."],
+ ["\\039."],
+ ["\\034."],
+ ["\\036."],
+ ["\\092."],
+ ["\\000."],
+ ["\\255."],
+ ["_test.all.54.non-escaped.characters.abcdefghijklmnopqrstuvwxyz.ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789.test."],
+ ["dot\\046embeded.in.a.label.test."]
+ ]
+ }
+]
+"qname"
+"test."
+"'."
+"""."
+"$."
+"\."
+"$."
+"$."
+"_test.all.54.non-escaped.characters.abcdefghijklmnopqrstuvwxyz.ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789.test."
+"dot.embeded.in.a.label.test."
+"qname"
+"test."
+"\039."
+"\034."
+"\036."
+"\092."
+"\000."
+"\255."
+"_test.all.54.non-escaped.characters.abcdefghijklmnopqrstuvwxyz.ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789.test."
+"dot\046embeded.in.a.label.test."
diff --git a/src/test/test8.sh b/src/test/test8.sh
new file mode 100755
index 0000000..1dde9bf
--- /dev/null
+++ b/src/test/test8.sh
@@ -0,0 +1,26 @@
+#!/bin/sh -e
+# Copyright (c) 2021, Internet Systems Consortium, Inc.
+# All rights reserved.
+#
+# This file is part of PacketQ.
+#
+# PacketQ is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# PacketQ is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+
+TESTPCAP="$srcdir/../../pcap/sample-rfc1035escape.pcap.gz"
+../packetq -s "select qname from dns" --json "$TESTPCAP" > test8.out
+../packetq -s "select qname from dns" --json --rfc1035 "$TESTPCAP" >> test8.out
+../packetq -s "select qname from dns" --csv "$TESTPCAP" >> test8.out
+../packetq -s "select qname from dns" --csv --rfc1035 "$TESTPCAP" >> test8.out
+
+diff -uw "$srcdir/test8.gold" test8.out
diff --git a/src/variant.h b/src/variant.h
new file mode 100644
index 0000000..7c20e04
--- /dev/null
+++ b/src/variant.h
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2017-2024 OARC, Inc.
+ * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
+ * All rights reserved.
+ *
+ * This file is part of PacketQ.
+ *
+ * PacketQ is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PacketQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PacketQ. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __packetq_variant_h
+#define __packetq_variant_h
+
+#include <cstdlib>
+
+#include "MurmurHash3.h"
+#include "refcountstring.h"
+
+namespace packetq {
+
+inline std::size_t hash_bytes(const char* bytes, int len)
+{
+ uint32_t result = 0;
+ MurmurHash3_x86_32(bytes, len, 0, &result);
+ return result;
+}
+
+// must be defined in this order - see the "if" statement
+#define COLTYPE_MAX 4
+namespace Coltype {
+ enum Type {
+ _bool = 0,
+ _int = 1,
+ _float = 2,
+ _text = 3,
+ };
+};
+
+typedef bool bool_column;
+static const int bool_size = sizeof(bool_column);
+static const int bool_align = ((sizeof(bool_column) / sizeof(void*)) + 1) * sizeof(void*);
+
+typedef int int_column;
+static const int int_size = sizeof(int_column);
+static const int int_align = ((sizeof(int_column) / sizeof(void*)) + 1) * sizeof(void*);
+
+typedef double float_column;
+static const int float_size = sizeof(float_column);
+static const int float_align = ((sizeof(float_column) / sizeof(void*)) + 1) * sizeof(void*);
+
+typedef RefCountString* text_column;
+static const int text_size = sizeof(text_column);
+static const int text_align = ((sizeof(text_column) / sizeof(void*)) + 1) * sizeof(void*);
+
+inline bool_column convert_column_to_bool(float_column v) { return v; }
+inline bool_column convert_column_to_bool(int_column v) { return v; }
+inline bool_column convert_column_to_bool(bool_column v) { return v; }
+inline bool_column convert_column_to_bool(text_column v)
+{
+ return std::atoi(v->data);
+}
+
+inline int_column convert_column_to_int(float_column v) { return int(v); }
+inline int_column convert_column_to_int(int_column v) { return v; }
+inline int_column convert_column_to_int(bool_column v) { return v; }
+inline int_column convert_column_to_int(text_column v)
+{
+ return v->data[0] != '\0';
+}
+
+inline float_column convert_column_to_float(float_column v) { return v; }
+inline float_column convert_column_to_float(int_column v) { return v; }
+inline float_column convert_column_to_float(bool_column v) { return v; }
+inline float_column convert_column_to_float(text_column v)
+{
+ return std::atof(v->data);
+}
+
+inline text_column convert_column_to_text(float_column v)
+{
+ const int bufsize = 50;
+ RefCountString* str = RefCountString::allocate(bufsize);
+ snprintf(str->data, bufsize, "%g", v); // lgtm[cpp/badly-bounded-write]
+ return str;
+}
+inline text_column convert_column_to_text(int_column v)
+{
+ const int bufsize = (sizeof(int_column) * 8 + 1) / 3 + 1;
+ RefCountString* str = RefCountString::allocate(bufsize);
+ snprintf(str->data, bufsize, "%d", v); // lgtm[cpp/badly-bounded-write]
+ return str;
+}
+inline text_column convert_column_to_text(bool_column v)
+{
+ const int bufsize = 1 + 1;
+ RefCountString* str = RefCountString::allocate(bufsize);
+ if (v)
+ str->data[0] = '1';
+ else
+ str->data[1] = '0';
+ str->data[1] = '\0';
+ return str;
+}
+inline text_column convert_column_to_text(text_column v)
+{
+ // to stay symmetric with above functions that allocate a new string,
+ // increment reference count
+ v->inc_refcount();
+ return v;
+}
+
+// Variant represents a value that can be either one of the column types,
+// plus a type field to figure out which kind it represents
+class Variant {
+public:
+ Variant()
+ {
+ m_type = Coltype::_int;
+ m_val.m_int = 0;
+ }
+
+ Variant(bool_column val)
+ {
+ m_type = Coltype::_bool;
+ m_val.m_bool = val;
+ }
+
+ Variant(int_column val)
+ {
+ m_type = Coltype::_int;
+ m_val.m_int = val;
+ }
+
+ Variant(float_column val)
+ {
+ m_type = Coltype::_float;
+ m_val.m_float = val;
+ }
+
+ Variant(text_column val)
+ {
+ m_type = Coltype::_text;
+ m_val.m_text = val;
+ m_val.m_text->inc_refcount();
+ }
+
+ Variant(const Variant& other)
+ {
+ m_type = other.m_type;
+ m_val = other.m_val;
+ if (m_type == Coltype::_text)
+ m_val.m_text->inc_refcount();
+ }
+
+ // move constructor
+ Variant(Variant&& other)
+ {
+ // would be cleaner to use default constructor, but alas
+ // constructor delegation requires GCC >= 4.7
+ m_type = Coltype::_int;
+ m_val.m_int = 0;
+
+ swap(*this, other);
+ }
+
+ ~Variant()
+ {
+ if (m_type == Coltype::_text)
+ m_val.m_text->dec_refcount();
+ }
+
+ Variant& operator=(Variant other)
+ {
+ // copy and swap idiom
+ swap(*this, other);
+ return *this;
+ }
+
+ inline friend void swap(Variant& first, Variant& second)
+ {
+ using std::swap;
+ swap(first.m_type, second.m_type);
+ swap(first.m_val, second.m_val);
+ }
+
+ bool_column get_bool() const
+ {
+ switch (m_type) {
+ case Coltype::_float:
+ return convert_column_to_bool(m_val.m_float);
+ case Coltype::_int:
+ return convert_column_to_bool(m_val.m_int);
+ case Coltype::_bool:
+ return convert_column_to_bool(m_val.m_bool);
+ case Coltype::_text:
+ return convert_column_to_bool(m_val.m_text);
+ }
+ return false;
+ }
+
+ int_column get_int() const
+ {
+ switch (m_type) {
+ case Coltype::_float:
+ return convert_column_to_int(m_val.m_float);
+ case Coltype::_int:
+ return convert_column_to_int(m_val.m_int);
+ case Coltype::_bool:
+ return convert_column_to_int(m_val.m_bool);
+ case Coltype::_text:
+ return convert_column_to_int(m_val.m_text);
+ }
+ return 0;
+ }
+
+ float_column get_float() const
+ {
+ switch (m_type) {
+ case Coltype::_float:
+ return convert_column_to_float(m_val.m_float);
+ case Coltype::_int:
+ return convert_column_to_float(m_val.m_int);
+ case Coltype::_bool:
+ return convert_column_to_float(m_val.m_bool);
+ case Coltype::_text:
+ return convert_column_to_float(m_val.m_text);
+ }
+ return 0.0;
+ }
+
+ // this returns a RefCountString with the ref-count incremented so
+ // caller is responsible for decrementing after use
+ text_column get_text() const
+ {
+ switch (m_type) {
+ case Coltype::_float:
+ return convert_column_to_text(m_val.m_float);
+ case Coltype::_int:
+ return convert_column_to_text(m_val.m_int);
+ case Coltype::_bool:
+ return convert_column_to_text(m_val.m_bool);
+ case Coltype::_text:
+ return convert_column_to_text(m_val.m_text);
+ }
+ return RefCountString::construct("");
+ }
+
+ int cmp(const Variant& rhs) const
+ {
+ switch (m_type) {
+ case (Coltype::_bool):
+ return m_val.m_bool - rhs.get_bool();
+ case (Coltype::_int):
+ return m_val.m_int - rhs.get_int();
+ case (Coltype::_float): {
+ float_column r = rhs.get_float();
+ if (m_val.m_float < r)
+ return -1;
+ if (m_val.m_float > r)
+ return 1;
+ return 0;
+ }
+ case (Coltype::_text): {
+ RefCountString* s = rhs.get_text();
+ auto res = strcmp(m_val.m_text->data, s->data);
+ s->dec_refcount();
+ return res;
+ }
+ }
+ return 0;
+ }
+
+ bool operator<(const Variant& rhs) const
+ {
+ return cmp(rhs) < 0;
+ }
+ bool operator==(const Variant& rhs) const
+ {
+ return cmp(rhs) == 0;
+ }
+
+ std::size_t hash() const
+ {
+ switch (m_type) {
+ case (Coltype::_bool):
+ return std::hash<bool>()(m_val.m_bool);
+ case (Coltype::_int):
+ return std::hash<int>()(m_val.m_int);
+ case (Coltype::_float):
+ return std::hash<float>()(m_val.m_float);
+ case (Coltype::_text):
+ return hash_bytes(m_val.m_text->data, strlen(m_val.m_text->data));
+ }
+ return 0;
+ }
+
+ Coltype::Type m_type;
+
+private:
+ union VariantUnion {
+ bool_column m_bool;
+ int_column m_int;
+ float_column m_float;
+ text_column m_text;
+ };
+
+ VariantUnion m_val;
+};
+
+} // namespace packetq
+
+#endif // __packetq_variant_h