Commit 6322f952 authored by Nils Goroll's avatar Nils Goroll

initial commit

parents
# Matches ALL Makefile and Makefile.in occurrences
Makefile
Makefile.in
# ...
.deps/
.libs/
*.o
*.lo
*.la
*~
*.sw[op]
*.trs
.dirstamp
# slink-ish configure call
CONFIGURE
# Various auto-tools artifacts
/aclocal.m4
/autom4te.cache/
/compile
/config.guess
/dcs_config.h
/dcs_config.h.in
/dcs_config.h.in~
/config.log
/config.status
/config.sub
/configure
/configure.lineno
/depcomp
/install-sh
/libtool
/ltmain.sh
/m4/libtool.m4
/m4/ltoptions.m4
/m4/ltsugar.m4
/m4/ltversion.m4
/m4/lt~obsolete.m4
/missing
/stamp-h1
/varnishapi.pc
/build-aux
TAGS
cscope.*out
#
# generated man
vmod_dcs.3
Copyright (c) 2014 UPLEX - Nils Goroll Systemoptimierung
All rights reserved.
Use only with permission
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
ACLOCAL_AMFLAGS = -I m4
SUBDIRS = src
dist_man_MANS = vmod_dcs.3
MAINTAINERCLEANFILES = $(dist_man_MANS)
doc_DATA = README.rst LICENSE
vmod_dcs.3: README.rst
if HAVE_RST2MAN
${RST2MAN} README.rst $@
else
@echo "========================================"
@echo "You need rst2man installed to make dist"
@echo "========================================"
@false
endif
dist_data_DATA = dcs_config.h
==============
DCS Classifier
==============
*TODO* fill in the skeleton
--------------------------------------------
Varnish Device Classification Service Module
--------------------------------------------
:Manual section: 3
:Authors: Nils Goroll
:Date: 2014-03-03
:Version: 0.1
SYNOPSIS
========
::
import dcs;
DESCRIPTION
===========
FUNCTIONS
=========
Example VCL::
backend foo { ... };
import dcs;
classify()
----------
Prototype
::
Returns
Description
Example
::
INSTALLATION
============
Installation requires the Varnish source tree (only the source matching the
binary installation).
1. `./autogen.sh` (for git-installation)
2. `./configure VARNISHSRC=/path/to/your/varnish/source/varnish-cache`
3. `make`
4. `make install` (may require root: sudo make install)
5. `make check` (Optional for regression tests)
VARNISHSRCDIR is the directory of the Varnish source tree for which to
compile your vmod. Both the VARNISHSRCDIR and VARNISHSRCDIR/include
will be added to the include search paths for your module.
Optionally you can also set the vmod install dir by adding VMODDIR=DIR
(defaults to the pkg-config discovered directory from your Varnish
installation).
ACKNOWLEDGEMENTS
================
HISTORY
=======
Version 0.1: Initial version, mostly feature-complete
BUGS
====
No bugs at all!
SEE ALSO
========
* varnishd(1)
* vcl(7)
COPYRIGHT
=========
See LICENSE for details.
* Copyright 2014 UPLEX - Nils Goroll Systemoptimierung
#!/bin/sh
warn() {
echo "WARNING: $@" 1>&2
}
case `uname -s` in
Darwin)
LIBTOOLIZE=glibtoolize
;;
FreeBSD)
LIBTOOLIZE=libtoolize
;;
Linux)
LIBTOOLIZE=libtoolize
;;
SunOS)
LIBTOOLIZE=libtoolize
;;
*)
warn "unrecognized platform:" `uname -s`
LIBTOOLIZE=libtoolize
esac
automake_version=`automake --version | tr ' ' '\n' | egrep '^[0-9]\.[0-9a-z.-]+'`
if [ -z "$automake_version" ] ; then
warn "unable to determine automake version"
else
case $automake_version in
0.*|1.[0-8]|1.[0-8][.-]*)
warn "automake ($automake_version) detected; 1.9 or newer recommended"
;;
*)
;;
esac
fi
set -ex
$LIBTOOLIZE --copy --force
aclocal -I m4
autoheader
automake --add-missing --copy --foreign
autoconf
AC_PREREQ(2.69)
AC_COPYRIGHT([Copyright 2014 UPLEX - Nils Goroll Systemoptimierung])
AC_INIT([dcs_classifier], [0.1])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_SRCDIR(src/gen_dcs_classifier.pl)
AM_CONFIG_HEADER(dcs_config.h)
AC_CANONICAL_SYSTEM
AC_LANG(C)
AM_INIT_AUTOMAKE([foreign])
AC_GNU_SOURCE
AC_PROG_CC
AC_PROG_CC_STDC
if test "x$ac_cv_prog_cc_c99" = xno; then
AC_MSG_ERROR([Could not find a C99 compatible compiler])
fi
AC_PROG_CPP
AC_PROG_INSTALL
AC_PROG_LIBTOOL
AC_PROG_MAKE_SET
# Check for rst utilities
AC_CHECK_PROGS(RST2MAN, [rst2man rst2man.py], "no")
if test "x$RST2MAN" = "xno"; then
AC_MSG_WARN([rst2man not found - not building man pages])
fi
AM_CONDITIONAL(HAVE_RST2MAN, [test "x$RST2MAN" != "xno"])
# Check for pkg-config
PKG_PROG_PKG_CONFIG
# Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([sys/stdlib.h])
############################################################
## DCS specifics
AC_ARG_VAR([DCS_KEY], [your DCS license key])
if test "x$DCS_KEY" = x; then
AC_MSG_ERROR([DCS_KEY is required])
fi
AC_ARG_VAR([DCS_ACCOUNT], [your netbiscuits account - optional, for online update only])
AC_ARG_VAR([DCS_DBFILE], [path to your DCS database file])
AC_CHECK_FILE([$DCS_DBFILE],
[],
[if test "x$DCS_ACCOUNT" = x; then
AC_MSG_ERROR([Need existing DCS_DBFILE if no DCS_ACCOUNT given])
fi
])
############################################################
## DEVELOPER flags etc
DEVELOPER_CFLAGS="-Werror -Wall -W -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wreturn-type -Wcast-qual -Wwrite-strings -Wswitch -Wshadow -Wunused-parameter -Wcast-align -Wchar-subscripts -Wnested-externs -Wextra"
# These are not compliable yet
DEVELOPER_GCC_CFLAGS="-Wold-style-definition -Wredundant-decls "
#DEVELOPER_CFLAGS="${DEVELOPER_CFLAGS} ${DEVELOPER_GCC_CFLAGS}"
# These are compilable
DEVELOPER_CLANG_CFLAGS="-Wmissing-variable-declarations -Wno-string-plus-int"
# -Wno-empty-body
# --enable-developer-warnings
AC_ARG_ENABLE(developer-warnings,
AS_HELP_STRING([--enable-developer-warnings],[enable strict warnings (default is NO)]),
[],
[enable_developer_warnings=no])
#DEVELOPER_CFLAGS="${DEVELOPER_CFLAGS} ${DEVELOPER_CLANG_CFLAGS}"
if test "x$enable_developer_warnings" != "xno"; then
CFLAGS="${CFLAGS} ${DEVELOPER_CFLAGS}"
OCFLAGS="${OCFLAGS} ${DEVELOPER_CFLAGS}"
fi
# --enable-debugging-symbols
AC_ARG_ENABLE(debugging-symbols,
AS_HELP_STRING([--enable-debugging-symbols],[enable debugging symbols (default is NO)]),
CFLAGS="${CFLAGS} -O0 -g -fno-inline")
AC_SUBST(AM_LT_LDFLAGS)
############################################################
## VARNISH whether to install Varnish inline-c sources
AC_ARG_ENABLE(varnish2,
AS_HELP_STRING([--enable-varnish2],[install code for varnish2 inline-C (default is NO)]),
CFLAGS="${CFLAGS} -O0 -g -fno-inline")
AM_CONDITIONAL([INSTALL_VARNISH2], [test "x$enable_varnish2" != x])
############################################################
## VARNISH sources for vmod support
# Varnish source tree
AC_ARG_VAR([VARNISHSRC], [path to Varnish source tree for vmod])
AM_CONDITIONAL([BUILD_VMOD], [test "x$VARNISHSRC" != x])
if test "x$VARNISHSRC" = x; then
AC_MSG_WARN([No Varnish source - wont build the vmod])
else
AC_CHECK_PROGS(PYTHON, [python3 python3.1 python3.2 python2.7 python2.6 python2.5 python2 python], [AC_MSG_ERROR([Python is needed to build this vmod, please install python.])])
VARNISHSRC=`cd $VARNISHSRC && pwd`
AC_CHECK_FILE([$VARNISHSRC/include/varnishapi.h],
[],
[AC_MSG_FAILURE(["$VARNISHSRC" is not a Varnish source directory])]
)
# Check that varnishtest is built in the varnish source directory
AC_CHECK_FILE([$VARNISHSRC/bin/varnishtest/varnishtest],
[],
[AC_MSG_FAILURE([Can't find "$VARNISHSRC/bin/varnishtest/varnishtest". Please build your varnish source directory])]
)
# vmod installation dir
AC_ARG_VAR([VMODDIR], [vmod installation directory @<:@LIBDIR/varnish/vmods@:>@])
if test "x$VMODDIR" = x; then
VMODDIR=`pkg-config --variable=vmoddir varnishapi`
if test "x$VMODDIR" = x; then
AC_MSG_FAILURE([Can't determine vmod installation directory])
fi
fi
fi
############################################################
## FIN
AC_CONFIG_FILES([
Makefile
src/Makefile
src/gen/Makefile
])
AC_OUTPUT
# src / Makefile.am
AUTOMAKE_OPTIONS = subdir-objects
SUBDIRS = gen
bin_PROGRAMS = dcs dcs_test
dcs_match_SOURCES = \
$(top_srcdir)/src/gen/dcs_classifier.h \
dcs_match.h \
dcs_match.c
dcs_type_SOURCES = \
$(top_srcdir)/src/gen/dcs_classifier.h \
dcs_type.h \
dcs_type.c
dcs_test_CPPFLAGS = -Igen
dcs_test_SOURCES = \
$(top_srcdir)/src/gen/dcs_classifier.c \
$(dcs_match_SOURCES) \
$(dcs_type_SOURCES) \
dcs_test.c
dcs_CPPFLAGS = -Igen
dcs_SOURCES = \
$(top_srcdir)/src/gen/dcs_classifier.c \
$(dcs_match_SOURCES) \
$(dcs_type_SOURCES) \
dcs.c
EXTRA_DIST = gen_dcs_classifier.pl
# included c source
if INSTALL_VARNISH2
dcs_varnish2.c: dcs_varnish.c \
$(top_srcdir)/src/gen/dcs_classifier.c \
$(top_srcdir)/src/gen/dcs_classifier.h \
$(dcs_match_SOURCES) \
$(dcs_type_SOURCES)
dist_data_DATA = dcs_varnish2.c \
dcs_varnish.c \
$(top_srcdir)/src/gen/dcs_classifier.c \
$(top_srcdir)/src/gen/dcs_classifier.h \
dcs_match.h \
dcs_match.c \
dcs_type.h \
dcs_type.c
endif
## varnish 3 vmod
if BUILD_VMOD
vmoddir = $(VMODDIR)
vmod_LTLIBRARIES = libvmod_dcs.la
libvmod_dcs_la_CPPFLAGS = -Igen -I$(VARNISHSRC)/include -I$(VARNISHSRC)
libvmod_dcs_la_LDFLAGS = -module -export-dynamic -avoid-version
libvmod_dcs_la_SOURCES = \
vcc_if.c \
vcc_if.h \
vmod_dcs.c
vcc_if_INCLUDES = -I$(VARNISHSRC)/include -I$(VARNISHSRC)
vcc_dcs_INCLUDES = -I$(VARNISHSRC)/include -I$(VARNISHSRC)
vcc_if.c vcc_if.h: $(VARNISHSRC)/lib/libvmod_std/vmod.py $(top_srcdir)/src/vmod_dcs.vcc
@PYTHON@ $(VARNISHSRC)/lib/libvmod_std/vmod.py $(top_srcdir)/src/vmod_dcs.vcc
BUILT_SOURCES = \
vcc_if.c \
vcc_if.h
CLEANFILES = \
vcc_if.c \
vcc_if.h
VMOD_TESTS = tests/*.vtc
.PHONY: $(VMOD_TESTS)
tests/*.vtc:
$(VARNISHSRC)/bin/varnishtest/varnishtest -Dvarnishd=$(VARNISHSRC)/bin/varnishd/varnishd -Dvmod_topbuild=$(abs_top_builddir) $@
check: $(VMOD_TESTS)
EXTRA_DIST += \
vmod_dcs.vcc \
$(VMOD_TESTS)
endif
/*
* Copyright (c) 2014 UPLEX - Nils Goroll Systemoptimierung
* All rights reserved.
* Use only with permission
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "dcs_config.h"
#include <stdio.h>
#include <ctype.h>
#include "dcs_match.h"
#include "dcs_type.h"
int
main (void) {
char *line = NULL;
int e, t;
size_t linesz;
while (getline(&line, &linesz, stdin) != -1) {
char *p = line;
for ( ; *p; ++p) *p = tolower(*p);
e = dcs_match(line);
t = dcs_match_type_id(e);
printf("--\n%sentry %d type %d - %s - %s\n", line,
e, t, dcs_type_mtd(t), dcs_type_name(t));
}
return 0;
}
/*
* Copyright (c) 2014 UPLEX - Nils Goroll Systemoptimierung
* All rights reserved.
* Use only with permission
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "dcs_config.h"
#ifdef DEBUG_K_MATCH
#include <stdlib.h>
#endif
#include <errno.h>
#include "dcs_classifier.h"
#include "dcs_match.h"
#ifdef DEBUG_K_MATCH
#define p_entry(state, fmt, name) \
do { \
if (state.name ## _match_entry) { \
printf(fmt, #name, \
state.name ## _match_entry, \
dcs_type[dcs_entry[state.name ## _match_entry].type], \
dcs_entry[state.name ## _match_entry].key); \
} \
} while(0)
#define p_entry_if_changed(state, prev_match,fmt) \
do if (state.last_match_entry != prev_match) { \
p_entry(state, fmt, last); \
prev_match = state.last_match_entry; \
} while(0)
#else
#define p_entry(...) do { } while(0)
#define p_entry_if_changed(...) do { } while(0)
#endif
/*
* we deliberately return int instead of dcs_entry_id_t here in order to not
* require dcs_classifier.h for callers
*/
int /* dcs_entry_id_t */
dcs_match(const char *p) {
struct dcs_matchstate state;
#ifdef DEBUG_K_MATCH
dcs_entry_id_t prev_match = 0;
printf("\n--\n%s",p);
#endif
dcs_init_matchstate(&state);
while (*p) {
dcs_parse_subkey(&state, p);
p_entry_if_changed(state, prev_match, " %s match %d: %s - %s\n");
p++;
}
dcs_state_eval_candidates(&state);
p_entry_if_changed(state, prev_match, " new %s from candidates %d: %s - %s\n");
p_entry(state, " %s match %d: %s - %s\n", min);
p_entry(state, " %s match %d: %s - %s\n", first);
p_entry(state, " %s match %d: %s - %s\n", last);
return state.min_match_entry;
}
#define check_entry_id(entry_id, ret) \
do { \
if ((entry_id < 0) || (entry_id > (DCS_ENTRY_COUNT - 1))) { \
errno = EINVAL; \
return (ret); \
} \
} while(0)
int /* enum dcs_type */
dcs_match_type_id(int entry_id /* dcs_entry_id_t */) {
check_entry_id(entry_id, -1);
return dcs_entry[entry_id].type;
}
const char const *
dcs_match_key(int entry_id /* dcs_entry_id_t */) {
check_entry_id(entry_id, 0);
return dcs_entry[entry_id].key;
}
/*
* Copyright (c) 2014 UPLEX - Nils Goroll Systemoptimierung
* All rights reserved.
* Use only with permission
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef DCS_MATCH_H
#define DCS_MATCH_H 1
int dcs_match(const char *p);
int dcs_match_type_id(int entry_id /* dcs_entry_id_t */);
const char const *dcs_match_key(int entry_id /* dcs_entry_id_t */);
#endif
/*
* Copyright (c) 2014 UPLEX - Nils Goroll Systemoptimierung
* All rights reserved.
* Use only with permission
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "dcs_config.h"
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>
#include "gen/dcs_classifier.h" /* need DCS_* */
#include "dcs_match.h"
int
test (const char *fixup_remove_name, const char *fixup_reorder_name);
int
main (void) {
return test(NULL, NULL);
}
static void
test_p_diff (const char *label, int i, const char *testkey, int r) {
if (dcs_entry[i].type != dcs_entry[r].type) {
printf("%s\ti: %8d %s %s\ntype diff\tr: %8d %s %s\n",
label,
i, testkey, dcs_type[dcs_entry[i].type],
r, dcs_entry[r].key, dcs_type[dcs_entry[r].type]);
} else {
printf("%s\ti: %8d %s\n\t\tr: %8d %s\n",
label,
i, testkey,
r, dcs_entry[r].key);
}
}
int
test (const char *fixup_remove_name, const char *fixup_reorder_name) {
int i, r, errcount = 0;
FILE *f_remove, *f_reorder;
(void) fixup_remove_name;
(void) fixup_reorder_name;
(void) f_remove;
(void) f_reorder;
for (i = 0; i < DCS_ENTRY_COUNT; i++) {
if (dcs_matchstate_init.matchmask[i] == 0) {
r = dcs_match(dcs_entry[i].key);
if (i != r) {
test_p_diff("miss\t", i, dcs_entry[i].key, r);
errcount++;
}
} else {
#define KEYLIM 256
const char *poskey = dcs_entry[i].key;
const char *negkey = dcs_entry[i].key;
char p1[KEYLIM], p2[KEYLIM];
char n1[KEYLIM], n2[KEYLIM];
char *save;
if (poskey[0] == '!') {
poskey = strchr(poskey + 1, '*');
assert(poskey);
poskey++;
assert(*poskey);
negkey++;
}
if (strstr(poskey, "*!")) {
const char *pp, *np;
assert(strlen(poskey) < KEYLIM);
assert(strlen(negkey) < KEYLIM);
strcpy(p1, poskey);
strcpy(n1, negkey);
pp = strtok_r(p1, "*", &save);
*p2 = '\0';
poskey = p2;
while (pp) {
if (*pp != '!') {
strcat(p2, pp);
}
pp = strtok_r(NULL, "*", &save);
}
np = strtok_r(n1, "*", &save);
*n2 = '\0';
negkey = n2;
while (np) {
if (*np == '!')
np++;
strcat(n2, np);
np = strtok_r(NULL, "*", &save);
}
}
r = dcs_match(poskey);
if (i != r) {
test_p_diff("miss n-p", i, poskey, r);
errcount++;
}
r = dcs_match(negkey);
if (r == i) {
test_p_diff("miss n-n", i, negkey, r);
errcount++;
}
}
}
return errcount;
}
/*
* Copyright (c) 2014 UPLEX - Nils Goroll Systemoptimierung
* All rights reserved.
* Use only with permission
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "dcs_config.h"
#include <errno.h>
#include "dcs_classifier.h"
#include "dcs_type.h"
#define check_type_id(type_id, ret) \
do { \
if ((type_id < 0) || (type_id > (DCS_TYPE_COUNT - 1))) { \
errno = EINVAL; \
return (ret); \
} \
} while(0)
const char *
dcs_type_name(int type_id /* enum dcs_type */) {
check_type_id(type_id, 0);
return dcs_type[type_id];
}
/*
* useful meta-classification to just three string constants:
*
*
*
*
*/
enum dcs_type_mtd {
T_MTD_MISSING = 0,
T_MTD_MOB,
T_MTD_TAB,
T_MTD_DSK
#define T_MTD_MAX T_MTD_DSK
};
const char * const dsc_type_mtd_str[T_MTD_MAX + 1] = {
[T_MTD_MISSING] = "dsk",
[T_MTD_MOB] = "mob",
[T_MTD_TAB] = "tab",
[T_MTD_DSK] = "dsk"
};
enum dcs_type dcs_type2mtd[DCS_TYPE_COUNT] = {
[NB_T_UNIDENTIFIED] = T_MTD_DSK,
[NB_T_BOT] = T_MTD_DSK,
[NB_T_CAMERA] = T_MTD_MOB,
[NB_T_CE_DEVICE] = T_MTD_MOB, // android 1.5; en-us; nimble
[NB_T_COMPUTER] = T_MTD_DSK, // macintosh*intel*mac os
[NB_T_DESKTOP_BROWSER] = T_MTD_DSK,
[NB_T_DEVTOOL] = T_MTD_DSK,
[NB_T_EREADER] = T_MTD_TAB,
[NB_T_MEDIAPLAYER] = T_MTD_MOB,
[NB_T_MOBILECONSOLE] = T_MTD_MOB,
[NB_T_MOBILE_BROWSER] = T_MTD_MOB,
[NB_T_MOBILE_PHONE] = T_MTD_MOB,
[NB_T_OPERATINGSYSTEM] = T_MTD_MOB, // ios 4.2
[NB_T_SETTOP_BOX_TV] = T_MTD_DSK,
[NB_T_TABLET] = T_MTD_TAB,
[NB_T_WEARABLE_COMPUTER] = T_MTD_MOB // android 4.0*glass
};
const char *
dcs_type_mtd(int type_id /* enum dcs_type */) {
check_type_id(type_id, 0);
return (dsc_type_mtd_str[dcs_type2mtd[type_id]]);
}
/*
* Copyright (c) 2014 UPLEX - Nils Goroll Systemoptimierung
* All rights reserved.
* Use only with permission
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef DCS_TYPE_H
#define DCS_TYPE_H 1
const char * dcs_type_name(int type_id /* enum dcs_type */);
const char * dcs_type_mtd(int type_id /* enum dcs_type */);
#endif
/*
* Copyright (c) 2014 UPLEX - Nils Goroll Systemoptimierung
* All rights reserved.
* Use only with permission
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*
* common code for varnish 2 (inline-C) and varnish 3 (vmod)
*
* this gets _INCLUDED_
*/
#include "dcs_classifier.c"
#include <ctype.h>
#include "dcs_match.c"
#include "dcs_type.c"
#define UA_LIMIT 1024
#define DCS_VARNISH2_NHDRS 4
const char * const hdrs[DCS_VARNISH2_NHDRS] = {
[0] = "\025X-OperaMini-Phone-UA:",
[1] = "\024X-Device-User-Agent:",
[2] = "\026X-Original-User-Agent:",
[3] = "\016X-Goog-Source:"
};
const char * const ua_prepend[DCS_VARNISH2_NHDRS] = {
[0] = " opera/ opera mini/ ",
[1] = NULL,
[2] = NULL,
[3] = NULL
};
#define appnd(w, space, r, l) \
l = strlen(r); \
strncpy(w, r, space); \
if (l > space) \
break; \
space -= l; \
w += l;
static int
dcs_varnish_classify(const struct sess *sp) {
const char *ua = VRT_GetHdr(sp, HDR_REQ, "\013User-Agent:");
const char *r;
char *w = NULL;
int i;
size_t l;
size_t space = UA_LIMIT;
char uabuf[space];
if ((! ua) || (! *ua))
return NB_T_UNIDENTIFIED;
if (VRT_GetHdr(sp, HDR_REQ, "\016x-wap-profile:"))
return NB_T_MOBILE_PHONE;
/* we need to copy to downcase the string for matching */
w = uabuf;
do {
appnd(w, space, ua, l);
} while(0);
ua = uabuf;
for (i = 0; i < DCS_VARNISH2_NHDRS; i++) {
r = VRT_GetHdr(sp, HDR_REQ, hdrs[i]);
if (r && *r) {
if (ua_prepend[i])
appnd(w, space, ua_prepend[i], l);
appnd(w, space, r, l);
}
}
uabuf[UA_LIMIT] = '\0';
assert(ua == uabuf);
assert(w > uabuf);
assert(w <= (uabuf + UA_LIMIT));
for (w = uabuf; *w; ++w) *w = tolower(*w);
return dcs_match(ua);
}
/*
* Copyright (c) 2014 UPLEX - Nils Goroll Systemoptimierung
* All rights reserved.
* Use only with permission
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "dcs_config.h"
#include "dcs_varnish.c"
void
dcs_varnish2_classify_hdrs(const struct sess *sp) {
const int e = dcs_varnish_classify(sp);
const int t = dcs_match_type_id(e);
VRT_SetHdr(sp, HDR_REQ, "\020x-nb-classified:", dcs_type_name(t), vrt_magic_string_end);
VRT_SetHdr(sp, HDR_REQ, "\012x-variant:", dcs_type_mtd(t), vrt_magic_string_end);
}
# gen/Makefile.am
BUILT_SOURCES = dcs_classifier.c dcs_classifier.h
dcs_classifier.c dcs_classifier.h: ../gen_dcs_classifier.pl $(DCS_DBFILE)
../gen_dcs_classifier.pl $(DCS_DBFILE) $(DCS_KEY)
CLEANFILES = dcs_classifier.c dcs_classifier.h
#!/usr/bin/perl
# Copyright (c) 2014 UPLEX - Nils Goroll Systemoptimierung
# All rights reserved.
# Use only with permission
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
use strict;
use warnings;
use Carp;
use Crypt::RC4;
use Digest::MD5 (qw(md5));
use MIME::Base64;
# avoid dependency
# use Carp::Assert;
sub assert ($;$) {
unless($_[0]) {
require Carp;
Carp::confess( _fail_msg($_[1]) );
}
return undef;
}
sub _fail_msg {
my($name) = shift;
my $msg = 'Assertion';
$msg .= " ($name)" if defined $name;
$msg .= " failed!\n";
return $msg;
}
sub c_escape ($) {
my $a = $_[0];
$a =~ s#(["\\?])#\\$1#g;
$a;
}
################################################################################
## parser gen and vcl code gen interface
use constant {
F_MIN => 1,
F_CLASSIFIER_C => 1,
F_CLASSIFIER_H => 2,
F_MAX => 2
};
my @filenames;
$filenames[F_CLASSIFIER_C] = 'dcs_classifier.c';
$filenames[F_CLASSIFIER_H] = 'dcs_classifier.h';
{
my @fhs;
sub _VCL {
my ($fid, $where) = (shift, shift);
assert($filenames[$fid]);
my $fh = $fhs[$fid];
unless(defined($fh)) {
open ($fh, '>', $filenames[$fid]) ||
croak('VCL: cant open '.$filenames[$fid].' for writing: '.$!);
$fhs[$fid] = $fh;
}
print $fh @_;
}
sub VCL_close {
map {
if (defined($fhs[$_])) {
close($fhs[$_]) || warn('error closing '.$filenames[$_].': '.$!);
undef $fhs[$_];
}
} (F_MIN..F_MAX);
}
}
use constant {
VCL_TOP => undef
};
my %str2label;
my %label2str;
sub label($) {
my ($in) = @_;
if (exists($str2label{$in})) {
return $str2label{$in};
}
my $label = $in;
$label =~ s/[^a-zA-Z0-9_]/_/g;
my $i=0;
while (exists($label2str{$label.$i})) {
$i++;
}
$label2str{$label.$i} = $in;
$str2label{$in} = $label.$i;
$label.$i;
}
sub Prefix {
{
my $e = scalar(@_);
return '' if ($e == 0);
return $_[0] if ($e == 1);
}
my $minlen;
my @d = map {
my @split = split(//, $_);
my $l = scalar(@split);
if (defined($minlen)) {
$minlen = $l if ($l < $minlen);
} else {
$minlen = $l;
}
\@split;
} @_;
my $p = '';
my $i = 0;
while($i < $minlen) {
my $c = $d[0]->[$i];
return $p unless ($c);
for (my $j = 1; $j <= $#d; $j++) {
return $p unless ($c eq $d[$j]->[$i]);
}
$p .= $c;
$i++;
}
$p
}
# this needs two integers declared before inserting the code
#
# int p, r;
#
# p: current positon in string
# r: result according to input
sub parse_token_code_gen {
my $varnish = shift;
my $prevpos = shift; # 0, 1 ...
my $prev = shift; # "G", "GE", ...
my $term = shift; # function(macro) name to check if a symbol is terminator
my $cb = shift; # function(macro) to call for each match
assert ((scalar(@_) % 2) == 0);
my %symbs = @_; # symbol => result, ...
my $nsymbs = scalar(keys %symbs);
unless ($nsymbs) {
_VCL ($varnish, VCL_TOP, "\tgoto done;\n\n");
return;
}
my $pos = $prevpos + 1;
# label
if ($prevpos != -1) {
_VCL ($varnish, VCL_TOP, ' '.label('_'.$prevpos.$prev).":\n");
}
if ($nsymbs == 1) {
my ($m, $r) = %symbs;
my @cond;
# optimize for just this one string
my $p;
for ($p = $pos; $p < length($m); $p++) {
push @cond, '(m['.$p."] == '".substr($m, $p, 1)."')";
}
push @cond, '('.$term.'(m['.$p."]))";
_VCL ($varnish, VCL_TOP, "\t//".$m."\n");
_VCL ($varnish, VCL_TOP, "\t".'if ('.join(" && ", @cond).') {'."\n");
_VCL ($varnish, VCL_TOP, "\t".' '.$cb.'('.$p.', '.$r.");\n");
_VCL ($varnish, VCL_TOP, "\t"."}\n");
_VCL ($varnish, VCL_TOP, "\t".'goto done;'."\n");
} else {
my @case;
my @tocall;
{
## terminating keys
foreach my $m (grep { (length($_) == $pos) } sort keys %symbs) {
my $r = delete $symbs{$m};
_VCL ($varnish, VCL_TOP, "\t//".$m."\n");
_VCL ($varnish, VCL_TOP, "\t".'if ('.$term.'(m['.$pos.']))'."\n");
_VCL ($varnish, VCL_TOP, "\t ".$cb.'('.$pos.', '.$r.");\n");
}
}
{
## non-terminating keys
my @keys = (sort keys %symbs);
# get the longest common prefix from here
my @prestr = map(substr($_, $pos), @keys);
my $prefix = Prefix(@prestr);
my $prelen = length($prefix);
if ($prelen) {
# swallow the common prefix
my @cond;
my $p;
for ($p = $pos; $p < $pos + $prelen; $p++) {
push @cond, '(m['.$p."] == '".substr($keys[0], $p, 1)."')";
}
_VCL ($varnish, VCL_TOP, "\t".'// common prefix ('.substr($keys[0],0,$pos).')'.$prefix."\n");
_VCL ($varnish, VCL_TOP, "\t".'if (! ('.join(" && ", @cond).'))'."\n");
_VCL ($varnish, VCL_TOP, "\t".' goto done;'."\n");
$pos += $prelen;
## terminating keys
foreach my $m (grep { (length($_) == $pos) } @keys) {
my $r = delete $symbs{$m};
_VCL ($varnish, VCL_TOP, "\t//".$m."\n");
_VCL ($varnish, VCL_TOP, "\t".'if ('.$term.'(m['.$pos.']))'."\n");
_VCL ($varnish, VCL_TOP, "\t ".$cb.'('.$pos.', '.$r.");\n");
}
}
@keys = (sort keys %symbs);
# hash by this char
my %h;
foreach my $k (@keys) {
assert( length($k) > $pos, "length($k) > $pos" );
push @{$h{substr($k, $pos, 1)}}, ($k);
}
foreach my $k (sort keys %h) {
my $la = label('_'.$pos.$prev.$k);
my @me = @{$h{$k}};
push @case, ("case '".$k."':\tgoto ".$la.";\t// ".
join(", ",@me));
push @tocall, [$prev.$k, \@me];
}
}
push @case, ("default:\tgoto done;");
_VCL ($varnish, VCL_TOP, "\t".'switch (m['.$pos."]) {\n\t");
_VCL ($varnish, VCL_TOP, join("\n\t", @case)."\n");
_VCL ($varnish, VCL_TOP, "\t}\n");
if (scalar(@tocall)) {
map {
parse_token_code_gen($varnish, $pos, $_->[0],
$term,
$cb,
(map { $_ => $symbs{$_}; }
@{$_->[1]}));
} @tocall;
}
}
}
################################################################################
## misc
sub boilerplate_autogen_c($$) {
my ($out, $dbboilerplate) = @_;
_VCL($out, VCL_TOP, <<'EOF');
/*
* THIS FILE HAS BEEN AUTOMATICALLY GENERATED. DO NOT EDIT
*
EOF
# 4emacs */
if (defined($dbboilerplate) && ($dbboilerplate ne "")) {
$dbboilerplate =~ s/#/*/go;
$dbboilerplate =~ s#\*/##go;
$dbboilerplate =~ s/^\s*\*?/ */mog;
_VCL($out, VCL_TOP, <<'EOF');
* the contents of this file have been generated from a database file which
* included the following notice:
*
EOF
_VCL($out, VCL_TOP, $dbboilerplate);
_VCL($out, VCL_TOP, <<'EOF');
*
* The following applies to those parts of the generated code which have not
* been derived from the database file:
*
EOF
}
_VCL($out, VCL_TOP, <<'EOF');
* Copyright (c) 2014 UPLEX - Nils Goroll Systemoptimierung
* All rights reserved.
* Use only with permission
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
EOF
}
################################################################################
## classifier specific
# the peculiar function name is from the apache module sources
sub compute_key($) {
my ($s, $l) = ($_[0], length($_[0]));
my $r;
for (my $i = 0; $i < $l; $i+=4) {
$r .= substr($s, $i, 1);
}
$r;
}
sub load_classifier_db($$) {
my ($fname, $secret) = @_;
my $fh;
open($fh, '<', $fname) || die 'cant open '.$fname.' '.$!;
binmode($fh);
my $crypt_data;
{
my $b64;
my $sz = (-s $fh);
my $res = read ($fh, $b64, $sz);
die 'error reading '.$fname.' '.$! if (! defined($res));
die 'only read '.$res.' bytes of '.$sz if ($res < $sz);
$crypt_data = decode_base64($b64);
}
close($fh);
my $decrypt = RC4(compute_key($secret), substr($crypt_data, 32));
{
my $h_file = substr($crypt_data, 0, 32);
my $h_decrypt = unpack('H*', md5($decrypt));
die 'checksum error: file '.$h_file.' decrypt '.$h_decrypt
if ($h_file ne $h_decrypt);
}
\$decrypt;
}
my %nbtype2typeenum;
my %typeenum2nbtype;
# convert a NB type string to an enum
sub type_enum($) {
my $s = $_[0];
if (exists($nbtype2typeenum{$s})) {
return $nbtype2typeenum{$s};
}
my $e = 'NB_T_'.uc($s);
$e =~ s:[-\s]:_:g;
$e =~ s:[^_A-Z0-9]::g;
if (exists($typeenum2nbtype{$e})) {
die 'name clash '.$e.' '.$typeenum2nbtype{$e}.' vs '.$s;
}
$typeenum2nbtype{$e} = $s;
$nbtype2typeenum{$s} = \$e;
\$e;
}
my $dbref = load_classifier_db($ARGV[0], $ARGV[1]);
my @entries;
use constant {
ENTRY_ID => 0,
ENTRY_MATCHMASK => 1,
ENTRY_INITMASK => 2,
ENTRY_TYPE => 3,
ENTRY_LINE => 4
};
my %subkeys;
my @subkeys_byid;
use constant {
SUBKEY_ID => 0,
SUBKEY_STRING => 1,
SUBKEY_ENTRIES => 2
};
use constant {
SUBKEY2ENTRY_ENTRY => 0,
SUBKEY2ENTRY_ENTRYSUBKEY => 1
};
use constant {
MAX_SUBKEY_PER_ENTRY => 32
};
my %manual_fixup_remove = (
'android 4.0' => 1,
);
my %types;
# id 0 is reserved for the 'no match' case
my $line = 0;
my $subkey_id = 0;
# init the special entry 0
{
my @e;
my $s = "unidentified";
$e[ENTRY_ID] = 0;
$e[ENTRY_MATCHMASK] = 0xffffffff;
$e[ENTRY_INITMASK] = 0;
$e[ENTRY_TYPE] = type_enum($s);
$e[ENTRY_LINE] = \$s;
$entries[$line++] = \@e;
my @n;
$n[SUBKEY_ID] = $subkey_id;
$n[SUBKEY_STRING] = \$s;
$n[SUBKEY_ENTRIES] = [[0, 0]];
assert(! defined($subkeys_byid[$subkey_id]));
$subkeys_byid[$subkey_id] = \@n;
$subkey_id++;
}
## XXX build a subkey -> entry array
## [ entry, entry_subkey_id ]
my $dbboilerplate;
foreach (split(/[\n\r\f]/, $$dbref)) {
if (/^\s*#/) {
$dbboilerplate .= $_."\n";
next;
}
my ($key, $type) = split(/\t/, $_);
die "empty key on line ".$line
unless (defined($key) && ($key ne ""));
die "empty type on line ".$line
unless (defined($type) && ($type ne ""));
if ($key =~ m#[\\"]#) {
warn 'key contains invalid characters - skipping '.$key;
next;
}
if ($manual_fixup_remove{$key}) {
warn 'removed due to manual fixup: '.$key;
next;
}
my $entry_subkey = 0;
my $matchmask = 0;
my $initmask = 0;
# remove any subkey which is wholly contained in another subkey
# sort reverse by length, only remove shorter ones
my @sks;
{
my @skss = (sort { length($b) <=> length($a) } split(/\*/, $key));
skss:
for (my $i = 0; $i <= $#skss; $i++) {
for (my $j = 0; $j <= $#sks; $j++) {
if (index($sks[$j], $skss[$i]) != -1) {
next skss;
}
}
push @sks, ($skss[$i]);
}
}
foreach my $subkey (@sks) {
assert($entry_subkey < MAX_SUBKEY_PER_ENTRY);
my $e;
if ($subkey =~ /^!(.*)/o) {
$subkey =~ s/^!//;
$initmask |= (1<<$entry_subkey);
$matchmask |= (1<<$entry_subkey);
$e = 0 - $line;
} else {
$matchmask |= (1<<$entry_subkey);
$e = $line;
}
my @se;
$se[SUBKEY2ENTRY_ENTRY] = $e;
$se[SUBKEY2ENTRY_ENTRYSUBKEY] = $entry_subkey;
if (exists($subkeys{$subkey})) {
push @{$subkeys{$subkey}->[SUBKEY_ENTRIES]}, (\@se);
} else {
my @n;
$n[SUBKEY_ID] = $subkey_id;
$n[SUBKEY_STRING] = \$subkey;
$n[SUBKEY_ENTRIES] = [\@se];
$subkeys{$subkey} = \@n;
assert(! defined($subkeys_byid[$subkey_id]));
$subkeys_byid[$subkey_id] = \@n;
$subkey_id++;
}
$entry_subkey++;
}
my @e;
$e[ENTRY_ID] = $line;
$e[ENTRY_MATCHMASK] = $matchmask;
$e[ENTRY_INITMASK] = $initmask;
$e[ENTRY_TYPE] = type_enum($type);
$e[ENTRY_LINE] = \$key;
$entries[$line++] = \@e;
}
my $dcs_type_count = scalar(keys %typeenum2nbtype);
# "
#use Smart::Comments;
### %nbtype2typeenum;
### %typeenum2nbtype;
### @entries
### %subkeys
################################################################################
## generate the data
boilerplate_autogen_c(F_CLASSIFIER_C, $dbboilerplate);
boilerplate_autogen_c(F_CLASSIFIER_H, undef);
_VCL (F_CLASSIFIER_H, VCL_TOP, <<EOF);
#ifndef DCS_CLASSIFIER_H
#define DCS_CLASSIFIER_H 1
#include <stdint.h>
/* tunable */
#define DCS_MAX_NEGMATCH_CAND 32
/*
* we need a huge bitmask to mark which subkeys we have seen already in order to
* avoid the effort of marking subkeys in the matchmask of potentially many
* entries. As, for all practical purposes, this bitmask will always be larger
* than any machine data type, we want to use the largest machine data type for
* which native machine operators exist. This should be a long int
* https://usrmisc.wordpress.com/2012/12/27/integer-sizes-in-c-on-32-bit-and-64-bit-linux/
*/
typedef unsigned long int dcs_seenmask_t;
/* values for the particular dcs db use to generate this code */
#define DCS_TYPE_COUNT $dcs_type_count
#define DCS_ENTRY_COUNT $line
#define DCS_SUBKEY_COUNT $subkey_id
#define DCS_MATCHSTATE_REGMASK_SZ ((DCS_SUBKEY_COUNT / sizeof(dcs_seenmask_t)) + 1)
/*
* forward declarations of enums are not allowed, so enum dcs_type cannot be
* moved into $filenames[F_CLASSIFIER_C]
*
* If dcs_type was fixed, we could avoid generating this file and use a static
* version of it, but as long as each DCS DB may define individual classifier
* types, can not.
*/
EOF
_VCL (F_CLASSIFIER_H, VCL_TOP,
"enum dcs_type {\n\t".
join(",\n\t",
'NB_T_UNIDENTIFIED = 0',
sort grep { $_ ne 'NB_T_UNIDENTIFIED' } keys %typeenum2nbtype).
"\n};\n");
_VCL (F_CLASSIFIER_H, VCL_TOP, <<EOF);
/*
* when changing these always set the correct _MAX define
*
* entry_id needs to be signed because we use the sign to signal negative matches
*/
typedef int16_t dcs_entry_id_t;
#define DCS_ENTRY_ID_MAX (INT16_MAX - 1)
typedef uint32_t dcs_matchmask_t;
typedef uint16_t dcs_subkey_id_t;
#define DCS_SUBKEY_ID_MAX UINT16_MAX
/* this only needs the number of bits in dcs_entry.matchmask */
typedef uint8_t dcs_entry_subkey_id_t;
#define DCS_ENTRY_SUBKEY_ID_MAX (sizeof(dcs_matchmask_t) * 4 - 1)
struct dcs_entry {
#define DCS_ENTRY_MAGIC 0x74b979a7
const uint32_t magic;
const dcs_matchmask_t matchmask;
const dcs_entry_id_t id; /* redundant, for assertions/debugging */
const enum dcs_type type;
const char const *key; /* for debug */
};
struct dcs_sk2e {
/*
* if entry id is negative, it's a negative match
* (and entry id needs to be inverted
*/
const dcs_entry_id_t entry;
const dcs_entry_subkey_id_t entry_subkey;
};
struct dcs_subkey {
#define DCS_SUBKEY_MAGIC 0xF15023D3
const uint32_t magic;
const dcs_subkey_id_t id;
const char const *s; /* debug */
/* number of entry ids to follow */
const dcs_entry_id_t n;
const struct dcs_sk2e * const sk2e;
};
struct dcs_matchstate {
#define DCS_MATCHSTATE_MAGIC 0x32bc524d
uint32_t magic;
dcs_entry_id_t min_match_entry;
dcs_entry_id_t first_match_entry;
dcs_entry_id_t last_match_entry;
dcs_matchmask_t matchmask[DCS_ENTRY_COUNT];
dcs_seenmask_t seenmask[DCS_MATCHSTATE_REGMASK_SZ];
dcs_entry_id_t n_candidates;
dcs_entry_id_t candidates[DCS_MAX_NEGMATCH_CAND];
};
const char * const dcs_type[DCS_TYPE_COUNT];
const struct dcs_entry dcs_entry[DCS_ENTRY_COUNT];
const struct dcs_subkey dcs_subkey[DCS_SUBKEY_COUNT];
const struct dcs_matchstate dcs_matchstate_init;
void dcs_init_matchstate (struct dcs_matchstate *state);
void dcs_parse_subkey(struct dcs_matchstate *state, const char const *m);
void dcs_state_eval_candidates(struct dcs_matchstate *state);
#endif /* DCS_CLASSIFIER_H */
EOF
assert($#entries == ($line - 1));
assert($#subkeys_byid == ($subkey_id - 1));
_VCL (F_CLASSIFIER_C, VCL_TOP, <<EOF);
#include <assert.h>
#include <string.h>
#ifdef DEBUG_SK_MATCH
#include <stdio.h>
#endif
#include "$filenames[F_CLASSIFIER_H]"
EOF
_VCL (F_CLASSIFIER_C, VCL_TOP,
'const char * const dcs_type['.scalar(keys %typeenum2nbtype)."] = {\n\t".
join(",\n\t",
map { '['.$_.'] = "'.c_escape($typeenum2nbtype{$_}).'"' }
sort keys %typeenum2nbtype).
"\n};\n\n");
_VCL (F_CLASSIFIER_C, VCL_TOP, <<EOF);
const struct dcs_entry dcs_entry[DCS_ENTRY_COUNT] = {
EOF
_VCL (F_CLASSIFIER_C, VCL_TOP,
join(",\n",
map {
assert($entries[$_]->[ENTRY_ID] == $_);
"\t{ .magic\t= DCS_ENTRY_MAGIC, ".
'.matchmask = 0x'.unpack("H*", pack("I>", $entries[$_]->[ENTRY_MATCHMASK])). ', '.
'.id = '.$entries[$_]->[ENTRY_ID].', '.
'.type = '.${$entries[$_]->[ENTRY_TYPE]}.', '.
'.key = "'.c_escape(${$entries[$_]->[ENTRY_LINE]}).'"}';
} (0..$#entries)));
_VCL (F_CLASSIFIER_C, VCL_TOP, <<EOF);
};
const struct dcs_subkey dcs_subkey[DCS_SUBKEY_COUNT] = {
EOF
_VCL (F_CLASSIFIER_C, VCL_TOP,
join(",\n",
map {
assert ($_ == $subkeys_byid[$_]->[SUBKEY_ID]);
"\t{ .magic = DCS_SUBKEY_MAGIC, ".
'.id = '.$_.', '.
'.s = "'.c_escape(${$subkeys_byid[$_]->[SUBKEY_STRING]}).'", '.
'.n = '.scalar(@{$subkeys_byid[$_]->[SUBKEY_ENTRIES]}).', '.
'.sk2e = (const struct dcs_sk2e []){'.join(', ',
map { '{'.join(', ',
$_->[SUBKEY2ENTRY_ENTRY],
$_->[SUBKEY2ENTRY_ENTRYSUBKEY]).'}' }
@{$subkeys_byid[$_]->[SUBKEY_ENTRIES]}).'} }';
} (0..$#subkeys_byid)));
_VCL (F_CLASSIFIER_C, VCL_TOP, <<'EOF');
};
const struct dcs_matchstate dcs_matchstate_init = {
.magic = DCS_MATCHSTATE_MAGIC,
.min_match_entry = 0,
.first_match_entry = 0,
.last_match_entry = 0,
EOF
_VCL (F_CLASSIFIER_C, VCL_TOP,
"\t.matchmask = { ".join(",\n\t ",
map { '['.$_->[ENTRY_ID].'] = 0x'.unpack("H*", pack("I>", $_->[ENTRY_INITMASK])) } @entries )." },\n");
_VCL (F_CLASSIFIER_C, VCL_TOP, <<'EOF');
.seenmask = { 0 },
.n_candidates = 0,
.candidates = { 0 }
};
EOF
################################################################################
## generate the parse code
_VCL(F_CLASSIFIER_C, undef, <<'EOF');
void dcs_register_subkey_match(struct dcs_matchstate *state, dcs_subkey_id_t subkey_id);
/* we only have subtring matches, so term is always true */
#define PARSE_TERM(c) (1)
#define PARSE_REGISTER(p, r) dcs_register_subkey_match(state, r)
/*
* parses the string argument and returns the index into the
* subkey array
*/
void
dcs_parse_subkey(struct dcs_matchstate *state, const char const *m) {
EOF
parse_token_code_gen(F_CLASSIFIER_C, -1, '',
'PARSE_TERM', # function(macro) name to check if a symbol is terminator
'PARSE_REGISTER', # function(macro) to call for each match
# make a hash symbol -> SUBKEY_ID
map { ($_ => $subkeys{$_}->[SUBKEY_ID]); } (sort keys %subkeys));
_VCL(F_CLASSIFIER_C, undef, <<'EOF');
done:
return;
}
#undef PARSE_TERM
#undef PARSE_REGISTER
void
dcs_init_matchstate (struct dcs_matchstate *state) {
/* if this fails, fix the init code */
assert(NB_T_UNIDENTIFIED == 0);
/* check range of chosen data types */
assert(DCS_ENTRY_COUNT <= DCS_ENTRY_ID_MAX);
assert(DCS_SUBKEY_COUNT <= DCS_SUBKEY_ID_MAX);
memcpy(state, &dcs_matchstate_init, sizeof(*state));
assert(state->magic == DCS_MATCHSTATE_MAGIC);
assert(state->min_match_entry == 0);
}
static inline int
dcs_register_subkey_match_seen(dcs_seenmask_t *mask, const dcs_subkey_id_t subkey_id) {
const dcs_subkey_id_t word = subkey_id / sizeof(dcs_seenmask_t);
const uint8_t bit = subkey_id % sizeof(dcs_seenmask_t);
assert(word <= DCS_MATCHSTATE_REGMASK_SZ);
if (mask[word] & (1<<bit))
return 1;
mask[word] |= (1<<bit);
return 0;
}
static inline void
dcs_state_add_candidate(struct dcs_matchstate *state, dcs_entry_id_t cand) {
dcs_entry_id_t i;
assert(cand > 0);
#ifndef NDEBUG
/* we must only add a candidate once */
for (i = 0; i < state->n_candidates; i++)
assert(state->candidates[i] != cand);
#endif
assert(state->n_candidates < DCS_MAX_NEGMATCH_CAND);
if (! (state->n_candidates < DCS_MAX_NEGMATCH_CAND)) {
/* XXX THROW ERROR */
return;
}
state->candidates[state->n_candidates] = cand;
state->n_candidates++;
}
static inline void
dcs_state_remove_candidate(struct dcs_matchstate *state, dcs_entry_id_t cand) {
dcs_entry_id_t i, j;
assert(cand > 0);
for (i = 0; i < state->n_candidates; i++) {
if (state->candidates[i] == cand) {
for (j = i; j < state->n_candidates - 1; j++)
state->candidates[j] =
state->candidates[j + 1];
state->n_candidates--;
assert(state->n_candidates >= 0);
return;
}
}
/* must not happen that we remove something not a candidate */
assert("removed something not a candidate" == NULL);
}
void
dcs_state_eval_candidates(struct dcs_matchstate *state) {
dcs_entry_id_t i;
for (i = 0; i < state->n_candidates; i++) {
dcs_entry_id_t entry_id = state->candidates[i];
if ((state->min_match_entry == 0) ||
(entry_id < state->min_match_entry))
state->min_match_entry = entry_id;
state->last_match_entry = entry_id;
}
}
#ifdef DEBUG_SK_MATCH
#define P_SK_MATCH(...) printf(__VA_ARGS__)
#else
#define P_SK_MATCH(...) do { } while(0)
#endif
void
dcs_register_subkey_match(struct dcs_matchstate *state, dcs_subkey_id_t subkey_id) {
const struct dcs_subkey *subkey;
const struct dcs_sk2e *sk2e;
dcs_entry_id_t entry_id, i;
assert(state->magic == DCS_MATCHSTATE_MAGIC);
if (dcs_register_subkey_match_seen(state->seenmask, subkey_id)) {
P_SK_MATCH("register %d - seen before\n", subkey_id);
return;
}
subkey = &dcs_subkey[subkey_id];
assert(subkey->magic == DCS_SUBKEY_MAGIC);
assert(subkey->id == subkey_id);
P_SK_MATCH("register %d\n", subkey_id);
sk2e = subkey->sk2e;
i = 0;
while (i++ < subkey->n) {
entry_id = sk2e->entry;
assert(sk2e->entry_subkey <= DCS_ENTRY_SUBKEY_ID_MAX);
assert(entry_id != 0);
/*
* matches with only positives are simple: once we see the mask is ok
* we got a match
*
* negatives are more complicates:
* - if we see the negative match before the last match, we will never
* get a match
* - otherwise we cannot know that it is a match before we have seen the
* whole string
*/
if (entry_id < 0) {
entry_id = 0 - entry_id;
/* if this was a candidate before, remove it */
if (state->matchmask[entry_id] == dcs_entry[entry_id].matchmask) {
dcs_state_remove_candidate(state, entry_id);
P_SK_MATCH("removed candidate %d this 0x%x mask 0x%x need 0x%x\n",
entry_id,
(1<<sk2e->entry_subkey),
state->matchmask[entry_id] & ~((uint32_t)(1 << sk2e->entry_subkey)),
dcs_entry[entry_id].matchmask);
}
state->matchmask[entry_id] &= ~((uint32_t)(1 << sk2e->entry_subkey));
} else {
state->matchmask[entry_id] |= ((uint32_t)(1 << sk2e->entry_subkey));
}
assert(entry_id > 0);
assert(entry_id < DCS_ENTRY_COUNT);
if (state->matchmask[entry_id] == dcs_entry[entry_id].matchmask) {
if (dcs_matchstate_init.matchmask[entry_id] == 0) {
/* has only positive matches, register now */
if ((state->min_match_entry == 0) ||
(entry_id < state->min_match_entry))
state->min_match_entry = entry_id;
if (state->first_match_entry == 0)
state->first_match_entry = entry_id;
state->last_match_entry = entry_id;
P_SK_MATCH(" match %d this 0x%x mask 0x%x need 0x%x\n",
entry_id,
(1<<sk2e->entry_subkey),
state->matchmask[entry_id],
dcs_entry[entry_id].matchmask);
} else {
/* has negative matches */
dcs_state_add_candidate(state, entry_id);
P_SK_MATCH("added candidate %d this 0x%x mask 0x%x need 0x%x\n",
entry_id,
(1<<sk2e->entry_subkey),
state->matchmask[entry_id],
dcs_entry[entry_id].matchmask);
}
}
sk2e++;
}
}
EOF
VCL_close();
warn("TODO common tokens matcher");
warn("TODO filter db");
# ~24 us
#slink@haggis:~/Projekte/Telekom_Varnish/netbiscuits$ time ./test <uas >/dev/null
#
#real 0m0.044s
#user 0m0.040s
#sys 0m0.004s
#slink@haggis:~/Projekte/Telekom_Varnish/netbiscuits$ wc -l uas
#1623 uas
# ~23.42 us
#slink@haggis:~/Projekte/Telekom_Varnish/netbiscuits$ wc -l uas_x1000
#1623000 uas_x1000
#slink@haggis:~/Projekte/Telekom_Varnish/netbiscuits$ time ./test <uas_x1000 >/dev/null
#
#real 0m38.018s
#user 0m37.980s
#sys 0m0.044s
# NO OUTPUT (print commented out)
# ~21.23 us
#slink@haggis:~/Projekte/Telekom_Varnish/netbiscuits$ time ./test <uas_x1000
#
#real 0m34.454s
#user 0m34.440s
#sys 0m0.024s
# compile with -O6 no -g
#
# ~ 7.33 us
#slink@haggis:~/Projekte/Telekom_Varnish/netbiscuits$ time ./test <uas_x1000
#
#real 0m11.909s
#user 0m11.896s
#sys 0m0.012s
#(gdb) p sizeof(struct dcs_matchstate)
#$1 = 54188
## fixed neg match
# ~25.15 us
#slink@haggis:~/Projekte/Telekom_Varnish/netbiscuits$ time ./test <uas_x1000 >/dev/null
#
#real 0m40.807s
#user 0m40.768s
#sys 0m0.048s
## same -O6
#real 0m18.456s
#user 0m18.392s
#sys 0m0.064s
## removed nested substrings
#
#real 0m37.939s
#user 0m37.880s
#sys 0m0.064s
## same -O6
#real 0m16.915s
#user 0m16.884s
#sys 0m0.032s
# $Id$
varnishtest "Test dcs vmod"
server s1 {
rxreq
txresp
} -start
varnish v1 -vcl+backend {
import dcs from "${vmod_topbuild}/src/.libs/libvmod_dcs.so";
sub vcl_recv {
# this code will classify
#
# for best performance in production, only call classify
# once or use inline-C to save the return value of
# classify (int)
set req.http.x-nb-classified = dcs.type_name(dcs.classify());
set req.http.x-variant = dcs.type_mtd(dcs.classify());
error 200;
}
sub vcl_error {
set obj.http.x-nb-classified = req.http.x-nb-classified;
set obj.http.x-variant = req.http.x-variant;
synthetic {"classified ad here
"};
return (deliver);
}
} -start
client c1 {
# no User-Agent
txreq -url "/"
rxresp
expect resp.status == 200
expect resp.http.x-nb-classified == "unidentified"
expect resp.http.x-variant == "dsk"
} -run
client c1 {
txreq -hdr "user-agent: willgetignored" -hdr "x-wap-profile: anything"
rxresp
expect resp.status == 200
expect resp.http.x-nb-classified == "Mobile Phone"
expect resp.http.x-variant == "mob"
} -run
client c1 {
txreq -hdr "User-Agent: Opera/9.80 (Android; Opera Mini/19.0.1340/34.1309; U; de) Presto/2.8.119 Version/11.10" \
-hdr "X-OperaMini-Phone-UA: Mozilla/5.0 (Linux; U; Android 4.3; de-de; ME302C Build/JSS15Q) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30"
rxresp
expect resp.status == 200
expect resp.http.x-nb-classified == "Mobile Phone"
expect resp.http.x-variant == "mob"
} -run
/*
* Copyright (c) 2014 UPLEX - Nils Goroll Systemoptimierung
* All rights reserved.
* Use only with permission
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "dcs_config.h"
#include "vrt.h"
#include "vcc_if.h"
#include "dcs_varnish.c"
int
vmod_classify(struct sess *sp) {
return dcs_varnish_classify(sp);
}
const char *
vmod_entry_key(struct sess *sp, int e) {
(void) sp;
return dcs_match_key(e);
}
int
vmod_type_id(struct sess *sp, int e) {
(void) sp;
return dcs_match_type_id(e);
}
const char *
vmod_type_name(struct sess *sp, int e) {
const int t = dcs_match_type_id(e);
(void) sp;
return dcs_type_name(t > 0 ? t : 0);
}
const char *
vmod_type_mtd(struct sess *sp, int e) {
const int t = dcs_match_type_id(e);
(void) sp;
return dcs_type_mtd(t > 0 ? t : 0);
}
Module dcs
Function INT classify()
# all of these take the return value from classify()
Function STRING entry_key(INT)
Function INT type_id(INT)
Function STRING type_name(INT)
Function STRING type_mtd(INT)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment