1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
|
/*
* Standalone sanity test for the bitmap IP-pool allocator core.
*
* Not part of the cmake build. Compile and run with:
* gcc -O2 -Wall -o /tmp/bitpool_test accel-pppd/extra/bitpool_test.c && /tmp/bitpool_test
*
* It validates:
* - bitpool.h: bm_set/clear/test, bm_find_free wrap + remainder bits +
* exhaustion, and that every freed bit is re-handed exactly once.
* - IPv4 addr<->bit round-trip for p2p and net30 geometry, incl. alignment
* and out-of-range rejection.
* - IPv6 prefix<->bit round-trip (the exact helpers used by ipv6pool.c),
* cross-checked against __int128 reference math, incl. prefix_len==128,
* mask<=64 / mask>64, and an unaligned base.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include "bitpool.h"
static int failures;
#define CHECK(cond) do { if (!(cond)) { \
fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); failures++; } } while (0)
/* ---- mimic a finalized range bitmap: count bits free, remainder bits set ---- */
static bm_word_t *make_map(uint64_t count)
{
uint64_t nw = BM_NWORDS(count), b;
bm_word_t *bm = calloc(nw, sizeof(bm_word_t));
for (b = count; b < nw * BM_WORD_BITS; b++)
bm_set(bm, b);
return bm;
}
static void test_bitmap_basic(void)
{
uint64_t count = 200; /* not a multiple of 64 -> exercises remainder */
bm_word_t *bm = make_map(count);
uint64_t i, bit, n = 0;
char *seen = calloc(count, 1);
/* allocate the whole pool via rolling cursor; each bit unique and < count */
uint64_t cursor = 0;
while ((bit = bm_find_free(bm, count, cursor)) != BM_INVALID) {
CHECK(bit < count);
CHECK(!seen[bit]);
seen[bit] = 1;
bm_set(bm, bit);
cursor = bit + 1;
n++;
CHECK(n <= count);
}
CHECK(n == count); /* exactly count handed out */
for (i = 0; i < count; i++)
CHECK(seen[i]); /* every unit handed out once */
CHECK(bm_find_free(bm, count, 0) == BM_INVALID); /* exhausted */
/* free three, confirm exactly those three come back */
bm_clear(bm, 5); bm_clear(bm, 130); bm_clear(bm, 199);
memset(seen, 0, count);
for (n = 0; (bit = bm_find_free(bm, count, 0)) != BM_INVALID; n++) {
CHECK(bit == 5 || bit == 130 || bit == 199);
seen[bit] = 1; bm_set(bm, bit);
}
CHECK(n == 3 && seen[5] && seen[130] && seen[199]);
/* wrap-around: only bit 1 free, search starting past it must still find it */
memset(bm, 0xff, BM_NWORDS(count) * sizeof(bm_word_t));
bm_clear(bm, 1);
CHECK(bm_find_free(bm, count, 50) == 1);
CHECK(bm_find_free(bm, count, 0) == 1);
/* zero-size pool */
CHECK(bm_find_free(bm, 0, 0) == BM_INVALID);
free(seen);
free(bm);
printf("ok bitmap basic/wrap/remainder/exhaustion\n");
}
/* ---- IPv4 geometry (mirrors ippool.c range_addr_to_bit / bit->addr) ---- */
static uint64_t v4_addr_to_bit(uint32_t start, uint32_t step, int peer_off,
uint64_t count, uint32_t peer)
{
uint32_t base, delta;
uint64_t bit;
if (peer < (uint32_t)peer_off) return BM_INVALID;
base = peer - peer_off;
if (base < start) return BM_INVALID;
delta = base - start;
if (delta % step) return BM_INVALID;
bit = delta / step;
if (bit >= count) return BM_INVALID;
return bit;
}
static void test_v4(void)
{
/* p2p: 10.0.0.0 - 10.0.1.255 (512 addrs), step 1, peer_off 0 */
uint32_t start = (10u << 24);
uint32_t end = (10u << 24) | 0x1ff;
uint64_t count = (uint64_t)(end - start) + 1;
uint64_t i;
CHECK(count == 512);
for (i = 0; i < count; i++) {
uint32_t peer = start + (uint32_t)i * 1 + 0;
CHECK(v4_addr_to_bit(start, 1, 0, count, peer) == i);
}
CHECK(v4_addr_to_bit(start, 1, 0, count, start - 1) == BM_INVALID);
CHECK(v4_addr_to_bit(start, 1, 0, count, end + 1) == BM_INVALID);
printf("ok v4 p2p round-trip (512 addrs)\n");
/* net30: same range, step 4, gw_off 1, peer_off 2 -> 128 blocks */
count = ((uint64_t)(end - start) + 1) / 4;
CHECK(count == 128);
for (i = 0; i < count; i++) {
uint32_t base = start + (uint32_t)i * 4;
uint32_t peer = base + 2; /* .2 client */
CHECK(v4_addr_to_bit(start, 4, 2, count, peer) == i);
/* .1 router and .0/.3 are NOT valid leases */
CHECK(v4_addr_to_bit(start, 4, 2, count, base + 1) == BM_INVALID);
CHECK(v4_addr_to_bit(start, 4, 2, count, base + 0) == BM_INVALID);
CHECK(v4_addr_to_bit(start, 4, 2, count, base + 3) == BM_INVALID);
}
printf("ok v4 net30 round-trip + alignment (128 blocks)\n");
}
/* ---- IPv6 helpers: EXACT copies of the ones in ipv6pool.c ---- */
static void in6_sub(uint8_t *o, const uint8_t *a, const uint8_t *b)
{
int i, borrow = 0;
for (i = 15; i >= 0; i--) {
int v = (int)a[i] - b[i] - borrow;
if (v < 0) { v += 256; borrow = 1; } else borrow = 0;
o[i] = v;
}
}
static int in6_shr_u64(const uint8_t *d, int shift, uint64_t *out)
{
int p; uint64_t v = 0;
for (p = 0; p < 64; p++) {
int sp = shift + p;
if (sp < 128 && (d[15 - (sp >> 3)] & (1 << (sp & 7)))) v |= (uint64_t)1 << p;
}
for (p = shift + 64; p < 128; p++)
if (d[15 - (p >> 3)] & (1 << (p & 7))) return -1;
*out = v; return 0;
}
static void in6_index_to_addr(uint8_t *out, const uint8_t *start, uint64_t i, int shift)
{
uint8_t add[16] = { 0 }; int p, b, carry = 0;
for (p = 0; p < 64; p++)
if (i & ((uint64_t)1 << p)) { int sp = shift + p; if (sp < 128) add[15 - (sp >> 3)] |= (1 << (sp & 7)); }
memcpy(out, start, 16);
for (b = 15; b >= 0; b--) { int s = out[b] + add[b] + carry; out[b] = s & 0xff; carry = s >> 8; }
}
static int in6_cmp(const uint8_t *a, const uint8_t *b)
{
int i; for (i = 0; i < 16; i++) { if (a[i] < b[i]) return -1; if (a[i] > b[i]) return 1; } return 0;
}
static uint64_t v6_addr_to_bit(const uint8_t *start, int shift, uint64_t count, const uint8_t *addr)
{
uint8_t delta[16]; uint64_t i; int p;
if (in6_cmp(addr, start) < 0) return BM_INVALID;
in6_sub(delta, addr, start);
for (p = 0; p < shift; p++)
if (delta[15 - (p >> 3)] & (1 << (p & 7))) return BM_INVALID;
if (in6_shr_u64(delta, shift, &i)) return BM_INVALID;
if (i >= count) return BM_INVALID;
return i;
}
/* ---- __int128 reference ---- */
static __uint128_t to_u128(const uint8_t a[16]) { __uint128_t v = 0; int i; for (i = 0; i < 16; i++) v = (v << 8) | a[i]; return v; }
static void from_u128(uint8_t a[16], __uint128_t v) { int i; for (i = 15; i >= 0; i--) { a[i] = v & 0xff; v >>= 8; } }
static void test_v6_one(const char *label, const uint8_t start[16], int mask, int prefix_len)
{
int shift = 128 - prefix_len;
__uint128_t s = to_u128(start);
__uint128_t hostmask = (mask == 0) ? ~(__uint128_t)0 : (((__uint128_t)1 << (128 - mask)) - 1);
__uint128_t end = s | hostmask;
uint8_t endb[16]; uint8_t delta[16]; uint64_t span, count, i, step_lo;
__uint128_t step = (__uint128_t)1 << shift;
from_u128(endb, end);
in6_sub(delta, endb, start);
CHECK(in6_shr_u64(delta, shift, &span) == 0);
count = span + 1;
CHECK(count == (uint64_t)(((end - s) >> shift) + 1)); /* matches reference */
/* round-trip boundary + sampled indices */
uint64_t samples[] = { 0, 1, count / 2, count - 1 };
for (size_t k = 0; k < sizeof(samples) / sizeof(samples[0]); k++) {
i = samples[k];
if (i >= count) continue;
uint8_t addr[16]; in6_index_to_addr(addr, start, i, shift);
__uint128_t ref = s + (__uint128_t)i * step;
uint8_t refb[16]; from_u128(refb, ref);
CHECK(memcmp(addr, refb, 16) == 0); /* bit->addr matches ref */
CHECK(v6_addr_to_bit(start, shift, count, addr) == i); /* addr->bit round-trip */
/* misaligned address (only when there is room below a prefix) */
if (shift > 0) {
uint8_t bad[16]; memcpy(bad, addr, 16); bad[15] |= 1;
CHECK(v6_addr_to_bit(start, shift, count, bad) == BM_INVALID);
}
}
/* below start and past end reject */
if (s > 0) { uint8_t below[16]; from_u128(below, s - 1); CHECK(v6_addr_to_bit(start, shift, count, below) == BM_INVALID); }
{ uint8_t past[16]; from_u128(past, s + (__uint128_t)count * step); CHECK(v6_addr_to_bit(start, shift, count, past) == BM_INVALID); }
(void)step_lo;
printf("ok v6 %s (mask=%d plen=%d shift=%d count=%llu)\n",
label, mask, prefix_len, shift, (unsigned long long)count);
}
static void test_v6(void)
{
uint8_t a[16];
/* 2001:db8::/48 carved into /64s -> 65536 prefixes (mask<=64) */
memset(a, 0, 16); a[0] = 0x20; a[1] = 0x01; a[2] = 0x0d; a[3] = 0xb8;
test_v6_one("/48->/64", a, 48, 64);
/* mask>64: 2001:db8:0:0:8000::/72 -> /80 (mask>64 path) */
memset(a, 0, 16); a[0] = 0x20; a[1] = 0x01; a[2] = 0x0d; a[3] = 0xb8; a[8] = 0x80;
test_v6_one("/72->/80", a, 72, 80);
/* prefix_len == 128: single addresses, step 1 */
memset(a, 0, 16); a[0] = 0xfc; a[15] = 0x00;
test_v6_one("/120->/128", a, 120, 128);
/* unaligned base within the mask host bits (start not on a /64 boundary) */
memset(a, 0, 16); a[0] = 0x20; a[1] = 0x01; a[2] = 0x0d; a[3] = 0xb8; a[7] = 0x05;
test_v6_one("/48->/64 unaligned base", a, 48, 64);
/* big delegation: /32 -> /56 = 2^24 prefixes (the cap boundary) */
memset(a, 0, 16); a[0] = 0x2a; a[1] = 0x00;
test_v6_one("/32->/56 (cap)", a, 32, 56);
}
int main(void)
{
test_bitmap_basic();
test_v4();
test_v6();
if (failures) {
printf("\n%d CHECK(s) FAILED\n", failures);
return 1;
}
printf("\nALL TESTS PASSED\n");
return 0;
}
|