| /* Test ns_name-related functions. |
| Copyright (C) 2017-2018 Free Software Foundation, Inc. |
| This file is part of the GNU C Library. |
| |
| The GNU C Library is free software; you can redistribute it and/or |
| modify it under the terms of the GNU Lesser General Public |
| License as published by the Free Software Foundation; either |
| version 2.1 of the License, or (at your option) any later version. |
| |
| The GNU C Library 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 |
| Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public |
| License along with the GNU C Library; if not, see |
| <http://www.gnu.org/licenses/>. */ |
| |
| /* This test program processes the tst-ns_name.data file. */ |
| |
| #include <ctype.h> |
| #include <resolv.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <support/check.h> |
| #include <support/support.h> |
| #include <support/xstdio.h> |
| |
| /* A byte buffer and its length. */ |
| struct buffer |
| { |
| unsigned char *data; |
| size_t length; |
| }; |
| |
| /* Convert a base64-encoded string to its binary representation. */ |
| static bool |
| base64_to_buffer (const char *base64, struct buffer *result) |
| { |
| /* "-" denotes an empty input. */ |
| if (strcmp (base64, "-") == 0) |
| { |
| result->data = xmalloc (1); |
| result->length = 0; |
| return true; |
| } |
| |
| size_t size = strlen (base64); |
| unsigned char *data = xmalloc (size); |
| int ret = b64_pton (base64, data, size); |
| if (ret < 0 || ret > size) |
| return false; |
| result->data = xrealloc (data, ret); |
| result->length = ret; |
| return true; |
| } |
| |
| /* A test case for ns_name_unpack and ns_name_ntop. */ |
| struct test_case |
| { |
| char *path; |
| size_t lineno; |
| struct buffer input; |
| size_t input_offset; |
| int unpack_result; |
| struct buffer unpack_output; |
| int ntop_result; |
| char *ntop_text; |
| }; |
| |
| /* Deallocate the buffers associated with the test case. */ |
| static void |
| free_test_case (struct test_case *t) |
| { |
| free (t->path); |
| free (t->input.data); |
| free (t->unpack_output.data); |
| free (t->ntop_text); |
| } |
| |
| /* Extract the test case information from a test file line. */ |
| static bool |
| parse_test_case (const char *path, size_t lineno, const char *line, |
| struct test_case *result) |
| { |
| memset (result, 0, sizeof (*result)); |
| result->path = xstrdup (path); |
| result->lineno = lineno; |
| result->ntop_result = -1; |
| char *input = NULL; |
| char *unpack_output = NULL; |
| int ret = sscanf (line, "%ms %zu %d %ms %d %ms", |
| &input, &result->input_offset, |
| &result->unpack_result, &unpack_output, |
| &result->ntop_result, &result->ntop_text); |
| if (ret < 3) |
| { |
| printf ("%s:%zu: error: missing input fields\n", path, lineno); |
| free (input); |
| return false; |
| } |
| if (!base64_to_buffer (input, &result->input)) |
| { |
| printf ("%s:%zu: error: malformed base64 input data\n", path, lineno); |
| free (input); |
| free (unpack_output); |
| free (result->ntop_text); |
| return false; |
| } |
| free (input); |
| |
| if (unpack_output == NULL) |
| result->unpack_output = (struct buffer) { NULL, 0 }; |
| else if (!base64_to_buffer (unpack_output, &result->unpack_output)) |
| { |
| printf ("%s:%zu: error: malformed base64 unpack data\n", path, lineno); |
| free (result->input.data); |
| free (unpack_output); |
| free (result->ntop_text); |
| return false; |
| } |
| free (unpack_output); |
| |
| /* At this point, all allocated buffers have been transferred to |
| *result. */ |
| |
| if (result->input_offset > result->input.length) |
| { |
| printf ("%s:%zu: error: input offset %zu exceeds buffer size %zu\n", |
| path, lineno, result->input_offset, result->input.length); |
| free_test_case (result); |
| return false; |
| } |
| if (result->unpack_result < -1) |
| { |
| printf ("%s:%zu: error: invalid unpack result %d\n", |
| path, lineno, result->unpack_result); |
| free_test_case (result); |
| return false; |
| } |
| if (result->ntop_result < -1) |
| { |
| printf ("%s:%zu: error: invalid ntop result %d\n", |
| path, lineno, result->ntop_result); |
| free_test_case (result); |
| return false; |
| } |
| |
| bool fields_consistent; |
| switch (ret) |
| { |
| case 3: |
| fields_consistent = result->unpack_result == -1; |
| break; |
| case 5: |
| fields_consistent = result->unpack_result != -1 |
| && result->ntop_result == -1; |
| break; |
| case 6: |
| fields_consistent = result->unpack_result != -1 |
| && result->ntop_result != -1; |
| break; |
| default: |
| fields_consistent = false; |
| } |
| if (!fields_consistent) |
| { |
| printf ("%s:%zu: error: wrong number of fields: %d\n", |
| path, lineno, ret); |
| free_test_case (result); |
| return false; |
| } |
| return true; |
| } |
| |
| /* Format the buffer as a hexadecimal string and write it to standard |
| output. */ |
| static void |
| print_hex (const char *label, struct buffer buffer) |
| { |
| printf (" %s ", label); |
| unsigned char *p = buffer.data; |
| unsigned char *end = p + buffer.length; |
| while (p < end) |
| { |
| printf ("%02X", *p & 0xFF); |
| ++p; |
| } |
| putchar ('\n'); |
| } |
| |
| /* Run the test case specified in *T. */ |
| static void |
| run_test_case (struct test_case *t) |
| { |
| /* Test ns_name_unpack. */ |
| unsigned char *unpacked = xmalloc (NS_MAXCDNAME); |
| int consumed = ns_name_unpack |
| (t->input.data, t->input.data + t->input.length, |
| t->input.data + t->input_offset, |
| unpacked, NS_MAXCDNAME); |
| if (consumed != t->unpack_result) |
| { |
| support_record_failure (); |
| printf ("%s:%zu: error: wrong result from ns_name_unpack\n" |
| " expected: %d\n" |
| " actual: %d\n", |
| t->path, t->lineno, t->unpack_result, consumed); |
| return; |
| } |
| if (consumed != -1) |
| { |
| if (memcmp (unpacked, t->unpack_output.data, |
| t->unpack_output.length) != 0) |
| { |
| support_record_failure (); |
| printf ("%s:%zu: error: wrong data from ns_name_unpack\n", |
| t->path, t->lineno); |
| print_hex ("expected:", t->unpack_output); |
| print_hex ("actual: ", |
| (struct buffer) { unpacked, t->unpack_output.length }); |
| return; |
| } |
| |
| /* Test ns_name_ntop. */ |
| char *text = xmalloc (NS_MAXDNAME); |
| int ret = ns_name_ntop (unpacked, text, NS_MAXDNAME); |
| if (ret != t->ntop_result) |
| { |
| support_record_failure (); |
| printf ("%s:%zu: error: wrong result from ns_name_top\n" |
| " expected: %d\n" |
| " actual: %d\n", |
| t->path, t->lineno, t->ntop_result, ret); |
| return; |
| } |
| if (ret != -1) |
| { |
| if (strcmp (text, t->ntop_text) != 0) |
| { |
| support_record_failure (); |
| printf ("%s:%zu: error: wrong data from ns_name_ntop\n", |
| t->path, t->lineno); |
| printf (" expected: \"%s\"\n", t->ntop_text); |
| printf (" actual: \"%s\"\n", text); |
| return; |
| } |
| |
| /* Test ns_name_pton. Unpacking does not check the |
| NS_MAXCDNAME limit, but packing does, so we need to |
| adjust the expected result. */ |
| int expected; |
| if (t->unpack_output.length > NS_MAXCDNAME) |
| expected = -1; |
| else if (strcmp (text, ".") == 0) |
| /* The root domain is fully qualified. */ |
| expected = 1; |
| else |
| /* The domain name is never fully qualified. */ |
| expected = 0; |
| unsigned char *repacked = xmalloc (NS_MAXCDNAME); |
| ret = ns_name_pton (text, repacked, NS_MAXCDNAME); |
| if (ret != expected) |
| { |
| support_record_failure (); |
| printf ("%s:%zu: error: wrong result from ns_name_pton\n" |
| " expected: %d\n" |
| " actual: %d\n", |
| t->path, t->lineno, expected, ret); |
| return; |
| } |
| if (ret >= 0 |
| && memcmp (repacked, unpacked, t->unpack_output.length) != 0) |
| { |
| support_record_failure (); |
| printf ("%s:%zu: error: wrong data from ns_name_pton\n", |
| t->path, t->lineno); |
| print_hex ("expected:", t->unpack_output); |
| print_hex ("actual: ", |
| (struct buffer) { repacked, t->unpack_output.length }); |
| return; |
| } |
| |
| /* Test ns_name_compress, no compression case. */ |
| if (t->unpack_output.length > NS_MAXCDNAME) |
| expected = -1; |
| else |
| expected = t->unpack_output.length; |
| memset (repacked, '$', NS_MAXCDNAME); |
| { |
| enum { ptr_count = 5 }; |
| const unsigned char *dnptrs[ptr_count] = { repacked, }; |
| ret = ns_name_compress (text, repacked, NS_MAXCDNAME, |
| dnptrs, dnptrs + ptr_count); |
| if (ret != expected) |
| { |
| support_record_failure (); |
| printf ("%s:%zu: error: wrong result from ns_name_compress\n" |
| " expected: %d\n" |
| " actual: %d\n", |
| t->path, t->lineno, expected, ret); |
| return; |
| } |
| if (ret < 0) |
| { |
| TEST_VERIFY (dnptrs[0] == repacked); |
| TEST_VERIFY (dnptrs[1] == NULL); |
| } |
| else |
| { |
| if (memcmp (repacked, unpacked, t->unpack_output.length) != 0) |
| { |
| support_record_failure (); |
| printf ("%s:%zu: error: wrong data from ns_name_compress\n", |
| t->path, t->lineno); |
| print_hex ("expected:", t->unpack_output); |
| print_hex ("actual: ", (struct buffer) { repacked, ret }); |
| return; |
| } |
| TEST_VERIFY (dnptrs[0] == repacked); |
| if (unpacked[0] == '\0') |
| /* The root domain is not a compression target. */ |
| TEST_VERIFY (dnptrs[1] == NULL); |
| else |
| { |
| TEST_VERIFY (dnptrs[1] == repacked); |
| TEST_VERIFY (dnptrs[2] == NULL); |
| } |
| } |
| } |
| |
| /* Test ns_name_compress, full compression case. Skip this |
| test for invalid names and the root domain. */ |
| if (expected >= 0 && unpacked[0] != '\0') |
| { |
| /* The destination buffer needs additional room for the |
| offset, the initial name, and the compression |
| reference. */ |
| enum { name_offset = 259 }; |
| size_t target_offset = name_offset + t->unpack_output.length; |
| size_t repacked_size = target_offset + 2; |
| repacked = xrealloc (repacked, repacked_size); |
| memset (repacked, '@', repacked_size); |
| memcpy (repacked + name_offset, |
| t->unpack_output.data, t->unpack_output.length); |
| enum { ptr_count = 5 }; |
| const unsigned char *dnptrs[ptr_count] |
| = { repacked, repacked + name_offset, }; |
| ret = ns_name_compress |
| (text, repacked + target_offset, NS_MAXCDNAME, |
| dnptrs, dnptrs + ptr_count); |
| if (ret != 2) |
| { |
| support_record_failure (); |
| printf ("%s:%zu: error: wrong result from ns_name_compress" |
| " (2)\n" |
| " expected: 2\n" |
| " actual: %d\n", |
| t->path, t->lineno, ret); |
| return; |
| } |
| if (memcmp (repacked + target_offset, "\xc1\x03", 2) != 0) |
| { |
| support_record_failure (); |
| printf ("%s:%zu: error: wrong data from ns_name_compress" |
| " (2)\n" |
| " expected: C103\n", |
| t->path, t->lineno); |
| print_hex ("actual: ", |
| (struct buffer) { repacked + target_offset, ret }); |
| return; |
| } |
| TEST_VERIFY (dnptrs[0] == repacked); |
| TEST_VERIFY (dnptrs[1] == repacked + name_offset); |
| TEST_VERIFY (dnptrs[2] == NULL); |
| } |
| |
| free (repacked); |
| } |
| free (text); |
| } |
| free (unpacked); |
| } |
| |
| /* Open the file at PATH, parse the test cases contained in it, and |
| run them. */ |
| static void |
| run_test_file (const char *path) |
| { |
| FILE *fp = xfopen (path, "re"); |
| char *line = NULL; |
| size_t line_allocated = 0; |
| size_t lineno = 0; |
| |
| while (true) |
| { |
| ssize_t ret = getline (&line, &line_allocated, fp); |
| if (ret < 0) |
| { |
| if (ferror (fp)) |
| { |
| printf ("%s: error reading file: %m\n", path); |
| exit (1); |
| } |
| TEST_VERIFY (feof (fp)); |
| break; |
| } |
| |
| ++lineno; |
| char *p = line; |
| while (isspace (*p)) |
| ++p; |
| if (*p == '\0' || *p == '#') |
| continue; |
| |
| struct test_case test_case; |
| if (!parse_test_case (path, lineno, line, &test_case)) |
| { |
| support_record_failure (); |
| continue; |
| } |
| run_test_case (&test_case); |
| free_test_case (&test_case); |
| } |
| free (line); |
| xfclose (fp); |
| } |
| |
| static int |
| do_test (void) |
| { |
| run_test_file ("tst-ns_name.data"); |
| return 0; |
| } |
| |
| #include <support/test-driver.c> |