Remove fragile JSON libraries from the firmware while retaining Meshtasticd JSON support (#10152)

This commit is contained in:
Thomas Göttgens
2026-06-03 16:47:30 +02:00
committed by GitHub
co-authored by GitHub
parent fe23dcfa3a
commit f86cb7781e
30 changed files with 724 additions and 2830 deletions
@@ -1,42 +1,31 @@
#include "../test_helpers.h"
// Helper function for all encrypted packet assertions
void assert_encrypted_packet(const std::string &json, meshtastic_MeshPacket packet)
static void assert_encrypted_packet(const std::string &json, const meshtastic_MeshPacket &packet)
{
// Parse and validate JSON
TEST_ASSERT_TRUE(json.length() > 0);
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
Json::Value root = parse_json(json);
TEST_ASSERT_TRUE(root.isObject());
JSONObject jsonObj = root->AsObject();
TEST_ASSERT_TRUE(root.isMember("from"));
TEST_ASSERT_EQUAL(packet.from, root["from"].asUInt());
// Assert basic packet fields
TEST_ASSERT_TRUE(jsonObj.find("from") != jsonObj.end());
TEST_ASSERT_EQUAL(packet.from, (uint32_t)jsonObj.at("from")->AsNumber());
TEST_ASSERT_TRUE(root.isMember("to"));
TEST_ASSERT_EQUAL(packet.to, root["to"].asUInt());
TEST_ASSERT_TRUE(jsonObj.find("to") != jsonObj.end());
TEST_ASSERT_EQUAL(packet.to, (uint32_t)jsonObj.at("to")->AsNumber());
TEST_ASSERT_TRUE(root.isMember("id"));
TEST_ASSERT_EQUAL(packet.id, root["id"].asUInt());
TEST_ASSERT_TRUE(jsonObj.find("id") != jsonObj.end());
TEST_ASSERT_EQUAL(packet.id, (uint32_t)jsonObj.at("id")->AsNumber());
TEST_ASSERT_TRUE(root.isMember("bytes"));
TEST_ASSERT_TRUE(root["bytes"].isString());
// Assert encrypted data fields
TEST_ASSERT_TRUE(jsonObj.find("bytes") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj.at("bytes")->IsString());
TEST_ASSERT_TRUE(root.isMember("size"));
TEST_ASSERT_EQUAL(packet.encrypted.size, (int)root["size"].asInt());
TEST_ASSERT_TRUE(jsonObj.find("size") != jsonObj.end());
TEST_ASSERT_EQUAL(packet.encrypted.size, (int)jsonObj.at("size")->AsNumber());
// Assert hex encoding
std::string encrypted_hex = jsonObj["bytes"]->AsString();
std::string encrypted_hex = root["bytes"].asString();
TEST_ASSERT_EQUAL(packet.encrypted.size * 2, encrypted_hex.length());
delete root;
}
// Test encrypted packet serialization
void test_encrypted_packet_serialization()
{
const char *data = "encrypted_payload_data";
@@ -48,7 +37,6 @@ void test_encrypted_packet_serialization()
assert_encrypted_packet(json, packet);
}
// Test empty encrypted packet
void test_empty_encrypted_packet()
{
meshtastic_MeshPacket packet =
@@ -13,7 +13,6 @@ static size_t encode_user_info(uint8_t *buffer, size_t buffer_size)
return stream.bytes_written;
}
// Test NODEINFO_APP port
void test_nodeinfo_serialization()
{
uint8_t buffer[256];
@@ -24,28 +23,20 @@ void test_nodeinfo_serialization()
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
Json::Value root = parse_json(json);
TEST_ASSERT_TRUE(root.isObject());
JSONObject jsonObj = root->AsObject();
TEST_ASSERT_TRUE(root.isMember("type"));
TEST_ASSERT_EQUAL_STRING("nodeinfo", root["type"].asString().c_str());
// Check message type
TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end());
TEST_ASSERT_EQUAL_STRING("nodeinfo", jsonObj["type"]->AsString().c_str());
TEST_ASSERT_TRUE(root.isMember("payload"));
TEST_ASSERT_TRUE(root["payload"].isObject());
// Check payload
TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
const Json::Value &payload = root["payload"];
JSONObject payload = jsonObj["payload"]->AsObject();
TEST_ASSERT_TRUE(payload.isMember("shortname"));
TEST_ASSERT_EQUAL_STRING("TEST", payload["shortname"].asString().c_str());
// Verify user data
TEST_ASSERT_TRUE(payload.find("shortname") != payload.end());
TEST_ASSERT_EQUAL_STRING("TEST", payload["shortname"]->AsString().c_str());
TEST_ASSERT_TRUE(payload.find("longname") != payload.end());
TEST_ASSERT_EQUAL_STRING("Test User", payload["longname"]->AsString().c_str());
delete root;
TEST_ASSERT_TRUE(payload.isMember("longname"));
TEST_ASSERT_EQUAL_STRING("Test User", payload["longname"].asString().c_str());
}
@@ -3,8 +3,8 @@
static size_t encode_position(uint8_t *buffer, size_t buffer_size)
{
meshtastic_Position position = meshtastic_Position_init_zero;
position.latitude_i = 374208000; // 37.4208 degrees * 1e7
position.longitude_i = -1221981000; // -122.1981 degrees * 1e7
position.latitude_i = 374208000;
position.longitude_i = -1221981000;
position.altitude = 123;
position.time = 1609459200;
position.has_altitude = true;
@@ -16,7 +16,6 @@ static size_t encode_position(uint8_t *buffer, size_t buffer_size)
return stream.bytes_written;
}
// Test POSITION_APP port
void test_position_serialization()
{
uint8_t buffer[256];
@@ -27,31 +26,23 @@ void test_position_serialization()
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
Json::Value root = parse_json(json);
TEST_ASSERT_TRUE(root.isObject());
JSONObject jsonObj = root->AsObject();
TEST_ASSERT_TRUE(root.isMember("type"));
TEST_ASSERT_EQUAL_STRING("position", root["type"].asString().c_str());
// Check message type
TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end());
TEST_ASSERT_EQUAL_STRING("position", jsonObj["type"]->AsString().c_str());
TEST_ASSERT_TRUE(root.isMember("payload"));
TEST_ASSERT_TRUE(root["payload"].isObject());
// Check payload
TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
const Json::Value &payload = root["payload"];
JSONObject payload = jsonObj["payload"]->AsObject();
TEST_ASSERT_TRUE(payload.isMember("latitude_i"));
TEST_ASSERT_EQUAL(374208000, payload["latitude_i"].asInt());
// Verify position data
TEST_ASSERT_TRUE(payload.find("latitude_i") != payload.end());
TEST_ASSERT_EQUAL(374208000, (int)payload["latitude_i"]->AsNumber());
TEST_ASSERT_TRUE(payload.isMember("longitude_i"));
TEST_ASSERT_EQUAL(-1221981000, payload["longitude_i"].asInt());
TEST_ASSERT_TRUE(payload.find("longitude_i") != payload.end());
TEST_ASSERT_EQUAL(-1221981000, (int)payload["longitude_i"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("altitude") != payload.end());
TEST_ASSERT_EQUAL(123, (int)payload["altitude"]->AsNumber());
delete root;
TEST_ASSERT_TRUE(payload.isMember("altitude"));
TEST_ASSERT_EQUAL(123, payload["altitude"].asInt());
}
@@ -1,6 +1,62 @@
#include "../test_helpers.h"
// Helper function to create and encode device metrics
static void fill_all_env_metrics(meshtastic_Telemetry &telemetry)
{
telemetry.variant.environment_metrics.temperature = 23.56f;
telemetry.variant.environment_metrics.has_temperature = true;
telemetry.variant.environment_metrics.relative_humidity = 65.43f;
telemetry.variant.environment_metrics.has_relative_humidity = true;
telemetry.variant.environment_metrics.barometric_pressure = 1013.27f;
telemetry.variant.environment_metrics.has_barometric_pressure = true;
telemetry.variant.environment_metrics.gas_resistance = 50.58f;
telemetry.variant.environment_metrics.has_gas_resistance = true;
telemetry.variant.environment_metrics.iaq = 120;
telemetry.variant.environment_metrics.has_iaq = true;
telemetry.variant.environment_metrics.voltage = 3.34f;
telemetry.variant.environment_metrics.has_voltage = true;
telemetry.variant.environment_metrics.current = 0.53f;
telemetry.variant.environment_metrics.has_current = true;
telemetry.variant.environment_metrics.lux = 450.12f;
telemetry.variant.environment_metrics.has_lux = true;
telemetry.variant.environment_metrics.white_lux = 380.95f;
telemetry.variant.environment_metrics.has_white_lux = true;
telemetry.variant.environment_metrics.ir_lux = 25.37f;
telemetry.variant.environment_metrics.has_ir_lux = true;
telemetry.variant.environment_metrics.uv_lux = 15.68f;
telemetry.variant.environment_metrics.has_uv_lux = true;
telemetry.variant.environment_metrics.distance = 150.29f;
telemetry.variant.environment_metrics.has_distance = true;
telemetry.variant.environment_metrics.wind_direction = 180;
telemetry.variant.environment_metrics.has_wind_direction = true;
telemetry.variant.environment_metrics.wind_speed = 5.52f;
telemetry.variant.environment_metrics.has_wind_speed = true;
telemetry.variant.environment_metrics.wind_gust = 8.24f;
telemetry.variant.environment_metrics.has_wind_gust = true;
telemetry.variant.environment_metrics.wind_lull = 2.13f;
telemetry.variant.environment_metrics.has_wind_lull = true;
telemetry.variant.environment_metrics.weight = 75.56f;
telemetry.variant.environment_metrics.has_weight = true;
telemetry.variant.environment_metrics.radiation = 0.13f;
telemetry.variant.environment_metrics.has_radiation = true;
telemetry.variant.environment_metrics.rainfall_1h = 2.57f;
telemetry.variant.environment_metrics.has_rainfall_1h = true;
telemetry.variant.environment_metrics.rainfall_24h = 15.89f;
telemetry.variant.environment_metrics.has_rainfall_24h = true;
telemetry.variant.environment_metrics.soil_moisture = 85;
telemetry.variant.environment_metrics.has_soil_moisture = true;
telemetry.variant.environment_metrics.soil_temperature = 18.54f;
telemetry.variant.environment_metrics.has_soil_temperature = true;
}
static size_t encode_telemetry_device_metrics(uint8_t *buffer, size_t buffer_size)
{
meshtastic_Telemetry telemetry = meshtastic_Telemetry_init_zero;
@@ -22,507 +78,233 @@ static size_t encode_telemetry_device_metrics(uint8_t *buffer, size_t buffer_siz
return stream.bytes_written;
}
// Helper function to create and encode empty environment metrics (no fields set)
static size_t encode_telemetry_environment_metrics_empty(uint8_t *buffer, size_t buffer_size)
{
meshtastic_Telemetry telemetry = meshtastic_Telemetry_init_zero;
telemetry.time = 1609459200;
telemetry.which_variant = meshtastic_Telemetry_environment_metrics_tag;
// NO fields are set - all has_* flags remain false
// This tests that empty environment metrics don't produce any JSON fields
pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size);
pb_encode(&stream, &meshtastic_Telemetry_msg, &telemetry);
return stream.bytes_written;
}
// Helper function to create environment metrics with ALL possible fields set
// This function should be updated whenever new fields are added to the protobuf
static size_t encode_telemetry_environment_metrics_all_fields(uint8_t *buffer, size_t buffer_size)
{
meshtastic_Telemetry telemetry = meshtastic_Telemetry_init_zero;
telemetry.time = 1609459200;
telemetry.which_variant = meshtastic_Telemetry_environment_metrics_tag;
// Basic environment metrics
telemetry.variant.environment_metrics.temperature = 23.56f;
telemetry.variant.environment_metrics.has_temperature = true;
telemetry.variant.environment_metrics.relative_humidity = 65.43f;
telemetry.variant.environment_metrics.has_relative_humidity = true;
telemetry.variant.environment_metrics.barometric_pressure = 1013.27f;
telemetry.variant.environment_metrics.has_barometric_pressure = true;
// Gas and air quality
telemetry.variant.environment_metrics.gas_resistance = 50.58f;
telemetry.variant.environment_metrics.has_gas_resistance = true;
telemetry.variant.environment_metrics.iaq = 120;
telemetry.variant.environment_metrics.has_iaq = true;
// Power measurements
telemetry.variant.environment_metrics.voltage = 3.34f;
telemetry.variant.environment_metrics.has_voltage = true;
telemetry.variant.environment_metrics.current = 0.53f;
telemetry.variant.environment_metrics.has_current = true;
// Light measurements (ALL 4 types)
telemetry.variant.environment_metrics.lux = 450.12f;
telemetry.variant.environment_metrics.has_lux = true;
telemetry.variant.environment_metrics.white_lux = 380.95f;
telemetry.variant.environment_metrics.has_white_lux = true;
telemetry.variant.environment_metrics.ir_lux = 25.37f;
telemetry.variant.environment_metrics.has_ir_lux = true;
telemetry.variant.environment_metrics.uv_lux = 15.68f;
telemetry.variant.environment_metrics.has_uv_lux = true;
// Distance measurement
telemetry.variant.environment_metrics.distance = 150.29f;
telemetry.variant.environment_metrics.has_distance = true;
// Wind measurements (ALL 4 types)
telemetry.variant.environment_metrics.wind_direction = 180;
telemetry.variant.environment_metrics.has_wind_direction = true;
telemetry.variant.environment_metrics.wind_speed = 5.52f;
telemetry.variant.environment_metrics.has_wind_speed = true;
telemetry.variant.environment_metrics.wind_gust = 8.24f;
telemetry.variant.environment_metrics.has_wind_gust = true;
telemetry.variant.environment_metrics.wind_lull = 2.13f;
telemetry.variant.environment_metrics.has_wind_lull = true;
// Weight measurement
telemetry.variant.environment_metrics.weight = 75.56f;
telemetry.variant.environment_metrics.has_weight = true;
// Radiation measurement
telemetry.variant.environment_metrics.radiation = 0.13f;
telemetry.variant.environment_metrics.has_radiation = true;
// Rainfall measurements (BOTH types)
telemetry.variant.environment_metrics.rainfall_1h = 2.57f;
telemetry.variant.environment_metrics.has_rainfall_1h = true;
telemetry.variant.environment_metrics.rainfall_24h = 15.89f;
telemetry.variant.environment_metrics.has_rainfall_24h = true;
// Soil measurements (BOTH types)
telemetry.variant.environment_metrics.soil_moisture = 85;
telemetry.variant.environment_metrics.has_soil_moisture = true;
telemetry.variant.environment_metrics.soil_temperature = 18.54f;
telemetry.variant.environment_metrics.has_soil_temperature = true;
// IMPORTANT: When new environment fields are added to the protobuf,
// they MUST be added here too, or the coverage test will fail!
pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size);
pb_encode(&stream, &meshtastic_Telemetry_msg, &telemetry);
return stream.bytes_written;
}
// Helper function to create and encode environment metrics with all current fields
static size_t encode_telemetry_environment_metrics(uint8_t *buffer, size_t buffer_size)
{
meshtastic_Telemetry telemetry = meshtastic_Telemetry_init_zero;
telemetry.time = 1609459200;
telemetry.which_variant = meshtastic_Telemetry_environment_metrics_tag;
// Basic environment metrics
telemetry.variant.environment_metrics.temperature = 23.56f;
telemetry.variant.environment_metrics.has_temperature = true;
telemetry.variant.environment_metrics.relative_humidity = 65.43f;
telemetry.variant.environment_metrics.has_relative_humidity = true;
telemetry.variant.environment_metrics.barometric_pressure = 1013.27f;
telemetry.variant.environment_metrics.has_barometric_pressure = true;
// Gas and air quality
telemetry.variant.environment_metrics.gas_resistance = 50.58f;
telemetry.variant.environment_metrics.has_gas_resistance = true;
telemetry.variant.environment_metrics.iaq = 120;
telemetry.variant.environment_metrics.has_iaq = true;
// Power measurements
telemetry.variant.environment_metrics.voltage = 3.34f;
telemetry.variant.environment_metrics.has_voltage = true;
telemetry.variant.environment_metrics.current = 0.53f;
telemetry.variant.environment_metrics.has_current = true;
// Light measurements
telemetry.variant.environment_metrics.lux = 450.12f;
telemetry.variant.environment_metrics.has_lux = true;
telemetry.variant.environment_metrics.white_lux = 380.95f;
telemetry.variant.environment_metrics.has_white_lux = true;
telemetry.variant.environment_metrics.ir_lux = 25.37f;
telemetry.variant.environment_metrics.has_ir_lux = true;
telemetry.variant.environment_metrics.uv_lux = 15.68f;
telemetry.variant.environment_metrics.has_uv_lux = true;
// Distance measurement
telemetry.variant.environment_metrics.distance = 150.29f;
telemetry.variant.environment_metrics.has_distance = true;
// Wind measurements
telemetry.variant.environment_metrics.wind_direction = 180;
telemetry.variant.environment_metrics.has_wind_direction = true;
telemetry.variant.environment_metrics.wind_speed = 5.52f;
telemetry.variant.environment_metrics.has_wind_speed = true;
telemetry.variant.environment_metrics.wind_gust = 8.24f;
telemetry.variant.environment_metrics.has_wind_gust = true;
telemetry.variant.environment_metrics.wind_lull = 2.13f;
telemetry.variant.environment_metrics.has_wind_lull = true;
// Weight measurement
telemetry.variant.environment_metrics.weight = 75.56f;
telemetry.variant.environment_metrics.has_weight = true;
// Radiation measurement
telemetry.variant.environment_metrics.radiation = 0.13f;
telemetry.variant.environment_metrics.has_radiation = true;
// Rainfall measurements
telemetry.variant.environment_metrics.rainfall_1h = 2.57f;
telemetry.variant.environment_metrics.has_rainfall_1h = true;
telemetry.variant.environment_metrics.rainfall_24h = 15.89f;
telemetry.variant.environment_metrics.has_rainfall_24h = true;
// Soil measurements
telemetry.variant.environment_metrics.soil_moisture = 85;
telemetry.variant.environment_metrics.has_soil_moisture = true;
telemetry.variant.environment_metrics.soil_temperature = 18.54f;
telemetry.variant.environment_metrics.has_soil_temperature = true;
fill_all_env_metrics(telemetry);
pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size);
pb_encode(&stream, &meshtastic_Telemetry_msg, &telemetry);
return stream.bytes_written;
}
// Test TELEMETRY_APP port with device metrics
static Json::Value serialize_and_get_payload(meshtastic_PortNum port, const uint8_t *buffer, size_t payload_size)
{
meshtastic_MeshPacket packet = create_test_packet(port, buffer, payload_size);
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
Json::Value root = parse_json(json);
TEST_ASSERT_TRUE(root.isObject());
TEST_ASSERT_TRUE(root.isMember("payload"));
TEST_ASSERT_TRUE(root["payload"].isObject());
return root;
}
void test_telemetry_device_metrics_serialization()
{
uint8_t buffer[256];
size_t payload_size = encode_telemetry_device_metrics(buffer, sizeof(buffer));
meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
Json::Value root = serialize_and_get_payload(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
TEST_ASSERT_TRUE(root.isMember("type"));
TEST_ASSERT_EQUAL_STRING("telemetry", root["type"].asString().c_str());
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
const Json::Value &payload = root["payload"];
JSONObject jsonObj = root->AsObject();
TEST_ASSERT_TRUE(payload.isMember("battery_level"));
TEST_ASSERT_EQUAL(85, payload["battery_level"].asInt());
// Check message type
TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end());
TEST_ASSERT_EQUAL_STRING("telemetry", jsonObj["type"]->AsString().c_str());
TEST_ASSERT_TRUE(payload.isMember("voltage"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 3.72f, payload["voltage"].asFloat());
// Check payload
TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
TEST_ASSERT_TRUE(payload.isMember("channel_utilization"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.56f, payload["channel_utilization"].asFloat());
JSONObject payload = jsonObj["payload"]->AsObject();
// Verify telemetry data
TEST_ASSERT_TRUE(payload.find("battery_level") != payload.end());
TEST_ASSERT_EQUAL(85, (int)payload["battery_level"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("voltage") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 3.72f, payload["voltage"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("channel_utilization") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.56f, payload["channel_utilization"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("uptime_seconds") != payload.end());
TEST_ASSERT_EQUAL(12345, (int)payload["uptime_seconds"]->AsNumber());
// Note: JSON serialization may not preserve exact 2-decimal formatting due to float precision
// We verify the numeric values are correct within tolerance
delete root;
TEST_ASSERT_TRUE(payload.isMember("uptime_seconds"));
TEST_ASSERT_EQUAL(12345, payload["uptime_seconds"].asInt());
}
// Test that telemetry environment metrics are properly serialized
void test_telemetry_environment_metrics_serialization()
{
uint8_t buffer[256];
size_t payload_size = encode_telemetry_environment_metrics(buffer, sizeof(buffer));
meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
Json::Value root = serialize_and_get_payload(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
const Json::Value &payload = root["payload"];
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
TEST_ASSERT_TRUE(payload.isMember("temperature"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 23.56f, payload["temperature"].asFloat());
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
TEST_ASSERT_TRUE(payload.isMember("relative_humidity"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 65.43f, payload["relative_humidity"].asFloat());
JSONObject jsonObj = root->AsObject();
// Check payload exists
TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
JSONObject payload = jsonObj["payload"]->AsObject();
// Test key fields that should be present in the serializer
TEST_ASSERT_TRUE(payload.find("temperature") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 23.56f, payload["temperature"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("relative_humidity") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 65.43f, payload["relative_humidity"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("distance") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 150.29f, payload["distance"]->AsNumber());
// Note: JSON serialization may have float precision limitations
// We focus on verifying numeric accuracy rather than exact string formatting
delete root;
TEST_ASSERT_TRUE(payload.isMember("distance"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 150.29f, payload["distance"].asFloat());
}
// Test comprehensive environment metrics coverage
void test_telemetry_environment_metrics_comprehensive()
{
uint8_t buffer[256];
size_t payload_size = encode_telemetry_environment_metrics(buffer, sizeof(buffer));
meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
Json::Value root = serialize_and_get_payload(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
const Json::Value &payload = root["payload"];
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
JSONObject jsonObj = root->AsObject();
// Check payload exists
TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
JSONObject payload = jsonObj["payload"]->AsObject();
// Check all 15 originally supported fields
TEST_ASSERT_TRUE(payload.find("temperature") != payload.end());
TEST_ASSERT_TRUE(payload.find("relative_humidity") != payload.end());
TEST_ASSERT_TRUE(payload.find("barometric_pressure") != payload.end());
TEST_ASSERT_TRUE(payload.find("gas_resistance") != payload.end());
TEST_ASSERT_TRUE(payload.find("voltage") != payload.end());
TEST_ASSERT_TRUE(payload.find("current") != payload.end());
TEST_ASSERT_TRUE(payload.find("iaq") != payload.end());
TEST_ASSERT_TRUE(payload.find("distance") != payload.end());
TEST_ASSERT_TRUE(payload.find("lux") != payload.end());
TEST_ASSERT_TRUE(payload.find("white_lux") != payload.end());
TEST_ASSERT_TRUE(payload.find("wind_direction") != payload.end());
TEST_ASSERT_TRUE(payload.find("wind_speed") != payload.end());
TEST_ASSERT_TRUE(payload.find("wind_gust") != payload.end());
TEST_ASSERT_TRUE(payload.find("wind_lull") != payload.end());
TEST_ASSERT_TRUE(payload.find("radiation") != payload.end());
delete root;
TEST_ASSERT_TRUE(payload.isMember("temperature"));
TEST_ASSERT_TRUE(payload.isMember("relative_humidity"));
TEST_ASSERT_TRUE(payload.isMember("barometric_pressure"));
TEST_ASSERT_TRUE(payload.isMember("gas_resistance"));
TEST_ASSERT_TRUE(payload.isMember("voltage"));
TEST_ASSERT_TRUE(payload.isMember("current"));
TEST_ASSERT_TRUE(payload.isMember("iaq"));
TEST_ASSERT_TRUE(payload.isMember("distance"));
TEST_ASSERT_TRUE(payload.isMember("lux"));
TEST_ASSERT_TRUE(payload.isMember("white_lux"));
TEST_ASSERT_TRUE(payload.isMember("wind_direction"));
TEST_ASSERT_TRUE(payload.isMember("wind_speed"));
TEST_ASSERT_TRUE(payload.isMember("wind_gust"));
TEST_ASSERT_TRUE(payload.isMember("wind_lull"));
TEST_ASSERT_TRUE(payload.isMember("radiation"));
}
// Test for the 7 environment fields that were added to complete coverage
void test_telemetry_environment_metrics_missing_fields()
{
uint8_t buffer[256];
size_t payload_size = encode_telemetry_environment_metrics(buffer, sizeof(buffer));
meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
Json::Value root = serialize_and_get_payload(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
const Json::Value &payload = root["payload"];
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
TEST_ASSERT_TRUE(payload.isMember("ir_lux"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 25.37f, payload["ir_lux"].asFloat());
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
TEST_ASSERT_TRUE(payload.isMember("uv_lux"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.68f, payload["uv_lux"].asFloat());
JSONObject jsonObj = root->AsObject();
TEST_ASSERT_TRUE(payload.isMember("weight"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 75.56f, payload["weight"].asFloat());
// Check payload exists
TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
TEST_ASSERT_TRUE(payload.isMember("rainfall_1h"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 2.57f, payload["rainfall_1h"].asFloat());
JSONObject payload = jsonObj["payload"]->AsObject();
TEST_ASSERT_TRUE(payload.isMember("rainfall_24h"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.89f, payload["rainfall_24h"].asFloat());
// Check the 7 fields that were previously missing
TEST_ASSERT_TRUE(payload.find("ir_lux") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 25.37f, payload["ir_lux"]->AsNumber());
TEST_ASSERT_TRUE(payload.isMember("soil_moisture"));
TEST_ASSERT_EQUAL(85, payload["soil_moisture"].asInt());
TEST_ASSERT_TRUE(payload.find("uv_lux") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.68f, payload["uv_lux"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("weight") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 75.56f, payload["weight"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("rainfall_1h") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 2.57f, payload["rainfall_1h"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("rainfall_24h") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.89f, payload["rainfall_24h"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("soil_moisture") != payload.end());
TEST_ASSERT_EQUAL(85, (int)payload["soil_moisture"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("soil_temperature") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 18.54f, payload["soil_temperature"]->AsNumber());
// Note: JSON float serialization may not preserve exact decimal formatting
// We verify the values are numerically correct within tolerance
delete root;
TEST_ASSERT_TRUE(payload.isMember("soil_temperature"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 18.54f, payload["soil_temperature"].asFloat());
}
// Test that ALL environment fields are serialized (canary test for forgotten fields)
// This test will FAIL if a new environment field is added to the protobuf but not to the serializer
// Canary test: if a new env field is added to the protobuf but not to the serializer
// (or to fill_all_env_metrics), this test will fail.
void test_telemetry_environment_metrics_complete_coverage()
{
uint8_t buffer[256];
size_t payload_size = encode_telemetry_environment_metrics_all_fields(buffer, sizeof(buffer));
size_t payload_size = encode_telemetry_environment_metrics(buffer, sizeof(buffer));
meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
Json::Value root = serialize_and_get_payload(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
const Json::Value &payload = root["payload"];
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
TEST_ASSERT_TRUE(payload.isMember("temperature"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 23.56f, payload["temperature"].asFloat());
TEST_ASSERT_TRUE(payload.isMember("relative_humidity"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 65.43f, payload["relative_humidity"].asFloat());
TEST_ASSERT_TRUE(payload.isMember("barometric_pressure"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 1013.27f, payload["barometric_pressure"].asFloat());
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
TEST_ASSERT_TRUE(payload.isMember("gas_resistance"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 50.58f, payload["gas_resistance"].asFloat());
TEST_ASSERT_TRUE(payload.isMember("iaq"));
TEST_ASSERT_EQUAL(120, payload["iaq"].asInt());
JSONObject jsonObj = root->AsObject();
TEST_ASSERT_TRUE(payload.isMember("voltage"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 3.34f, payload["voltage"].asFloat());
TEST_ASSERT_TRUE(payload.isMember("current"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 0.53f, payload["current"].asFloat());
// Check payload exists
TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
TEST_ASSERT_TRUE(payload.isMember("lux"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 450.12f, payload["lux"].asFloat());
TEST_ASSERT_TRUE(payload.isMember("white_lux"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 380.95f, payload["white_lux"].asFloat());
TEST_ASSERT_TRUE(payload.isMember("ir_lux"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 25.37f, payload["ir_lux"].asFloat());
TEST_ASSERT_TRUE(payload.isMember("uv_lux"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.68f, payload["uv_lux"].asFloat());
JSONObject payload = jsonObj["payload"]->AsObject();
TEST_ASSERT_TRUE(payload.isMember("distance"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 150.29f, payload["distance"].asFloat());
// ✅ ALL 22 environment fields MUST be present and correct
// If this test fails, it means either:
// 1. A new field was added to the protobuf but not to the serializer
// 2. The encode_telemetry_environment_metrics_all_fields() function wasn't updated
TEST_ASSERT_TRUE(payload.isMember("wind_direction"));
TEST_ASSERT_EQUAL(180, payload["wind_direction"].asInt());
TEST_ASSERT_TRUE(payload.isMember("wind_speed"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 5.52f, payload["wind_speed"].asFloat());
TEST_ASSERT_TRUE(payload.isMember("wind_gust"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 8.24f, payload["wind_gust"].asFloat());
TEST_ASSERT_TRUE(payload.isMember("wind_lull"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 2.13f, payload["wind_lull"].asFloat());
// Basic environment (3 fields)
TEST_ASSERT_TRUE(payload.find("temperature") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 23.56f, payload["temperature"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("relative_humidity") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 65.43f, payload["relative_humidity"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("barometric_pressure") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 1013.27f, payload["barometric_pressure"]->AsNumber());
TEST_ASSERT_TRUE(payload.isMember("weight"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 75.56f, payload["weight"].asFloat());
// Gas and air quality (2 fields)
TEST_ASSERT_TRUE(payload.find("gas_resistance") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 50.58f, payload["gas_resistance"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("iaq") != payload.end());
TEST_ASSERT_EQUAL(120, (int)payload["iaq"]->AsNumber());
TEST_ASSERT_TRUE(payload.isMember("radiation"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 0.13f, payload["radiation"].asFloat());
// Power measurements (2 fields)
TEST_ASSERT_TRUE(payload.find("voltage") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 3.34f, payload["voltage"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("current") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 0.53f, payload["current"]->AsNumber());
TEST_ASSERT_TRUE(payload.isMember("rainfall_1h"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 2.57f, payload["rainfall_1h"].asFloat());
TEST_ASSERT_TRUE(payload.isMember("rainfall_24h"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.89f, payload["rainfall_24h"].asFloat());
// Light measurements (4 fields)
TEST_ASSERT_TRUE(payload.find("lux") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 450.12f, payload["lux"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("white_lux") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 380.95f, payload["white_lux"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("ir_lux") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 25.37f, payload["ir_lux"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("uv_lux") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.68f, payload["uv_lux"]->AsNumber());
// Distance measurement (1 field)
TEST_ASSERT_TRUE(payload.find("distance") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 150.29f, payload["distance"]->AsNumber());
// Wind measurements (4 fields)
TEST_ASSERT_TRUE(payload.find("wind_direction") != payload.end());
TEST_ASSERT_EQUAL(180, (int)payload["wind_direction"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("wind_speed") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 5.52f, payload["wind_speed"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("wind_gust") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 8.24f, payload["wind_gust"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("wind_lull") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 2.13f, payload["wind_lull"]->AsNumber());
// Weight measurement (1 field)
TEST_ASSERT_TRUE(payload.find("weight") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 75.56f, payload["weight"]->AsNumber());
// Radiation measurement (1 field)
TEST_ASSERT_TRUE(payload.find("radiation") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 0.13f, payload["radiation"]->AsNumber());
// Rainfall measurements (2 fields)
TEST_ASSERT_TRUE(payload.find("rainfall_1h") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 2.57f, payload["rainfall_1h"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("rainfall_24h") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.89f, payload["rainfall_24h"]->AsNumber());
// Soil measurements (2 fields)
TEST_ASSERT_TRUE(payload.find("soil_moisture") != payload.end());
TEST_ASSERT_EQUAL(85, (int)payload["soil_moisture"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("soil_temperature") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 18.54f, payload["soil_temperature"]->AsNumber());
// Total: 22 environment fields
// This test ensures 100% coverage of environment metrics
// Note: JSON float serialization precision may vary due to the underlying library
// The important aspect is that all values are numerically accurate within tolerance
delete root;
TEST_ASSERT_TRUE(payload.isMember("soil_moisture"));
TEST_ASSERT_EQUAL(85, payload["soil_moisture"].asInt());
TEST_ASSERT_TRUE(payload.isMember("soil_temperature"));
TEST_ASSERT_FLOAT_WITHIN(0.01f, 18.54f, payload["soil_temperature"].asFloat());
}
// Test that unset environment fields are not present in JSON
void test_telemetry_environment_metrics_unset_fields()
{
uint8_t buffer[256];
size_t payload_size = encode_telemetry_environment_metrics_empty(buffer, sizeof(buffer));
meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
Json::Value root = serialize_and_get_payload(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
const Json::Value &payload = root["payload"];
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
JSONObject jsonObj = root->AsObject();
// Check payload exists
TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
JSONObject payload = jsonObj["payload"]->AsObject();
// With completely empty environment metrics, NO fields should be present
// Only basic telemetry fields like "time" might be present
// All 22 environment fields should be absent (none were set)
TEST_ASSERT_TRUE(payload.find("temperature") == payload.end());
TEST_ASSERT_TRUE(payload.find("relative_humidity") == payload.end());
TEST_ASSERT_TRUE(payload.find("barometric_pressure") == payload.end());
TEST_ASSERT_TRUE(payload.find("gas_resistance") == payload.end());
TEST_ASSERT_TRUE(payload.find("iaq") == payload.end());
TEST_ASSERT_TRUE(payload.find("voltage") == payload.end());
TEST_ASSERT_TRUE(payload.find("current") == payload.end());
TEST_ASSERT_TRUE(payload.find("lux") == payload.end());
TEST_ASSERT_TRUE(payload.find("white_lux") == payload.end());
TEST_ASSERT_TRUE(payload.find("ir_lux") == payload.end());
TEST_ASSERT_TRUE(payload.find("uv_lux") == payload.end());
TEST_ASSERT_TRUE(payload.find("distance") == payload.end());
TEST_ASSERT_TRUE(payload.find("wind_direction") == payload.end());
TEST_ASSERT_TRUE(payload.find("wind_speed") == payload.end());
TEST_ASSERT_TRUE(payload.find("wind_gust") == payload.end());
TEST_ASSERT_TRUE(payload.find("wind_lull") == payload.end());
TEST_ASSERT_TRUE(payload.find("weight") == payload.end());
TEST_ASSERT_TRUE(payload.find("radiation") == payload.end());
TEST_ASSERT_TRUE(payload.find("rainfall_1h") == payload.end());
TEST_ASSERT_TRUE(payload.find("rainfall_24h") == payload.end());
TEST_ASSERT_TRUE(payload.find("soil_moisture") == payload.end());
TEST_ASSERT_TRUE(payload.find("soil_temperature") == payload.end());
delete root;
TEST_ASSERT_FALSE(payload.isMember("temperature"));
TEST_ASSERT_FALSE(payload.isMember("relative_humidity"));
TEST_ASSERT_FALSE(payload.isMember("barometric_pressure"));
TEST_ASSERT_FALSE(payload.isMember("gas_resistance"));
TEST_ASSERT_FALSE(payload.isMember("iaq"));
TEST_ASSERT_FALSE(payload.isMember("voltage"));
TEST_ASSERT_FALSE(payload.isMember("current"));
TEST_ASSERT_FALSE(payload.isMember("lux"));
TEST_ASSERT_FALSE(payload.isMember("white_lux"));
TEST_ASSERT_FALSE(payload.isMember("ir_lux"));
TEST_ASSERT_FALSE(payload.isMember("uv_lux"));
TEST_ASSERT_FALSE(payload.isMember("distance"));
TEST_ASSERT_FALSE(payload.isMember("wind_direction"));
TEST_ASSERT_FALSE(payload.isMember("wind_speed"));
TEST_ASSERT_FALSE(payload.isMember("wind_gust"));
TEST_ASSERT_FALSE(payload.isMember("wind_lull"));
TEST_ASSERT_FALSE(payload.isMember("weight"));
TEST_ASSERT_FALSE(payload.isMember("radiation"));
TEST_ASSERT_FALSE(payload.isMember("rainfall_1h"));
TEST_ASSERT_FALSE(payload.isMember("rainfall_24h"));
TEST_ASSERT_FALSE(payload.isMember("soil_moisture"));
TEST_ASSERT_FALSE(payload.isMember("soil_temperature"));
}
@@ -1,48 +1,30 @@
#include "../test_helpers.h"
#include <memory>
// Helper function to test common packet fields and structure
void verify_text_message_packet_structure(const std::string &json, const char *expected_text)
static void verify_text_message_packet_structure(const std::string &json, const char *expected_text)
{
TEST_ASSERT_TRUE(json.length() > 0);
// Use smart pointer for automatic memory management
std::unique_ptr<JSONValue> root(JSON::Parse(json.c_str()));
TEST_ASSERT_NOT_NULL(root.get());
TEST_ASSERT_TRUE(root->IsObject());
Json::Value root = parse_json(json);
TEST_ASSERT_TRUE(root.isObject());
JSONObject jsonObj = root->AsObject();
TEST_ASSERT_TRUE(root.isMember("from"));
TEST_ASSERT_EQUAL(0x11223344u, root["from"].asUInt());
TEST_ASSERT_TRUE(root.isMember("to"));
TEST_ASSERT_EQUAL(0x55667788u, root["to"].asUInt());
TEST_ASSERT_TRUE(root.isMember("id"));
TEST_ASSERT_EQUAL(0x9999u, root["id"].asUInt());
// Check basic packet fields - use helper function to reduce duplication
auto check_field = [&](const char *field, uint32_t expected_value) {
auto it = jsonObj.find(field);
TEST_ASSERT_TRUE(it != jsonObj.end());
TEST_ASSERT_EQUAL(expected_value, (uint32_t)it->second->AsNumber());
};
TEST_ASSERT_TRUE(root.isMember("type"));
TEST_ASSERT_EQUAL_STRING("text", root["type"].asString().c_str());
check_field("from", 0x11223344);
check_field("to", 0x55667788);
check_field("id", 0x9999);
TEST_ASSERT_TRUE(root.isMember("payload"));
TEST_ASSERT_TRUE(root["payload"].isObject());
// Check message type
auto type_it = jsonObj.find("type");
TEST_ASSERT_TRUE(type_it != jsonObj.end());
TEST_ASSERT_EQUAL_STRING("text", type_it->second->AsString().c_str());
// Check payload
auto payload_it = jsonObj.find("payload");
TEST_ASSERT_TRUE(payload_it != jsonObj.end());
TEST_ASSERT_TRUE(payload_it->second->IsObject());
JSONObject payload = payload_it->second->AsObject();
auto text_it = payload.find("text");
TEST_ASSERT_TRUE(text_it != payload.end());
TEST_ASSERT_EQUAL_STRING(expected_text, text_it->second->AsString().c_str());
// No need for manual delete with smart pointer
const Json::Value &payload = root["payload"];
TEST_ASSERT_TRUE(payload.isMember("text"));
TEST_ASSERT_EQUAL_STRING(expected_text, payload["text"].asString().c_str());
}
// Test TEXT_MESSAGE_APP port
void test_text_message_serialization()
{
const char *test_text = "Hello Meshtastic!";
@@ -53,7 +35,6 @@ void test_text_message_serialization()
verify_text_message_packet_structure(json, test_text);
}
// Test with nullptr to check robustness
void test_text_message_serialization_null()
{
meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, nullptr, 0);
@@ -62,11 +43,9 @@ void test_text_message_serialization_null()
verify_text_message_packet_structure(json, "");
}
// Test TEXT_MESSAGE_APP port with very long message (boundary testing)
void test_text_message_serialization_long_text()
{
// Test with actual message size limits
constexpr size_t MAX_MESSAGE_SIZE = 200; // Typical LoRa payload limit
constexpr size_t MAX_MESSAGE_SIZE = 200;
std::string long_text(MAX_MESSAGE_SIZE, 'A');
meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP,
@@ -76,30 +55,25 @@ void test_text_message_serialization_long_text()
verify_text_message_packet_structure(json, long_text.c_str());
}
// Test with message over size limit (should fail)
void test_text_message_serialization_oversized()
{
constexpr size_t OVERSIZED_MESSAGE = 250; // Over the limit
constexpr size_t OVERSIZED_MESSAGE = 250;
std::string oversized_text(OVERSIZED_MESSAGE, 'B');
meshtastic_MeshPacket packet = create_test_packet(
meshtastic_PortNum_TEXT_MESSAGE_APP, reinterpret_cast<const uint8_t *>(oversized_text.c_str()), oversized_text.length());
// Should fail or return empty/error
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
// Should only verify first 234 characters for oversized messages
std::string expected_text = oversized_text.substr(0, 234);
verify_text_message_packet_structure(json, expected_text.c_str());
}
// Add test for malformed UTF-8 sequences
void test_text_message_serialization_invalid_utf8()
{
const uint8_t invalid_utf8[] = {0xFF, 0xFE, 0xFD, 0x00}; // Invalid UTF-8
const uint8_t invalid_utf8[] = {0xFF, 0xFE, 0xFD, 0x00};
meshtastic_MeshPacket packet =
create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, invalid_utf8, sizeof(invalid_utf8) - 1);
// Should not crash, may produce replacement characters
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
}
}
@@ -6,7 +6,7 @@ static size_t encode_waypoint(uint8_t *buffer, size_t buffer_size)
waypoint.id = 12345;
waypoint.latitude_i = 374208000;
waypoint.longitude_i = -1221981000;
waypoint.expire = 1609459200 + 3600; // 1 hour from now
waypoint.expire = 1609459200 + 3600;
strcpy(waypoint.name, "Test Point");
strcpy(waypoint.description, "Test waypoint description");
@@ -15,7 +15,6 @@ static size_t encode_waypoint(uint8_t *buffer, size_t buffer_size)
return stream.bytes_written;
}
// Test WAYPOINT_APP port
void test_waypoint_serialization()
{
uint8_t buffer[256];
@@ -26,28 +25,20 @@ void test_waypoint_serialization()
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
Json::Value root = parse_json(json);
TEST_ASSERT_TRUE(root.isObject());
JSONObject jsonObj = root->AsObject();
TEST_ASSERT_TRUE(root.isMember("type"));
TEST_ASSERT_EQUAL_STRING("waypoint", root["type"].asString().c_str());
// Check message type
TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end());
TEST_ASSERT_EQUAL_STRING("waypoint", jsonObj["type"]->AsString().c_str());
TEST_ASSERT_TRUE(root.isMember("payload"));
TEST_ASSERT_TRUE(root["payload"].isObject());
// Check payload
TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
const Json::Value &payload = root["payload"];
JSONObject payload = jsonObj["payload"]->AsObject();
TEST_ASSERT_TRUE(payload.isMember("id"));
TEST_ASSERT_EQUAL(12345, payload["id"].asInt());
// Verify waypoint data
TEST_ASSERT_TRUE(payload.find("id") != payload.end());
TEST_ASSERT_EQUAL(12345, (int)payload["id"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("name") != payload.end());
TEST_ASSERT_EQUAL_STRING("Test Point", payload["name"]->AsString().c_str());
delete root;
TEST_ASSERT_TRUE(payload.isMember("name"));
TEST_ASSERT_EQUAL_STRING("Test Point", payload["name"].asString().c_str());
}
+16 -2
View File
@@ -1,8 +1,9 @@
#pragma once
#include "serialization/JSON.h"
#include "serialization/MeshPacketSerializer.h"
#include <Arduino.h>
#include <json/json.h>
#include <memory>
#include <meshtastic/mesh.pb.h>
#include <meshtastic/mqtt.pb.h>
#include <meshtastic/telemetry.pb.h>
@@ -10,6 +11,18 @@
#include <pb_encode.h>
#include <unity.h>
// Parse a JSON string into a Json::Value; returns Json::nullValue on failure.
static inline Json::Value parse_json(const std::string &s)
{
Json::CharReaderBuilder b;
Json::Value root;
std::string errs;
std::unique_ptr<Json::CharReader> reader(b.newCharReader());
if (!reader->parse(s.c_str(), s.c_str() + s.size(), &root, &errs))
return Json::Value();
return root;
}
// Helper function to create a test packet with the given port and payload
static meshtastic_MeshPacket create_test_packet(meshtastic_PortNum port, const uint8_t *payload, size_t payload_size,
int payload_variant = meshtastic_MeshPacket_decoded_tag)
@@ -36,7 +49,8 @@ static meshtastic_MeshPacket create_test_packet(meshtastic_PortNum port, const u
packet.encrypted.size = payload_size;
memcpy(packet.encrypted.bytes, payload, packet.encrypted.size);
}
memcpy(packet.decoded.payload.bytes, payload, payload_size);
if (payload && payload_size)
memcpy(packet.decoded.payload.bytes, payload, payload_size);
packet.decoded.payload.size = payload_size;
packet.decoded.want_response = false;
packet.decoded.dest = 0x55667788;