19 Commits d249dcc9b0 ... 2d08a8fd0c

Author SHA1 Message Date
  Ariadne Devos 2d08a8fd0c Merge branch 'odd-fixes' into webdav 5 years ago
  Ariadne Devos 507f054a2d Introduce in-place string concatenation 5 years ago
  Ariadne Devos cecbed3721 Correct remark in sHT_size_check 5 years ago
  Ariadne Devos ac38bfe820 Stop infinite loop in sHT_index_iterate 5 years ago
  Ariadne Devos 47d30cb937 Correct minimum calculation 5 years ago
  Ariadne Devos eb05f47af6 Correct specification of sHT_index_iterate 5 years ago
  Ariadne Devos 50deb977d0 Use sHT_index_iterate abstraction 5 years ago
  Ariadne Devos 6d6476329d Abstract index iteration 5 years ago
  Ariadne Devos 459e920121 Use size_t length in schedule code 5 years ago
  Ariadne Devos 761d9f6fc9 Hash header names 5 years ago
  Ariadne Devos 91d5cba23a Generate HTTP method name hash 5 years ago
  Ariadne Devos fe3d8b16b1 Merge branch 'odd-fixes' into webdav 5 years ago
  Ariadne Devos 12c57f701f Make sHT_memeq speculatively safe 5 years ago
  Ariadne Devos b5dfe220fc Merge branch 'fork/perfect-hash' into webdav 5 years ago
  Ariadne Devos 39a9b58d45 Generate perfect, speculatively safe hash tables 5 years ago
  Ariadne Devos 2dee1d6d18 Merge branch 'odd-fixes' into webdav 5 years ago
  Ariadne Devos b03286d137 Add Spectre-safe string equality test 5 years ago
  Ariadne Devos 9e638af626 Add not-equal test operator 5 years ago
  Ariadne Devos 1ecf659179 Strengthen sHT_index_nospec 5 years ago
10 changed files with 499 additions and 46 deletions
  1. 14 2
      Makefile.am
  2. 31 0
      buffer/append.c
  3. 210 0
      buffer/gen-phash.sh
  4. 34 0
      buffer/memeq.c
  5. 32 0
      buffer/phash.c
  6. 76 0
      collateral/index-foreach.smpl
  7. 73 0
      http/headers.sh
  8. 0 44
      http/method.c
  9. 29 0
      http/methods.sh
  10. 0 0
      sHT/index.h

+ 14 - 2
Makefile.am

@@ -1,5 +1,5 @@
 # shttpd - build system -- list of dependencies
-# Copyright (C) 2018 Ariadne Devos
+# Copyright (C) 2018-2019 Ariadne Devos
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -21,10 +21,14 @@ AM_CPPFLAGS = -D_GNU_SOURCE
 bin_PROGRAMS = shttpd
 shtsources = \
   bitops/zero-index/any.c \
+  buffer/append.c \
+  buffer/memeq.c \
+  buffer/phash.c \
   fd/fd.c \
   fd/inet.c \
   generic/bug.c \
-  http/method.c \
+  http/method-hash.c \
+  http/header-hash.c \
   task/accept.c \
   task/perform.c \
   task/sockrw.c \
@@ -37,6 +41,14 @@ shtsources = \
   worker/schedule.c \
   worker/vector.c
 
+phash_recipe = bash $(srcdir)/buffer/gen-phash.sh --gen $< > $@T && mv $@T $@
+
+http/method-hash.c: http/methods.sh buffer/gen-phash.sh
+	${phash_recipe}
+
+http/header-hash.c: http/headers.sh buffer/gen-phash.sh
+	${phash_recipe}
+
 worker/objcache.c: worker/objcache-bless-table.c
 	touch -c $@
 worker/objcache-bless-table.c: tools/gen-vector-variants.sh

+ 31 - 0
buffer/append.c

@@ -0,0 +1,31 @@
+/* sHT -- append and iterate a suffix
+   Copyright (C) 2019 Ariadne Devos
+
+   This program 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.
+
+   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#include <stddef.h>
+#include <sHT/index.h>
+#include <sHT/string.h>
+
+size_t
+sHT_append(char *to, const char *from, size_t length0, size_t length1, size_t i0, size_t i1)
+{
+	/* Compute the number of bytes that can be read and written. */
+        size_t todo = sHT_min_size(length0 - i0, length1 - i1);
+        size_t i;
+	/* Then copy them. */
+        sHT_index_iterate(i, todo)
+                to[i0 + i] = from[i1 + i];
+        return i;
+}

+ 210 - 0
buffer/gen-phash.sh

@@ -0,0 +1,210 @@
+c='/* s2 - perfect hash table lookups
+  Copyright (C) 2019 Ariadne Devos
+
+  This program 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.
+
+  This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. */
+/* This comment applies to both the script and code embedded in the
+  generated output. */'
+# This assumes the Bash shell!
+IFS=' '
+# * Local variables
+# input for gperf
+tmpgperf=""
+# output for gperf
+gperfout=""
+# code to write before the generated code
+prefix=""
+# code to write after the generated code
+postfix=""
+# name of the table, set by prepare
+table=""
+
+function oops() {
+	echo -E "$1 (status code: $?)" >&2;
+	exit 2;
+}
+
+function opentmp() {
+	# pass-by-reference
+	local -n fd="$1"
+	local fd="$1"
+	local name="`mktemp`"
+	if [ -z "$name" ]; then
+		oops 'cannot create temporary file';
+	fi
+	# {fd}: allocate a file descriptor
+	exec {fd}<>"${name}" || oops 'cannot open temporary file'
+	# close the temporary file while it is open,
+	# to avoid any trapping
+	#
+	# There is a little race window, but empty files
+	# are small.
+	rm "${name}"
+}
+
+
+function prepare() {
+	table="$1"
+	# TODO: validate ${table}
+	# open a temporary file for gperf's input
+	opentmp tmpgperf
+	opentmp gperfout
+	opentmp prefix
+	opentmp postfix
+	cat >&"${tmpgperf}" <<EOF
+/* don't hide the hash table */
+%global-table
+/* Don't put strings into the wordlist, only offsets -- reduces memory usage */
+%pic
+/* Don't allow lookup tables to be modified -- facilitates static analysis,
+   may reduce memory usage on some shared memory systems
+   and catches accidental writes. */
+%readonly-tables
+%define hash-function-name ${table}_hash
+%define lookup-function-name ${table}_lookup_ignore
+%define string-pool-name ${table}_strings
+/* a hash table from keywords to their value */
+%define word-array-name ${table}_entries
+
+/* gperf requires the string pool offset to be named 'name'. */
+/* This has been duplicated from <sHT/phash.h>. It will be deleted from the
+   output. */
+struct sHT_perfect_entry { uint_least16_t name; uint_least16_t length; uint_least32_t value; };
+
+%struct-type
+%language=ANSI-C
+
+%{
+$c
+/* This is a generated file, do not edit manually! */
+#include <sHT/phash.h>
+/* For size_t, offsetof */
+#include <stddef.h>
+%}
+%%
+EOF
+	[ "$?" -eq 0 ] || oops 'cannot write gperf header'
+}
+
+function entry() {
+	local name="$1"
+	local value="$2"
+	# - TODO: generate verification conditions for the hashing of all
+	#   keywords, for formal verification
+	if [ -z "$name" ] || [ -z "$value"  ]; then
+		oops 'keywords and values may not be empty'
+	fi
+
+	echo -E "\"${name}\", sizeof(\"${name}\") - 1, $value" >&"${tmpgperf}" || oops 'cannot write hash table entry'
+}
+
+
+function pre() {
+	echo -E "$1" >&"$prefix" || oops 'cannot write preprocessor input'
+}
+
+function post() {
+	echo -E "$1" >&"$postfix" || oops 'cannot write preprocessor input'
+}
+
+function solve() {
+	# Generate the hash function and table
+	# /dev/fd is recognised by Bash and exists on my
+	# Linux box -- Debian 9. Bash doesn't have a rewind or seek builtin
+	#
+	# Post-process the output:
+	# - let the 'len' argument of the hash function be a size_t
+	#   instead of unsigned int.
+	#   (Also correct the hash function output type,
+	#   and let it be inline)
+	# - don't allocate memory for a NUL terminator
+	# - don't emit the lookup function (static inline)
+	#   (TODO: remove completely)
+	# - compute string offsets more according to the standard
+	#   -- with offsetof
+	# - make sure string offsets of empty entries are within
+	#   bounds (zero offset and length)
+	#   (also, make the 'unknown' value explicit)
+	#   (this may need to be repeated multiple times in a line)
+	# - remove the reference to strcmp
+	# - remove the definition of sHT_perfect_entry
+	set -o pipefail
+	gperf < "/dev/fd/${tmpgperf}" | \
+	sed -E \
+	  -e 's/\bunsigned int len\b/size_t len/' \
+	  -e 's/^static unsigned int$/static inline size_t/' \
+	  -e 's/(char [a-zA-Z0-9_]+_strings_str[0-9]+\[sizeof\(".*"\))\];/\1 - 1];/' \
+	  -e 's/^const struct sHT_perfect_entry \*$/static inline const struct sHT_perfect_entry \*/' \
+	  -e 's/\(int\)\(long\)&\(\(struct ([a-zA-Z0-9_]+_strings)_t \*\)0\)->\1_str([0-9]+)\b/offsetof\(struct \1_t, \1_str\2\)/' \
+	  -e ':begin s/\{-1\}/{ 0, 0, 0 }/;t begin' \
+	  -e 's/!strcmp \(str \+ 1\, s \+ 1\)/0/' \
+	  -e 's/^(struct sHT_perfect_entry) \{.*\};$/\1;/' \
+	>&"${gperfout}"
+	local err="$?"
+	if [ ! "$err" -eq 0 ]; then
+		echo -E "cannot generate hash function and table (status code: $err)"
+		# output the test case, to ease debugging,
+		echo -E 'Begin failed input:' >&2
+		# The input must be rewinded!
+		cat >&2 <"/dev/fd/${tmpgperf}"
+		echo -E 'End failed input.' >&2
+		exit 2;
+	fi
+	# Check the output:
+	#   (the input must be rewinded!)
+	# - for switch statements (unhidden conditionals)
+	#   (these probably can be hidden)
+	grep -E '\bswitch\b' >/dev/null < "/dev/fd/${gperfout}"
+	case "$?" in
+	0) oops 'TODO: generated code gperf output contains unguarded branches' ;;
+	1) ;;
+	2) oops 'grep does not recognise unhidden conditional pattern' ;;
+	*) oops 'unexpected grep exit status' ;;
+	esac
+	# Generate an exported lookup function
+	#   (Why only export a lookup function? For simpler live-patch consistency.)
+	# The input must be rewinded!
+	cat >&"${gperfout}" <<EOF
+
+static const struct sHT_perfect_table ${table}_table = {
+	.string_pool = ${table}_strings,
+	.values_length = sizeof(${table}_entries) / sizeof(*${table}_entries),
+	.values = ${table}_entries,
+};
+
+/* Carefully read <sHT/phash.h> before use */
+size_t
+${table}_lookup(const uint8_t string[], size_t length)
+{
+	size_t hash = ${table}_hash(string, length);
+	return sHT_perfect_lookup(&${table}_table, string, length, hash);
+}
+EOF
+	[ "$?" -eq 0 ] || oops 'cannot generate lookup function'
+	cat < "/dev/fd/${prefix}" || oops 'cannot output prefix'
+	cat < "/dev/fd/${gperfout}" || oops 'cannot output generated code'
+	cat < "/dev/fd/${postfix}" || oops 'cannot output postfix'
+}
+
+case "$1" in
+--help)
+	cat <<EOF
+Generate perfect hash tables.
+How to use:
+bash ./buffer/gen-phash.sh --gen instructions.sh > output.c
+EOF
+	[ "$?" -eq 0 ] || oops 'cannot output help message' ;;
+--gen)
+	source "$2" || oops 'cannot generate hash table' ;;
+*) oops 'unsupported option' ;;
+esac

+ 34 - 0
buffer/memeq.c

@@ -0,0 +1,34 @@
+/* s2 - compare byte arrays
+   Copyright (C) 2019 Ariadne Devos
+
+   This program 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.
+
+   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#include <stddef.h>
+#include <sHT/index.h>
+#include <sHT/string.h>
+#include <sHT/test.h>
+
+size_t
+sHT_memeq(const char *buffer0, const char *buffer1, size_t correct, size_t otherwise, size_t length)
+{
+	size_t i;
+	/* TODO: optimise -- but without Spectre issues
+	   Perhaps do a word at a time. */
+	sHT_index_iterate(i, length) {
+		/* @var{sHT_and_none} is incorrect here! Strings can contain NUL. */
+		if (sHT_neq(buffer0[i], buffer1[i]))
+			return otherwise;
+	}
+	return correct;
+}

+ 32 - 0
buffer/phash.c

@@ -0,0 +1,32 @@
+/* s2 - perfect hash table lookups
+   Copyright (C) 2019 Ariadne Devos
+
+   This program 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.
+
+   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#include <stddef.h>
+#include <stdint.h>
+#include <sHT/phash.h>
+
+#include <sHT/nospec.h>
+#include <sHT/string.h>
+
+size_t
+sHT_perfect_lookup(const struct sHT_perfect_table *table, const uint8_t string[], size_t length, size_t hash)
+{
+	const struct sHT_perfect_entry *e;
+	hash = sHT_index_nospec(hash, table->values_length);
+	e = &table->values[hash];
+	/* try to do a tail call */
+	return sHT_streq(table->string_pool + e->name, string, e->value, 0, e->length, length);
+}

+ 76 - 0
collateral/index-foreach.smpl

@@ -0,0 +1,76 @@
+// sHT -- introduce sHT_index_iterate
+// Copyright (C) 2019 Ariadne Devos
+//
+// This program 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.
+//
+// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+
+// sHT_index_iterate does index speculative index clipping itself
+// (see Spectre), reduces work for formal analysis (is the correct
+// comparison operator used? Is the index an integer? Is it clipped?),
+// centralises optimisation decisions (branch predicition information),
+// and is informative.
+
+@ abstract_index_iterate0 @
+identifier i;
+expression n;
+iterator name sHT_index_iterate;
+expression a;
+type T;
+@@
+(
+- for (i = 0; \(i < n\|sHT_gt(n, i)\|!sHT_ge(i, n)\); i++)
++ sHT_index_iterate(i, n)
+  {
+(
+    ... sHT_index_nospec(i, n) ...
+|
+    ... a[i] ...
+)
+  }
+|
++ size_t i;
+- for (T i = 0; \(i < n\|sHT_gt(n, i)\|!sHT_ge(i, n)\); i++)
++ sHT_index_iterate(i, n)
+  {
+(
+    ... sHT_index_nospec(i, n) ...
+|
+    ... a[i] ...
+)
+  }
+)
+
+@ no_clip_assign @
+identifier i;
+expression n;
+@@
+  sHT_index_iterate(i, n) {
+  ...
+- i = sHT_index_nospec(i, n);
+  ...
+  }
+
+@ has_header @
+@@
+  #include <sHT/index.h>
+
+@ use_index @
+statement S;
+@@
+  sHT_index_iterate(...)
+  S
+
+@ add_header depends on use_index && !has_header @
+@@
+  #include <sHT/...>
++ #include <sHT/index.h>

+ 73 - 0
http/headers.sh

@@ -0,0 +1,73 @@
+prepare s2_http_header
+pre '/* s^2 - hash HTTP header names'
+pre '   Copyright (C) 2019 Ariadne Devos'
+pre ''
+pre '   This program is free software: you can redistribute it and/or modify'
+pre '   it under the terms of the GNU General Public License as published by'
+pre '   the Free Software Foundation, either version 3 of the License, or'
+pre '   (at your option) any later version.'
+pre ''
+pre '   This program is distributed in the hope that it will be useful,'
+pre '   but WITHOUT ANY WARRANTY; without even the implied warranty of'
+pre '   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the'
+pre '   GNU General Public License for more details.'
+pre ''
+pre '   You should have received a copy of the GNU General Public License'
+pre '   along with this program.  If not, see <http://www.gnu.org/licenses/>. */'
+pre ''
+pre '#include <sHT/web/header.h>'
+
+# (RFC 7540 (HTTP/2), 8.1.2 HTTP Header Fields, p. 35)
+# ‘Just as in HTTP/1.x, header field names are strings of ASCII characters
+# that are compared in a case-insensitive fashion. However, header field
+# names MUST be converted to lowercase prior to their encoding in HTTP/2.’
+#
+# (RFC 2616 (HTTP/1.1), 4.2 Message Headers, p. 31)
+# ‘Field names are case-insensitive’
+#
+# Therefore, internally always use lowercase, and in HTTP/1.1 mode, downcase
+# all input. (TODO: gperf's %ignore-case option might be interesting.)
+
+function entries() {
+	local class="$1"
+	local x
+	local y
+	shift
+	for x in "$@"; do
+		y="${x^^}"
+		y="${y//-/_}"
+		entry "${x,,}" "S2_HTTP_${class}_${y}"
+	done
+}
+
+# (RFC 2616 (HTTP/1.1), 4.5 General Header Fields, p. 34-35)
+# Applicable to both requests and responses.
+# ‘Unrecognized header fields are treated as entity-header fields’
+entries 'GENERAL' Cache-Control Connection Date Pragma Trailer \
+  Transfer-Encoding Upgrade Via Warning
+
+# (RFC 2616 (HTTP/1.1), 5.3 Request Header Fields, p. 38-39)
+# Only for requests.
+# ‘Unrecognized header fields are treated as entity-header fields’
+entries 'REQUEST' Accept Accept-Charset Accept-Encoding Accept-Language \
+  Authorization Expect From Host If-Match If-Modified-Since If-None-Match \
+  If-Range If-Unmodified-Since Max-Forwards Proxy-Authorization Range Referer \
+  TE User-Agent
+
+# (RFC 2616 (HTTP/1.1), 6.2 Response Header Fields, p. 41-42)
+# Only for responses.
+# ‘Unrecognized header fields are treated as entity-header fields’
+entries 'RESPONSE' Accept-Ranges Age ETag Location Proxy-Authenticate \
+  Retry-After Server Vary WWW-Authenticate
+
+# (RFC 2616 (HTTP/1.1), 7.1 Entity Header Fields, p. 42)
+# For requests with a body (e.g. POST) or responses.
+# E.g., about a PDF document for a resource requested via GET,
+# or about an upload via POST.
+#
+# ‘Unrecognized header fields SHOULD be ignored by the recipient’
+# ‘Unrecognized header fields MUST be forwarded by transparent proxies’
+entries 'ENTITY' Allow Content-Encoding Content-Language Content-Length \
+  Content-Location Content-MD5 Content-Range Content-Type Expires Last-Modified
+
+solve

+ 0 - 44
http/method.c

@@ -1,44 +0,0 @@
-/* s2 - has method names
-   Copyright (C) 2018 Ariadne Devos
-
-   This program 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.
-
-   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. */
-
-#include <sHT/bitops.h>
-#include <sHT/nospec.h>
-#include <sHT/web/state.h>
-
-/* May be incorrect if method doesn't exist */
-static int
-sHT_index_first_space8(char *bytes)
-{
-#ifdef sHT_word_byte_index64
-	return sHT_word_byte_index64(sHT_unaligned_load64(bytes), ' ');
-#endif
-	for (int i = S2_HTTP_METHOD_MIN_LENGTH; sHT_test_hidden(i, i < S2_HTTP_METHOD_MAX_LENGTH); i++) {
-		i = sHT_index_nospec(i, S2_HTTP_METHOD_MAX_LENGTH);
-		char byte = bytes[i];
-		if (sHT_test_hidden(byte, byte == ' '))
-			return i;
-	}
-	return S2_HTTP_METHOD_MAX_LENGTH;
-}
-
-struct s2_http_method_parsed
-s2_http_method_parse_fast(char bytes[8])
-{
-	struct s2_http_method_parsed ret;
-	ret.length = sHT_index_first_space8(bytes);
-	ret.method = sHT_modulo_nospec(3 * ret.length + bytes[1], 12);
-	return ret;
-}

+ 29 - 0
http/methods.sh

@@ -0,0 +1,29 @@
+prepare s2_http_method
+pre '/* s^2 - HTTP methods'
+pre '   Copyright (C) 2018 Ariadne Devos'
+pre ''
+pre '   This program is free software: you can redistribute it and/or modify'
+pre '   it under the terms of the GNU General Public License as published by'
+pre '   the Free Software Foundation, either version 3 of the License, or'
+pre '   (at your option) any later version.'
+pre ''
+pre '   This program is distributed in the hope that it will be useful,'
+pre '   but WITHOUT ANY WARRANTY; without even the implied warranty of'
+pre '   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the'
+pre '   GNU General Public License for more details.'
+pre ''
+pre '   You should have received a copy of the GNU General Public License'
+pre '   along with this program.  If not, see <http://www.gnu.org/licenses/>. */'
+pre ''
+pre '#include <sHT/web/method.h>'
+
+# (RFC 2616 (HTTP/1.1), 5.1.1 Method, p. 36)
+for x in OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT; do
+	entry "$x" "S2_HTTP_METHOD_$x"
+done
+# (RFC 4918 (WebDAV), Table of Contents, p. 2-3)
+for x in PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK; do
+	entry "$x" "S2_WEBDAV_METHOD_$x"
+done
+
+solve

+ 0 - 0
sHT/index.h


Some files were not shown because too many files changed in this diff