[v5,3/5] test/hash: add additional thash tests

Message ID 1618847995-91229-4-git-send-email-vladimir.medvedkin@intel.com (mailing list archive)
State Accepted, archived
Delegated to: Thomas Monjalon
Headers
Series Predictable RSS feature |

Checks

Context Check Description
ci/checkpatch success coding style OK

Commit Message

Vladimir Medvedkin April 19, 2021, 3:59 p.m. UTC
  This patch adds tests for predictable RSS feature.

Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
Acked-by: Yipeng Wang <yipeng1.wang@intel.com>
---
 app/test/test_thash.c | 469 +++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 463 insertions(+), 6 deletions(-)
  

Comments

David Marchand April 29, 2021, 9:13 a.m. UTC | #1
Hello Vladimir,

On Mon, Apr 19, 2021 at 6:00 PM Vladimir Medvedkin
<vladimir.medvedkin@intel.com> wrote:
> +static int
> +test_adjust_tuple(void)
> +{
> +       struct rte_thash_ctx *ctx;
> +       struct rte_thash_subtuple_helper *h;
> +       const int key_len = 40;
> +       const uint8_t *new_key;
> +       uint8_t tuple[TUPLE_SZ];
> +       uint32_t tmp_tuple[TUPLE_SZ / sizeof(uint32_t)];
> +       uint32_t tuple_copy[TUPLE_SZ / sizeof(uint32_t)];
> +       uint32_t hash;
> +       int reta_sz = CHAR_BIT;
> +       int ret;
> +       unsigned int i, desired_value = rte_rand() & HASH_MSK(reta_sz);
> +
> +       memset(tuple, 0xab, TUPLE_SZ);
> +
> +       ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
> +       RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
> +
> +       /*
> +        * set offset to be in the middle of a byte
> +        * set size of the subtuple to be 2 * rets_sz
> +        * to have the room for random bits
> +        */
> +       ret = rte_thash_add_helper(ctx, "test", reta_sz * 2,
> +               (5 * CHAR_BIT) + 4);
> +       RTE_TEST_ASSERT(ret == 0, "can not add helper, ret %d\n", ret);
> +
> +       new_key = rte_thash_get_key(ctx);
> +
> +       h = rte_thash_get_helper(ctx, "test");
> +       RTE_TEST_ASSERT(h != NULL, "can not find helper\n");
> +
> +       ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
> +               1, NULL, NULL);
> +       RTE_TEST_ASSERT(ret == 0, "can not adjust tuple, ret %d\n", ret);
> +
> +       for (i = 0; i < (TUPLE_SZ / 4); i++)
> +               tmp_tuple[i] =
> +                       rte_be_to_cpu_32(*(uint32_t *)&tuple[i * 4]);
> +
> +       hash = rte_softrss(tmp_tuple, TUPLE_SZ / 4, new_key);
> +       RTE_TEST_ASSERT((hash & HASH_MSK(reta_sz)) ==
> +               desired_value, "bad desired value\n");
> +
> +
> +       /* Pass previously calculated tuple to callback function */
> +       memcpy(tuple_copy, tuple, TUPLE_SZ);
> +
> +       memset(tuple, 0xab, TUPLE_SZ);
> +       ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
> +               1, cmp_tuple_eq, tuple_copy);
> +       RTE_TEST_ASSERT(ret == -EEXIST,
> +               "adjust tuple didn't indicate collision\n");
> +
> +       /*
> +        * Make the function to generate random bits into subtuple
> +        * after first adjustment attempt.
> +        */
> +       memset(tuple, 0xab, TUPLE_SZ);
> +       ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
> +               2, cmp_tuple_eq, tuple_copy);
> +       RTE_TEST_ASSERT(ret == 0, "can not adjust tuple, ret %d\n", ret);


This check failed at least once in the CI, for no obvious (for me) reason.
https://github.com/ovsrobot/dpdk/actions/runs/794713806

87/96 DPDK:fast-tests / thash_autotest        FAIL     0.17 s (exit
status 255 or signal 127 SIGinvalid)

--- command ---
DPDK_TEST='thash_autotest'
/home/runner/work/dpdk/dpdk/build/app/test/dpdk-test -l 0-1
--file-prefix=thash_autotest
--- stdout ---
RTE>>thash_autotest
 + ------------------------------------------------------- +
 + Test Suite : thash autotest
 + ------------------------------------------------------- +
 + TestCase [ 0] : test_toeplitz_hash_calc succeeded
 + TestCase [ 1] : test_create_invalid succeeded
 + TestCase [ 2] : test_multiple_create succeeded
 + TestCase [ 3] : test_free_null succeeded
 + TestCase [ 4] : test_add_invalid_helper succeeded
 + TestCase [ 5] : test_find_existing succeeded
 + TestCase [ 6] : test_get_helper succeeded
 + TestCase [ 7] : test_period_overflow succeeded
 + TestCase [ 8] : test_predictable_rss_min_seq succeeded
 + TestCase [ 9] : test_predictable_rss_multirange succeeded
 + TestCase [10] : test_adjust_tuple failed
 + ------------------------------------------------------- +
 + Test Suite Summary
 + Tests Total :       11
 + Tests Skipped :      0
 + Tests Executed :    11
 + Tests Unsupported:   0
 + Tests Passed :      10
 + Tests Failed :       1
 + ------------------------------------------------------- +
Test Failed
RTE>>
--- stderr ---
EAL: Detected 2 lcore(s)
EAL: Detected 1 NUMA nodes
EAL: Detected shared linkage of DPDK
EAL: Multi-process socket /var/run/dpdk/thash_autotest/mp_socket
EAL: Selected IOVA mode 'PA'
EAL: No available 1048576 kB hugepages reported
EAL: VFIO support initialized
APP: HPET is not enabled, using TSC as default timer
HASH: Can't add helper  due to conflict with existing helper second_range
HASH: Can't generate m-sequence due to period overflow
EAL: Test assert test_adjust_tuple line 559 failed: can not adjust
tuple, ret -17

-------



> +
> +       for (i = 0; i < (TUPLE_SZ / 4); i++)
> +               tmp_tuple[i] =
> +                       rte_be_to_cpu_32(*(uint32_t *)&tuple[i * 4]);
> +
> +       hash = rte_softrss(tmp_tuple, TUPLE_SZ / 4, new_key);
> +       RTE_TEST_ASSERT((hash & HASH_MSK(reta_sz)) ==
> +               desired_value, "bad desired value\n");
> +
> +       rte_thash_free_ctx(ctx);
> +
> +       return TEST_SUCCESS;
> +}
  
Vladimir Medvedkin April 29, 2021, 9:17 a.m. UTC | #2
Hello David,

Oh, interesting, thanks for the report, I'll take a look!

On 29/04/2021 12:13, David Marchand wrote:
> Hello Vladimir,
> 
> On Mon, Apr 19, 2021 at 6:00 PM Vladimir Medvedkin
> <vladimir.medvedkin@intel.com> wrote:
>> +static int
>> +test_adjust_tuple(void)
>> +{
>> +       struct rte_thash_ctx *ctx;
>> +       struct rte_thash_subtuple_helper *h;
>> +       const int key_len = 40;
>> +       const uint8_t *new_key;
>> +       uint8_t tuple[TUPLE_SZ];
>> +       uint32_t tmp_tuple[TUPLE_SZ / sizeof(uint32_t)];
>> +       uint32_t tuple_copy[TUPLE_SZ / sizeof(uint32_t)];
>> +       uint32_t hash;
>> +       int reta_sz = CHAR_BIT;
>> +       int ret;
>> +       unsigned int i, desired_value = rte_rand() & HASH_MSK(reta_sz);
>> +
>> +       memset(tuple, 0xab, TUPLE_SZ);
>> +
>> +       ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
>> +       RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
>> +
>> +       /*
>> +        * set offset to be in the middle of a byte
>> +        * set size of the subtuple to be 2 * rets_sz
>> +        * to have the room for random bits
>> +        */
>> +       ret = rte_thash_add_helper(ctx, "test", reta_sz * 2,
>> +               (5 * CHAR_BIT) + 4);
>> +       RTE_TEST_ASSERT(ret == 0, "can not add helper, ret %d\n", ret);
>> +
>> +       new_key = rte_thash_get_key(ctx);
>> +
>> +       h = rte_thash_get_helper(ctx, "test");
>> +       RTE_TEST_ASSERT(h != NULL, "can not find helper\n");
>> +
>> +       ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
>> +               1, NULL, NULL);
>> +       RTE_TEST_ASSERT(ret == 0, "can not adjust tuple, ret %d\n", ret);
>> +
>> +       for (i = 0; i < (TUPLE_SZ / 4); i++)
>> +               tmp_tuple[i] =
>> +                       rte_be_to_cpu_32(*(uint32_t *)&tuple[i * 4]);
>> +
>> +       hash = rte_softrss(tmp_tuple, TUPLE_SZ / 4, new_key);
>> +       RTE_TEST_ASSERT((hash & HASH_MSK(reta_sz)) ==
>> +               desired_value, "bad desired value\n");
>> +
>> +
>> +       /* Pass previously calculated tuple to callback function */
>> +       memcpy(tuple_copy, tuple, TUPLE_SZ);
>> +
>> +       memset(tuple, 0xab, TUPLE_SZ);
>> +       ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
>> +               1, cmp_tuple_eq, tuple_copy);
>> +       RTE_TEST_ASSERT(ret == -EEXIST,
>> +               "adjust tuple didn't indicate collision\n");
>> +
>> +       /*
>> +        * Make the function to generate random bits into subtuple
>> +        * after first adjustment attempt.
>> +        */
>> +       memset(tuple, 0xab, TUPLE_SZ);
>> +       ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
>> +               2, cmp_tuple_eq, tuple_copy);
>> +       RTE_TEST_ASSERT(ret == 0, "can not adjust tuple, ret %d\n", ret);
> 
> 
> This check failed at least once in the CI, for no obvious (for me) reason.
> https://github.com/ovsrobot/dpdk/actions/runs/794713806
> 
> 87/96 DPDK:fast-tests / thash_autotest        FAIL     0.17 s (exit
> status 255 or signal 127 SIGinvalid)
> 
> --- command ---
> DPDK_TEST='thash_autotest'
> /home/runner/work/dpdk/dpdk/build/app/test/dpdk-test -l 0-1
> --file-prefix=thash_autotest
> --- stdout ---
> RTE>>thash_autotest
>   + ------------------------------------------------------- +
>   + Test Suite : thash autotest
>   + ------------------------------------------------------- +
>   + TestCase [ 0] : test_toeplitz_hash_calc succeeded
>   + TestCase [ 1] : test_create_invalid succeeded
>   + TestCase [ 2] : test_multiple_create succeeded
>   + TestCase [ 3] : test_free_null succeeded
>   + TestCase [ 4] : test_add_invalid_helper succeeded
>   + TestCase [ 5] : test_find_existing succeeded
>   + TestCase [ 6] : test_get_helper succeeded
>   + TestCase [ 7] : test_period_overflow succeeded
>   + TestCase [ 8] : test_predictable_rss_min_seq succeeded
>   + TestCase [ 9] : test_predictable_rss_multirange succeeded
>   + TestCase [10] : test_adjust_tuple failed
>   + ------------------------------------------------------- +
>   + Test Suite Summary
>   + Tests Total :       11
>   + Tests Skipped :      0
>   + Tests Executed :    11
>   + Tests Unsupported:   0
>   + Tests Passed :      10
>   + Tests Failed :       1
>   + ------------------------------------------------------- +
> Test Failed
> RTE>>
> --- stderr ---
> EAL: Detected 2 lcore(s)
> EAL: Detected 1 NUMA nodes
> EAL: Detected shared linkage of DPDK
> EAL: Multi-process socket /var/run/dpdk/thash_autotest/mp_socket
> EAL: Selected IOVA mode 'PA'
> EAL: No available 1048576 kB hugepages reported
> EAL: VFIO support initialized
> APP: HPET is not enabled, using TSC as default timer
> HASH: Can't add helper  due to conflict with existing helper second_range
> HASH: Can't generate m-sequence due to period overflow
> EAL: Test assert test_adjust_tuple line 559 failed: can not adjust
> tuple, ret -17
> 
> -------
> 
> 
> 
>> +
>> +       for (i = 0; i < (TUPLE_SZ / 4); i++)
>> +               tmp_tuple[i] =
>> +                       rte_be_to_cpu_32(*(uint32_t *)&tuple[i * 4]);
>> +
>> +       hash = rte_softrss(tmp_tuple, TUPLE_SZ / 4, new_key);
>> +       RTE_TEST_ASSERT((hash & HASH_MSK(reta_sz)) ==
>> +               desired_value, "bad desired value\n");
>> +
>> +       rte_thash_free_ctx(ctx);
>> +
>> +       return TEST_SUCCESS;
>> +}
> 
>
  
Stanislaw Kardach April 29, 2021, 6:45 p.m. UTC | #3
On Thu, Apr 29, 2021 at 12:17:08PM +0300, Medvedkin, Vladimir wrote:
<snip>
> > Test Failed
> > RTE>>
> > --- stderr ---
> > EAL: Detected 2 lcore(s)
> > EAL: Detected 1 NUMA nodes
> > EAL: Detected shared linkage of DPDK
> > EAL: Multi-process socket /var/run/dpdk/thash_autotest/mp_socket
> > EAL: Selected IOVA mode 'PA'
> > EAL: No available 1048576 kB hugepages reported
> > EAL: VFIO support initialized
> > APP: HPET is not enabled, using TSC as default timer
> > HASH: Can't add helper  due to conflict with existing helper second_range
> > HASH: Can't generate m-sequence due to period overflow
> > EAL: Test assert test_adjust_tuple line 559 failed: can not adjust
> > tuple, ret -17
> > 

I can see the same issue on my side. Happening randomly, more often on a
RISC-V target than on my laptop (i5-10210U). Though the reproduction
seems to be a lot of patience and the following:

  meson test --repeat 100000 DPDK:fast-tests / thash_autotest

I wonder if it can be related to the desired_value in test_adjust_tuple
being a randomized value without setting the seed prior to the test?
I haven't analyzed the code in-depth but it seems that the
rte_thash_add_helper() also uses a random lfsr which is then used in the
subkey generation. Could this contribute to the randomness of the issue?
  
Vladimir Medvedkin May 4, 2021, 2:06 p.m. UTC | #4
Hi Stanislaw,

On 29/04/2021 21H:45, Stanislaw Kardach wrote:
> On Thu, Apr 29, 2021 at 12:17:08PM +0300, Medvedkin, Vladimir wrote:
> <snip>
>>> Test Failed
>>> RTE>>
>>> --- stderr ---
>>> EAL: Detected 2 lcore(s)
>>> EAL: Detected 1 NUMA nodes
>>> EAL: Detected shared linkage of DPDK
>>> EAL: Multi-process socket /var/run/dpdk/thash_autotest/mp_socket
>>> EAL: Selected IOVA mode 'PA'
>>> EAL: No available 1048576 kB hugepages reported
>>> EAL: VFIO support initialized
>>> APP: HPET is not enabled, using TSC as default timer
>>> HASH: Can't add helper  due to conflict with existing helper second_range
>>> HASH: Can't generate m-sequence due to period overflow
>>> EAL: Test assert test_adjust_tuple line 559 failed: can not adjust
>>> tuple, ret -17
>>>
> 
> I can see the same issue on my side. Happening randomly, more often on a
> RISC-V target than on my laptop (i5-10210U). Though the reproduction
> seems to be a lot of patience and the following:
> 
>    meson test --repeat 100000 DPDK:fast-tests / thash_autotest
> 
> I wonder if it can be related to the desired_value in test_adjust_tuple
> being a randomized value without setting the seed prior to the test?
> I haven't analyzed the code in-depth but it seems that the
> rte_thash_add_helper() also uses a random lfsr which is then used in the
> subkey generation. Could this contribute to the randomness of the issue?
> 

The problem here is that the rte_thash_adjust_tuple() function does not 
guarantee that it will find a tuple in a given number of attempts in the 
case when the fn() callback is passed. It depends on random, the logic 
of the fn() callback, and the content of the userdata.

So, in the test we have:
- 96-bit("sizeof(tuple) * CHAR_BITS") tuple
- 16-bit("reta_sz * 2") changeable part of the tuple (i.e. subtuple)
- we want to have a collision in the hash value for 8("reta_sz") least 
significant bits

In other words there are 16 changeable bits inside the subtuple and 8 
least significant bits of them are changed by the 
rte_thash_get_complement() in order to produce collision. The rest 8 
bits are our entropy, i.e. 2^8 different subtuples can produce the 
required collision.

in the problematic call
ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
                 2, cmp_tuple_eq, tuple_copy);

the original tuple is passed to be changed and the tuple_copy we got 
from the previous invocation. Content of the tuple_copy previously was 
derived from the original tuple applying 8 bit complement on the 
subtuple part.

On the first attempt the function gets the complement and applies it to 
the original tuple, tuple becomes equal to the tuple_copy. After it 
calls the callback and finds that tuple is equal to the tuple_copy. Then 
on the second attempt random bits are xored with 16 bit subtuple value. 
If the first 8 MSBs of random are zeroes, then after applying a new 
complement to the tuple it will be equal to the tuple_copy. While we 
allow only 2 attempts function returns -EEXIST.

I reworked rte_thash_adjust_tuple() removing the randomness. Instead it 
increments the subtuple part which is free of complementary bits.
  

Patch

diff --git a/app/test/test_thash.c b/app/test/test_thash.c
index a6aadd1..d8981fb 100644
--- a/app/test/test_thash.c
+++ b/app/test/test_thash.c
@@ -5,11 +5,15 @@ 
 #include <rte_common.h>
 #include <rte_eal.h>
 #include <rte_ip.h>
+#include <rte_random.h>
 
 #include "test.h"
 
 #include <rte_thash.h>
 
+#define HASH_MSK(reta_sz)	((1 << reta_sz) - 1)
+#define TUPLE_SZ	(RTE_THASH_V4_L4_LEN * 4)
+
 struct test_thash_v4 {
 	uint32_t	dst_ip;
 	uint32_t	src_ip;
@@ -75,7 +79,7 @@  uint8_t default_rss_key[] = {
 };
 
 static int
-test_thash(void)
+test_toeplitz_hash_calc(void)
 {
 	uint32_t i, j;
 	union rte_thash_tuple tuple;
@@ -100,7 +104,7 @@  test_thash(void)
 				RTE_THASH_V4_L4_LEN, default_rss_key);
 		if ((rss_l3 != v4_tbl[i].hash_l3) ||
 				(rss_l3l4 != v4_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 		/*Calculate hash with converted key*/
 		rss_l3 = rte_softrss_be((uint32_t *)&tuple,
 				RTE_THASH_V4_L3_LEN, rss_key_be);
@@ -108,7 +112,7 @@  test_thash(void)
 				RTE_THASH_V4_L4_LEN, rss_key_be);
 		if ((rss_l3 != v4_tbl[i].hash_l3) ||
 				(rss_l3l4 != v4_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 	}
 	for (i = 0; i < RTE_DIM(v6_tbl); i++) {
 		/*Fill ipv6 hdr*/
@@ -127,7 +131,7 @@  test_thash(void)
 				RTE_THASH_V6_L4_LEN, default_rss_key);
 		if ((rss_l3 != v6_tbl[i].hash_l3) ||
 				(rss_l3l4 != v6_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 		/*Calculate hash with converted key*/
 		rss_l3 = rte_softrss_be((uint32_t *)&tuple,
 				RTE_THASH_V6_L3_LEN, rss_key_be);
@@ -135,9 +139,462 @@  test_thash(void)
 				RTE_THASH_V6_L4_LEN, rss_key_be);
 		if ((rss_l3 != v6_tbl[i].hash_l3) ||
 				(rss_l3l4 != v6_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 	}
-	return 0;
+	return TEST_SUCCESS;
+}
+
+static int
+test_create_invalid(void)
+{
+	struct rte_thash_ctx *ctx;
+	int key_len = 40;
+	int reta_sz = 7;
+
+	ctx = rte_thash_init_ctx(NULL, key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	ctx = rte_thash_init_ctx("test", 0, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	ctx = rte_thash_init_ctx(NULL, key_len, 1, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	ctx = rte_thash_init_ctx(NULL, key_len, 17, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_multiple_create(void)
+{
+	struct rte_thash_ctx *ctx;
+	int key_len = 40;
+	int reta_sz = 7;
+	int i;
+
+	for (i = 0; i < 100; i++) {
+		ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+		RTE_TEST_ASSERT(ctx != NULL, "Can not create CTX\n");
+
+		rte_thash_free_ctx(ctx);
+	}
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_free_null(void)
+{
+	struct rte_thash_ctx *ctx;
+
+	ctx = rte_thash_init_ctx("test", 40, 7, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create CTX\n");
+
+	rte_thash_free_ctx(ctx);
+	rte_thash_free_ctx(NULL);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_add_invalid_helper(void)
+{
+	struct rte_thash_ctx *ctx;
+	const int key_len = 40;
+	int reta_sz = 7;
+	int ret;
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	ret = rte_thash_add_helper(NULL, "test", reta_sz, 0);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, NULL, reta_sz, 0);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "test", reta_sz - 1, 0);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "test", reta_sz, key_len * 8);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "first_range", reta_sz, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	ret = rte_thash_add_helper(ctx, "first_range", reta_sz, 0);
+	RTE_TEST_ASSERT(ret == -EEXIST,
+		"Call succeeded with duplicated name\n");
+
+	/*
+	 * Create second helper with offset 3 * reta_sz.
+	 * Note first_range helper created range in key:
+	 * [0, 32 + length{= reta_sz} - 1), i.e [0, 37).
+	 * second range is [44, 81)
+	 */
+	ret = rte_thash_add_helper(ctx, "second_range", reta_sz,
+		32 +  2 * reta_sz);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	/*
+	 * Try to create overlapping with first_ and second_ ranges,
+	 * i.e. [6, 49)
+	 */
+	ret = rte_thash_add_helper(ctx, "third_range", 2 * reta_sz, reta_sz);
+	RTE_TEST_ASSERT(ret == -EEXIST,
+		"Call succeeded with overlapping ranges\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_find_existing(void)
+{
+	struct rte_thash_ctx *ctx, *ret_ctx;
+
+	ctx = rte_thash_init_ctx("test", 40, 7, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	ret_ctx = rte_thash_find_existing("test");
+	RTE_TEST_ASSERT(ret_ctx != NULL, "can not find existing ctx\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_get_helper(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h;
+	int ret;
+
+	ctx = rte_thash_init_ctx("test", 40, 7, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create thash ctx\n");
+
+	h = rte_thash_get_helper(NULL, "first_range");
+	RTE_TEST_ASSERT(h == NULL, "Call succeeded with invalid parameters\n");
+
+	h = rte_thash_get_helper(ctx, NULL);
+	RTE_TEST_ASSERT(h == NULL, "Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "first_range", 8, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	h = rte_thash_get_helper(ctx, "first_range");
+	RTE_TEST_ASSERT(h != NULL, "Can not find helper\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_period_overflow(void)
+{
+	struct rte_thash_ctx *ctx;
+	int reta_sz = 7; /* reflects polynomial degree */
+	int ret;
+
+	/* first create without RTE_THASH_IGNORE_PERIOD_OVERFLOW flag */
+	ctx = rte_thash_init_ctx("test", 40, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create thash ctx\n");
+
+	/* requested range > (2^reta_sz) - 1 */
+	ret = rte_thash_add_helper(ctx, "test", (1 << reta_sz), 0);
+	RTE_TEST_ASSERT(ret == -ENOSPC,
+		"Call succeeded with invalid parameters\n");
+
+	/* requested range == len + 32 - 1, smaller than (2^reta_sz) - 1 */
+	ret = rte_thash_add_helper(ctx, "test", (1 << reta_sz) - 32, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	rte_thash_free_ctx(ctx);
+
+	/* create with RTE_THASH_IGNORE_PERIOD_OVERFLOW flag */
+	ctx = rte_thash_init_ctx("test", 40, reta_sz, NULL,
+		RTE_THASH_IGNORE_PERIOD_OVERFLOW);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create thash ctx\n");
+
+	/* requested range > (2^reta_sz - 1) */
+	ret = rte_thash_add_helper(ctx, "test", (1 << reta_sz) + 10, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_predictable_rss_min_seq(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h;
+	const int key_len = 40;
+	int reta_sz = 6;
+	uint8_t initial_key[key_len];
+	const uint8_t *new_key;
+	int ret;
+	union rte_thash_tuple tuple;
+	uint32_t orig_hash, adj_hash, adj;
+	unsigned int desired_value = 27 & HASH_MSK(reta_sz);
+	uint16_t port_value = 22;
+
+	memset(initial_key, 0, key_len);
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, initial_key,
+		RTE_THASH_MINIMAL_SEQ);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	ret = rte_thash_add_helper(ctx, "snat", sizeof(uint16_t) * 8,
+		offsetof(union rte_thash_tuple, v4.sport) * 8);
+	RTE_TEST_ASSERT(ret == 0, "can not add helper, ret %d\n", ret);
+
+	h = rte_thash_get_helper(ctx, "snat");
+	RTE_TEST_ASSERT(h != NULL, "can not find helper\n");
+
+	new_key = rte_thash_get_key(ctx);
+	tuple.v4.src_addr = RTE_IPV4(0, 0, 0, 0);
+	tuple.v4.dst_addr = RTE_IPV4(0, 0, 0, 0);
+	tuple.v4.sport = 0;
+	tuple.v4.sport = rte_cpu_to_be_16(port_value);
+	tuple.v4.dport = 0;
+	tuple.v4.sctp_tag = rte_be_to_cpu_32(tuple.v4.sctp_tag);
+
+	orig_hash = rte_softrss((uint32_t *)&tuple,
+		RTE_THASH_V4_L4_LEN, new_key);
+	adj = rte_thash_get_complement(h, orig_hash, desired_value);
+
+	tuple.v4.sctp_tag = rte_cpu_to_be_32(tuple.v4.sctp_tag);
+	tuple.v4.sport ^= rte_cpu_to_be_16(adj);
+	tuple.v4.sctp_tag = rte_be_to_cpu_32(tuple.v4.sctp_tag);
+
+	adj_hash = rte_softrss((uint32_t *)&tuple,
+		RTE_THASH_V4_L4_LEN, new_key);
+	RTE_TEST_ASSERT((adj_hash & HASH_MSK(reta_sz)) ==
+		desired_value, "bad desired value\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * This test creates 7 subranges in the following order:
+ * range_one	= [56, 95),	len = 8, offset = 56
+ * range_two	= [64, 103),	len = 8, offset = 64
+ * range_three	= [120, 159),	len = 8, offset = 120
+ * range_four	= [48, 87),	len = 8, offset = 48
+ * range_five	= [57, 95),	len = 7, offset = 57
+ * range_six	= [40, 111),	len = 40, offset = 40
+ * range_seven	= [0, 39),	len = 8, offset = 0
+ */
+struct range {
+	const char *name;
+	int len;
+	int offset;
+	int byte_idx;
+};
+
+struct range rng_arr[] = {
+	{"one",   8,  56,  7},
+	{"two",   8,  64,  8},
+	{"three", 8,  120, 15},
+	{"four",  8,  48,  6},
+	{"six",   40, 40,  9},
+	{"five",  7,  57,  7},
+	{"seven", 8,  0,   0}
+};
+
+static int
+test_predictable_rss_multirange(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h[RTE_DIM(rng_arr)];
+	const uint8_t *new_key;
+	const int key_len = 40;
+	int reta_sz = 7;
+	unsigned int i, j, k;
+	int ret;
+	uint32_t desired_value = rte_rand() & HASH_MSK(reta_sz);
+	uint8_t tuples[RTE_DIM(rng_arr)][16] = { {0} };
+	uint32_t *ptr;
+	uint32_t hashes[RTE_DIM(rng_arr)];
+	uint32_t adj_hashes[RTE_DIM(rng_arr)];
+	uint32_t adj;
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	for (i = 0; i < RTE_DIM(rng_arr); i++) {
+		ret = rte_thash_add_helper(ctx, rng_arr[i].name,
+			rng_arr[i].len, rng_arr[i].offset);
+		RTE_TEST_ASSERT(ret == 0, "can not add helper\n");
+
+		h[i] = rte_thash_get_helper(ctx, rng_arr[i].name);
+		RTE_TEST_ASSERT(h[i] != NULL, "can not find helper\n");
+	}
+	new_key = rte_thash_get_key(ctx);
+
+	/*
+	 * calculate hashes, complements, then adjust keys with
+	 * complements and recalsulate hashes
+	 */
+	for (i = 0; i < RTE_DIM(rng_arr); i++) {
+		for (k = 0; k < 100; k++) {
+			/* init with random keys */
+			ptr = (uint32_t *)&tuples[i][0];
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_rand();
+			/* convert keys from BE to CPU byte order */
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_be_to_cpu_32(ptr[j]);
+
+			hashes[i] = rte_softrss(ptr, 4, new_key);
+			adj = rte_thash_get_complement(h[i], hashes[i],
+				desired_value);
+			/* convert back to BE to adjust the value */
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_cpu_to_be_32(ptr[j]);
+
+			tuples[i][rng_arr[i].byte_idx] ^= adj;
+
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_be_to_cpu_32(ptr[j]);
+
+			adj_hashes[i] = rte_softrss(ptr, 4, new_key);
+			RTE_TEST_ASSERT((adj_hashes[i] & HASH_MSK(reta_sz)) ==
+				desired_value,
+				"bad desired value for %d tuple\n", i);
+		}
+	}
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+cmp_tuple_eq(void *userdata, uint8_t *tuple)
+{
+	return memcmp(userdata, tuple, TUPLE_SZ);
+}
+
+static int
+test_adjust_tuple(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h;
+	const int key_len = 40;
+	const uint8_t *new_key;
+	uint8_t tuple[TUPLE_SZ];
+	uint32_t tmp_tuple[TUPLE_SZ / sizeof(uint32_t)];
+	uint32_t tuple_copy[TUPLE_SZ / sizeof(uint32_t)];
+	uint32_t hash;
+	int reta_sz = CHAR_BIT;
+	int ret;
+	unsigned int i, desired_value = rte_rand() & HASH_MSK(reta_sz);
+
+	memset(tuple, 0xab, TUPLE_SZ);
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	/*
+	 * set offset to be in the middle of a byte
+	 * set size of the subtuple to be 2 * rets_sz
+	 * to have the room for random bits
+	 */
+	ret = rte_thash_add_helper(ctx, "test", reta_sz * 2,
+		(5 * CHAR_BIT) + 4);
+	RTE_TEST_ASSERT(ret == 0, "can not add helper, ret %d\n", ret);
+
+	new_key = rte_thash_get_key(ctx);
+
+	h = rte_thash_get_helper(ctx, "test");
+	RTE_TEST_ASSERT(h != NULL, "can not find helper\n");
+
+	ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
+		1, NULL, NULL);
+	RTE_TEST_ASSERT(ret == 0, "can not adjust tuple, ret %d\n", ret);
+
+	for (i = 0; i < (TUPLE_SZ / 4); i++)
+		tmp_tuple[i] =
+			rte_be_to_cpu_32(*(uint32_t *)&tuple[i * 4]);
+
+	hash = rte_softrss(tmp_tuple, TUPLE_SZ / 4, new_key);
+	RTE_TEST_ASSERT((hash & HASH_MSK(reta_sz)) ==
+		desired_value, "bad desired value\n");
+
+
+	/* Pass previously calculated tuple to callback function */
+	memcpy(tuple_copy, tuple, TUPLE_SZ);
+
+	memset(tuple, 0xab, TUPLE_SZ);
+	ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
+		1, cmp_tuple_eq, tuple_copy);
+	RTE_TEST_ASSERT(ret == -EEXIST,
+		"adjust tuple didn't indicate collision\n");
+
+	/*
+	 * Make the function to generate random bits into subtuple
+	 * after first adjustment attempt.
+	 */
+	memset(tuple, 0xab, TUPLE_SZ);
+	ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
+		2, cmp_tuple_eq, tuple_copy);
+	RTE_TEST_ASSERT(ret == 0, "can not adjust tuple, ret %d\n", ret);
+
+	for (i = 0; i < (TUPLE_SZ / 4); i++)
+		tmp_tuple[i] =
+			rte_be_to_cpu_32(*(uint32_t *)&tuple[i * 4]);
+
+	hash = rte_softrss(tmp_tuple, TUPLE_SZ / 4, new_key);
+	RTE_TEST_ASSERT((hash & HASH_MSK(reta_sz)) ==
+		desired_value, "bad desired value\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static struct unit_test_suite thash_tests = {
+	.suite_name = "thash autotest",
+	.setup = NULL,
+	.teardown = NULL,
+	.unit_test_cases = {
+	TEST_CASE(test_toeplitz_hash_calc),
+	TEST_CASE(test_create_invalid),
+	TEST_CASE(test_multiple_create),
+	TEST_CASE(test_free_null),
+	TEST_CASE(test_add_invalid_helper),
+	TEST_CASE(test_find_existing),
+	TEST_CASE(test_get_helper),
+	TEST_CASE(test_period_overflow),
+	TEST_CASE(test_predictable_rss_min_seq),
+	TEST_CASE(test_predictable_rss_multirange),
+	TEST_CASE(test_adjust_tuple),
+	TEST_CASES_END()
+	}
+};
+
+static int
+test_thash(void)
+{
+	return unit_test_suite_runner(&thash_tests);
 }
 
 REGISTER_TEST_COMMAND(thash_autotest, test_thash);