#include "doctest.h" #include #include namespace { MeshtasticChannelKey defaultKey() { const uint8_t psk = 1; MeshtasticChannelKey key {}; REQUIRE(meshtastic_expand_psk(&psk, 1, &key)); return key; } } // namespace TEST_CASE("Meshtastic default channel matches the known LongFast values") { const auto key = defaultKey(); CHECK_EQ(key.length, 16); CHECK_EQ(memcmp(key.bytes, MESHTASTIC_DEFAULT_KEY, 16), 0); // LongFast with the default key is channel hash 8 CHECK_EQ(meshtastic_channel_hash("LongFast", key), 0x08); // US LongFast sits at 906.875 MHz, EU_868 has a single slot CHECK_EQ(meshtastic_channel_frequency("LongFast", 902000000, 928000000, 250000), 906875000); CHECK_EQ(meshtastic_channel_frequency("LongFast", 869400000, 869650000, 250000), 869525000); } TEST_CASE("Meshtastic PSK expansion") { const uint8_t second = 2; MeshtasticChannelKey key {}; REQUIRE(meshtastic_expand_psk(&second, 1, &key)); CHECK_EQ(key.bytes[15], MESHTASTIC_DEFAULT_KEY[15] + 1); const uint8_t none = 0; CHECK_FALSE(meshtastic_expand_psk(&none, 1, &key)); uint8_t long_key[32] = {}; CHECK(meshtastic_expand_psk(long_key, 32, &key)); CHECK_EQ(key.length, 32); CHECK_FALSE(meshtastic_expand_psk(long_key, 20, &key)); } TEST_CASE("Meshtastic header round trip") { MeshtasticHeader header; header.to = 0x11223344; header.from = 0xAABBCCDD; header.id = 0x01020304; header.hopLimit = 2; header.wantAck = true; header.hopStart = 3; header.channelHash = 8; uint8_t data[MESHTASTIC_HEADER_SIZE]; meshtastic_write_header(header, data); CHECK_EQ(data[0], 0x44); CHECK_EQ(data[7], 0xAA); CHECK_EQ(data[12], 2 | 0x08 | (3 << 5)); MeshtasticHeader parsed; REQUIRE(meshtastic_read_header(data, sizeof(data), &parsed)); CHECK_EQ(parsed.to, header.to); CHECK_EQ(parsed.from, header.from); CHECK_EQ(parsed.id, header.id); CHECK_EQ(parsed.hopLimit, 2); CHECK(parsed.wantAck); CHECK_EQ(parsed.hopStart, 3); CHECK_EQ(parsed.channelHash, 8); } TEST_CASE("Meshtastic Data encoding and AES-CTR round trip") { MeshtasticData data; data.port = MeshtasticPort::TextMessage; const char* text = "hello mesh"; data.payload.assign(text, text + strlen(text)); data.requestId = 0x12345678; const auto encoded = meshtastic_encode_data(data); // portnum = 1, then the payload CHECK_EQ(encoded[0], 0x08); CHECK_EQ(encoded[1], 0x01); CHECK_EQ(encoded[2], 0x12); CHECK_EQ(encoded[3], strlen(text)); const auto key = defaultKey(); uint8_t ciphertext[64]; uint8_t plaintext[64]; REQUIRE(meshtastic_crypt(key, 42, 0xAABBCCDD, encoded.data(), ciphertext, encoded.size())); CHECK_NE(memcmp(ciphertext, encoded.data(), encoded.size()), 0); REQUIRE(meshtastic_crypt(key, 42, 0xAABBCCDD, ciphertext, plaintext, encoded.size())); MeshtasticData decoded; REQUIRE(meshtastic_decode_data(plaintext, encoded.size(), &decoded)); CHECK_EQ(decoded.port, MeshtasticPort::TextMessage); CHECK_EQ(std::string(decoded.payload.begin(), decoded.payload.end()), text); CHECK_EQ(decoded.requestId, 0x12345678); // Decrypting with another packet id yields garbage REQUIRE(meshtastic_crypt(key, 43, 0xAABBCCDD, ciphertext, plaintext, encoded.size())); CHECK_NE(memcmp(plaintext, encoded.data(), encoded.size()), 0); } TEST_CASE("Meshtastic User encoding round trip") { MeshtasticUser user; user.id = meshtastic_node_id(0xDEADBEEF); user.longName = "Tactility"; user.shortName = "Tact"; CHECK_EQ(user.id, "!deadbeef"); const auto encoded = meshtastic_encode_user(user); MeshtasticUser decoded; REQUIRE(meshtastic_decode_user(encoded.data(), encoded.size(), &decoded)); CHECK_EQ(decoded.id, "!deadbeef"); CHECK_EQ(decoded.longName, "Tactility"); CHECK_EQ(decoded.shortName, "Tact"); }