diff --git a/UnitTest1/unittest1.cpp b/UnitTest1/unittest1.cpp index aefd5ce99f6f19b1c69d5a10cac81bf10a141af7..37e9a2cd2c2c7668e38718a27595814d9d2cbfcd 100644 --- a/UnitTest1/unittest1.cpp +++ b/UnitTest1/unittest1.cpp @@ -574,6 +574,15 @@ namespace UnitTest1 int ret = stress_test(); Assert::AreEqual(ret, 0); - } + } + + + TEST_METHOD(splay) + { + int ret = splay_test(); + + Assert::AreEqual(ret, 0); + } + }; } diff --git a/picoquic/picoquic.vcxproj b/picoquic/picoquic.vcxproj index 269b4d982677ef5bf5de48307e9f8a18a38edc75..516992d54301c87c361e9fccc218f1b05a168cdd 100644 --- a/picoquic/picoquic.vcxproj +++ b/picoquic/picoquic.vcxproj @@ -142,6 +142,7 @@ <ClCompile Include="logger.c" /> <ClCompile Include="newreno.c" /> <ClCompile Include="picosocks.c" /> + <ClCompile Include="picosplay.c" /> <ClCompile Include="quicctx.c" /> <ClCompile Include="packet.c" /> <ClCompile Include="picohash.c" /> @@ -157,6 +158,7 @@ <ClInclude Include="picohash.h" /> <ClInclude Include="picoquic_internal.h" /> <ClInclude Include="picosocks.h" /> + <ClInclude Include="picosplay.h" /> <ClInclude Include="picotlsapi.h" /> <ClInclude Include="picoquic.h" /> <ClInclude Include="tls_api.h" /> diff --git a/picoquic/picoquic.vcxproj.filters b/picoquic/picoquic.vcxproj.filters index 89dba5362a4ddf7565a0d77fcb15eb3d98ece8b6..740662eba2b131daf0a06ad34e8bddc54625dd33 100644 --- a/picoquic/picoquic.vcxproj.filters +++ b/picoquic/picoquic.vcxproj.filters @@ -66,6 +66,9 @@ <ClCompile Include="ticket_store.c"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="picosplay.c"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="picoquic.h"> @@ -92,5 +95,8 @@ <ClInclude Include="picosocks.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="picosplay.h"> + <Filter>Header Files</Filter> + </ClInclude> </ItemGroup> </Project> \ No newline at end of file diff --git a/picoquic/picosplay.c b/picoquic/picosplay.c new file mode 100644 index 0000000000000000000000000000000000000000..ecf2eb6616c6ac91562402f5d28da145e463f6ef --- /dev/null +++ b/picoquic/picosplay.c @@ -0,0 +1,284 @@ +/* +* Author: Christian Huitema +* Copyright (c) 2018, Private Octopus, Inc. +* All rights reserved. +* +* Permission to use, copy, modify, and distribute this software for any +* purpose with or without fee is hereby granted, provided that the above +* copyright notice and this permission notice appear in all copies. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL Private Octopus, Inc. 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. +* +* This code is copied and adapted from https://github.com/lrem/splay, +* copyright (c) Remigiusz Modrzejewski 2014, filed on Github with MIT license. +*/ + +#include <stdlib.h> +#include <assert.h> +#include "picosplay.h" + +void picosplay_check_sanity(picosplay_tree *tree); + +/* The single most important utility function. */ +static void rotate(picosplay_node *child); +/* And a few more. */ +static picosplay_node* leftmost(picosplay_node *node); +static picosplay_node* rightmost(picosplay_node *node); + + +/* The meat: splay the node x. */ +static void zig(picosplay_node *x, picosplay_node *p); +static void zigzig(picosplay_node *x, picosplay_node *p); +static void zigzag(picosplay_node *x, picosplay_node *p); +static void splay(picosplay_tree *tree, picosplay_node *x) { + while(1) { + picosplay_node *p = x->parent; + if(p == NULL) { + tree->root = x; + return; + } + picosplay_node *g = p->parent; + if(p->parent == NULL) + zig(x, p); + else + if((x == p->left && p == g->left) || + (x == p->right && p == g->right)) + zigzig(x, p); + else + zigzag(x, p); + } +} + +/* When p is root, rotate on the edge between x and p.*/ +static void zig(picosplay_node *x, picosplay_node *p) { + rotate(x); +} + +/* When both x and p are left (or both right) children, + * rotate on edge between p and g, then on edge between x and p. + */ +static void zigzig(picosplay_node *x, picosplay_node *p) { + rotate(p); + rotate(x); +} + +/* When one of x and p is a left child and the other a right child, + * rotate on the edge between x and p, then on the new edge between x and g. + */ +static void zigzag(picosplay_node *x, picosplay_node *p) { + rotate(x); + rotate(x); +} + +/* Return an empty tree, storing the picosplay_comparator. */ +picosplay_tree* picosplay_new_tree(picosplay_comparator comp) { + picosplay_tree *new = malloc(sizeof(picosplay_tree)); + new->comp = comp; + new->root = NULL; + new->size = 0; + return new; +} + +/* picosplay_insert and return a new node with the given value, splaying the tree. + * The insertion is essentially a generic BST insertion. + */ +picosplay_node* picosplay_insert(picosplay_tree *tree, void *value) { + picosplay_node *new = malloc(sizeof(picosplay_node)); + new->value = value; + new->left = NULL; + new->right = NULL; + if(tree->root == NULL) { + tree->root = new; + new->parent = NULL; + } else { + picosplay_node *curr = tree->root; + picosplay_node *parent; + int left; + while(curr != NULL) { + parent = curr; + if(tree->comp(new->value, curr->value) < 0) { + left = 1; + curr = curr->left; + } else { + left = 0; + curr = curr->right; + } + } + new->parent = parent; + if(left) + parent->left = new; + else + parent->right = new; + } + splay(tree, new); + tree->size++; + return new; +} + +/* Find a node with the given value, splaying the tree. */ +picosplay_node* picosplay_find(picosplay_tree *tree, void *value) { + picosplay_node *curr = tree->root; + int found = 0; + while(curr != NULL && !found) { + int relation = tree->comp(value, curr->value); + if(relation == 0) { + found = 1; + } else if(relation < 0) { + curr = curr->left; + } else { + curr = curr->right; + } + } + if(curr != NULL) + splay(tree, curr); + return curr; +} + +/* Remove a node with the given value, splaying the tree. */ +void picosplay_delete(picosplay_tree *tree, void *value) { + picosplay_node *node = picosplay_find(tree, value); + picosplay_delete_hint(tree, node); +} + +/* Remove the node given by the pointer, splaying the tree. */ +void picosplay_delete_hint(picosplay_tree *tree, picosplay_node *node) { + if(node == NULL) + return; + splay(tree, node); /* Now node is tree's root. */ + if(node->left == NULL) { + tree->root = node->right; + if(tree->root != NULL) + tree->root->parent = NULL; + } else if(node->right == NULL) { + tree->root = node->left; + tree->root->parent = NULL; + } else { + picosplay_node *x = leftmost(node->right); + if(x->parent != node) { + x->parent->left = x->right; + if(x->right != NULL) + x->right->parent = x->parent; + x->right = node->right; + x->right->parent = x; + } + tree->root = x; + x->parent = NULL; + x->left = node->left; + x->left->parent = x; + } + free(node); + tree->size--; +} + +void picosplay_delete_tree(picosplay_tree * tree) +{ + if (tree != NULL) { + while (tree->root != NULL) { + picosplay_delete_hint(tree, tree->root); + } + free(tree); + } +} + +picosplay_node* picosplay_first(picosplay_tree *tree) { + return leftmost(tree->root); +} + +/* Return the minimal node that is bigger than the given. + * This is either: + * - leftmost child in the right subtree + * - closest ascendant for which given node is in left subtree + */ +picosplay_node* picosplay_next(picosplay_node *node) { + if(node->right != NULL) + return leftmost(node->right); + while(node->parent != NULL && node == node->parent->right) + node = node->parent; + return node->parent; +} + +picosplay_node* picosplay_last(picosplay_tree *tree) { + return rightmost(tree->root); +} + +/* An in-order traversal of the tree. */ +static void store(picosplay_node *node, void ***out); +void* picosplay_contents(picosplay_tree *tree) { + if(tree->size == 0) + return NULL; + void **out = malloc(tree->size * sizeof(void*)); + void ***tmp = &out; + store(tree->root, tmp); + return out - tree->size; +} + +static void store(picosplay_node *node, void ***out) { + if(node->left != NULL) + store(node->left, out); + **out = node->value; + (*out)++; + if(node->right != NULL) + store(node->right, out); +} +/* This mutates the parental relationships, copy pointer to old parent. */ +static void mark_gp(picosplay_node *child); + +/* Rotate to make the given child take its parent's place in the tree. */ +static void rotate(picosplay_node *child) { + picosplay_node *parent = child->parent; + assert(parent != NULL); + if(parent->left == child) { /* A left child given. */ + mark_gp(child); + parent->left = child->right; + if(child->right != NULL) + child->right->parent = parent; + child->right = parent; + } else { /* A right child given. */ + mark_gp(child); + parent->right = child->left; + if(child->left != NULL) + child->left->parent = parent; + child->left = parent; + } +} + +static void mark_gp(picosplay_node *child) { + picosplay_node *parent = child->parent; + picosplay_node *grand = parent->parent; + child->parent = grand; + parent->parent = child; + if(grand == NULL) + return; + if(grand->left == parent) + grand->left = child; + else + grand->right = child; +} + +static picosplay_node* leftmost(picosplay_node *node) { + picosplay_node *parent = NULL; + while(node != NULL) { + parent = node; + node = node->left; + } + return parent; +} + +static picosplay_node* rightmost(picosplay_node *node) { + picosplay_node *parent = NULL; + while(node != NULL) { + parent = node; + node = node->right; + } + return parent; +} + diff --git a/picoquic/picosplay.h b/picoquic/picosplay.h new file mode 100644 index 0000000000000000000000000000000000000000..c6439f88c33c2259e9539ee674f21f4f3274e720 --- /dev/null +++ b/picoquic/picosplay.h @@ -0,0 +1,52 @@ +/* +* Author: Christian Huitema +* Copyright (c) 2018, Private Octopus, Inc. +* All rights reserved. +* +* Permission to use, copy, modify, and distribute this software for any +* purpose with or without fee is hereby granted, provided that the above +* copyright notice and this permission notice appear in all copies. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL Private Octopus, Inc. 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. +* +* This code is copied and adapted from https://github.com/lrem/splay, +* copyright (c) Remigiusz Modrzejewski 2014, filed on Github with MIT license. +*/ + +#ifndef PICOSPLAY_H +#define PICOSPLAY_H + +typedef int (*picosplay_comparator)(void *left, void *right); + +typedef struct picosplay_node { + struct picosplay_node *parent, *left, *right; + void *value; +} picosplay_node; + +typedef struct picosplay_tree { + picosplay_node *root; + picosplay_comparator comp; + int size; +} picosplay_tree; + +picosplay_tree* picosplay_new_tree(picosplay_comparator comp); +picosplay_node* picosplay_insert(picosplay_tree *tree, void *value); +picosplay_node* picosplay_find(picosplay_tree *tree, void *value); +picosplay_node* picosplay_first(picosplay_tree *tree); +picosplay_node* picosplay_next(picosplay_node *node); +picosplay_node* picosplay_last(picosplay_tree *tree); +void* picosplay_contents(picosplay_tree *tree); +void picosplay_delete(picosplay_tree *tree, void *value); +void picosplay_delete_hint(picosplay_tree *tree, picosplay_node *node); +void picosplay_delete_tree(picosplay_tree *tree); + +#endif /* PICOSPLAY_H */ diff --git a/picoquic_t/picoquic_t.c b/picoquic_t/picoquic_t.c index f02860bfee9982dc4b23cbd0c849dd29849bd11c..edacb214ecdd380af5850ef68ffc7e6617604608 100644 --- a/picoquic_t/picoquic_t.c +++ b/picoquic_t/picoquic_t.c @@ -111,7 +111,8 @@ static const picoquic_test_def_t test_table[] = { { "zero_rtt_spurious", zero_rtt_spurious_test }, { "zero_rtt_retry", zero_rtt_retry_test }, { "parse_frames", parse_frame_test }, - { "stress", stress_test } + { "stress", stress_test }, + { "splay", splay_test } }; static size_t const nb_tests = sizeof(test_table) / sizeof(picoquic_test_def_t); diff --git a/picoquictest/picoquictest.h b/picoquictest/picoquictest.h index e75d459dba2fa2ca5a35d1353aba604d08cdc6c5..32711e7eed1773af145262ddeff87d15dbf11954 100644 --- a/picoquictest/picoquictest.h +++ b/picoquictest/picoquictest.h @@ -111,6 +111,7 @@ int zero_rtt_spurious_test(); int zero_rtt_retry_test(); int parse_frame_test(); int stress_test(); +int splay_test(); #ifdef __cplusplus } diff --git a/picoquictest/picoquictest.vcxproj b/picoquictest/picoquictest.vcxproj index d4d4c012508b39b0b62f459b9718e96b87ce9854..d0778752613977928fbf361a761fc29376de5fa0 100644 --- a/picoquictest/picoquictest.vcxproj +++ b/picoquictest/picoquictest.vcxproj @@ -149,6 +149,7 @@ <ClCompile Include="sacktest.c" /> <ClCompile Include="skip_frame_test.c" /> <ClCompile Include="socket_test.c" /> + <ClCompile Include="splay_test.c" /> <ClCompile Include="stream0_frame_test.c" /> <ClCompile Include="stresstest.c" /> <ClCompile Include="ticket_store_test.c" /> diff --git a/picoquictest/picoquictest.vcxproj.filters b/picoquictest/picoquictest.vcxproj.filters index 5d322c73df9e3584adbb785eebf45eb722ec302b..9de4666418b59af1d9949041f3c565f5cd29eaec 100644 --- a/picoquictest/picoquictest.vcxproj.filters +++ b/picoquictest/picoquictest.vcxproj.filters @@ -75,6 +75,9 @@ <ClCompile Include="stresstest.c"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="splay_test.c"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="picoquictest.h"> diff --git a/picoquictest/splay_test.c b/picoquictest/splay_test.c new file mode 100644 index 0000000000000000000000000000000000000000..61ab67e4d4ea23eeb805d35c60cc5b41f3fa381b --- /dev/null +++ b/picoquictest/splay_test.c @@ -0,0 +1,151 @@ +/* +* Author: Christian Huitema +* Copyright (c) 2018, Private Octopus, Inc. +* All rights reserved. +* +* Permission to use, copy, modify, and distribute this software for any +* purpose with or without fee is hereby granted, provided that the above +* copyright notice and this permission notice appear in all copies. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL Private Octopus, Inc. 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. +* +* This code is copied and adapted from https://github.com/lrem/splay, +* copyright (c) Remigiusz Modrzejewski 2014, filed on Github with MIT license. +*/ + +#include <stdio.h> +#include "../picoquic/util.h" +#include "../picoquic/picosplay.h" + +static int compare_int(void *l, void *r) { + return *((int*)l) - *((int*)r); +} + +static int check_node_sanity(picosplay_node *x, void *floor, void *ceil, picosplay_comparator comp) { + int count = 0; + + if (x != NULL) { + count = 1; + if (x->left != NULL) { + if (x->left->parent == x) { + void *new_floor; + if (floor == NULL || comp(x->value, floor) < 0) + new_floor = x->value; + else + new_floor = floor; + count += check_node_sanity(x->left, new_floor, ceil, comp); + } + else { + DBG_PRINTF("%s", "Invalid node, left->parent != node.\n"); + count = -1; + } + } + if (x->right != NULL && count > 0) { + if (x->right->parent == x) { + void *new_ceil; + if (ceil == NULL || comp(x->value, ceil) > 0) + new_ceil = x->value; + else + new_ceil = ceil; + count += check_node_sanity(x->right, floor, new_ceil, comp); + } + else { + DBG_PRINTF("%s", "Invalid node, left->parent != node.\n"); + count = -1; + } + } + } + + return count; +} + +int splay_test() { + int ret = 0; + int count = 0; + picosplay_tree *tree = picosplay_new_tree(&compare_int); + int values[] = {3, 4, 1, 2, 8, 5, 7}; + int values_first[] = { 3, 3, 1, 1, 1, 1, 1 }; + int values_last[] = { 3, 4, 4, 4, 8, 8, 8 }; + int value2_first[] = { 1, 1, 2, 5, 5, 7, 0 }; + int value2_last[] = { 8, 8, 8, 8, 7, 7, 0 }; + + if (tree == NULL) { + DBG_PRINTF("%s", "Cannot create tree.\n"); + } + + for(int i = 0; ret == 0 && i < 7; i++) { + picosplay_insert(tree, &values[i]); + /* Verify sanity and count after each insertion */ + count = check_node_sanity(tree->root, NULL, NULL, &compare_int); + if (count != i + 1) { + DBG_PRINTF("Insert v[%d] = %d, expected %d nodes, got %d instead\n", + i, values[i], i + 1, count); + ret = -1; + } + else if (tree->size != count) { + DBG_PRINTF("Insert v[%d] = %d, expected tree size %d, got %d instead\n", + i, values[i], count, tree->size); + ret = -1; + } + else if (*(int*)picosplay_first(tree)->value != values_first[i]) { + DBG_PRINTF("Insert v[%d] = %d, expected first = %d, got %d instead\n", + i, values[i], + values_first[i], *(int*)picosplay_first(tree)->value); + ret = -1; + } + else if (*(int*)picosplay_last(tree)->value != values_last[i]) { + DBG_PRINTF("Insert v[%d] = %d, expected first = %d, got %d instead\n", + i, values[i], + values_last[i], *(int*)picosplay_last(tree)->value); + ret = -1; + } + } + + for(int i = 0; ret == 0 && i < 7; i++) { + picosplay_delete(tree, &values[i]); + /* Verify sanity and count after each deletion */ + count = check_node_sanity(tree->root, NULL, NULL, &compare_int); + if (count != 6 - i) { + DBG_PRINTF("Delete v[%d] = %d, expected %d nodes, got %d instead\n", + i, values[i], 6 - i, count); + ret = -1; + } + else if (tree->size != count) { + DBG_PRINTF("Insert v[%d] = %d, expected tree size %d, got %d instead\n", + i, values[i], count, tree->size); + ret = -1; + } + else if (i < 6) { + if (*(int*)picosplay_first(tree)->value != value2_first[i]) { + DBG_PRINTF("Delete v[%d] = %d, expected first = %d, got %d instead\n", + i, values[i], value2_first[i], *(int*)picosplay_first(tree)->value); + ret = -1; + } + else if (*(int*)picosplay_last(tree)->value != value2_last[i]) { + DBG_PRINTF("Delete v[%d] = %d, expected first = %d, got %d instead\n", + i, values[i], value2_last[i], *(int*)picosplay_last(tree)->value); + ret = -1; + } + } + } + + if (tree != NULL) { + if (ret == 0 && tree->root != NULL) { + DBG_PRINTF("%s", "Final tree root should be NULL, is not.\n"); + ret = -1; + } + picosplay_delete_tree(tree); + tree = NULL; + } + + return ret; +}