From: Luiz Augusto von Dentz <luiz.dentz@gmail.com>
To: linux-bluetooth@vger.kernel.org
Subject: [PATCH BlueZ v1 8/8] unit/test-util: Cover strtoutf8 with the str2utf8 tests
Date: Wed, 19 Aug 2026 16:40:08 -0400 [thread overview]
Message-ID: <20260819204008.2292225-9-luiz.dentz@gmail.com> (raw)
In-Reply-To: <20260819204008.2292225-1-luiz.dentz@gmail.com>
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
strtoutf8() and str2utf8() are the two ways of dealing with a name that
is not valid UTF-8, so run them over the same inputs and keep the two
expected results side by side, which documents how they differ:
strtoutf8() truncates at the first ill-formed sequence and leaves the
whitespace alone, str2utf8() replaces the ill-formed sequences and
strips.
The expected results were checked against Python, taking the longest
prefix that decodes as strict UTF-8, over every one, two and three byte
sequence, 16646655 of them, with no mismatch.
Assisted-by: Claude:claude-opus-5
---
unit/test-util.c | 94 +++++++++++++++++++++++++++++++++---------------
1 file changed, 66 insertions(+), 28 deletions(-)
diff --git a/unit/test-util.c b/unit/test-util.c
index f0b1bb7994fb..e605d17b6b56 100644
--- a/unit/test-util.c
+++ b/unit/test-util.c
@@ -83,65 +83,101 @@ static void test_min_max(const void *data)
tester_test_passed();
}
-struct str2utf8_data {
+struct utf8_data {
const char *input; /* Not NUL terminated, len bytes are used */
size_t len;
- const char *expected;
+ const char *str2utf8; /* Ill-formed sequences replaced, stripped */
+ const char *strtoutf8; /* Truncated at the first ill-formed one */
};
#define FFFD "\xef\xbf\xbd" /* U+FFFD REPLACEMENT CHARACTER */
-static const struct str2utf8_data str2utf8_tests[] = {
+static const struct utf8_data utf8_tests[] = {
/* Nothing to do */
- { "", 0, "" },
- { "Pixel 7", 7, "Pixel 7" },
+ { "", 0, "", "" },
+ { "Pixel 7", 7, "Pixel 7", "Pixel 7" },
/* Well-formed multi-byte sequences are kept as they are */
- { "\xe2\x82\xac 5", 5, "\xe2\x82\xac 5" }, /* U+20AC */
- { "\xf0\x9f\x94\x8a", 4, "\xf0\x9f\x94\x8a" }, /* U+1F50A */
+ { "\xe2\x82\xac 5", 5, "\xe2\x82\xac 5", /* U+20AC */
+ "\xe2\x82\xac 5" },
+ { "\xf0\x9f\x94\x8a", 4, "\xf0\x9f\x94\x8a", /* U+1F50A */
+ "\xf0\x9f\x94\x8a" },
/* Leading and trailing whitespace is removed */
- { " spaced ", 10, "spaced" },
- { "\t\r\nname\n\r\t", 10, "name" },
- { " ", 3, "" },
+ { " spaced ", 10, "spaced", " spaced " },
+ { "\t\r\nname\n\r\t", 10, "name", "\t\r\nname\n\r\t" },
+ { " ", 3, "", " " },
/* The name is not NUL terminated, only len bytes are used */
- { "truncated", 4, "trun" },
+ { "truncated", 4, "trun", "trun" },
/* A byte that can never appear in UTF-8 */
- { "ab\xff""cd", 5, "ab" FFFD "cd" },
+ { "ab\xff""cd", 5, "ab" FFFD "cd", "ab" },
/* A continuation byte cannot start a sequence */
- { "ab\x80""cd", 5, "ab" FFFD "cd" },
+ { "ab\x80""cd", 5, "ab" FFFD "cd", "ab" },
/* One U+FFFD per maximal subpart, not per byte */
- { "ab\xe2\x82""cd", 6, "ab" FFFD "cd" },
+ { "ab\xe2\x82""cd", 6, "ab" FFFD "cd", "ab" },
/* A sequence cut short by len is still one maximal subpart */
- { "ab\xe2\x82\xac", 4, "ab" FFFD },
+ { "ab\xe2\x82\xac", 4, "ab" FFFD, "ab" },
/* Latin-1 text is not valid UTF-8 */
- { "caf\xe9", 4, "caf" FFFD },
+ { "caf\xe9", 4, "caf" FFFD, "caf" },
/* Overlong encodings are rejected, C0 and C1 are never valid */
- { "\xc0\x80", 2, FFFD FFFD },
- { "\xc0\xaf", 2, FFFD FFFD },
+ { "\xc0\x80", 2, FFFD FFFD, "" },
+ { "\xc0\xaf", 2, FFFD FFFD, "" },
/* UTF-16 surrogates have no UTF-8 encoding */
- { "\xed\xa0\x80", 3, FFFD FFFD FFFD },
+ { "\xed\xa0\x80", 3, FFFD FFFD FFFD, "" },
/* U+10FFFF is the last code point, F5 to FF are out of range */
- { "\xf4\x90\x80\x80", 4, FFFD FFFD FFFD FFFD },
- { "\xf5\x80\x80\x80", 4, FFFD FFFD FFFD FFFD },
+ { "\xf4\x90\x80\x80", 4, FFFD FFFD FFFD FFFD, "" },
+ { "\xf5\x80\x80\x80", 4, FFFD FFFD FFFD FFFD, "" },
/* The last code point itself is fine */
- { "\xf4\x8f\xbf\xbf", 4, "\xf4\x8f\xbf\xbf" },
+ { "\xf4\x8f\xbf\xbf", 4, "\xf4\x8f\xbf\xbf",
+ "\xf4\x8f\xbf\xbf" },
/* Replacement and stripping combined */
- { " \xff ", 3, FFFD },
+ { " \xff ", 3, FFFD, " " },
};
static void test_str2utf8(const void *data)
{
size_t i;
- for (i = 0; i < sizeof(str2utf8_tests) /
- sizeof(str2utf8_tests[0]); i++) {
- const struct str2utf8_data *test = &str2utf8_tests[i];
+ for (i = 0; i < sizeof(utf8_tests) / sizeof(utf8_tests[0]); i++) {
+ const struct utf8_data *test = &utf8_tests[i];
char *str = str2utf8((const uint8_t *) test->input,
test->len);
assert(str);
- if (strcmp(str, test->expected)) {
+ if (strcmp(str, test->str2utf8)) {
printf("test %zu: expected \"%s\", got \"%s\"\n", i,
- test->expected, str);
+ test->str2utf8, str);
+ free(str);
+ tester_test_failed();
+ return;
+ }
+
+ /* The result is always well-formed UTF-8 */
+ assert(strisutf8(str, strlen(str)));
+
+ free(str);
+ }
+
+ tester_test_passed();
+}
+
+static void test_strtoutf8(const void *data)
+{
+ size_t i;
+
+ for (i = 0; i < sizeof(utf8_tests) / sizeof(utf8_tests[0]); i++) {
+ const struct utf8_data *test = &utf8_tests[i];
+ char *str;
+
+ /* strtoutf8() works in place, so it needs a writable copy */
+ str = malloc(test->len + 1);
+ assert(str);
+ memcpy(str, test->input, test->len);
+ str[test->len] = '\0';
+
+ assert(strtoutf8(str, test->len) == str);
+
+ if (strcmp(str, test->strtoutf8)) {
+ printf("test %zu: expected \"%s\", got \"%s\"\n", i,
+ test->strtoutf8, str);
free(str);
tester_test_failed();
return;
@@ -178,6 +214,8 @@ int main(int argc, char *argv[])
test_str2utf8, NULL);
tester_add("/util/str2utf8_null", NULL, NULL,
test_str2utf8_null, NULL);
+ tester_add("/util/strtoutf8", NULL, NULL,
+ test_strtoutf8, NULL);
return tester_run();
}
--
2.54.0
prev parent reply other threads:[~2026-08-19 20:40 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-19 20:40 [PATCH BlueZ v1 0/8] Replace the name2utf8 copies with str2utf8 Luiz Augusto von Dentz
2026-08-19 20:40 ` [PATCH BlueZ v1 1/8] eir: Fix stack buffer overflow when parsing the remote name Luiz Augusto von Dentz
2026-08-19 21:27 ` Replace the name2utf8 copies with str2utf8 bluez.test.bot
2026-08-20 7:46 ` [PATCH BlueZ v1 1/8] eir: Fix stack buffer overflow when parsing the remote name Bastien Nocera
2026-08-20 8:14 ` Bastien Nocera
2026-08-19 20:40 ` [PATCH BlueZ v1 2/8] shared/util: Make strnlenutf8 reject ill-formed sequences Luiz Augusto von Dentz
2026-08-19 20:40 ` [PATCH BlueZ v1 3/8] shared/util: Add str2utf8 Luiz Augusto von Dentz
2026-08-19 20:40 ` [PATCH BlueZ v1 4/8] unit/test-util: Add str2utf8 tests Luiz Augusto von Dentz
2026-08-19 20:40 ` [PATCH BlueZ v1 5/8] Replace the name2utf8 copies with str2utf8 Luiz Augusto von Dentz
2026-08-19 20:40 ` [PATCH BlueZ v1 6/8] device: Fix the name truncation splitting UTF-8 sequences Luiz Augusto von Dentz
2026-08-19 20:40 ` [PATCH BlueZ v1 7/8] device: Rename btd_device_device_set_name to btd_device_set_name Luiz Augusto von Dentz
2026-08-19 20:40 ` Luiz Augusto von Dentz [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260819204008.2292225-9-luiz.dentz@gmail.com \
--to=luiz.dentz@gmail.com \
--cc=linux-bluetooth@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.