diff options
Diffstat (limited to 'tests/contrib')
-rw-r--r-- | tests/contrib/test_base32hex.c | 267 | ||||
-rw-r--r-- | tests/contrib/test_base64.c | 237 | ||||
-rw-r--r-- | tests/contrib/test_base64url.c | 252 | ||||
-rw-r--r-- | tests/contrib/test_heap.c | 166 | ||||
-rw-r--r-- | tests/contrib/test_inet_ntop.c | 85 | ||||
-rw-r--r-- | tests/contrib/test_net.c | 718 | ||||
-rw-r--r-- | tests/contrib/test_net_shortwrite.c | 151 | ||||
-rw-r--r-- | tests/contrib/test_qp-cow.c | 282 | ||||
-rw-r--r-- | tests/contrib/test_qp-trie.c | 284 | ||||
-rw-r--r-- | tests/contrib/test_siphash.c | 135 | ||||
-rw-r--r-- | tests/contrib/test_sockaddr.c | 229 | ||||
-rw-r--r-- | tests/contrib/test_spinlock.c | 78 | ||||
-rw-r--r-- | tests/contrib/test_string.c | 59 | ||||
-rw-r--r-- | tests/contrib/test_strtonum.c | 156 | ||||
-rw-r--r-- | tests/contrib/test_time.c | 203 | ||||
-rw-r--r-- | tests/contrib/test_toeplitz.c | 93 | ||||
-rw-r--r-- | tests/contrib/test_wire_ctx.c | 287 |
17 files changed, 3682 insertions, 0 deletions
diff --git a/tests/contrib/test_base32hex.c b/tests/contrib/test_base32hex.c new file mode 100644 index 0000000..541667c --- /dev/null +++ b/tests/contrib/test_base32hex.c @@ -0,0 +1,267 @@ +/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <https://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <tap/basic.h> + +#include "libknot/libknot.h" +#include "contrib/base32hex.h" +#include "contrib/openbsd/strlcpy.h" + +#define BUF_LEN 256 +#define MAX_BIN_DATA_LEN ((INT32_MAX / 8) * 5) + +int main(int argc, char *argv[]) +{ + plan(67); + + int32_t ret; + uint8_t in[BUF_LEN], ref[BUF_LEN], out[BUF_LEN], out2[BUF_LEN], *out3; + uint32_t in_len, ref_len; + + // 0. test invalid input + ret = knot_base32hex_encode(NULL, 0, out, BUF_LEN); + is_int(KNOT_EINVAL, ret, "knot_base32hex_encode: NULL input buffer"); + ret = knot_base32hex_encode(in, BUF_LEN, NULL, 0); + is_int(KNOT_EINVAL, ret, "knot_base32hex_encode: NULL output buffer"); + ret = knot_base32hex_encode(in, MAX_BIN_DATA_LEN + 1, out, BUF_LEN); + is_int(KNOT_ERANGE, ret, "knot_base32hex_encode: input buffer too large"); + ret = knot_base32hex_encode(in, BUF_LEN, out, BUF_LEN); + is_int(KNOT_ERANGE, ret, "knot_base32hex_encode: output buffer too small"); + + ret = knot_base32hex_encode_alloc(NULL, 0, &out3); + is_int(KNOT_EINVAL, ret, "knot_base32hex_encode_alloc: NULL input buffer"); + ret = knot_base32hex_encode_alloc(in, MAX_BIN_DATA_LEN + 1, &out3); + is_int(KNOT_ERANGE, ret, "knot_base32hex_encode_alloc: input buffer too large"); + ret = knot_base32hex_encode_alloc(in, BUF_LEN, NULL); + is_int(KNOT_EINVAL, ret, "knot_base32hex_encode_alloc: NULL output buffer"); + + ret = knot_base32hex_decode(NULL, 0, out, BUF_LEN); + is_int(KNOT_EINVAL, ret, "knot_base32hex_decode: NULL input buffer"); + ret = knot_base32hex_decode(in, BUF_LEN, NULL, 0); + is_int(KNOT_EINVAL, ret, "knot_base32hex_decode: NULL output buffer"); + ret = knot_base32hex_decode(in, UINT32_MAX, out, BUF_LEN); + is_int(KNOT_ERANGE, ret, "knot_base32hex_decode: input buffer too large"); + ret = knot_base32hex_decode(in, BUF_LEN, out, 0); + is_int(KNOT_ERANGE, ret, "knot_base32hex_decode: output buffer too small"); + + ret = knot_base32hex_decode_alloc(NULL, 0, &out3); + is_int(KNOT_EINVAL, ret, "knot_base32hex_decode_alloc: NULL input buffer"); + ret = knot_base32hex_decode_alloc(in, UINT32_MAX, &out3); + is_int(KNOT_ERANGE, ret, "knot_base32hex_decode_aloc: input buffer too large"); + ret = knot_base32hex_decode_alloc(in, BUF_LEN, NULL); + is_int(KNOT_EINVAL, ret, "knot_base32hex_decode_alloc: NULL output buffer"); + + // 1. test vector -> ENC -> DEC + strlcpy((char *)in, "", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "", BUF_LEN); + ref_len = strlen((char *)ref); + ret = knot_base32hex_encode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "1. test vector - ENC output length"); + if (ret < 0) { + skip("Encode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "1. test vector - ENC output content"); + } + ret = knot_base32hex_decode(out, ret, out2, BUF_LEN); + ok(ret == in_len, "1. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out2, in, ret) == 0, "1. test vector - DEC output content"); + } + + // 2. test vector -> ENC -> DEC + strlcpy((char *)in, "f", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "co======", BUF_LEN); + ref_len = strlen((char *)ref); + ret = knot_base32hex_encode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "2. test vector - ENC output length"); + if (ret < 0) { + skip("Encode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "2. test vector - ENC output content"); + } + ret = knot_base32hex_decode(out, ret, out2, BUF_LEN); + ok(ret == in_len, "2. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out2, in, ret) == 0, "2. test vector - DEC output content"); + } + + // 3. test vector -> ENC -> DEC + strlcpy((char *)in, "fo", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "cpng====", BUF_LEN); + ref_len = strlen((char *)ref); + ret = knot_base32hex_encode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "3. test vector - ENC output length"); + if (ret < 0) { + skip("Encode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "3. test vector - ENC output content"); + } + ret = knot_base32hex_decode(out, ret, out2, BUF_LEN); + ok(ret == in_len, "3. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out2, in, ret) == 0, "3. test vector - DEC output content"); + } + + // 4. test vector -> ENC -> DEC + strlcpy((char *)in, "foo", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "cpnmu===", BUF_LEN); + ref_len = strlen((char *)ref); + ret = knot_base32hex_encode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "4. test vector - ENC output length"); + if (ret < 0) { + skip("Encode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "4. test vector - ENC output content"); + } + ret = knot_base32hex_decode(out, ret, out2, BUF_LEN); + ok(ret == in_len, "4. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out2, in, ret) == 0, "4. test vector - DEC output content"); + } + + // 5. test vector -> ENC -> DEC + strlcpy((char *)in, "foob", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "cpnmuog=", BUF_LEN); + ref_len = strlen((char *)ref); + ret = knot_base32hex_encode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "5. test vector - ENC output length"); + if (ret < 0) { + skip("Encode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "5. test vector - ENC output content"); + } + ret = knot_base32hex_decode(out, ret, out2, BUF_LEN); + ok(ret == in_len, "5. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out2, in, ret) == 0, "5. test vector - DEC output content"); + } + + // 6. test vector -> ENC -> DEC + strlcpy((char *)in, "fooba", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "cpnmuoj1", BUF_LEN); + ref_len = strlen((char *)ref); + ret = knot_base32hex_encode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "6. test vector - ENC output length"); + if (ret < 0) { + skip("Encode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "6. test vector - ENC output content"); + } + ret = knot_base32hex_decode(out, ret, out2, BUF_LEN); + ok(ret == in_len, "6. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out2, in, ret) == 0, "6. test vector - DEC output content"); + } + + // 7. test vector -> ENC -> DEC + strlcpy((char *)in, "foobar", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "cpnmuoj1e8======", BUF_LEN); + ref_len = strlen((char *)ref); + ret = knot_base32hex_encode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "7. test vector - ENC output length"); + if (ret < 0) { + skip("Encode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "7. test vector - ENC output content"); + } + ret = knot_base32hex_decode(out, ret, out2, BUF_LEN); + ok(ret == in_len, "7. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out2, in, ret) == 0, "7. test vector - DEC output content"); + } + + // Bad paddings + ret = knot_base32hex_decode((uint8_t *)"AAAAAA==", 8, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ECHAR, "Bad padding length 2"); + ret = knot_base32hex_decode((uint8_t *)"AAA=====", 8, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ECHAR, "Bad padding length 5"); + ret = knot_base32hex_decode((uint8_t *)"A=======", 8, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ECHAR, "Bad padding length 7"); + ret = knot_base32hex_decode((uint8_t *)"========", 8, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ECHAR, "Bad padding length 8"); + ret = knot_base32hex_decode((uint8_t *)"AAAAA=A=", 8, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ECHAR, "Bad padding character on position 2"); + ret = knot_base32hex_decode((uint8_t *)"AA=A====", 8, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ECHAR, "Bad padding character on position 5"); + ret = knot_base32hex_decode((uint8_t *)"=A======", 8, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ECHAR, "Bad padding character on position 7"); + ret = knot_base32hex_decode((uint8_t *)"CO======CO======", 16, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ECHAR, "Two octects with padding"); + + // Bad data length + ret = knot_base32hex_decode((uint8_t *)"A", 1, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 1"); + ret = knot_base32hex_decode((uint8_t *)"AA", 2, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 2"); + ret = knot_base32hex_decode((uint8_t *)"AAA", 3, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 3"); + ret = knot_base32hex_decode((uint8_t *)"AAAA", 4, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 4"); + ret = knot_base32hex_decode((uint8_t *)"AAAAA", 5, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 5"); + ret = knot_base32hex_decode((uint8_t *)"AAAAAA", 6, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 6"); + ret = knot_base32hex_decode((uint8_t *)"AAAAAAA", 7, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 7"); + ret = knot_base32hex_decode((uint8_t *)"AAAAAAAAA", 9, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 9"); + + // Bad data character + ret = knot_base32hex_decode((uint8_t *)"AAAAAAA$", 8, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ECHAR, "Bad data character dollar"); + ret = knot_base32hex_decode((uint8_t *)"AAAAAAA ", 8, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ECHAR, "Bad data character space"); + ret = knot_base32hex_decode((uint8_t *)"AAAAAA$A", 8, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ECHAR, "Bad data character dollar on position 7"); + ret = knot_base32hex_decode((uint8_t *)"AAAAA$AA", 8, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ECHAR, "Bad data character dollar on position 6"); + ret = knot_base32hex_decode((uint8_t *)"AAAA$AAA", 8, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ECHAR, "Bad data character dollar on position 5"); + ret = knot_base32hex_decode((uint8_t *)"AAA$AAAA", 8, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ECHAR, "Bad data character dollar on position 4"); + ret = knot_base32hex_decode((uint8_t *)"AA$AAAAA", 8, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ECHAR, "Bad data character dollar on position 3"); + ret = knot_base32hex_decode((uint8_t *)"A$AAAAAA", 8, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ECHAR, "Bad data character dollar on position 2"); + ret = knot_base32hex_decode((uint8_t *)"$AAAAAAA", 8, out, BUF_LEN); + ok(ret == KNOT_BASE32HEX_ECHAR, "Bad data character dollar on position 1"); + + return 0; +} diff --git a/tests/contrib/test_base64.c b/tests/contrib/test_base64.c new file mode 100644 index 0000000..82eee7d --- /dev/null +++ b/tests/contrib/test_base64.c @@ -0,0 +1,237 @@ +/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <https://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <tap/basic.h> + +#include "libknot/libknot.h" +#include "contrib/base64.h" +#include "contrib/openbsd/strlcpy.h" + +#define BUF_LEN 256 +#define MAX_BIN_DATA_LEN ((INT32_MAX / 4) * 3) + +int main(int argc, char *argv[]) +{ + plan(52); + + int32_t ret; + uint8_t in[BUF_LEN], ref[BUF_LEN], out[BUF_LEN], out2[BUF_LEN], *out3; + uint32_t in_len, ref_len; + + // 0. test invalid input + ret = knot_base64_encode(NULL, 0, out, BUF_LEN); + is_int(KNOT_EINVAL, ret, "knot_base64_encode: NULL input buffer"); + ret = knot_base64_encode(in, BUF_LEN, NULL, 0); + is_int(KNOT_EINVAL, ret, "knot_base64_encode: NULL output buffer"); + ret = knot_base64_encode(in, MAX_BIN_DATA_LEN + 1, out, BUF_LEN); + is_int(KNOT_ERANGE, ret, "knot_base64_encode: input buffer too large"); + ret = knot_base64_encode(in, BUF_LEN, out, BUF_LEN); + is_int(KNOT_ERANGE, ret, "knot_base64_encode: output buffer too small"); + + ret = knot_base64_encode_alloc(NULL, 0, &out3); + is_int(KNOT_EINVAL, ret, "knot_base64_encode_alloc: NULL input buffer"); + ret = knot_base64_encode_alloc(in, MAX_BIN_DATA_LEN + 1, &out3); + is_int(KNOT_ERANGE, ret, "knot_base64_encode_alloc: input buffer too large"); + ret = knot_base64_encode_alloc(in, BUF_LEN, NULL); + is_int(KNOT_EINVAL, ret, "knot_base64_encode_alloc: NULL output buffer"); + + ret = knot_base64_decode(NULL, 0, out, BUF_LEN); + is_int(KNOT_EINVAL, ret, "knot_base64_decode: NULL input buffer"); + ret = knot_base64_decode(in, BUF_LEN, NULL, 0); + is_int(KNOT_EINVAL, ret, "knot_base64_decode: NULL output buffer"); + ret = knot_base64_decode(in, UINT32_MAX, out, BUF_LEN); + is_int(KNOT_ERANGE, ret, "knot_base64_decode: input buffer too large"); + ret = knot_base64_decode(in, BUF_LEN, out, 0); + is_int(KNOT_ERANGE, ret, "knot_base64_decode: output buffer too small"); + + ret = knot_base64_decode_alloc(NULL, 0, &out3); + is_int(KNOT_EINVAL, ret, "knot_base64_decode_alloc: NULL input buffer"); + ret = knot_base64_decode_alloc(in, UINT32_MAX, &out3); + is_int(KNOT_ERANGE, ret, "knot_base64_decode_aloc: input buffer too large"); + ret = knot_base64_decode_alloc(in, BUF_LEN, NULL); + is_int(KNOT_EINVAL, ret, "knot_base64_decode_alloc: NULL output buffer"); + + // 1. test vector -> ENC -> DEC + strlcpy((char *)in, "", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "", BUF_LEN); + ref_len = strlen((char *)ref); + ret = knot_base64_encode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "1. test vector - ENC output length"); + if (ret < 0) { + skip("Encode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "1. test vector - ENC output content"); + } + ret = knot_base64_decode(out, ret, out2, BUF_LEN); + ok(ret == in_len, "1. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out2, in, ret) == 0, "1. test vector - DEC output content"); + } + + // 2. test vector -> ENC -> DEC + strlcpy((char *)in, "f", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "Zg==", BUF_LEN); + ref_len = strlen((char *)ref); + ret = knot_base64_encode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "2. test vector - ENC output length"); + if (ret < 0) { + skip("Encode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "2. test vector - ENC output content"); + } + ret = knot_base64_decode(out, ret, out2, BUF_LEN); + ok(ret == in_len, "2. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out2, in, ret) == 0, "2. test vector - DEC output content"); + } + + // 3. test vector -> ENC -> DEC + strlcpy((char *)in, "fo", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "Zm8=", BUF_LEN); + ref_len = strlen((char *)ref); + ret = knot_base64_encode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "3. test vector - ENC output length"); + if (ret < 0) { + skip("Encode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "3. test vector - ENC output content"); + } + ret = knot_base64_decode(out, ret, out2, BUF_LEN); + ok(ret == in_len, "3. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out2, in, ret) == 0, "3. test vector - DEC output content"); + } + + // 4. test vector -> ENC -> DEC + strlcpy((char *)in, "foo", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "Zm9v", BUF_LEN); + ref_len = strlen((char *)ref); + ret = knot_base64_encode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "4. test vector - ENC output length"); + if (ret < 0) { + skip("Encode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "4. test vector - ENC output content"); + } + ret = knot_base64_decode(out, ret, out2, BUF_LEN); + ok(ret == in_len, "4. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out2, in, ret) == 0, "4. test vector - DEC output content"); + } + + // 5. test vector -> ENC -> DEC + strlcpy((char *)in, "foob", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "Zm9vYg==", BUF_LEN); + ref_len = strlen((char *)ref); + ret = knot_base64_encode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "5. test vector - ENC output length"); + if (ret < 0) { + skip("Encode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "5. test vector - ENC output content"); + } + ret = knot_base64_decode(out, ret, out2, BUF_LEN); + ok(ret == in_len, "5. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out2, in, ret) == 0, "5. test vector - DEC output content"); + } + + // 6. test vector -> ENC -> DEC + strlcpy((char *)in, "fooba", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "Zm9vYmE=", BUF_LEN); + ref_len = strlen((char *)ref); + ret = knot_base64_encode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "6. test vector - ENC output length"); + if (ret < 0) { + skip("Encode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "6. test vector - ENC output content"); + } + ret = knot_base64_decode(out, ret, out2, BUF_LEN); + ok(ret == in_len, "6. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out2, in, ret) == 0, "6. test vector - DEC output content"); + } + + // 7. test vector -> ENC -> DEC + strlcpy((char *)in, "foobar", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "Zm9vYmFy", BUF_LEN); + ref_len = strlen((char *)ref); + ret = knot_base64_encode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "7. test vector - ENC output length"); + if (ret < 0) { + skip("Encode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "7. test vector - ENC output content"); + } + ret = knot_base64_decode(out, ret, out2, BUF_LEN); + ok(ret == in_len, "7. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out2, in, ret) == 0, "7. test vector - DEC output content"); + } + + // Bad paddings + ret = knot_base64_decode((uint8_t *)"A===", 4, out, BUF_LEN); + ok(ret == KNOT_BASE64_ECHAR, "Bad padding length 3"); + ret = knot_base64_decode((uint8_t *)"====", 4, out, BUF_LEN); + ok(ret == KNOT_BASE64_ECHAR, "Bad padding length 4"); + ret = knot_base64_decode((uint8_t *)"AA=A", 4, out, BUF_LEN); + ok(ret == KNOT_BASE64_ECHAR, "Bad padding character on position 2"); + ret = knot_base64_decode((uint8_t *)"Zg==Zg==", 8, out, BUF_LEN); + ok(ret == KNOT_BASE64_ECHAR, "Two quartets with padding"); + + // Bad data length + ret = knot_base64_decode((uint8_t *)"A", 1, out, BUF_LEN); + ok(ret == KNOT_BASE64_ESIZE, "Bad data length 1"); + ret = knot_base64_decode((uint8_t *)"AA", 2, out, BUF_LEN); + ok(ret == KNOT_BASE64_ESIZE, "Bad data length 2"); + ret = knot_base64_decode((uint8_t *)"AAA", 3, out, BUF_LEN); + ok(ret == KNOT_BASE64_ESIZE, "Bad data length 3"); + ret = knot_base64_decode((uint8_t *)"AAAAA", 5, out, BUF_LEN); + ok(ret == KNOT_BASE64_ESIZE, "Bad data length 5"); + + // Bad data character + ret = knot_base64_decode((uint8_t *)"AAA$", 4, out, BUF_LEN); + ok(ret == KNOT_BASE64_ECHAR, "Bad data character dollar"); + ret = knot_base64_decode((uint8_t *)"AAA ", 4, out, BUF_LEN); + ok(ret == KNOT_BASE64_ECHAR, "Bad data character space"); + + return 0; +} diff --git a/tests/contrib/test_base64url.c b/tests/contrib/test_base64url.c new file mode 100644 index 0000000..710aa29 --- /dev/null +++ b/tests/contrib/test_base64url.c @@ -0,0 +1,252 @@ +/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <https://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <tap/basic.h> + +#include "libknot/libknot.h" +#include "contrib/base64url.h" +#include "contrib/openbsd/strlcpy.h" + +#define BUF_LEN 256 +#define MAX_BIN_DATA_LEN ((INT32_MAX / 4) * 3) + +int main(int argc, char *argv[]) +{ + plan(50); + + int32_t ret; + uint8_t in[BUF_LEN], ref[BUF_LEN], out[BUF_LEN], out2[BUF_LEN], *out3; + uint32_t in_len, ref_len; + + // 0. test invalid input + ret = knot_base64url_encode(NULL, 0, out, BUF_LEN); + is_int(KNOT_EINVAL, ret, "knot_base64ulr_encode: NULL input buffer"); + ret = knot_base64url_encode(in, BUF_LEN, NULL, 0); + is_int(KNOT_EINVAL, ret, "knot_base64ulr_encode: NULL output buffer"); + ret = knot_base64url_encode(in, MAX_BIN_DATA_LEN + 1, out, BUF_LEN); + is_int(KNOT_ERANGE, ret, "knot_base64ulr_encode: input buffer too large"); + ret = knot_base64url_encode(in, BUF_LEN, out, BUF_LEN); + is_int(KNOT_ERANGE, ret, "knot_base64ulr_encode: output buffer too small"); + + ret = knot_base64url_encode_alloc(NULL, 0, &out3); + is_int(KNOT_EINVAL, ret, "knot_base64ulr_encode_alloc: NULL input buffer"); + ret = knot_base64url_encode_alloc(in, MAX_BIN_DATA_LEN + 1, &out3); + is_int(KNOT_ERANGE, ret, "knot_base64ulr_encode_alloc: input buffer too large"); + ret = knot_base64url_encode_alloc(in, BUF_LEN, NULL); + is_int(KNOT_EINVAL, ret, "knot_base64ulr_encode_alloc: NULL output buffer"); + + ret = knot_base64url_decode(NULL, 0, out, BUF_LEN); + is_int(KNOT_EINVAL, ret, "knot_base64ulr_decode: NULL input buffer"); + ret = knot_base64url_decode(in, BUF_LEN, NULL, 0); + is_int(KNOT_EINVAL, ret, "knot_base64ulr_decode: NULL output buffer"); + ret = knot_base64url_decode(in, BUF_LEN, out, 0); + is_int(KNOT_ERANGE, ret, "knot_base64ulr_decode: output buffer too small"); + + ret = knot_base64url_decode_alloc(NULL, 0, &out3); + is_int(KNOT_EINVAL, ret, "knot_base64ulr_decode_alloc: NULL input buffer"); + ret = knot_base64url_decode_alloc(in, BUF_LEN, NULL); + is_int(KNOT_EINVAL, ret, "knot_base64ulr_decode_alloc: NULL output buffer"); + + // 1. test vector -> ENC -> DEC + strlcpy((char *)in, "", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "", BUF_LEN); + ref_len = strlen((char *)ref); + ret = knot_base64url_encode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "1. test vector - ENC output length"); + if (ret < 0) { + skip("Encode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "1. test vector - ENC output content"); + } + ret = knot_base64url_decode(out, ret, out2, BUF_LEN); + ok(ret == in_len, "1. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out2, in, ret) == 0, "1. test vector - DEC output content"); + } + + // 2. test vector -> ENC -> DEC + strlcpy((char *)in, "f", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "Zg", BUF_LEN); + ref_len = strlen((char *)ref); + ret = knot_base64url_encode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "2. test vector - ENC output length"); + if (ret < 0) { + skip("Encode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "2. test vector - ENC output content"); + } + ret = knot_base64url_decode(out, ret, out2, BUF_LEN); + ok(ret == in_len, "2. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out2, in, ret) == 0, "2. test vector - DEC output content"); + } + + // 3. test vector -> ENC -> DEC + strlcpy((char *)in, "fo", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "Zm8", BUF_LEN); + ref_len = strlen((char *)ref); + ret = knot_base64url_encode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "3. test vector - ENC output length"); + if (ret < 0) { + skip("Encode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "3. test vector - ENC output content"); + } + ret = knot_base64url_decode(out, ret, out2, BUF_LEN); + ok(ret == in_len, "3. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out2, in, ret) == 0, "3. test vector - DEC output content"); + } + + // 4. test vector -> ENC -> DEC + strlcpy((char *)in, "foo", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "Zm9v", BUF_LEN); + ref_len = strlen((char *)ref); + ret = knot_base64url_encode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "4. test vector - ENC output length"); + if (ret < 0) { + skip("Encode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "4. test vector - ENC output content"); + } + ret = knot_base64url_decode(out, ret, out2, BUF_LEN); + ok(ret == in_len, "4. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out2, in, ret) == 0, "4. test vector - DEC output content"); + } + + // 5. test vector -> ENC -> DEC + strlcpy((char *)in, "foob", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "Zm9vYg", BUF_LEN); + ref_len = strlen((char *)ref); + ret = knot_base64url_encode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "5. test vector - ENC output length"); + if (ret < 0) { + skip("Encode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "5. test vector - ENC output content"); + } + ret = knot_base64url_decode(out, ret, out2, BUF_LEN); + ok(ret == in_len, "5. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out2, in, ret) == 0, "5. test vector - DEC output content"); + } + + // 6. test vector -> ENC -> DEC + strlcpy((char *)in, "fooba", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "Zm9vYmE", BUF_LEN); + ref_len = strlen((char *)ref); + ret = knot_base64url_encode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "6. test vector - ENC output length"); + if (ret < 0) { + skip("Encode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "6. test vector - ENC output content"); + } + ret = knot_base64url_decode(out, ret, out2, BUF_LEN); + ok(ret == in_len, "6. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out2, in, ret) == 0, "6. test vector - DEC output content"); + } + + // 7. test vector -> ENC -> DEC + strlcpy((char *)in, "foobar", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "Zm9vYmFy", BUF_LEN); + ref_len = strlen((char *)ref); + ret = knot_base64url_encode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "7. test vector - ENC output length"); + if (ret < 0) { + skip("Encode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "7. test vector - ENC output content"); + } + ret = knot_base64url_decode(out, ret, out2, BUF_LEN); + ok(ret == in_len, "7. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out2, in, ret) == 0, "7. test vector - DEC output content"); + } + + // 8. ENC (percent-encoded padding) -> DEC + strlcpy((char *)in, "Zm9vYmE%3D", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "fooba", BUF_LEN); + ref_len = strlen((char *)ref); + + ret = knot_base64url_decode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "8. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "8. test vector - DEC output content"); + } + + strlcpy((char *)in, "Zm9vYmFyCg%3d%3d", BUF_LEN); + in_len = strlen((char *)in); + strlcpy((char *)ref, "foobar\n", BUF_LEN); + ref_len = strlen((char *)ref); + + ret = knot_base64url_decode(in, in_len, out, BUF_LEN); + ok(ret == ref_len, "9. test vector - DEC output length"); + if (ret < 0) { + skip("Decode err"); + } else { + ok(memcmp(out, ref, ret) == 0, "9. test vector - DEC output content"); + } + + // Bad paddings + ret = knot_base64url_decode((uint8_t *)"A", 1, out, BUF_LEN); + ok(ret == KNOT_BASE64_ECHAR, "Bad padding length 3"); + ret = knot_base64url_decode((uint8_t *)"%3D", 3, out, BUF_LEN); + ok(ret == KNOT_BASE64_ECHAR, "Bad padding length 4"); + + // Paddings not at the end + ret = knot_base64url_decode((uint8_t *)"AB%3DCDEFG", 10, out, BUF_LEN); + ok(ret == KNOT_BASE64_ECHAR, "Bad padding 1"); + ret = knot_base64url_decode((uint8_t *)"AB\0CDEFG", 8, out, BUF_LEN); + ok(ret == KNOT_BASE64_ECHAR, "Bad padding 2"); + + // Bad data character + ret = knot_base64url_decode((uint8_t *)"AAA$", 4, out, BUF_LEN); + ok(ret == KNOT_BASE64_ECHAR, "Bad data character dollar"); + ret = knot_base64url_decode((uint8_t *)"AAA ", 4, out, BUF_LEN); + ok(ret == KNOT_BASE64_ECHAR, "Bad data character space"); + + return 0; +} diff --git a/tests/contrib/test_heap.c b/tests/contrib/test_heap.c new file mode 100644 index 0000000..7dc5975 --- /dev/null +++ b/tests/contrib/test_heap.c @@ -0,0 +1,166 @@ +/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <https://www.gnu.org/licenses/>. + */ + +#include <tap/basic.h> + +#include <assert.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "contrib/ucw/heap.h" + +static void seed_random(void) +{ + unsigned short int seed[3] = { 0 }; + + FILE *f = fopen("/dev/urandom", "r"); + if (f) { + if (fread(seed, sizeof(seed), 1, f) != 1) { + diag("failed to seed random source"); + } + fclose(f); + } + + diag("seed %hu %hu %hu", seed[0], seed[1], seed[2]); + seed48(seed); +} + +struct value { + heap_val_t _heap; + int data; +}; + +static int value_cmp(void *_a, void *_b) +{ + const struct value *a = _a; + const struct value *b = _b; + return (a->data - b->data); +} + +int main(int argc, char *argv[]) +{ + plan_lazy(); + + seed_random(); + + static const int VALUE_COUNT = 1000; + static const int VALUE_RANGE = 950; + static const int VALUE_REPLACE = 300; + static const int VALUE_DELETE = 100; + + struct heap heap; + heap_init(&heap, value_cmp, 0); + + ok(EMPTY_HEAP(&heap), "heap is empty"); + + // fill the heap with random values (with duplicates) + + struct value *values = calloc(VALUE_COUNT, sizeof(struct value)); + assert(values); + assert(VALUE_RANGE < VALUE_COUNT); + + bool valid = true; + for (int i = 0; i < VALUE_COUNT; i++) { + values[i].data = lrand48() % VALUE_RANGE; + if (heap_insert(&heap, &values[i]._heap) == 0) { + valid = false; + } + } + ok(valid, "heap_insert"); + ok(!EMPTY_HEAP(&heap), "heap is non-empty"); + + // exercise heap_insert + + valid = true; + for (int i = 0; i < VALUE_COUNT; i++) { + int pos = heap_find(&heap, &values[i]._heap); + if (*HELEMENT(&heap, pos) != &values[i]._heap) { + valid = false; + } + } + ok(valid, "heap_find"); + + // exercise heap_replace + + assert(VALUE_REPLACE <= VALUE_COUNT); + struct value *replaced = calloc(VALUE_REPLACE, sizeof(struct value)); + assert(replaced); + + valid = true; + for (int i = 0; i < VALUE_REPLACE; i++) { + replaced[i].data = lrand48() % VALUE_RANGE; + int pos = heap_find(&heap, &values[i]._heap); + if (pos < 1) { + valid = false; + continue; + } + + heap_replace(&heap, pos, &replaced[i]._heap); + int newpos = heap_find(&heap, &replaced[i]._heap); + if (newpos < 1) { + valid = false; + } + } + ok(valid, "heap_replace"); + + // exercise heap_delete + + assert(VALUE_REPLACE + VALUE_DELETE < VALUE_COUNT); + + valid = true; + for (int i = 0; i < VALUE_DELETE; i++) { + heap_val_t *value = &values[i + VALUE_REPLACE]._heap; + int pos = heap_find(&heap, value); + if (pos < 1) { + valid = false; + continue; + + } + heap_delete(&heap, pos); + pos = heap_find(&heap, value); + if (pos != 0) { + valid = false; + } + } + ok(valid, "heap_delete"); + + // exercise item retrieval + + assert(VALUE_COUNT > VALUE_DELETE); + + valid = true; + int current = -1; + for (int i = 0; i < VALUE_COUNT - VALUE_DELETE; i++) { + struct value *val = (struct value *)*HHEAD(&heap); + heap_delmin(&heap); + if (current <= val->data) { + current = val->data; + } else { + valid = false; + } + } + + ok(valid, "heap ordering"); + ok(EMPTY_HEAP(&heap), "heap_delmin"); + + free(replaced); + free(values); + heap_deinit(&heap); + + return 0; +} diff --git a/tests/contrib/test_inet_ntop.c b/tests/contrib/test_inet_ntop.c new file mode 100644 index 0000000..33e7b7e --- /dev/null +++ b/tests/contrib/test_inet_ntop.c @@ -0,0 +1,85 @@ +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <https://www.gnu.org/licenses/>. + */ + +#include <tap/basic.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <string.h> + +#include "contrib/musl/inet_ntop.h" + +uint8_t bin[sizeof(struct in6_addr)]; +const socklen_t len = INET6_ADDRSTRLEN; +char buf[INET6_ADDRSTRLEN]; +const char *txt; + +#define CHECK4(addr) \ + ok(inet_pton(AF_INET, addr, bin) == 1, "inet_pton(%s)", addr); \ + ok((txt = knot_inet_ntop(AF_INET, bin, buf, len)) != NULL, "knot_inet_ntop(%s)", addr); \ + ok(strcmp(txt, addr) == 0, "match %s", addr); + +#define CHECK6(addr, ref) \ + ok(inet_pton(AF_INET6, addr, bin) == 1, "inet_pton(%s)", addr); \ + ok((txt = knot_inet_ntop(AF_INET6, bin, buf, len)) != NULL, "knot_inet_ntop(%s)", addr); \ + ok(strcmp(txt, ref) == 0, "match %s %s", txt, ref); + +int main(int argc, char *argv[]) +{ + plan_lazy(); + + diag("IPv4 addresses"); + CHECK4("0.0.0.0"); + CHECK4("1.2.3.4"); + CHECK4("11.12.13.14"); + CHECK4("255.255.255.255"); + + diag("IPv6 addresses"); + CHECK6("::0", "::"); + CHECK6("::00", "::"); + CHECK6("::000", "::"); + CHECK6("::0000", "::"); + + CHECK6("::1", "::1"); + CHECK6("::01", "::1"); + CHECK6("::001", "::1"); + CHECK6("::0001", "::1"); + + CHECK6("::10", "::10"); + CHECK6("::100", "::100"); + CHECK6("::1000", "::1000"); + + CHECK6("::1:0", "::1:0"); + CHECK6("::1:0:0", "::1:0:0"); + CHECK6("::1:0:0:0", "::1:0:0:0"); + CHECK6("::1:0:0:0:0", "0:0:0:1::"); + CHECK6("::1:0:0:0:0:0", "0:0:1::"); + CHECK6("::1:0:0:0:0:0:0", "0:1::"); + CHECK6("1:0:0:0:0:0:0:0", "1::"); + + // IPv4-Compatible IPv6 Addresses (not supported). + CHECK6("::0:1:1", "::1:1"); + CHECK6("::0:1.2.3.4", "::102:304"); + + // IPv4-Mapped IPv6 Addresses. + CHECK6("::ffff:1:1", "::ffff:0.1.0.1"); + CHECK6("::ffff:1.2.3.4", "::ffff:1.2.3.4"); + + CHECK6("1::1", "1::1"); + CHECK6("1000::1", "1000::1"); + CHECK6("1:20:300:4000:0005:006:07:8", "1:20:300:4000:5:6:7:8"); + + return 0; +} diff --git a/tests/contrib/test_net.c b/tests/contrib/test_net.c new file mode 100644 index 0000000..c0061cd --- /dev/null +++ b/tests/contrib/test_net.c @@ -0,0 +1,718 @@ +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <https://www.gnu.org/licenses/>. + */ + +#include <tap/basic.h> + +#include <assert.h> +#include <fcntl.h> +#include <poll.h> +#include <pthread.h> +#include <signal.h> +#include <stdbool.h> +#include <string.h> +#include <unistd.h> + +#include "libknot/errcode.h" +#include "contrib/net.h" +#include "contrib/sockaddr.h" + +const int TIMEOUT = 5000; +const int TIMEOUT_SHORT = 500; + +/*! + * \brief Get loopback socket address with unset port. + */ +static struct sockaddr_storage addr_local(void) +{ + struct sockaddr_storage addr = { 0 }; + + struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr; + addr4->sin_family = AF_INET; + addr4->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + return addr; +} + +/*! + * \brief Get address of a socket. + */ +static struct sockaddr_storage addr_from_socket(int sock) +{ + struct sockaddr_storage addr = { 0 }; + socklen_t len = sizeof(addr); + int ret = getsockname(sock, (struct sockaddr *)&addr, &len); + is_int(0, ret, "check getsockname return"); + + return addr; +} + +static const char *socktype_name(int type) +{ + switch (type) { + case SOCK_STREAM: return "TCP"; + case SOCK_DGRAM: return "UDP"; + default: return "unknown"; + } +} + +static bool socktype_is_stream(int type) +{ + return type == SOCK_STREAM; +} + +/* -- mock server ---------------------------------------------------------- */ + +#define LISTEN_BACKLOG 5 + +struct server_ctx; +typedef struct server_ctx server_ctx_t; + +typedef void (*server_cb)(int sock, void *data); + +/*! + * \brief Server context. + */ +struct server_ctx { + int sock; + int type; + bool terminate; + server_cb handler; + void *handler_data; + + pthread_t thr; + pthread_mutex_t mx; +}; + +static int poll_read(int sock) +{ + struct pollfd pfd = { .fd = sock, .events = POLLIN }; + return poll(&pfd, 1, TIMEOUT); +} + +static void server_handle(server_ctx_t *ctx) +{ + int remote = ctx->sock; + + assert(ctx->type == SOCK_STREAM || ctx->type == SOCK_DGRAM); + + if (socktype_is_stream(ctx->type)) { + remote = accept(ctx->sock, 0, 0); + if (remote < 0) { + return; + } + } + + pthread_mutex_lock(&ctx->mx); + server_cb handler = ctx->handler; + pthread_mutex_unlock(&ctx->mx); + handler(remote, ctx->handler_data); + + if (socktype_is_stream(ctx->type)) { + close(remote); + } +} + +/*! + * \brief Simple server. + * + * Terminated when a one-byte message is delivered. + */ +static void *server_main(void *_ctx) +{ + server_ctx_t *ctx = _ctx; + + for (;;) { + pthread_mutex_lock(&ctx->mx); + bool terminate = ctx->terminate; + pthread_mutex_unlock(&ctx->mx); + if (terminate) { + break; + } + + int r = poll_read(ctx->sock); + if (r == -1) { + if (errno == EINTR) { + continue; + } else { + break; + } + } else if (r == 0) { + continue; + } + + assert(r == 1); + server_handle(ctx); + } + + return NULL; +} + +static bool server_start(server_ctx_t *ctx, int sock, int type, + server_cb handler, void *handler_data) +{ + memset(ctx, 0, sizeof(*ctx)); + + ctx->sock = sock; + ctx->type = type; + ctx->handler = handler; + ctx->handler_data = handler_data; + + ctx->terminate = false; + + pthread_mutex_init(&ctx->mx, NULL); + return (pthread_create(&ctx->thr, NULL, server_main, ctx) == 0); +} + +static void server_stop(server_ctx_t *ctx) +{ + pthread_mutex_lock(&ctx->mx); + ctx->terminate = true; + pthread_mutex_unlock(&ctx->mx); + + pthread_kill(ctx->thr, SIGUSR1); + pthread_join(ctx->thr, NULL); +} + +/* -- tests ---------------------------------------------------------------- */ + +static void handler_echo(int sock, void *_server) +{ + server_ctx_t *server = _server; + uint8_t buffer[16] = { 0 }; + + struct sockaddr_storage remote = { 0 }; + struct sockaddr_storage *addr = NULL; + if (!socktype_is_stream(server->type)) { + addr = &remote; + } + + int in = net_base_recv(sock, buffer, sizeof(buffer), addr, TIMEOUT); + if (in <= 0) { + return; + } + + net_base_send(sock, buffer, in, addr, TIMEOUT); +} + +static void test_connected_one(const struct sockaddr_storage *server_addr, + const struct sockaddr_storage *source_addr, + int type, const char *name, const char *addr_name) +{ + int r; + + int client = net_connected_socket(type, server_addr, source_addr, false); + ok(client >= 0, "%s, %s: client, create connected socket", name, addr_name); + + const uint8_t out[] = "test message"; + const size_t out_len = sizeof(out); + if (socktype_is_stream(type)) { + r = net_stream_send(client, out, out_len, TIMEOUT); + } else { + r = net_dgram_send(client, out, out_len, NULL); + } + is_int(out_len, r, "%s, %s: client, send message", name, addr_name); + + r = net_is_connected(client); + ok(r, "%s, %s: client, is connected", name, addr_name); + + uint8_t in[128] = { 0 }; + if (socktype_is_stream(type)) { + r = net_stream_recv(client, in, sizeof(in), TIMEOUT); + } else { + r = net_dgram_recv(client, in, sizeof(in), TIMEOUT); + } + is_int(out_len, r, "%s, %s: client, receive message length", name, addr_name); + ok(memcmp(out, in, out_len) == 0, "%s, %s: client, receive message", name, addr_name); + + close(client); +} + +static void test_connected(int type) +{ + const char *name = socktype_name(type); + const struct sockaddr_storage empty_addr = { 0 }; + const struct sockaddr_storage local_addr = addr_local(); + + int r; + + // setup server + + int server = net_bound_socket(type, &local_addr, 0, 0); + ok(server >= 0, "%s: server, create bound socket", name); + + if (socktype_is_stream(type)) { + r = listen(server, LISTEN_BACKLOG); + is_int(0, r, "%s: server, start listening", name); + } + + server_ctx_t server_ctx = { 0 }; + r = server_start(&server_ctx, server, type, handler_echo, &server_ctx); + ok(r, "%s: server, start", name); + + const struct sockaddr_storage server_addr = addr_from_socket(server); + + // connected socket, send and receive + + test_connected_one(&server_addr, NULL, type, name, "NULL source"); + test_connected_one(&server_addr, &empty_addr, type, name, "zero source"); + test_connected_one(&server_addr, &local_addr, type, name, "valid source"); + + // cleanup + + server_stop(&server_ctx); + close(server); +} + +static void handler_noop(int sock, void *data) +{ +} + +static void test_unconnected(void) +{ + int r = 0; + int sock = -1; + const struct sockaddr_storage local = addr_local(); + + uint8_t buffer[] = { 'k', 'n', 'o', 't' }; + ssize_t buffer_len = sizeof(buffer); + + // server + + int server = net_bound_socket(SOCK_DGRAM, &local, 0, 0); + ok(server >= 0, "UDP, create server socket"); + + server_ctx_t server_ctx = { 0 }; + r = server_start(&server_ctx, server, SOCK_DGRAM, handler_noop, NULL); + ok(r, "UDP, start server"); + + // UDP + + sock = net_unbound_socket(SOCK_DGRAM, &local); + ok(sock >= 0, "UDP, create unbound socket"); + + ok(!net_is_connected(sock), "UDP, is not connected"); + + r = net_dgram_send(sock, buffer, buffer_len, NULL); + is_int(KNOT_ECONN, r, "UDP, send failure on unconnected socket"); + + r = net_dgram_recv(sock, buffer, buffer_len, TIMEOUT_SHORT); + is_int(KNOT_ETIMEOUT, r, "UDP, receive timeout on unconnected socket"); + + struct sockaddr_storage server_addr = addr_from_socket(server); + r = net_dgram_send(sock, buffer, buffer_len, &server_addr); + is_int(buffer_len, r, "UDP, send on defined address"); + + close(sock); + + // TCP + + sock = net_unbound_socket(SOCK_STREAM, &local); + ok(sock >= 0, "TCP, create unbound socket"); + + ok(!net_is_connected(sock), "TCP, is not connected"); + +#ifdef __linux__ + const int expected = KNOT_ECONN; + const char *expected_msg = "failure"; + const int expected_timeout = TIMEOUT; +#else + const int expected = KNOT_ETIMEOUT; + const char *expected_msg = "timeout"; + const int expected_timeout = TIMEOUT_SHORT; +#endif + + r = net_stream_send(sock, buffer, buffer_len, expected_timeout); + is_int(expected, r, "TCP, send %s on unconnected socket", expected_msg); + + r = net_stream_recv(sock, buffer, sizeof(buffer), expected_timeout); + is_int(expected, r, "TCP, receive %s on unconnected socket", expected_msg); + + close(sock); + + // server termination + + server_stop(&server_ctx); + close(server); +} + +static void test_refused(void) +{ + int r = -1; + + struct sockaddr_storage addr = { 0 }; + uint8_t buffer[1] = { 0 }; + int server, client; + + // listening, not accepting + + addr = addr_local(); + server = net_bound_socket(SOCK_STREAM, &addr, 0, 0); + ok(server >= 0, "server, create server"); + addr = addr_from_socket(server); + + r = listen(server, LISTEN_BACKLOG); + is_int(0, r, "server, start listening"); + + client = net_connected_socket(SOCK_STREAM, &addr, NULL, false); + ok(client >= 0, "client, connect"); + + r = net_stream_send(client, (uint8_t *)"", 1, TIMEOUT); + is_int(1, r, "client, successful write"); + + r = net_stream_recv(client, buffer, sizeof(buffer), TIMEOUT_SHORT); + is_int(KNOT_ETIMEOUT, r, "client, timeout on read"); + + close(client); + + // listening, closed immediately + + client = net_connected_socket(SOCK_STREAM, &addr, NULL, false); + ok(client >= 0, "client, connect"); + + r = close(server); + is_int(0, r, "server, close socket"); + usleep(50000); + + r = net_stream_send(client, (uint8_t *)"", 1, TIMEOUT); + is_int(KNOT_ECONN, r, "client, refused on write"); + + close(client); +} + +struct dns_handler_ctx { + const uint8_t *expected; + int len; + bool raw; + bool success; +}; + +static bool _sync(int remote, int send) +{ + uint8_t buf[1] = { 0 }; + int r; + if (send) { + r = net_stream_send(remote, buf, sizeof(buf), TIMEOUT); + } else { + r = net_stream_recv(remote, buf, sizeof(buf), TIMEOUT); + + } + return r == sizeof(buf); +} + +static bool sync_signal(int remote) +{ + return _sync(remote, true); +} + +static bool sync_wait(int remote) +{ + return _sync(remote, false); +} + +static void handler_dns(int sock, void *_ctx) +{ + struct dns_handler_ctx *ctx = _ctx; + + uint8_t in[16] = { 0 }; + int in_len = 0; + + sync_signal(sock); + + if (ctx->raw) { + in_len = net_stream_recv(sock, in, sizeof(in), TIMEOUT); + } else { + in_len = net_dns_tcp_recv(sock, in, sizeof(in), TIMEOUT); + } + + ctx->success = in_len == ctx->len && + (ctx->len < 0 || memcmp(in, ctx->expected, in_len) == 0); +} + +static void dns_send_hello(int sock) +{ + net_dns_tcp_send(sock, (uint8_t *)"wimbgunts", 9, TIMEOUT, NULL); +} + +static void dns_send_fragmented(int sock) +{ + struct fragment { const uint8_t *data; size_t len; }; + + const struct fragment fragments[] = { + { (uint8_t *)"\x00", 1 }, + { (uint8_t *)"\x08""qu", 3 }, + { (uint8_t *)"oopisk", 6 }, + { NULL } + }; + + for (const struct fragment *f = fragments; f->len > 0; f++) { + net_stream_send(sock, f->data, f->len, TIMEOUT); + } +} + +static void dns_send_incomplete(int sock) +{ + net_stream_send(sock, (uint8_t *)"\x00\x08""korm", 6, TIMEOUT); +} + +static void dns_send_trailing(int sock) +{ + net_stream_send(sock, (uint8_t *)"\x00\x05""bloitxx", 9, TIMEOUT); +} + +static void test_dns_tcp(void) +{ + struct testcase { + const char *name; + const uint8_t *expected; + size_t expected_len; + bool expected_raw; + void (*send_callback)(int sock); + }; + + const struct testcase testcases[] = { + { "single DNS", (uint8_t *)"wimbgunts", 9, false, dns_send_hello }, + { "single RAW", (uint8_t *)"\x00\x09""wimbgunts", 11, true, dns_send_hello }, + { "fragmented", (uint8_t *)"quoopisk", 8, false, dns_send_fragmented }, + { "incomplete", NULL, KNOT_ECONN, false, dns_send_incomplete }, + { "trailing garbage", (uint8_t *)"bloit", 5, false, dns_send_trailing }, + { NULL } + }; + + for (const struct testcase *t = testcases; t->name != NULL; t++) { + struct dns_handler_ctx handler_ctx = { + .expected = t->expected, + .len = t->expected_len, + .raw = t->expected_raw, + .success = false + }; + + struct sockaddr_storage addr = addr_local(); + int server = net_bound_socket(SOCK_STREAM, &addr, 0, 0); + ok(server >= 0, "%s, server, create socket", t->name); + + int r = listen(server, LISTEN_BACKLOG); + is_int(0, r, "%s, server, start listening", t->name); + + server_ctx_t server_ctx = { 0 }; + r = server_start(&server_ctx, server, SOCK_STREAM, handler_dns, &handler_ctx); + ok(r, "%s, server, start handler", t->name); + + addr = addr_from_socket(server); + int client = net_connected_socket(SOCK_STREAM, &addr, NULL, false); + ok(client >= 0, "%s, client, create connected socket", t->name); + + r = sync_wait(client); + ok(r, "%s, client, wait for stream read", t->name); + t->send_callback(client); + + close(client); + server_stop(&server_ctx); + close(server); + + ok(handler_ctx.success, "%s, expected result", t->name); + } +} + +static bool socket_is_blocking(int sock) +{ + return fcntl(sock, F_GETFL, O_NONBLOCK) == 0; +} + +static void test_nonblocking_mode(int type) +{ + const char *name = socktype_name(type); + const struct sockaddr_storage addr = addr_local(); + + int client = net_unbound_socket(type, &addr); + ok(client >= 0, "%s: unbound, create", name); + ok(!socket_is_blocking(client), "%s: unbound, nonblocking mode", name); + close(client); + + int server = net_bound_socket(type, &addr, 0, 0); + ok(server >= 0, "%s: bound, create", name); + ok(!socket_is_blocking(server), "%s: bound, nonblocking mode", name); + + if (socktype_is_stream(type)) { + int r = listen(server, LISTEN_BACKLOG); + is_int(0, r, "%s: bound, start listening", name); + } + + struct sockaddr_storage server_addr = addr_from_socket(server); + client = net_connected_socket(type, &server_addr, NULL, false); + ok(client >= 0, "%s: connected, create", name); + ok(!socket_is_blocking(client), "%s: connected, nonblocking mode", name); + + close(client); + close(server); +} + +static void test_nonblocking_accept(void) +{ + int r; + + // create server + + struct sockaddr_storage addr_server = addr_local(); + + int server = net_bound_socket(SOCK_STREAM, &addr_server, 0, 0); + ok(server >= 0, "server, create socket"); + + r = listen(server, LISTEN_BACKLOG); + is_int(0, r, "server, start listening"); + + addr_server = addr_from_socket(server); + + // create client + + int client = net_connected_socket(SOCK_STREAM, &addr_server, NULL, false); + ok(client >= 0, "client, create connected socket"); + + struct sockaddr_storage addr_client = addr_from_socket(client); + + // accept connection + + r = poll_read(server); + is_int(1, r, "server, pending connection"); + + struct sockaddr_storage addr_accepted = { 0 }; + int accepted = net_accept(server, &addr_accepted); + ok(accepted >= 0, "server, accept connection"); + + ok(!socket_is_blocking(accepted), "accepted, nonblocking mode"); + + ok(sockaddr_cmp(&addr_client, &addr_accepted, false) == 0, + "accepted, correct address"); + + close(client); + + // client reconnect + + close(client); + client = net_connected_socket(SOCK_STREAM, &addr_server, NULL, false); + ok(client >= 0, "client, reconnect"); + + r = poll_read(server); + is_int(1, r, "server, pending connection"); + + accepted = net_accept(server, NULL); + ok(accepted >= 0, "server, accept connection (no remote address)"); + + ok(!socket_is_blocking(accepted), "accepted, nonblocking mode"); + + // cleanup + + close(client); + close(server); +} + +static void test_socket_types(void) +{ + struct sockaddr_storage addr = addr_local(); + + struct testcase { + const char *name; + int type; + bool is_stream; + }; + + const struct testcase testcases[] = { + { "UDP", SOCK_DGRAM, false }, + { "TCP", SOCK_STREAM, true }, + { NULL } + }; + + for (const struct testcase *t = testcases; t->name != NULL; t++) { + int sock = net_unbound_socket(t->type, &addr); + ok(sock >= 0, "%s, create socket", t->name); + + is_int(t->type, net_socktype(sock), "%s, socket type", t->name); + + ok(net_is_stream(sock) == t->is_stream, "%s, is stream", t->name); + + close(sock); + } + + is_int(AF_UNSPEC, net_socktype(-1), "invalid, socket type"); + ok(!net_is_stream(-1), "invalid, is stream"); +} + +static void test_bind_multiple(void) +{ + const struct sockaddr_storage addr = addr_local(); + + // bind first socket + + int sock_one = net_bound_socket(SOCK_DGRAM, &addr, NET_BIND_MULTIPLE, 0); + if (sock_one == KNOT_ENOTSUP) { + skip("not supported on this system"); + return; + } + ok(sock_one >= 0, "bind first socket"); + + // bind second socket to the same address + + const struct sockaddr_storage addr_one = addr_from_socket(sock_one); + int sock_two = net_bound_socket(SOCK_DGRAM, &addr_one, NET_BIND_MULTIPLE, 0); + ok(sock_two >= 0, "bind second socket"); + + // compare sockets + + ok(sock_one != sock_two, "descriptors are different"); + + const struct sockaddr_storage addr_two = addr_from_socket(sock_two); + ok(sockaddr_cmp(&addr_one, &addr_two, false) == 0, + "addresses are the same"); + + close(sock_one); + close(sock_two); +} + +static void signal_noop(int sig) +{ +} + +int main(int argc, char *argv[]) +{ + plan_lazy(); + + signal(SIGUSR1, signal_noop); + + diag("nonblocking mode"); + test_nonblocking_mode(SOCK_DGRAM); + test_nonblocking_mode(SOCK_STREAM); + test_nonblocking_accept(); + + diag("socket types"); + test_socket_types(); + + diag("connected sockets"); + test_connected(SOCK_DGRAM); + test_connected(SOCK_STREAM); + + diag("unconnected sockets"); + test_unconnected(); + + diag("refused connections"); + test_refused(); + + diag("DNS messages over TCP"); + test_dns_tcp(); + + diag("flag NET_BIND_MULTIPLE"); + test_bind_multiple(); + + return 0; +} diff --git a/tests/contrib/test_net_shortwrite.c b/tests/contrib/test_net_shortwrite.c new file mode 100644 index 0000000..f3d4c6e --- /dev/null +++ b/tests/contrib/test_net_shortwrite.c @@ -0,0 +1,151 @@ +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <https://www.gnu.org/licenses/>. + */ + +#include <tap/basic.h> + +#include <fcntl.h> +#include <stdint.h> +#include <string.h> +#include <pthread.h> +#include <poll.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> + +#include "libknot/errcode.h" +#include "contrib/net.h" +#include "contrib/sockaddr.h" + +const int TIMEOUT = 2000; + +static struct sockaddr_storage localhost(void) +{ + struct sockaddr_storage addr = { 0 }; + + struct addrinfo *res = NULL; + if (getaddrinfo(NULL, "0", NULL, &res) == 0) { + memcpy(&addr, res->ai_addr, res->ai_addrlen); + freeaddrinfo(res); + } + + return addr; +} + +struct data { + int server_fd; + uint8_t *buffer; + size_t size; + int result; +}; + +static void *thr_receive(void *data) +{ + struct data *d = data; + + struct pollfd pfd = { .fd = d->server_fd, .events = POLLIN }; + int r = poll(&pfd, 1, TIMEOUT); + if (r != 1) { + d->result = KNOT_ETIMEOUT; + return NULL; + } + + int client = accept(d->server_fd, NULL, NULL); + if (client < 0) { + d->result = KNOT_ECONN; + return NULL; + } + + d->result = net_dns_tcp_recv(client, d->buffer, d->size, TIMEOUT); + + close(client); + + return NULL; +} + +int main(int argc, char *argv[]) +{ + plan_lazy(); + + int r; + + // create TCP server + + struct sockaddr_storage addr = localhost(); + int server = net_bound_socket(SOCK_STREAM, &addr, 0, 0); + ok(server >= 0, "server: bind socket"); + + r = listen(server, 1); + ok(r == 0, "server: start listening"); + + struct sockaddr *sa = (struct sockaddr *)&addr; + socklen_t salen = sockaddr_len(&addr); + r = getsockname(server, sa, &salen); + ok(r == 0, "server: get bound address"); + + // create TCP client + + int client = net_connected_socket(SOCK_STREAM, &addr, NULL, false); + ok(client >= 0, "client: connect to server"); + + int optval = 8192; + socklen_t optlen = sizeof(optval); + r = setsockopt(client, SOL_SOCKET, SO_SNDBUF, &optval, optlen); + ok(r == 0, "client: configure small send buffer"); + + // accept TCP connection on the background + + uint8_t recvbuf[UINT16_MAX] = { 0 }; + struct data recv_data = { + .server_fd = server, + .buffer = recvbuf, + .size = sizeof(recvbuf) + }; + + pthread_t thr; + r = pthread_create(&thr, NULL, thr_receive, &recv_data); + ok(r == 0, "server: start receiver thread"); + + // send message (should handle partial-write correctly) + + uint8_t sndbuf[UINT16_MAX]; + for (size_t i = 0; i < sizeof(sndbuf); i++) { + sndbuf[i] = i; + } + r = net_dns_tcp_send(client, sndbuf, sizeof(sndbuf), TIMEOUT, NULL); + ok(r == sizeof(sndbuf), "client: net_dns_tcp_send() with short-write"); + + // receive message + + r = pthread_join(thr, NULL); + ok(r == 0, "server: wait for receiver thread to terminate"); + + ok(recv_data.result == sizeof(recvbuf) && + memcmp(sndbuf, recvbuf, sizeof(recvbuf)) == 0, + "server: net_dns_tcp_recv() complete and valid data"); + + // clean up + + if (server >= 0) { + close(server); + } + + if (client >= 0) { + close(client); + } + + return 0; +} diff --git a/tests/contrib/test_qp-cow.c b/tests/contrib/test_qp-cow.c new file mode 100644 index 0000000..4cd8c6c --- /dev/null +++ b/tests/contrib/test_qp-cow.c @@ -0,0 +1,282 @@ +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + Copyright (C) 2018 Tony Finch <dot@dotat.at> + + 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 <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <err.h> +#include <unistd.h> + +#include "contrib/qp-trie/trie.h" +#include "contrib/string.h" +#include "libknot/attribute.h" +#include "libknot/errcode.h" +#include "tap/basic.h" + +/* Constants. */ +#define MAX_KEYLEN 64 +#define MAX_LEAVES 12345 +#define MAX_MUTATIONS 123 +#define MAX_TRANSACTIONS 1234 + +enum cowstate { + cow_absent, // not in trie + cow_unmarked, + cow_shared, + cow_old, // deleted from new trie + cow_new, // added to new trie + deadbeef, +}; + +struct cowleaf { + char *key; + size_t len; + int cowstate; +}; + +static inline size_t +prng(size_t max) { + /* good enough these days */ + return (size_t)rand() % max; +} + +static struct cowleaf * +grow_leaves(size_t maxlen, size_t leaves) +{ + struct cowleaf *leaf = bcalloc(leaves, sizeof(*leaf)); + + trie_t *trie = trie_create(NULL); + if (!trie) sysbail("trie_create"); + + for (size_t i = 0; i < leaves; i++) { + trie_val_t *valp; + char *str = NULL; + size_t len = 0; + do { + free(str); + len = prng(maxlen); + str = bmalloc(len + 1); + for (size_t j = 0; j < len; j++) + str[j] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + [prng(62)]; + str[len] = '\0'; + valp = trie_get_ins(trie, (uint8_t *)str, (uint32_t)len); + if (!valp) bail("trie_get_ins"); + } while (*valp != NULL); + *valp = &leaf[i]; + leaf[i].key = str; + leaf[i].len = len; + leaf[i].cowstate = cow_absent; + } + trie_free(trie); + + return (leaf); +} + +static void +dead_leaves(struct cowleaf *leaf, size_t leaves) +{ + for (size_t i = 0; i < leaves; i++) + free(leaf[i].key); + free(leaf); +} + +static void +mark_cb(trie_val_t val, const uint8_t *key, size_t len, void *d) +{ + struct cowleaf *leaf = val; + assert(leaf->cowstate == cow_unmarked && + "leaf should go from unmarked to shared exactly once"); + leaf->cowstate = cow_shared; + (void)key; + (void)len; + (void)d; +} + +static void +commit_rollback(trie_val_t val, const uint8_t *key, size_t len, void *d) +{ + struct cowleaf *leaf = val; + int *commit = d; + if (*commit) + assert((leaf->cowstate == cow_shared || + leaf->cowstate == cow_old) && + "committing deletes from old trie"); + else + assert((leaf->cowstate == cow_shared || + leaf->cowstate == cow_new) && + "roll back deletes from new trie"); + if (leaf->cowstate != cow_shared) + leaf->cowstate = deadbeef; + (void)key; + (void)len; +} + +static void +del_cow(trie_cow_t *x, struct cowleaf *leaf) +{ + _unused_ trie_val_t val; + assert(KNOT_EOK == trie_del_cow(x, + (uint8_t *)leaf->key, + (uint32_t)leaf->len, + &val)); + assert(val == leaf); +} + +static void +usage(void) { + fprintf(stderr, + "usage: test_qp-cow [-k N] [-l N] [-t N]\n" + " -k N maximum key length (default %d)\n" + " -l N number of leaves (default %d)\n" + " -m N mutations per transaction (default %d)\n" + " -t N number of transactions (default %d)\n", + MAX_KEYLEN, + MAX_LEAVES, + MAX_MUTATIONS, + MAX_TRANSACTIONS); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + size_t keylen = MAX_KEYLEN; + size_t leaves = MAX_LEAVES; + int mutations = MAX_MUTATIONS; + int transactions = MAX_TRANSACTIONS; + + int opt; + while ((opt = getopt(argc, argv, "k:l:m:t:h")) != -1) + switch (opt) { + case('k'): + keylen = (unsigned)atoi(optarg); + continue; + case('l'): + leaves = (unsigned)atoi(optarg); + continue; + case('m'): + mutations = atoi(optarg); + continue; + case('t'): + transactions = atoi(optarg); + continue; + default: + usage(); + } + + if (argc != optind) + usage(); + + plan(transactions); + + struct cowleaf *leaf = grow_leaves(keylen, leaves); + trie_t *t = trie_create(NULL); + + for (int round = 0; round < transactions; round++) { + trie_cow_t *x = trie_cow(t, mark_cb, NULL); + if (!x) sysbail("trie_cow"); + + int hits = prng(mutations); + for (int hit = 0; hit < hits; hit++) { + size_t i = prng(leaves); + switch (leaf[i].cowstate) { + case(cow_absent): { + trie_val_t *val = + trie_get_cow(x, + (uint8_t *)leaf[i].key, + (uint32_t)leaf[i].len); + if (!val) sysbail("trie_get_cow"); + assert(*val == NULL && "new leaf"); + *val = &leaf[i]; + leaf[i].cowstate = cow_new; + } break; + case(cow_unmarked): { + del_cow(x, &leaf[i]); + assert(leaf[i].cowstate == cow_shared && + "state changed unmarked -> shared"); + leaf[i].cowstate = cow_old; + } break; + case(cow_shared): { + del_cow(x, &leaf[i]); + assert(leaf[i].cowstate == cow_shared && + "state remained shared"); + leaf[i].cowstate = cow_old; + } break; + case(cow_new): { + del_cow(x, &leaf[i]); + assert(leaf[i].cowstate == cow_new && + "state remained new"); + leaf[i].cowstate = cow_absent; + } break; + case(cow_old): { + // don't want to mess with old tree + } break; + case(deadbeef): { + assert(!"deadbeef should not be possible"); + } break; + default: + assert(!"bug - unhandled state"); + } + } + + int commit = !prng(2); + if (commit) + t = trie_cow_commit(x, commit_rollback, &commit); + else + t = trie_cow_rollback(x, commit_rollback, &commit); + + trie_it_t *it = trie_it_begin(t); + while (!trie_it_finished(it)) { + trie_val_t *val = trie_it_val(it); + assert(val != NULL); + struct cowleaf *l = *val; + if (commit) + assert((l->cowstate == cow_unmarked || + l->cowstate == cow_shared || + l->cowstate == cow_new) && + "committing expected state"); + else + assert((l->cowstate == cow_unmarked || + l->cowstate == cow_shared || + l->cowstate == cow_old) && + "roll back expected state"); + l->cowstate = cow_unmarked; + trie_it_next(it); + } + trie_it_free(it); + + for (size_t i = 0; i < leaves; i++) { + assert((leaf[i].cowstate == cow_unmarked || + leaf[i].cowstate == cow_absent || + leaf[i].cowstate == deadbeef) && + "cleanup leaves either unmarked or dead"); + if (leaf[i].cowstate == deadbeef) + leaf[i].cowstate = cow_absent; + } + ok(1, "transaction done"); + } + + trie_free(t); + dead_leaves(leaf, leaves); + + return 0; +} diff --git a/tests/contrib/test_qp-trie.c b/tests/contrib/test_qp-trie.c new file mode 100644 index 0000000..a50661f --- /dev/null +++ b/tests/contrib/test_qp-trie.c @@ -0,0 +1,284 @@ +/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <https://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <tap/basic.h> + +#include "contrib/qp-trie/trie.h" +#include "contrib/macros.h" +#include "contrib/string.h" +#include "libknot/dname.h" +#include "libknot/errcode.h" + +/* UCW array sorting defines. */ +#define ASORT_PREFIX(X) str_key_##X +#define ASORT_KEY_TYPE char* +#define ASORT_LT(x, y) (strcmp((x), (y)) < 0) +#include "contrib/ucw/array-sort.h" + +/* Constants. */ +#define KEY_MAXLEN 64 + +/* Generate random key. */ +static const char *alphabet = "abcdefghijklmn0123456789"; +static char *str_key_rand(size_t len) +{ + char *s = malloc(len); + memset(s, 0, len); + for (unsigned i = 0; i < len - 1; ++i) { + s[i] = alphabet[rand() % strlen(alphabet)]; + } + return s; +} + +/* Check lesser or equal result. */ +static bool str_key_get_leq(trie_t *trie, char **keys, size_t i, size_t size) +{ + static char key_buf[KEY_MAXLEN]; + + int ret = 0; + trie_val_t *val = NULL; + const char *key = keys[i]; + size_t key_len = strlen(key) + 1; + memcpy(key_buf, key, key_len); + + /* Count equal first keys. */ + size_t first_key_count = 1; + for (size_t k = 1; k < size; ++k) { + if (strcmp(keys[0], keys[k]) == 0) { + first_key_count += 1; + } else { + break; + } + } + + /* Before current key. */ + key_buf[key_len - 2] -= 1; + if (i < first_key_count) { + ret = trie_get_leq(trie, (uint8_t *)key_buf, key_len, &val); + if (ret != KNOT_ENOENT) { + diag("%s: leq for key BEFORE %zu/'%s' ret = %d", __func__, i, keys[i], ret); + return false; /* No key before first. */ + } + } else { + ret = trie_get_leq(trie, (uint8_t *)key_buf, key_len, &val); + if (ret < KNOT_EOK || strcmp(*val, key_buf) > 0) { + diag("%s: '%s' is not before the key %zu/'%s'", __func__, (char*)*val, i, keys[i]); + return false; /* Found key must be LEQ than searched. */ + } + } + + /* Current key. */ + key_buf[key_len - 2] += 1; + ret = trie_get_leq(trie, (uint8_t *)key_buf, key_len, &val); + if (! (ret == KNOT_EOK && val && strcmp(*val, key_buf) == 0)) { + diag("%s: leq for key %zu/'%s' ret = %d", __func__, i, keys[i], ret); + return false; /* Must find equal match. */ + } + + /* After the current key. */ + key_buf[key_len - 2] += 1; + ret = trie_get_leq(trie, (uint8_t *)key_buf, key_len, &val); + if (! (ret >= KNOT_EOK && strcmp(*val, key_buf) <= 0)) { + diag("%s: leq for key AFTER %zu/'%s' ret = %d %s", __func__, i, keys[i], ret, (char*)*val); + return false; /* Every key must have its LEQ match. */ + } + + return true; + +} + +static void test_wildcards(void) +{ + /* Test zone. */ + const char *names[] = { + "*", + "example.cz", + "*.example.cz", + "+.example.cz", + + "*.exampld.cz", + "www.exampld.cz", + }; + /* Query-answer pairs for wildcard search. */ + const char *qa_pairs[][2] = { + { ".", NULL }, + { "*", "*" }, + { "bar", "*" }, + { "foo.test.", "*" }, + { "example.cz", "example.cz" }, + { "*.example.cz", "*.example.cz" }, + { "a.example.cz", "*.example.cz" }, + { "ab.cd.example.cz", "*.example.cz" }, + { "a+.example.cz", "*.example.cz" }, + { "+.example.cz", "+.example.cz" }, + { "exampld.cz", NULL }, + { ":.exampld.cz", "*.exampld.cz" }, + { "ww.exampld.cz", "*.exampld.cz" }, + }; + + trie_t *trie = trie_create(NULL); + if (!trie) ok(false, "trie: create"); + + /* Insert the whole zone. */ + for (int i = 0; i < sizeof(names) / sizeof(names[0]); ++i) { + knot_dname_storage_t dname_st, lf_st; + const knot_dname_t + *dname = knot_dname_from_str(dname_st, names[i], sizeof(dname_st)), + *lf = knot_dname_lf(dname, lf_st); + if (!dname || !lf) { + ok(false, "trie: converting '%s'", names[i]); + return; + } + + trie_val_t *val = trie_get_ins(trie, lf + 1 , lf[0]); + if (!val || *val != NULL) { + ok(false, "trie: inserting '%s' (as dname_lf)", names[i]); + return; + } + *val = (void *)names[i]; + } + + /* Perform each test query. */ + for (int i = 0; i < sizeof(qa_pairs) / sizeof(qa_pairs[0]); ++i) { + knot_dname_storage_t q_dname_st, q_lf_st; + const knot_dname_t *q_dname = + knot_dname_from_str(q_dname_st, qa_pairs[i][0], sizeof(q_dname_st)); + const knot_dname_t *q_lf = knot_dname_lf(q_dname, q_lf_st); + if (!q_dname || !q_lf) { + ok(false, "trie: converting '%s'", qa_pairs[i][0]); + return; + } + + const char **ans = (const char **)trie_get_try_wildcard(trie, q_lf + 1, q_lf[0]); + bool is_ok = !!ans == !!qa_pairs[i][1] && (!ans || !strcmp(*ans, qa_pairs[i][1])); + if (!is_ok) { + ok(false, "trie: wildcard test for '%s' -> '%s'", + qa_pairs[i][0], ans ? *ans : "<null>"); + return; + } + } + + trie_free(trie); + ok(true, "trie: wildcard searches"); +} + +int main(int argc, char *argv[]) +{ + plan_lazy(); + + /* Random keys. */ + srand(time(NULL)); + unsigned key_count = 100000; + char **keys = malloc(sizeof(char*) * key_count); + /* key must have at least one char and a nul terminator + so that the before/after checks have a char to modify */ + for (unsigned i = 0; i < key_count; ++i) { + keys[i] = str_key_rand(rand() % (KEY_MAXLEN - 2) + 2); + } + + /* Sort random keys. */ + str_key_sort(keys, key_count); + + /* Create trie */ + trie_val_t *val = NULL; + trie_t *trie = trie_create(NULL); + ok(trie != NULL, "trie: create"); + + /* Insert keys */ + bool passed = true; + size_t inserted = 0; + for (unsigned i = 0; i < key_count; ++i) { + val = trie_get_ins(trie, (uint8_t *)keys[i], strlen(keys[i]) + 1); + if (!val) { + passed = false; + break; + } + if (*val == NULL) { + *val = keys[i]; + ++inserted; + } + } + ok(passed, "trie: insert"); + + /* Check total insertions against trie weight. */ + is_int(trie_weight(trie), inserted, "trie: trie weight matches insertions"); + + /* Lookup all keys */ + passed = true; + for (unsigned i = 0; i < key_count; ++i) { + val = trie_get_try(trie, (uint8_t *)keys[i], strlen(keys[i]) + 1); + if (val && (*val == keys[i] || strcmp(*val, keys[i]) == 0)) { + continue; + } else { + diag("trie: mismatch on element '%u'", i); + passed = false; + break; + } + } + ok(passed, "trie: lookup all keys"); + + /* Lesser or equal lookup. */ + passed = true; + for (unsigned i = 0; i < key_count; ++i) { + if (!str_key_get_leq(trie, keys, i, key_count)) { + passed = false; + for (int off = -10; off < 10; ++off) { + int k = (int)i + off; + if (k < 0 || k >= key_count) { + continue; + } + diag("[%u/%d]: %s%s", i, off, off == 0?">":"",keys[k]); + } + break; + } + } + ok(passed, "trie: find lesser or equal for all keys"); + + /* Sorted iteration. */ + char key_buf[KEY_MAXLEN] = {'\0'}; + size_t iterated = 0; + trie_it_t *it = trie_it_begin(trie); + while (!trie_it_finished(it)) { + size_t cur_key_len = 0; + const char *cur_key = (const char *)trie_it_key(it, &cur_key_len); + if (iterated > 0) { /* Only if previous exists. */ + if (strcmp(key_buf, cur_key) > 0) { + diag("'%s' <= '%s' FAIL\n", key_buf, cur_key); + break; + } + } + ++iterated; + memcpy(key_buf, cur_key, cur_key_len); + trie_it_next(it); + } + is_int(inserted, iterated, "trie: sorted iteration"); + trie_it_free(it); + + /* Cleanup */ + for (unsigned i = 0; i < key_count; ++i) { + free(keys[i]); + } + free(keys); + trie_free(trie); + + /* Test trie_get_try_wildcard(). */ + test_wildcards(); + + return 0; +} diff --git a/tests/contrib/test_siphash.c b/tests/contrib/test_siphash.c new file mode 100644 index 0000000..1ca019b --- /dev/null +++ b/tests/contrib/test_siphash.c @@ -0,0 +1,135 @@ +/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <https://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <tap/basic.h> + +// Prevent possible linking with a system SiphHash (OpenBSD). +#include "contrib/openbsd/siphash.c" + +// https://github.com/veorq/SipHash +const uint8_t vectors24[64][8] = { + { 0x31, 0x0e, 0x0e, 0xdd, 0x47, 0xdb, 0x6f, 0x72 }, + { 0xfd, 0x67, 0xdc, 0x93, 0xc5, 0x39, 0xf8, 0x74 }, + { 0x5a, 0x4f, 0xa9, 0xd9, 0x09, 0x80, 0x6c, 0x0d }, + { 0x2d, 0x7e, 0xfb, 0xd7, 0x96, 0x66, 0x67, 0x85 }, + { 0xb7, 0x87, 0x71, 0x27, 0xe0, 0x94, 0x27, 0xcf }, + { 0x8d, 0xa6, 0x99, 0xcd, 0x64, 0x55, 0x76, 0x18 }, + { 0xce, 0xe3, 0xfe, 0x58, 0x6e, 0x46, 0xc9, 0xcb }, + { 0x37, 0xd1, 0x01, 0x8b, 0xf5, 0x00, 0x02, 0xab }, + { 0x62, 0x24, 0x93, 0x9a, 0x79, 0xf5, 0xf5, 0x93 }, + { 0xb0, 0xe4, 0xa9, 0x0b, 0xdf, 0x82, 0x00, 0x9e }, + { 0xf3, 0xb9, 0xdd, 0x94, 0xc5, 0xbb, 0x5d, 0x7a }, + { 0xa7, 0xad, 0x6b, 0x22, 0x46, 0x2f, 0xb3, 0xf4 }, + { 0xfb, 0xe5, 0x0e, 0x86, 0xbc, 0x8f, 0x1e, 0x75 }, + { 0x90, 0x3d, 0x84, 0xc0, 0x27, 0x56, 0xea, 0x14 }, + { 0xee, 0xf2, 0x7a, 0x8e, 0x90, 0xca, 0x23, 0xf7 }, + { 0xe5, 0x45, 0xbe, 0x49, 0x61, 0xca, 0x29, 0xa1 }, + { 0xdb, 0x9b, 0xc2, 0x57, 0x7f, 0xcc, 0x2a, 0x3f }, + { 0x94, 0x47, 0xbe, 0x2c, 0xf5, 0xe9, 0x9a, 0x69 }, + { 0x9c, 0xd3, 0x8d, 0x96, 0xf0, 0xb3, 0xc1, 0x4b }, + { 0xbd, 0x61, 0x79, 0xa7, 0x1d, 0xc9, 0x6d, 0xbb }, + { 0x98, 0xee, 0xa2, 0x1a, 0xf2, 0x5c, 0xd6, 0xbe }, + { 0xc7, 0x67, 0x3b, 0x2e, 0xb0, 0xcb, 0xf2, 0xd0 }, + { 0x88, 0x3e, 0xa3, 0xe3, 0x95, 0x67, 0x53, 0x93 }, + { 0xc8, 0xce, 0x5c, 0xcd, 0x8c, 0x03, 0x0c, 0xa8 }, + { 0x94, 0xaf, 0x49, 0xf6, 0xc6, 0x50, 0xad, 0xb8 }, + { 0xea, 0xb8, 0x85, 0x8a, 0xde, 0x92, 0xe1, 0xbc }, + { 0xf3, 0x15, 0xbb, 0x5b, 0xb8, 0x35, 0xd8, 0x17 }, + { 0xad, 0xcf, 0x6b, 0x07, 0x63, 0x61, 0x2e, 0x2f }, + { 0xa5, 0xc9, 0x1d, 0xa7, 0xac, 0xaa, 0x4d, 0xde }, + { 0x71, 0x65, 0x95, 0x87, 0x66, 0x50, 0xa2, 0xa6 }, + { 0x28, 0xef, 0x49, 0x5c, 0x53, 0xa3, 0x87, 0xad }, + { 0x42, 0xc3, 0x41, 0xd8, 0xfa, 0x92, 0xd8, 0x32 }, + { 0xce, 0x7c, 0xf2, 0x72, 0x2f, 0x51, 0x27, 0x71 }, + { 0xe3, 0x78, 0x59, 0xf9, 0x46, 0x23, 0xf3, 0xa7 }, + { 0x38, 0x12, 0x05, 0xbb, 0x1a, 0xb0, 0xe0, 0x12 }, + { 0xae, 0x97, 0xa1, 0x0f, 0xd4, 0x34, 0xe0, 0x15 }, + { 0xb4, 0xa3, 0x15, 0x08, 0xbe, 0xff, 0x4d, 0x31 }, + { 0x81, 0x39, 0x62, 0x29, 0xf0, 0x90, 0x79, 0x02 }, + { 0x4d, 0x0c, 0xf4, 0x9e, 0xe5, 0xd4, 0xdc, 0xca }, + { 0x5c, 0x73, 0x33, 0x6a, 0x76, 0xd8, 0xbf, 0x9a }, + { 0xd0, 0xa7, 0x04, 0x53, 0x6b, 0xa9, 0x3e, 0x0e }, + { 0x92, 0x59, 0x58, 0xfc, 0xd6, 0x42, 0x0c, 0xad }, + { 0xa9, 0x15, 0xc2, 0x9b, 0xc8, 0x06, 0x73, 0x18 }, + { 0x95, 0x2b, 0x79, 0xf3, 0xbc, 0x0a, 0xa6, 0xd4 }, + { 0xf2, 0x1d, 0xf2, 0xe4, 0x1d, 0x45, 0x35, 0xf9 }, + { 0x87, 0x57, 0x75, 0x19, 0x04, 0x8f, 0x53, 0xa9 }, + { 0x10, 0xa5, 0x6c, 0xf5, 0xdf, 0xcd, 0x9a, 0xdb }, + { 0xeb, 0x75, 0x09, 0x5c, 0xcd, 0x98, 0x6c, 0xd0 }, + { 0x51, 0xa9, 0xcb, 0x9e, 0xcb, 0xa3, 0x12, 0xe6 }, + { 0x96, 0xaf, 0xad, 0xfc, 0x2c, 0xe6, 0x66, 0xc7 }, + { 0x72, 0xfe, 0x52, 0x97, 0x5a, 0x43, 0x64, 0xee }, + { 0x5a, 0x16, 0x45, 0xb2, 0x76, 0xd5, 0x92, 0xa1 }, + { 0xb2, 0x74, 0xcb, 0x8e, 0xbf, 0x87, 0x87, 0x0a }, + { 0x6f, 0x9b, 0xb4, 0x20, 0x3d, 0xe7, 0xb3, 0x81 }, + { 0xea, 0xec, 0xb2, 0xa3, 0x0b, 0x22, 0xa8, 0x7f }, + { 0x99, 0x24, 0xa4, 0x3c, 0xc1, 0x31, 0x57, 0x24 }, + { 0xbd, 0x83, 0x8d, 0x3a, 0xaf, 0xbf, 0x8d, 0xb7 }, + { 0x0b, 0x1a, 0x2a, 0x32, 0x65, 0xd5, 0x1a, 0xea }, + { 0x13, 0x50, 0x79, 0xa3, 0x23, 0x1c, 0xe6, 0x60 }, + { 0x93, 0x2b, 0x28, 0x46, 0xe4, 0xd7, 0x06, 0x66 }, + { 0xe1, 0x91, 0x5f, 0x5c, 0xb1, 0xec, 0xa4, 0x6c }, + { 0xf3, 0x25, 0x96, 0x5c, 0xa1, 0x6d, 0x62, 0x9f }, + { 0x57, 0x5f, 0xf2, 0x8e, 0x60, 0x38, 0x1b, 0xe5 }, + { 0x72, 0x45, 0x06, 0xeb, 0x4c, 0x32, 0x8a, 0x95 }, +}; + +static void block_test(SIPHASH_KEY *key, uint8_t *data, int data_len, int block_len) +{ + int count = data_len / block_len; + int rest = data_len % block_len; + + SIPHASH_CTX ctx; + SipHash24_Init(&ctx, key); + + for (int i = 0; i < count; i++) { + SipHash24_Update(&ctx, data + i * block_len, block_len); + } + SipHash24_Update(&ctx, data + count * block_len, rest); + + uint64_t hash = SipHash24_End(&ctx); + ok(memcmp(&hash, vectors24[data_len], sizeof(uint64_t)) == 0, + "siphash24: %i-byte block updates", block_len); +} + +int main(void) +{ + plan_lazy(); + + SIPHASH_KEY key; + memcpy(&key.k0, "\x00\x01\x02\x03\x04\x05\x06\x07", sizeof(uint64_t)); + memcpy(&key.k1, "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", sizeof(uint64_t)); + + uint8_t data[64]; + for (int i = 0; i < sizeof(data); i++) { + data[i] = i; + } + + for (int data_len = 0; data_len < sizeof(data); data_len++) { + diag("data length %i", data_len); + + uint64_t hash = SipHash24(&key, data, data_len); + ok(memcmp(&hash, vectors24[data_len], sizeof(uint64_t)) == 0, + "siphash24: 1-block update"); + + for (int block_len = 1; block_len <= 8; block_len++) { + block_test(&key, data, data_len, block_len); + } + } + + return 0; +} diff --git a/tests/contrib/test_sockaddr.c b/tests/contrib/test_sockaddr.c new file mode 100644 index 0000000..87d2d77 --- /dev/null +++ b/tests/contrib/test_sockaddr.c @@ -0,0 +1,229 @@ +/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <https://www.gnu.org/licenses/>. + */ + +#include <tap/basic.h> + +#include "contrib/sockaddr.h" +#include "libknot/errcode.h" + +static void test_sockaddr_is_any(void) +{ + struct sockaddr_storage invalid = { 0 }; + ok(!sockaddr_is_any(&invalid), "sockaddr_is_any: invalid"); + + struct sockaddr_storage path = { 0 }; + path.ss_family = AF_UNIX; + ok(!sockaddr_is_any(&path), "sockaddr_is_any: unix"); + + struct sockaddr_storage ipv4_local = { 0 }; + sockaddr_set(&ipv4_local, AF_INET, "127.0.0.1", 0); + ok(!sockaddr_is_any(&ipv4_local), "sockaddr_is_any: IPv4 local"); + + struct sockaddr_storage ipv4_any = { 0 }; + sockaddr_set(&ipv4_any, AF_INET, "0.0.0.0", 0); + ok(sockaddr_is_any(&ipv4_any), "sockaddr_is_any: IPv4 any"); + + struct sockaddr_storage ipv6_local = { 0 }; + sockaddr_set(&ipv6_local, AF_INET6, "::1", 0); + ok(!sockaddr_is_any(&ipv6_local), "sockaddr_is_any: IPv6 local"); + + struct sockaddr_storage ipv6_any = { 0 }; + sockaddr_set(&ipv6_any, AF_INET6, "::", 0); + ok(sockaddr_is_any(&ipv6_any), "sockaddr_is_any: IPv6 any"); +} + +static void check_sockaddr_set(struct sockaddr_storage *ss, int family, + const char *straddr, int port) +{ + int ret = sockaddr_set(ss, family, straddr, port); + is_int(KNOT_EOK, ret, "set address '%s'", straddr); +} + +static void test_net_match(void) +{ + int ret; + struct sockaddr_storage t = { 0 }; + + // 127 dec ~ 01111111 bin + // 170 dec ~ 10101010 bin + struct sockaddr_storage ref4 = { 0 }; + check_sockaddr_set(&ref4, AF_INET, "127.170.170.127", 0); + + // 7F hex ~ 01111111 bin + // AA hex ~ 10101010 bin + struct sockaddr_storage ref6 = { 0 }; + check_sockaddr_set(&ref6, AF_INET6, "7FAA::AA7F", 0); + + ret = sockaddr_net_match(&ref4, &ref6, 32); + ok(ret == false, "match: family mismatch"); + + ret = sockaddr_net_match(NULL, &ref4, 32); + ok(ret == false, "match: NULL first parameter"); + ret = sockaddr_net_match(&ref4, NULL, 32); + ok(ret == false, "match: NULL second parameter"); + + ret = sockaddr_net_match(&ref4, &ref4, -1); + ok(ret == true, "match: ipv4 - identity, auto full prefix"); + ret = sockaddr_net_match(&ref4, &ref4, 31); + ok(ret == true, "match: ipv4 - identity, subnet"); + ret = sockaddr_net_match(&ref4, &ref4, 32); + ok(ret == true, "match: ipv4 - identity, full prefix"); + ret = sockaddr_net_match(&ref4, &ref4, 33); + ok(ret == true, "match: ipv4 - identity, prefix overflow"); + + ret = sockaddr_net_match(&ref6, &ref6, -1); + ok(ret == true, "match: ipv6 - identity, auto full prefix"); + ret = sockaddr_net_match(&ref6, &ref6, 127); + ok(ret == true, "match: ipv6 - identity, subnet"); + ret = sockaddr_net_match(&ref6, &ref6, 128); + ok(ret == true, "match: ipv6 - identity, full prefix"); + ret = sockaddr_net_match(&ref6, &ref6, 129); + ok(ret == true, "match: ipv6 - identity, prefix overflow"); + + // 124 dec ~ 01111100 bin + check_sockaddr_set(&t, AF_INET, "124.0.0.0", 0); + ret = sockaddr_net_match(&t, &ref4, 5); + ok(ret == true, "match: ipv4 - first byte, shorter prefix"); + ret = sockaddr_net_match(&t, &ref4, 6); + ok(ret == true, "match: ipv4 - first byte, precise prefix"); + ret = sockaddr_net_match(&t, &ref4, 7); + ok(ret == false, "match: ipv4 - first byte, not match"); + + check_sockaddr_set(&t, AF_INET, "127.170.170.124", 0); + ret = sockaddr_net_match(&t, &ref4, 29); + ok(ret == true, "match: ipv4 - last byte, shorter prefix"); + ret = sockaddr_net_match(&t, &ref4, 30); + ok(ret == true, "match: ipv4 - last byte, precise prefix"); + ret = sockaddr_net_match(&t, &ref4, 31); + ok(ret == false, "match: ipv4 - last byte, not match"); + + // 7C hex ~ 01111100 bin + check_sockaddr_set(&t, AF_INET6, "7CAA::", 0); + ret = sockaddr_net_match(&t, &ref6, 5); + ok(ret == true, "match: ipv6 - first byte, shorter prefix"); + ret = sockaddr_net_match(&t, &ref6, 6); + ok(ret == true, "match: ipv6 - first byte, precise prefix"); + ret = sockaddr_net_match(&t, &ref6, 7); + ok(ret == false, "match: ipv6 - first byte, not match"); + + check_sockaddr_set(&t, AF_INET6, "7FAA::AA7C", 0); + ret = sockaddr_net_match(&t, &ref6, 125); + ok(ret == true, "match: ipv6 - last byte, shorter prefix"); + ret = sockaddr_net_match(&t, &ref6, 126); + ok(ret == true, "match: ipv6 - last byte, precise prefix"); + ret = sockaddr_net_match(&t, &ref6, 127); + ok(ret == false, "match: ipv6 - last byte, not match"); +} + +static void test_range_match(void) +{ + bool ret; + struct sockaddr_storage t = { 0 }; + struct sockaddr_storage min = { 0 }; + struct sockaddr_storage max = { 0 }; + + // IPv4 tests. + + check_sockaddr_set(&min, AF_INET, "0.0.0.0", 0); + check_sockaddr_set(&max, AF_INET, "255.255.255.255", 0); + + check_sockaddr_set(&t, AF_INET, "0.0.0.0", 0); + ret = sockaddr_range_match(&t, &min, &max); + ok(ret == true, "match: ipv4 max range - minimum"); + check_sockaddr_set(&t, AF_INET, "255.255.255.255", 0); + ret = sockaddr_range_match(&t, &min, &max); + ok(ret == true, "match: ipv4 max range - maximum"); + + check_sockaddr_set(&min, AF_INET, "1.13.113.213", 0); + check_sockaddr_set(&max, AF_INET, "2.24.124.224", 0); + + check_sockaddr_set(&t, AF_INET, "1.12.113.213", 0); + ret = sockaddr_range_match(&t, &min, &max); + ok(ret == false, "match: ipv4 middle range - negative far min"); + check_sockaddr_set(&t, AF_INET, "1.13.113.212", 0); + ret = sockaddr_range_match(&t, &min, &max); + ok(ret == false, "match: ipv4 middle range - negative close min"); + check_sockaddr_set(&t, AF_INET, "1.13.113.213", 0); + ret = sockaddr_range_match(&t, &min, &max); + ok(ret == true, "match: ipv4 middle range - minimum"); + check_sockaddr_set(&t, AF_INET, "1.13.213.213", 0); + ret = sockaddr_range_match(&t, &min, &max); + ok(ret == true, "match: ipv4 middle range - middle"); + check_sockaddr_set(&t, AF_INET, "2.24.124.224", 0); + ret = sockaddr_range_match(&t, &min, &max); + ok(ret == true, "match: ipv4 middle range - max"); + check_sockaddr_set(&t, AF_INET, "2.24.124.225", 0); + ret = sockaddr_range_match(&t, &min, &max); + ok(ret == false, "match: ipv4 middle range - negative close max"); + check_sockaddr_set(&t, AF_INET, "2.25.124.225", 0); + ret = sockaddr_range_match(&t, &min, &max); + ok(ret == false, "match: ipv4 middle range - negative far max"); + + // IPv6 tests. + + check_sockaddr_set(&min, AF_INET6, "::0", 0); + check_sockaddr_set(&max, AF_INET6, + "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", 0); + + check_sockaddr_set(&t, AF_INET6, "::0", 0); + ret = sockaddr_range_match(&t, &min, &max); + ok(ret == true, "match: ipv6 max range - minimum"); + check_sockaddr_set(&t, AF_INET6, + "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", 0); + ret = sockaddr_range_match(&t, &min, &max); + ok(ret == true, "match: ipv6 max range - maximum"); + + check_sockaddr_set(&min, AF_INET6, "1:13::ABCD:200B", 0); + check_sockaddr_set(&max, AF_INET6, "2:A24::124:224", 0); + + check_sockaddr_set(&t, AF_INET6, "1:12::BCD:2000", 0); + ret = sockaddr_range_match(&t, &min, &max); + ok(ret == false, "match: ipv6 middle range - negative far min"); + check_sockaddr_set(&t, AF_INET6, "1:13::ABCD:200A", 0); + ret = sockaddr_range_match(&t, &min, &max); + ok(ret == false, "match: ipv6 middle range - negative close min"); + check_sockaddr_set(&t, AF_INET6, "1:13::ABCD:200B", 0); + ret = sockaddr_range_match(&t, &min, &max); + ok(ret == true, "match: ipv6 middle range - minimum"); + check_sockaddr_set(&t, AF_INET6, "1:13:0:12:34:0:ABCD:200B", 0); + ret = sockaddr_range_match(&t, &min, &max); + ok(ret == true, "match: ipv6 middle range - middle"); + check_sockaddr_set(&t, AF_INET6, "2:A24::124:224", 0); + ret = sockaddr_range_match(&t, &min, &max); + ok(ret == true, "match: ipv6 middle range - max"); + check_sockaddr_set(&t, AF_INET6, "2:A24::124:225", 0); + ret = sockaddr_range_match(&t, &min, &max); + ok(ret == false, "match: ipv6 middle range - negative close max"); + check_sockaddr_set(&t, AF_INET6, "2:FA24::4:24", 0); + ret = sockaddr_range_match(&t, &min, &max); + ok(ret == false, "match: ipv6 middle range - negative far max"); +} + +int main(int argc, char *argv[]) +{ + plan_lazy(); + + diag("sockaddr_is_any"); + test_sockaddr_is_any(); + + diag("sockaddr_net_match"); + test_net_match(); + + diag("sockaddr_range_match"); + test_range_match(); + + return 0; +} diff --git a/tests/contrib/test_spinlock.c b/tests/contrib/test_spinlock.c new file mode 100644 index 0000000..0d5122b --- /dev/null +++ b/tests/contrib/test_spinlock.c @@ -0,0 +1,78 @@ +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <https://www.gnu.org/licenses/>. + */ + +#include <signal.h> +#include <tap/basic.h> + +#include "knot/server/dthreads.h" +#include "contrib/spinlock.h" + +#define THREADS 8 +#define CYCLES 100000 + +static volatile int counter = 0; +static volatile int tens_counter = 0; +static knot_spin_t spinlock; + +static int thread(struct dthread *thread) +{ + volatile int i, j, k; + + for (i = 0; i < CYCLES; i++) { + knot_spin_lock(&spinlock); + j = counter; + k = tens_counter; + if (++j % 10 == 0) { + k++; + } + tens_counter = k; + counter = j; + knot_spin_unlock(&spinlock); + } + + return 0; +} + +// Signal handler +static void interrupt_handle(int s) +{ +} + +int main(int argc, char *argv[]) +{ + plan_lazy(); + + // Register service and signal handler + struct sigaction sa; + sa.sa_handler = interrupt_handle; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGALRM, &sa, NULL); // Interrupt + + knot_spin_init(&spinlock); + + dt_unit_t *unit = dt_create(THREADS, thread, NULL, NULL); + dt_start(unit); + dt_join(unit); + dt_delete(&unit); + + knot_spin_destroy(&spinlock); + + is_int(THREADS * CYCLES, counter, "spinlock: protected counter one"); + is_int(THREADS * CYCLES / 10, tens_counter, "spinlock: protected counter two"); + + return 0; +} diff --git a/tests/contrib/test_string.c b/tests/contrib/test_string.c new file mode 100644 index 0000000..681dd61 --- /dev/null +++ b/tests/contrib/test_string.c @@ -0,0 +1,59 @@ +/* Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <https://www.gnu.org/licenses/>. + */ + +#include <tap/basic.h> + +#include <stdlib.h> + +#include "contrib/string.h" + +static void test_strstrip(void) +{ + char *c = NULL; + + c = strstrip("hello"); + is_string("hello", c, "strstrip: no whitespace"); + free(c); + + c = strstrip("world \n"); + is_string("world", c, "strstrip: trailing whitespace"); + free(c); + + c = strstrip(" \n banana"); + is_string("banana", c, "strstrip: leading whitespace"); + free(c); + + c = strstrip(" \t hello world \n"); + is_string("hello world", c, "strstrip: leading and trailing"); + free(c); + + c = strstrip(""); + is_string("", c, "strstrip: empty string"); + free(c); + + c = strstrip(" "); + is_string("", c, "strstrip: just whitespaces"); + free(c); +} + +int main(int argc, char *argv[]) +{ + plan_lazy(); + + test_strstrip(); + + return 0; +} diff --git a/tests/contrib/test_strtonum.c b/tests/contrib/test_strtonum.c new file mode 100644 index 0000000..e883575 --- /dev/null +++ b/tests/contrib/test_strtonum.c @@ -0,0 +1,156 @@ +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <tap/basic.h> + +#include "contrib/strtonum.h" +#include "libdnssec/error.h" +#include "libknot/attribute.h" + +static void test_u8(const char *in, uint8_t expected, int errcode) +{ + uint8_t out = 0x11; + assert(expected != out); + + ok(str_to_u8(in, &out) == errcode && + (errcode != KNOT_EOK || out == expected), + "str_to_u8 %s on \"%s\"", + (errcode == KNOT_EOK ? "succeeds" : "fails"), in); +} + +static void test_u16(const char *in, uint16_t expected, int errcode) +{ + uint16_t out = 0x0101; + assert(expected != out); + + ok(str_to_u16(in, &out) == errcode && + (errcode != KNOT_EOK || out == expected), + "str_to_u16 %s on \"%s\"", + (errcode == KNOT_EOK ? "succeeds" : "fails"), in); +} + +static void test_u32(const char *in, uint32_t expected, int errcode) +{ + uint32_t out = 0x010101; + assert(expected != out); + + ok(str_to_u32(in, &out) == errcode && + (errcode != KNOT_EOK || out == expected), + "str_to_u32 %s on \"%s\"", + (errcode == KNOT_EOK ? "succeeds" : "fails"), in); +} + +static void test_int(const char *in, int expected, int errcode) +{ + int out = 12345; + assert(expected != out); + + ok(str_to_int(in, &out, INT_MIN, INT_MAX) == errcode && + (errcode != KNOT_EOK || out == expected), + "str_to_int %s on \"%s\"", + (errcode == KNOT_EOK ? "succeeds" : "fails"), in); +} + +static void test_size(const char *in, size_t expected, size_t min, size_t max, + int errcode) +{ + size_t out = 12345; + assert(expected != out); + + ok(str_to_size(in, &out, min, max) == errcode && + (errcode != KNOT_EOK || out == expected), + "str_to_int %s on \"%s\"", + (errcode == KNOT_EOK ? "succeeds" : "fails"), in); +} + +#define asprintf(args, ...) do { \ + _unused_ int r = (asprintf)(args, ##__VA_ARGS__); assert(r >= 0); \ +} while (0); + +int main(int argc, char *argv[]) +{ + plan_lazy(); + + test_u8("-1", 0, KNOT_EINVAL); + test_u8("256", 0, KNOT_ERANGE); + test_u8("0x1", 0, KNOT_EINVAL); + test_u8(" 1", 0, KNOT_EINVAL); + test_u8("1 ", 0, KNOT_EINVAL); + test_u8("0", 0, KNOT_EOK); + test_u8("42", 42, KNOT_EOK); + test_u8("+84", 84, KNOT_EOK); + test_u8("255", UINT8_MAX, KNOT_EOK); + + test_u16("-1", 0, KNOT_EINVAL); + test_u16("65536", 0, KNOT_ERANGE); + test_u16("0x1", 0, KNOT_EINVAL); + test_u16(" 1", 0, KNOT_EINVAL); + test_u16("1 ", 0, KNOT_EINVAL); + test_u16("0", 0, KNOT_EOK); + test_u16("65280", 65280, KNOT_EOK); + test_u16("+256", 256, KNOT_EOK); + test_u16("65535", UINT16_MAX, KNOT_EOK); + + test_u32("-1", 0, KNOT_EINVAL); + test_u32("4294967296", 0, KNOT_ERANGE); + test_u32("0x1", 0, KNOT_EINVAL); + test_u32(" 1", 0, KNOT_EINVAL); + test_u32("1 ", 0, KNOT_EINVAL); + test_u32("0", 0, KNOT_EOK); + test_u32("65280", 65280, KNOT_EOK); + test_u32("+256", 256, KNOT_EOK); + test_u32("4294967295", UINT32_MAX, KNOT_EOK); + + test_size("-1", 0, 0, 1, KNOT_EINVAL); + test_size("4294967296", 0, 0, 1, KNOT_ERANGE); + test_size("0", 0, 1, 2, KNOT_ERANGE); + test_size("0x1", 0, 0, 1, KNOT_EINVAL); + test_size(" 1", 0, 0, 1, KNOT_EINVAL); + test_size("1 ", 0, 0, 1, KNOT_EINVAL); + test_size("0", 0, 0, 1, KNOT_EOK); + test_size("65280", 65280, 0, 65280, KNOT_EOK); + test_size("+256", 256, 0, 65280, KNOT_EOK); + + char *int_under = NULL; + asprintf(&int_under, "%lld", (long long)INT_MIN - 1); + char *int_min = NULL; + asprintf(&int_min, "%lld", (long long)INT_MIN); + char *int_max = NULL; + asprintf(&int_max, "%lld", (long long)INT_MAX); + char *int_over = NULL; + asprintf(&int_over, "%lld", (long long)INT_MAX + 1); + + test_int(int_under, 0, KNOT_ERANGE); + test_int(int_over, 0, KNOT_ERANGE); + test_int("0x1", 0, KNOT_EINVAL); + test_int(" 1", 0, KNOT_EINVAL); + test_int("1 ", 0, KNOT_EINVAL); + test_int(int_min, INT_MIN, KNOT_EOK); + test_int("0", 0, KNOT_EOK); + test_int("268435459", 268435459, KNOT_EOK); + test_int("+1073741827", 1073741827, KNOT_EOK); + test_int(int_max, INT_MAX, KNOT_EOK); + + free(int_under); + free(int_min); + free(int_max); + free(int_over); + + return 0; +} diff --git a/tests/contrib/test_time.c b/tests/contrib/test_time.c new file mode 100644 index 0000000..84518f2 --- /dev/null +++ b/tests/contrib/test_time.c @@ -0,0 +1,203 @@ +/* Copyright (C) 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <https://www.gnu.org/licenses/>. + */ + +#include <tap/basic.h> + +#include <string.h> + +#include "contrib/time.h" + +static void test_now(void) +{ + struct timespec t = time_now(); + ok(t.tv_sec != 0, "time_now() returns something"); +} + +static void test_diff(void) +{ + struct timespec t1 = { 10, 1000 }; + struct timespec t2 = { 50, 1500 }; + struct timespec t3 = { 70, 500 }; + + struct timespec res; + + res = time_diff(&t1, &t2); + ok(res.tv_sec == 40 && res.tv_nsec == 500, "time_diff()"); + + res = time_diff(&t2, &t3); + ok(res.tv_sec == 19 && res.tv_nsec == 999999000, "time_diff() ns overflow"); + + res = time_diff(&t3, &t1); + ok(res.tv_sec == -60 && res.tv_nsec == 500, "time_diff() negative"); + + res = time_diff(&t2, &t1); + ok(res.tv_sec == -41 && res.tv_nsec == 999999500, "time_diff() negative"); +} + +static void test_diff_ms(void) +{ + struct timespec t1 = { 10, 1000 }; + struct timespec t2 = { 50, 500 }; + + float ms = 0.0; + + ms = time_diff_ms(&t1, &t2); + ok(39990.0 < ms && ms < 40010.0, "time_diff_ms()"); + + ms = time_diff_ms(&t2, &t1); + ok(-40010.0 < ms && ms < -39990.0, "time_diff_ms() negative"); +} + +static void test_knot_time(void) +{ + knot_time_t a = knot_time(); + knot_time_t inf = 0; + knot_time_t c; + knot_timediff_t d; + int ret; + + ok(a != 0, "knot time not zero"); + + ret = knot_time_cmp(a, a); + ok(ret == 0, "compare same times"); + + ret = knot_time_cmp(a - 1, a + 1); + ok(ret == -1, "compare smaller time"); + + ret = knot_time_cmp(a + 10, a - 10); + ok(ret == 1, "compare bigger time"); + + ret = knot_time_cmp(inf, inf); + ok(ret == 0, "compare two infinities"); + + ret = knot_time_cmp(a, inf); + ok(ret == -1, "compare time and infinity"); + + ret = knot_time_cmp(inf, a); + ok(ret == 1, "compare infinity and time"); + + c = knot_time_min(a, a); + ok(c == a, "take same time"); + + c = knot_time_min(a, a + 1); + ok(c == a, "take first smaller"); + + c = knot_time_min(a + 1, a); + ok(c == a, "take second smaller"); + + c = knot_time_min(inf, inf); + ok(c == inf, "take same infinity"); + + c = knot_time_min(a, inf); + ok(c == a, "take first finite"); + + c = knot_time_min(inf, a); + ok(c == a, "take second finite"); + + d = knot_time_diff(a + 1, a); + ok(d == 1, "positive diff"); + + d = knot_time_diff(a, a + 1); + ok(d == -1, "negative diff"); + + d = knot_time_diff(inf, inf); + ok(d == KNOT_TIMEDIFF_MAX, "positive double infinity diff"); + + d = knot_time_diff(inf, a); + ok(d == KNOT_TIMEDIFF_MAX, "positive infinity diff"); + + d = knot_time_diff(a, inf); + ok(d == KNOT_TIMEDIFF_MIN, "negative infinity diff"); +} + +static void test_time_parse_expect(int ret, knot_time_t res, + knot_time_t expected, const char *msg) +{ + ok(ret == 0, "time_parse %s ok", msg); + ok(res == expected, "time_parse %s result", msg); +} + +static void test_time_parse(void) +{ + knot_time_t res; + int ret; + + ret = knot_time_parse("", "", &res); + test_time_parse_expect(ret, res, 0, "nihilist"); + + ret = knot_time_parse("#", "12345", &res); + test_time_parse_expect(ret, res, 12345, "unix"); + + ret = knot_time_parse("+-#U", "-1h", &res); + test_time_parse_expect(ret, res, knot_time() - 3600, "hour"); + + ret = knot_time_parse("+-#u'nths'|+-#u'nutes'", "+1minutes", &res); + test_time_parse_expect(ret, res, knot_time() + 60, "minute"); +} + +static void test_time_print_expect(int ret, const char *res, int res_len, + const char *expected, const char *msg) +{ + ok(ret == 0, "time_print %s ok", msg); + ok(strncmp(res, expected, res_len) == 0, "time_print %s result", msg); +} + +static void test_time_print(void) +{ + char buff[100]; + int bufl = sizeof(buff); + int ret; + knot_time_t t = 44000, t2, big; + + ret = knot_time_print(TIME_PRINT_UNIX, t, buff, bufl); + test_time_print_expect(ret, buff, bufl, "44000", "unix"); + + t2 = knot_time_add(knot_time(), -10000); + ret = knot_time_print(TIME_PRINT_RELSEC, t2, buff, bufl); + test_time_print_expect(ret, buff, bufl, "-10000", "relsec"); + + ret = knot_time_print(TIME_PRINT_ISO8601, t, buff, bufl); + buff[11] = '0', buff[12] = '0'; // zeroing 'hours' field to avoid locality issues + test_time_print_expect(ret, buff, bufl, "1970-01-01T00:13:20Z", "iso"); + + t2 = knot_time_add(knot_time(), -10000); + ret = knot_time_print(TIME_PRINT_HUMAN_MIXED, t2, buff, bufl); + test_time_print_expect(ret, buff, bufl, "-2h46m40s", "negative human mixed"); + big = knot_time_add(knot_time(), 2 * 365 * 24 * 3600 + 1); + ret = knot_time_print(TIME_PRINT_HUMAN_MIXED, big, buff, bufl); + test_time_print_expect(ret, buff, bufl, "+2Y1s", "big human mixed"); + + t2 = knot_time_add(knot_time(), -10000); + ret = knot_time_print(TIME_PRINT_HUMAN_LOWER, t2, buff, bufl); + test_time_print_expect(ret, buff, bufl, "-2h46mi40s", "negative human lower"); + big = knot_time_add(knot_time(), 2 * 365 * 24 * 3600 + 1); + ret = knot_time_print(TIME_PRINT_HUMAN_LOWER, big, buff, bufl); + test_time_print_expect(ret, buff, bufl, "+2y1s", "big human lower"); +} + +int main(int argc, char *argv[]) +{ + plan_lazy(); + + test_now(); + test_diff(); + test_diff_ms(); + test_knot_time(); + test_time_parse(); + test_time_print(); + + return 0; +} diff --git a/tests/contrib/test_toeplitz.c b/tests/contrib/test_toeplitz.c new file mode 100644 index 0000000..244137c --- /dev/null +++ b/tests/contrib/test_toeplitz.c @@ -0,0 +1,93 @@ +/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <https://www.gnu.org/licenses/>. + */ + +#include <arpa/inet.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <tap/basic.h> + +#include "contrib/toeplitz.h" +#include "contrib/wire_ctx.h" + +// Test vectors come from Intel Ethernet Controller X710/XXV710/XL710 Series Datasheet +const uint8_t key[] = { + 0x6d, 0x5a, 0x56, 0xda, 0x25, 0x5b, 0x0e, 0xc2, + 0x41, 0x67, 0x25, 0x3d, 0x43, 0xa3, 0x8f, 0xb0, + 0xd0, 0xca, 0x2b, 0xcb, 0xae, 0x7b, 0x30, 0xb4, + 0x77, 0xcb, 0x2d, 0xa3, 0x80, 0x30, 0xf2, 0x0c, + 0x6a, 0x42, 0xb7, 0x3b, 0xbe, 0xac, 0x01, 0xfa, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + +void toeplitz_check(int family, const char *src_ip, const char *dst_ip, + uint16_t src_port, uint16_t dst_port, uint32_t expected) +{ + uint8_t data[2 * sizeof(struct in6_addr) + 2 * sizeof(uint16_t)]; + + wire_ctx_t ctx = wire_ctx_init(data, sizeof(data)); + + struct in_addr src_addr4, dst_addr4; + struct in6_addr src_addr6, dst_addr6; + + if (family == AF_INET && + inet_pton(AF_INET, src_ip, &src_addr4) == 1 && + inet_pton(AF_INET, dst_ip, &dst_addr4) == 1) { + wire_ctx_write(&ctx, (uint8_t *)&(src_addr4.s_addr), sizeof(struct in_addr)); + wire_ctx_write(&ctx, (uint8_t *)&(dst_addr4.s_addr), sizeof(struct in_addr)); + } else if (family == AF_INET6 && + inet_pton(AF_INET6, src_ip, &src_addr6) == 1 && + inet_pton(AF_INET6, dst_ip, &dst_addr6) == 1) { + wire_ctx_write(&ctx, (uint8_t *)&(src_addr6.s6_addr), sizeof(struct in6_addr)); + wire_ctx_write(&ctx, (uint8_t *)&(dst_addr6.s6_addr), sizeof(struct in6_addr)); + } else { + assert(0); + } + + wire_ctx_write_u16(&ctx, src_port); + wire_ctx_write_u16(&ctx, dst_port); + + if (ctx.error != KNOT_EOK) { + assert(0); + } + + uint32_t value = toeplitz_hash(key, sizeof(key), data, wire_ctx_offset(&ctx)); + is_int(expected, value, "toeplitz_hash: %u", expected); + + toeplitz_ctx_t toepl; + for (int i = 0; i <= wire_ctx_offset(&ctx); i++) { + toeplitz_init(&toepl, i, key, sizeof(key), data, wire_ctx_offset(&ctx)); + value = toeplitz_finish(&toepl); + is_int(expected, value, "toeplitz_init to %i: %u", i, expected); + } +} + +int main(void) +{ + plan_lazy(); + + toeplitz_check(AF_INET, "66.9.149.187", "161.142.100.80", 2794, 1766, 0x51ccc178); + toeplitz_check(AF_INET, "199.92.111.2", "65.69.140.83", 14230, 4739, 0xc626b0ea); + toeplitz_check(AF_INET, "24.19.198.95", "12.22.207.184", 12898, 38024, 0x5c2b394a); + toeplitz_check(AF_INET, "38.27.205.30", "209.142.163.6", 48228, 2217, 0xafc7327f); + toeplitz_check(AF_INET, "153.39.163.191", "202.188.127.2", 44251, 1303, 0x10e828a2); + + toeplitz_check(AF_INET6, "3ffe:2501:200:1fff::7", "3ffe:2501:200:3::1", 2794, 1766, 0x40207d3d); + toeplitz_check(AF_INET6, "3ffe:501:8::260:97ff:fe40:efab", "ff02::1", 14230, 4739, 0xdde51bbf); + toeplitz_check(AF_INET6, "3ffe:1900:4545:3:200:f8ff:fe21:67cf", "fe80::200:f8ff:fe21:67cf", 44251, 38024, 0x02d1feef); + + return 0; +} diff --git a/tests/contrib/test_wire_ctx.c b/tests/contrib/test_wire_ctx.c new file mode 100644 index 0000000..81386c9 --- /dev/null +++ b/tests/contrib/test_wire_ctx.c @@ -0,0 +1,287 @@ +/* Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <https://www.gnu.org/licenses/>. + */ + +#include <tap/basic.h> +#include <netinet/in.h> +#include <stdio.h> + +#include "libknot/errcode.h" +#include "contrib/wire_ctx.h" + +#define OK(wire) { \ + is_int(KNOT_EOK, (wire)->error, "check for no error"); \ +} + +#define NOK(wire, code) { \ + is_int(code, (wire)->error, "check for error"); \ +} + +void ok_offset(wire_ctx_t *wire, size_t max, size_t i) +{ + wire_ctx_set_offset(wire, i); + OK(wire); + is_int(max - i, wire_ctx_available(wire), "get available %zu", max - i); + OK(wire); + is_int(i, wire_ctx_offset(wire), "get start position %zu", i); + OK(wire); +} + +void nok_offset(wire_ctx_t *wire, size_t max) +{ + wire_ctx_set_offset(wire, max); + OK(wire); + wire_ctx_set_offset(wire, max + 1); + NOK(wire, KNOT_ERANGE); + is_int(0, wire_ctx_available(wire), "get available %i", 0); + NOK(wire, KNOT_ERANGE); + is_int(max, wire_ctx_offset(wire), "get last start position %zu", max); + NOK(wire, KNOT_ERANGE); +} + +void offset_test(void) +{ + diag("offset operation"); + + const size_t LEN = 3; + uint8_t data[LEN]; + + wire_ctx_t wire = wire_ctx_init(data, sizeof(data)); + + // First free byte. + ok_offset(&wire, LEN, 0); + // Last free byte. + ok_offset(&wire, LEN, 2); + // First non-free byte. + ok_offset(&wire, LEN, 3); + // Invalid offset. + nok_offset(&wire, LEN); +} + +void skip_test(void) +{ + diag("skip operation"); + + uint8_t data[3]; + + // Forward skips. + + wire_ctx_t wire = wire_ctx_init(data, sizeof(data)); + + wire_ctx_skip(&wire, 2); + OK(&wire); + is_int(2, wire_ctx_offset(&wire), "skip by offset %i", 2); + + wire_ctx_skip(&wire, 1); + OK(&wire); + is_int(3, wire_ctx_offset(&wire), "skip by offset %i", 1); + + // Out-of-bounds skip. + wire_ctx_skip(&wire, 1); + NOK(&wire, KNOT_ERANGE); + is_int(3, wire_ctx_offset(&wire), "out-of-bounds skip by %i", 1); + + // Backward skips. + + wire = wire_ctx_init(data, sizeof(data)); + + wire_ctx_set_offset(&wire, 3); + OK(&wire); + + wire_ctx_skip(&wire, -2); + OK(&wire); + is_int(1, wire_ctx_offset(&wire), "skip by offset %i", -2); + + wire_ctx_skip(&wire, -1); + OK(&wire); + is_int(0, wire_ctx_offset(&wire), "skip by offset %i", -1); + + // Out-of-bounds skip. + wire_ctx_skip(&wire, -1); + NOK(&wire, KNOT_ERANGE); + is_int(0, wire_ctx_offset(&wire), "out-of-bounds skip by %i", -1); +} + +void clear_test(void) +{ + diag("clear operation"); + + uint8_t data[] = { 1, 2, 3 }; + + wire_ctx_t wire = wire_ctx_init(data, sizeof(data)); + + wire_ctx_clear(&wire, 10); + NOK(&wire, KNOT_ESPACE); + is_int(1, data[0], "no attempt to clear"); + + wire = wire_ctx_init(data, sizeof(data)); + wire_ctx_clear(&wire, 3); + OK(&wire); + is_int(0, wire_ctx_available(&wire), "no space available"); + for (int i = 0; i < sizeof(data); i++) { + is_int(0, data[i], "wire position %i is zero", i); + } +} + +#define check_rw(size, value, ...) { \ + const uint8_t expect[] = { __VA_ARGS__ }; \ + uint8_t data[sizeof(expect)] = { 0 }; \ + \ + wire_ctx_t wire = wire_ctx_init(data, sizeof(data)); \ + \ + wire_ctx_write_u ## size(&wire, value); \ + OK(&wire); \ + ok(memcmp(data, expect, sizeof(expect)) == 0, "write %i value", size); \ + is_int(size/8, wire_ctx_offset(&wire), "write %i offset", size); \ + \ + wire_ctx_set_offset(&wire, 0); \ + OK(&wire); \ + \ + uint64_t num = wire_ctx_read_u ## size(&wire); \ + OK(&wire); \ + is_int(value, num, "read %i value", size); \ + is_int(size/8, wire_ctx_offset(&wire), "read %i offset", size); \ +} + +#define check_general_rw(...) { \ + const uint8_t expect[] = { __VA_ARGS__ }; \ + uint8_t data[sizeof(expect)] = { 0 }; \ + \ + wire_ctx_t wire = wire_ctx_init(data, sizeof(data)); \ + \ + wire_ctx_write(&wire, expect, sizeof(expect)); \ + OK(&wire); \ + ok(memcmp(data, expect, sizeof(expect)) == 0, "write value"); \ + is_int(sizeof(expect), wire_ctx_offset(&wire), "write offset"); \ + \ + wire_ctx_set_offset(&wire, 0); \ + OK(&wire); \ + \ + uint8_t d[sizeof(expect)] = { 0 }; \ + wire_ctx_read(&wire, d, sizeof(expect)); \ + OK(&wire); \ + ok(memcmp(d, expect, sizeof(expect)) == 0, "read value"); \ + is_int(sizeof(expect), wire_ctx_offset(&wire), "read offset"); \ +} + +void read_write_test(void) +{ + diag("read and write operation"); + + check_rw( 8, 0x11, 0x11); + check_rw(16, 0x1122, 0x11, 0x22); + check_rw(32, 0x11223344, 0x11, 0x22, 0x33, 0x44); + check_rw(48, 0x112233445566, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66); + check_rw(64, 0x1122334455667788, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88); + + check_general_rw(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x10); +} + +#define check_rw_over(size) { \ + uint8_t data[1] = { 0 }; \ + \ + wire_ctx_t wire = wire_ctx_init(data, sizeof(data)); \ + wire_ctx_set_offset(&wire, 1); \ + OK(&wire); \ + \ + wire_ctx_write_u ## size(&wire, 0); \ + NOK(&wire, KNOT_ESPACE); \ + is_int(1, wire_ctx_offset(&wire), "err write %i offset", size); \ + \ + wire = wire_ctx_init(data, sizeof(data)); \ + wire_ctx_set_offset(&wire, 1); \ + OK(&wire); \ + \ + uint64_t num = wire_ctx_read_u ## size(&wire); \ + NOK(&wire, KNOT_EFEWDATA); \ + is_int(0, num, "err read %i value", size); \ + is_int(1, wire_ctx_offset(&wire), "err read %i offset", size); \ +} + +#define check_general_rw_over(void) { \ + uint8_t data[1] = { 0 }; \ + uint8_t d[2] = { 0 }; \ + \ + wire_ctx_t wire = wire_ctx_init(data, sizeof(data)); \ + wire_ctx_write(&wire, d, sizeof(d)); \ + NOK(&wire, KNOT_ESPACE); \ + is_int(0, wire_ctx_offset(&wire), "err write offset"); \ + \ + wire = wire_ctx_init(data, sizeof(data)); \ + wire_ctx_read(&wire, d, sizeof(d)); \ + NOK(&wire, KNOT_EFEWDATA); \ + is_int(0, wire_ctx_offset(&wire), "err read offset"); \ +} + +void read_write_overflow_test(void) +{ + diag("overflow read and write operation"); + + check_rw_over(8); + check_rw_over(16); + check_rw_over(32); + check_rw_over(48); + check_rw_over(64); + + check_general_rw_over(); +} + +#define check_ro(size) { \ + uint8_t data[8] = { 0 }; \ + \ + wire_ctx_t wire = wire_ctx_init_const(data, sizeof(data)); \ + \ + wire_ctx_write_u ## size(&wire, 0); \ + NOK(&wire, KNOT_EACCES); \ + is_int(0, wire_ctx_offset(&wire), "err write %i offset", size); \ +} + +#define check_general_ro(void) { \ + uint8_t data[8] = { 0 }; \ + uint8_t d[2] = { 0 }; \ + \ + wire_ctx_t wire = wire_ctx_init_const(data, sizeof(data)); \ + \ + wire_ctx_write(&wire, d, sizeof(d)); \ + NOK(&wire, KNOT_EACCES); \ + is_int(0, wire_ctx_offset(&wire), "err write offset"); \ +} + +void write_readonly_test(void) +{ + diag("readonly write operation"); + + check_ro(8); + check_ro(16); + check_ro(32); + check_ro(48); + check_ro(64); + + check_general_ro(); +} + +int main(void) +{ + plan_lazy(); + + offset_test(); + skip_test(); + clear_test(); + read_write_test(); + read_write_overflow_test(); + write_readonly_test(); + + return 0; +} |