From: Luiz Augusto von Dentz <luiz.dentz@gmail.com>
To: linux-bluetooth@vger.kernel.org
Subject: [PATCH BlueZ v1 4/8] unit/test-util: Add str2utf8 tests
Date: Wed, 19 Aug 2026 16:40:04 -0400 [thread overview]
Message-ID: <20260819204008.2292225-5-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>
Cover the cases str2utf8() is meant to handle: well-formed input that
has to be left alone, whitespace stripping, input that is not NUL
terminated, and the ill-formed sequences that have to be replaced,
including the overlong encodings, the UTF-16 surrogates and the code
points past U+10FFFF.
Also check that a maximal subpart is replaced by a single U+FFFD rather
than one per byte, and that the result is always well-formed UTF-8.
Assisted-by: Claude:claude-opus-5
---
unit/test-util.c | 83 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 83 insertions(+)
diff --git a/unit/test-util.c b/unit/test-util.c
index 1672b32eb39c..f0b1bb7994fb 100644
--- a/unit/test-util.c
+++ b/unit/test-util.c
@@ -83,6 +83,85 @@ static void test_min_max(const void *data)
tester_test_passed();
}
+struct str2utf8_data {
+ const char *input; /* Not NUL terminated, len bytes are used */
+ size_t len;
+ const char *expected;
+};
+
+#define FFFD "\xef\xbf\xbd" /* U+FFFD REPLACEMENT CHARACTER */
+
+static const struct str2utf8_data str2utf8_tests[] = {
+ /* Nothing to do */
+ { "", 0, "" },
+ { "Pixel 7", 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 */
+ /* Leading and trailing whitespace is removed */
+ { " spaced ", 10, "spaced" },
+ { "\t\r\nname\n\r\t", 10, "name" },
+ { " ", 3, "" },
+ /* The name is not NUL terminated, only len bytes are used */
+ { "truncated", 4, "trun" },
+ /* A byte that can never appear in UTF-8 */
+ { "ab\xff""cd", 5, "ab" FFFD "cd" },
+ /* A continuation byte cannot start a sequence */
+ { "ab\x80""cd", 5, "ab" FFFD "cd" },
+ /* One U+FFFD per maximal subpart, not per byte */
+ { "ab\xe2\x82""cd", 6, "ab" FFFD "cd" },
+ /* A sequence cut short by len is still one maximal subpart */
+ { "ab\xe2\x82\xac", 4, "ab" FFFD },
+ /* Latin-1 text is not valid UTF-8 */
+ { "caf\xe9", 4, "caf" FFFD },
+ /* Overlong encodings are rejected, C0 and C1 are never valid */
+ { "\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 },
+ /* 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 },
+ /* The last code point itself is fine */
+ { "\xf4\x8f\xbf\xbf", 4, "\xf4\x8f\xbf\xbf" },
+ /* Replacement and stripping combined */
+ { " \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];
+ char *str = str2utf8((const uint8_t *) test->input,
+ test->len);
+
+ assert(str);
+ if (strcmp(str, test->expected)) {
+ printf("test %zu: expected \"%s\", got \"%s\"\n", i,
+ test->expected, 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_str2utf8_null(const void *data)
+{
+ assert(!str2utf8(NULL, 0));
+ tester_test_passed();
+}
+
int main(int argc, char *argv[])
{
tester_init(&argc, &argv);
@@ -95,6 +174,10 @@ int main(int argc, char *argv[])
test_cleanup_type, NULL);
tester_add("/util/cleanup_fd", NULL, NULL,
test_cleanup_fd, NULL);
+ tester_add("/util/str2utf8", NULL, NULL,
+ test_str2utf8, NULL);
+ tester_add("/util/str2utf8_null", NULL, NULL,
+ test_str2utf8_null, NULL);
return tester_run();
}
--
2.54.0
next 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 ` Luiz Augusto von Dentz [this message]
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 ` [PATCH BlueZ v1 8/8] unit/test-util: Cover strtoutf8 with the str2utf8 tests Luiz Augusto von Dentz
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-5-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.