diff options
| -rw-r--r-- | ot_vector.c | 47 | ||||
| -rw-r--r-- | tests/t_vector.c | 46 |
2 files changed, 73 insertions, 20 deletions
diff --git a/ot_vector.c b/ot_vector.c index 924d468..a56d55b 100644 --- a/ot_vector.c +++ b/ot_vector.c | |||
| @@ -57,34 +57,39 @@ static uint8_t vector_hash_peer(ot_peer const *peer, size_t compare_size, size_t | |||
| 57 | */ | 57 | */ |
| 58 | void *vector_find_or_insert(ot_vector *vector, void *key, size_t member_size, size_t compare_size, int *exactmatch) { | 58 | void *vector_find_or_insert(ot_vector *vector, void *key, size_t member_size, size_t compare_size, int *exactmatch) { |
| 59 | uint8_t *match = vector_binary_search(key, vector->data, vector->size, member_size, compare_size, exactmatch); | 59 | uint8_t *match = vector_binary_search(key, vector->data, vector->size, member_size, compare_size, exactmatch); |
| 60 | size_t match_offset, bytes_after; | ||
| 60 | 61 | ||
| 61 | if (*exactmatch) | 62 | if (*exactmatch) |
| 62 | return match; | 63 | return match; |
| 63 | 64 | ||
| 65 | /* Hold the insertion point as an offset rather than a pointer: it survives | ||
| 66 | the realloc below, and an empty vector has no data pointer to offset from | ||
| 67 | in the first place */ | ||
| 68 | match_offset = vector->size ? (size_t)(match - (uint8_t *)vector->data) : 0; | ||
| 69 | bytes_after = member_size * vector->size - match_offset; | ||
| 70 | |||
| 64 | if (vector->size + 1 > vector->space) { | 71 | if (vector->size + 1 > vector->space) { |
| 65 | size_t new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS; | 72 | size_t new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS; |
| 66 | ptrdiff_t match_off = match - (uint8_t *)vector->data; | 73 | uint8_t *new_data = realloc(vector->data, new_space * member_size); |
| 67 | uint8_t *new_data = realloc(vector->data, new_space * member_size); | ||
| 68 | 74 | ||
| 69 | if (!new_data) | 75 | if (!new_data) |
| 70 | return NULL; | 76 | return NULL; |
| 71 | 77 | ||
| 72 | /* Adjust pointer if it moved by realloc */ | ||
| 73 | match = new_data + match_off; | ||
| 74 | |||
| 75 | vector->data = new_data; | 78 | vector->data = new_data; |
| 76 | vector->space = new_space; | 79 | vector->space = new_space; |
| 77 | } | 80 | } |
| 78 | memmove(match + member_size, match, ((uint8_t *)vector->data) + member_size * vector->size - match); | 81 | |
| 82 | match = (uint8_t *)vector->data + match_offset; | ||
| 83 | memmove(match + member_size, match, bytes_after); | ||
| 79 | 84 | ||
| 80 | vector->size++; | 85 | vector->size++; |
| 81 | return match; | 86 | return match; |
| 82 | } | 87 | } |
| 83 | 88 | ||
| 84 | ot_peer *vector_find_or_insert_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size, int *exactmatch) { | 89 | ot_peer *vector_find_or_insert_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size, int *exactmatch) { |
| 85 | ot_peer *match, *end; | 90 | ot_peer *match; |
| 86 | const size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size); | 91 | const size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size); |
| 87 | size_t match_to_end; | 92 | size_t match_offset, bytes_after; |
| 88 | 93 | ||
| 89 | /* If space is zero but size is set, we're dealing with a list of vector->size buckets */ | 94 | /* If space is zero but size is set, we're dealing with a list of vector->size buckets */ |
| 90 | if (vector->space < vector->size) | 95 | if (vector->space < vector->size) |
| @@ -94,26 +99,26 @@ ot_peer *vector_find_or_insert_peer(ot_vector *vector, ot_peer const *peer, size | |||
| 94 | if (*exactmatch) | 99 | if (*exactmatch) |
| 95 | return match; | 100 | return match; |
| 96 | 101 | ||
| 97 | /* This is the amount of bytes that needs to be pushed backwards by peer_size bytes to make room for new peer */ | 102 | /* Hold the insertion point as an offset rather than a pointer: it survives |
| 98 | end = (ot_peer *)vector->data + vector->size * peer_size; | 103 | the realloc below, and an empty vector has no data pointer to offset from |
| 99 | match_to_end = end - match; | 104 | in the first place. bytes_after is what has to move up by peer_size to |
| 105 | make room for the new peer */ | ||
| 106 | match_offset = vector->size ? (size_t)(match - (ot_peer *)vector->data) : 0; | ||
| 107 | bytes_after = vector->size * peer_size - match_offset; | ||
| 100 | 108 | ||
| 101 | if (vector->size + 1 > vector->space) { | 109 | if (vector->size + 1 > vector->space) { |
| 102 | ptrdiff_t offset = match - (ot_peer *)vector->data; | 110 | size_t new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS; |
| 103 | size_t new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS; | 111 | ot_peer *new_data = realloc(vector->data, new_space * peer_size); |
| 104 | ot_peer *new_data = realloc(vector->data, new_space * peer_size); | ||
| 105 | 112 | ||
| 106 | if (!new_data) | 113 | if (!new_data) |
| 107 | return NULL; | 114 | return NULL; |
| 108 | /* Adjust pointer if it moved by realloc */ | ||
| 109 | match = new_data + offset; | ||
| 110 | 115 | ||
| 111 | vector->data = new_data; | 116 | vector->data = new_data; |
| 112 | vector->space = new_space; | 117 | vector->space = new_space; |
| 113 | } | 118 | } |
| 114 | 119 | ||
| 115 | /* Here we're guaranteed to have enough space in vector to move the block of peers after insertion point */ | 120 | match = (ot_peer *)vector->data + match_offset; |
| 116 | memmove(match + peer_size, match, match_to_end); | 121 | memmove(match + peer_size, match, bytes_after); |
| 117 | 122 | ||
| 118 | vector->size++; | 123 | vector->size++; |
| 119 | return match; | 124 | return match; |
| @@ -150,11 +155,13 @@ int vector_remove_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size) | |||
| 150 | } | 155 | } |
| 151 | 156 | ||
| 152 | void vector_remove_torrent(ot_vector *vector, ot_torrent *match) { | 157 | void vector_remove_torrent(ot_vector *vector, ot_torrent *match) { |
| 153 | ot_torrent *end = ((ot_torrent *)vector->data) + vector->size; | 158 | ot_torrent *end; |
| 154 | 159 | ||
| 155 | if (!vector->size) | 160 | if (!vector->size) |
| 156 | return; | 161 | return; |
| 157 | 162 | ||
| 163 | end = ((ot_torrent *)vector->data) + vector->size; | ||
| 164 | |||
| 158 | free_peerlist(match->peer_list6); | 165 | free_peerlist(match->peer_list6); |
| 159 | free_peerlist(match->peer_list4); | 166 | free_peerlist(match->peer_list4); |
| 160 | 167 | ||
diff --git a/tests/t_vector.c b/tests/t_vector.c index 541a6da..6e048e1 100644 --- a/tests/t_vector.c +++ b/tests/t_vector.c | |||
| @@ -112,6 +112,51 @@ static void case_insert_keeps_order(void) { | |||
| 112 | free(vector.data); | 112 | free(vector.data); |
| 113 | } | 113 | } |
| 114 | 114 | ||
| 115 | /* The peer variant is reached with an empty vector on every first announce | ||
| 116 | for a torrent, and again whenever a swarm empties and refills, because | ||
| 117 | vector_fixup_peers drops the allocation and nulls the data pointer. */ | ||
| 118 | |||
| 119 | static void case_insert_peer_into_empty_vector(void) { | ||
| 120 | ot_vector vector = {NULL, 0, 0}; | ||
| 121 | ot_peer6 low, high; | ||
| 122 | int exactmatch; | ||
| 123 | ot_peer *slot; | ||
| 124 | |||
| 125 | memset(low, 0x11, sizeof(low)); | ||
| 126 | memset(high, 0x99, sizeof(high)); | ||
| 127 | |||
| 128 | test_case("the first peer goes into a vector that has no allocation yet"); | ||
| 129 | slot = vector_find_or_insert_peer(&vector, low, OT_PEER_SIZE6, &exactmatch); | ||
| 130 | CHECK(slot != NULL); | ||
| 131 | CHECK_INT(exactmatch, 0); | ||
| 132 | memcpy(slot, low, OT_PEER_SIZE6); | ||
| 133 | CHECK_INT(vector.size, 1); | ||
| 134 | CHECK_INT(vector.space, OT_VECTOR_MIN_MEMBERS); | ||
| 135 | CHECK_PTR(slot, vector.data); | ||
| 136 | |||
| 137 | test_case("a second peer keeps the order"); | ||
| 138 | slot = vector_find_or_insert_peer(&vector, high, OT_PEER_SIZE6, &exactmatch); | ||
| 139 | CHECK(slot != NULL); | ||
| 140 | CHECK_INT(exactmatch, 0); | ||
| 141 | memcpy(slot, high, OT_PEER_SIZE6); | ||
| 142 | CHECK_INT(vector.size, 2); | ||
| 143 | CHECK_MEM((char *)vector.data, low, OT_PEER_COMPARE_SIZE6); | ||
| 144 | CHECK_MEM((char *)vector.data + OT_PEER_SIZE6, high, OT_PEER_COMPARE_SIZE6); | ||
| 145 | |||
| 146 | test_case("and emptying the vector hands the allocation back"); | ||
| 147 | CHECK(vector_remove_peer(&vector, low, OT_PEER_SIZE6) != 0); | ||
| 148 | CHECK(vector_remove_peer(&vector, high, OT_PEER_SIZE6) != 0); | ||
| 149 | CHECK_INT(vector.size, 0); | ||
| 150 | CHECK_PTR(vector.data, NULL); | ||
| 151 | |||
| 152 | test_case("so the next peer starts from no allocation again"); | ||
| 153 | slot = vector_find_or_insert_peer(&vector, low, OT_PEER_SIZE6, &exactmatch); | ||
| 154 | CHECK(slot != NULL); | ||
| 155 | CHECK_INT(vector.size, 1); | ||
| 156 | |||
| 157 | free(vector.data); | ||
| 158 | } | ||
| 159 | |||
| 115 | static void case_insert_finds_existing(void) { | 160 | static void case_insert_finds_existing(void) { |
| 116 | ot_vector vector = {NULL, 0, 0}; | 161 | ot_vector vector = {NULL, 0, 0}; |
| 117 | const unsigned char five[4] = {0, 0, 0, 5}; | 162 | const unsigned char five[4] = {0, 0, 0, 5}; |
| @@ -257,6 +302,7 @@ void test_vector(void) { | |||
| 257 | case_search_past_the_end(); | 302 | case_search_past_the_end(); |
| 258 | case_search_empty(); | 303 | case_search_empty(); |
| 259 | case_insert_keeps_order(); | 304 | case_insert_keeps_order(); |
| 305 | case_insert_peer_into_empty_vector(); | ||
| 260 | case_insert_finds_existing(); | 306 | case_insert_finds_existing(); |
| 261 | case_insert_compare_prefix(); | 307 | case_insert_compare_prefix(); |
| 262 | case_redistribute_holds_every_peer(); | 308 | case_redistribute_holds_every_peer(); |
