Browse Source

Merge 79dc1de6fc into adc631f315

pull/3004/merge
Alexander Hoff ❍ 16 hours ago
committed by GitHub
parent
commit
9798929cb8
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      docs/cli_commands.md
  2. 14
      src/helpers/AdvertDataHelpers.cpp
  3. 56
      src/helpers/UTF8Helpers.h
  4. 38
      test/test_utf8_helpers/test_utf8_helpers.cpp

2
docs/cli_commands.md

@ -305,7 +305,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore
**Default:** Varies by board
**Note:** Max length varies. If a location is set, the max length is 24 bytes; 32 otherwise. Emoji and unicode characters may take more than one byte.
**Note:** Advertised names can use up to 23 bytes when location is included and 31 bytes otherwise. Emoji and Unicode characters may take more than one byte. Names that exceed the available advert space are truncated at a valid UTF-8 code point boundary.
---

14
src/helpers/AdvertDataHelpers.cpp

@ -1,4 +1,5 @@
#include <helpers/AdvertDataHelpers.h>
#include <helpers/UTF8Helpers.h>
uint8_t AdvertDataBuilder::encodeTo(uint8_t app_data[]) {
app_data[0] = _type;
@ -16,11 +17,12 @@
app_data[0] |= ADV_FEAT2_MASK;
memcpy(&app_data[i], &_extra2, 2); i += 2;
}
if (_name && *_name != 0) {
app_data[0] |= ADV_NAME_MASK;
const char* sp = _name;
while (*sp && i < MAX_ADVERT_DATA_SIZE) {
app_data[i++] = *sp++;
if (_name && *_name != 0) {
size_t name_len = mesh::validUtf8PrefixLength(_name, MAX_ADVERT_DATA_SIZE - i);
if (name_len > 0) {
app_data[0] |= ADV_NAME_MASK;
memcpy(&app_data[i], _name, name_len);
i += name_len;
}
}
return i;
@ -84,4 +86,4 @@ void AdvertTimeHelper::formatRelativeTimeDiff(char dest[], int32_t seconds_from_
}
}
}
}
}

56
src/helpers/UTF8Helpers.h

@ -0,0 +1,56 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
namespace mesh {
inline bool isUtf8Continuation(uint8_t byte) {
return (byte & 0xC0) == 0x80;
}
inline size_t validUtf8PrefixLength(const char* text, size_t max_bytes) {
if (text == nullptr) return 0;
size_t offset = 0;
while (text[offset] != '\0') {
const uint8_t first = static_cast<uint8_t>(text[offset]);
size_t sequence_length = 0;
if (first <= 0x7F) {
sequence_length = 1;
} else if (first >= 0xC2 && first <= 0xDF) {
sequence_length = 2;
} else if (first >= 0xE0 && first <= 0xEF) {
sequence_length = 3;
} else if (first >= 0xF0 && first <= 0xF4) {
sequence_length = 4;
} else {
break;
}
if (offset + sequence_length > max_bytes) break;
bool complete = true;
for (size_t i = 1; i < sequence_length; i++) {
if (text[offset + i] == '\0' || !isUtf8Continuation(static_cast<uint8_t>(text[offset + i]))) {
complete = false;
break;
}
}
if (!complete) break;
if (sequence_length == 3) {
const uint8_t second = static_cast<uint8_t>(text[offset + 1]);
if ((first == 0xE0 && second < 0xA0) || (first == 0xED && second > 0x9F)) break;
} else if (sequence_length == 4) {
const uint8_t second = static_cast<uint8_t>(text[offset + 1]);
if ((first == 0xF0 && second < 0x90) || (first == 0xF4 && second > 0x8F)) break;
}
offset += sequence_length;
}
return offset;
}
} // namespace mesh

38
test/test_utf8_helpers/test_utf8_helpers.cpp

@ -0,0 +1,38 @@
#include <gtest/gtest.h>
#include <helpers/UTF8Helpers.h>
TEST(UTF8Helpers, KeepsCompleteNameWithinLimit) {
const char* name = "Example RPT 🔋🇵🇱";
EXPECT_EQ(24u, mesh::validUtf8PrefixLength(name, 24));
}
TEST(UTF8Helpers, StopsBeforeCodePointCrossingLimit) {
const char* name = "Example RPT 🔋🇵🇱";
EXPECT_EQ(20u, mesh::validUtf8PrefixLength(name, 23));
}
TEST(UTF8Helpers, RejectsMalformedAndTruncatedSequences) {
const char overlong[] = {'A', static_cast<char>(0xC0), static_cast<char>(0xAF), 0};
const char surrogate[] = {'A', static_cast<char>(0xED), static_cast<char>(0xA0), static_cast<char>(0x80), 0};
const char out_of_range[] = {'A', static_cast<char>(0xF4), static_cast<char>(0x90), static_cast<char>(0x80), static_cast<char>(0x80), 0};
const char truncated[] = {'A', static_cast<char>(0xF0), static_cast<char>(0x9F), 0};
EXPECT_EQ(1u, mesh::validUtf8PrefixLength(overlong, sizeof(overlong)));
EXPECT_EQ(1u, mesh::validUtf8PrefixLength(surrogate, sizeof(surrogate)));
EXPECT_EQ(1u, mesh::validUtf8PrefixLength(out_of_range, sizeof(out_of_range)));
EXPECT_EQ(1u, mesh::validUtf8PrefixLength(truncated, sizeof(truncated)));
}
TEST(UTF8Helpers, RejectsUnexpectedContinuationByte) {
const char invalid[] = {'A', static_cast<char>(0x80), 'B', 0};
EXPECT_EQ(1u, mesh::validUtf8PrefixLength(invalid, sizeof(invalid)));
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Loading…
Cancel
Save