Files
wadamesh/test/test_channel_sender_split.cpp
T
Ruben Laban 78ca05f5bd touch: a long name no longer pushes the author into the message body
A channel post arrives on the wire as "SenderName: body" — the author is
in the text, not a separate field. Both split sites gave up when the name
was longer than MAX_SENDER_NAME (24), and giving up meant the sender fell
back to from_name, which for a channel is the CHANNEL name. The bubble
then read

  #channel      date
  nick: message

instead of the author over their own message. MeshCore names run to 31
chars (ContactInfo::name[32]), so this hit every post from a node with a
25+ char name — the "occasionally" in the report.

Truncate the label instead of rejecting the split, backing off to a UTF-8
character boundary so a multi-byte name is never cut mid-sequence (the
missing-glyph sanitiser would render the tail as '*'), and mark the cut
with "..." so a shortened name does not read as a different node.

Widening MAX_SENDER_NAME is the real cure but not an option here: sender[]
sits in the MIDDLE of the persisted UiHistoryMsg record, and this file
format only tolerates fields APPENDED to the end — moving `text` would
force k_ui_history_min_version up and discard everyone's chat history.

The two copies of the split are now one host-tested header, matching how
Utf8Text/ReaderContent are factored.

Signed-off-by: Ruben Laban <ruben@tun0.nl>
2026-09-01 07:53:14 +02:00

96 lines
4.1 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
#include <assert.h>
#include <string.h>
#include "ui-touch/ChannelSenderSplit.h"
// UIMessage::sender is char[MAX_SENDER_NAME + 1] with MAX_SENDER_NAME == 24.
static const size_t kSenderCap = 25;
int main() {
char sender[kSenderCap];
// Ordinary channel post: author out, body in.
const char* body = ChannelSenderSplit::split("nick: hello there", sender, sizeof sender);
assert(strcmp(sender, "nick") == 0);
assert(strcmp(body, "hello there") == 0);
// The regression: a 27-char MeshCore name (they run to 31) used to fail the
// split outright, so the bubble header showed the channel and the body kept
// reading "nick: message". It must split, truncating the label instead — and a
// truncated label ends in "..." so it does not read as a shorter name.
const char* long_name = "Ouderkerk Repeater Node Two: on air";
body = ChannelSenderSplit::split(long_name, sender, sizeof sender);
assert(strlen(sender) == 24);
assert(strcmp(sender, "Ouderkerk Repeater No...") == 0);
assert(strcmp(body, "on air") == 0);
// Truncation lands on a character boundary, never inside a multi-byte sequence.
// "Ouderkerkk" (10) + U+2600 (3 bytes) x 5 = 25 bytes; the 21-byte name budget
// (24 minus the marker) falls inside the fourth sun, so it backs off to 19.
const char* utf8_name = "Ouderkerkk\xE2\x98\x80\xE2\x98\x80\xE2\x98\x80\xE2\x98\x80\xE2\x98\x80: hi";
body = ChannelSenderSplit::split(utf8_name, sender, sizeof sender);
assert(strcmp(sender, "Ouderkerkk\xE2\x98\x80\xE2\x98\x80\xE2\x98\x80...") == 0);
assert(strlen(sender) == 22);
assert(strcmp(body, "hi") == 0);
// Same shape one char shorter, so the budget already sits on a boundary: the
// back-off must not eat a whole character that fits.
const char* utf8_fits = "Ouderkerk\xE2\x98\x80\xE2\x98\x80\xE2\x98\x80\xE2\x98\x80\xE2\x98\x80\xE2\x98\x80: hi";
body = ChannelSenderSplit::split(utf8_fits, sender, sizeof sender);
assert(strcmp(sender, "Ouderkerk\xE2\x98\x80\xE2\x98\x80\xE2\x98\x80\xE2\x98\x80...") == 0);
assert(strlen(sender) == 24);
assert(strcmp(body, "hi") == 0);
// One char over the cap still gets the marker (name budget 21, not 24).
const char* one_over = "1234567890123456789012345: body";
body = ChannelSenderSplit::split(one_over, sender, sizeof sender);
assert(strcmp(sender, "123456789012345678901...") == 0);
assert(strcmp(body, "body") == 0);
// Destination too small to hold a marker: truncate plainly rather than
// spending the whole label on dots.
char tiny[5];
body = ChannelSenderSplit::split("nickname: body", tiny, sizeof tiny);
assert(strcmp(tiny, "nick") == 0);
assert(strcmp(body, "body") == 0);
// Exactly at the cap: 24 chars, no truncation.
const char* exact = "123456789012345678901234: body";
body = ChannelSenderSplit::split(exact, sender, sizeof sender);
assert(strcmp(sender, "123456789012345678901234") == 0);
assert(strcmp(body, "body") == 0);
// No author prefix — text passes through untouched, sender stays empty so the
// caller falls back to from_name.
const char* plain = "just a message";
body = ChannelSenderSplit::split(plain, sender, sizeof sender);
assert(sender[0] == '\0');
assert(body == plain);
// A bare colon without the space is not a separator.
const char* no_space = "nick:hello";
body = ChannelSenderSplit::split(no_space, sender, sizeof sender);
assert(sender[0] == '\0');
assert(body == no_space);
// Leading separator: empty author is not a name.
const char* leading = ": body";
body = ChannelSenderSplit::split(leading, sender, sizeof sender);
assert(sender[0] == '\0');
assert(body == leading);
// First separator wins — a colon in the body does not re-split.
body = ChannelSenderSplit::split("nick: ratio 3: 1", sender, sizeof sender);
assert(strcmp(sender, "nick") == 0);
assert(strcmp(body, "ratio 3: 1") == 0);
// Degenerate inputs.
assert(strcmp(ChannelSenderSplit::split(nullptr, sender, sizeof sender), "") == 0);
assert(sender[0] == '\0');
const char* keep = "nick: hi";
assert(ChannelSenderSplit::split(keep, nullptr, 0) == keep);
return 0;
}