-
Notifications
You must be signed in to change notification settings - Fork 78
fnjson.cpp Value Mangling Analysis
The JSON parser in FujiNet firmware is mangling numeric values during the conversion from JSON to strings. The issue occurs inside fnJson in the getValue() function, which converts all numbers through floating-point types and loses precision.
fnjson.cpp:205-228
When handling JSON numbers, the code follows this process:
-
Line 207: cJSON extracts the number as a
doubledouble num = cJSON_GetNumberValue(item); -
Line 208: Checks if the number is "approximately an integer"
bool isInt = isApproximatelyInteger(num); -
Lines 210-216: If it looks like an integer, casts to
int64_tand outputsif (isInt) { ss << (int64_t)num; }
-
Lines 218-225: Otherwise, outputs as double with only 10 digits of precision
else { ss << std::setprecision(10) << num; }
Located in utils.cpp:1064-1066:
bool isApproximatelyInteger(double value, double tolerance) {
return std::abs(value - std::floor(value)) < tolerance;
}This function checks if a number is within 1e-6 (0.000001) of an integer value.
- Large integers like
1234567890123in JSON are stored asdoubleby cJSON - When cast to
int64_t, precision can be lost if the value exceeds what can be exactly represented as a double - IEEE 754 double-precision can only represent integers exactly up to 2^53 (about 9 quadrillion)
- Values larger than this lose precision in the
double→int64_t→stringconversion
- Limited to 10 digits of precision via
std::setprecision(10) - Original string representation is lost
- Values like
3.141592653589793become3.141592654(rounded to 10 digits) - Scientific notation and trailing zeros are not preserved
| Original JSON Value | What cJSON Stores | What Gets Output | Problem |
|---|---|---|---|
1234567890123456789 |
1.234567890123457e+18 (double) |
1234567890123456768 |
Lost precision in large integer |
3.141592653589793 |
3.141592653589793 (double) |
3.141592654 |
Truncated to 10 digits |
1.0 |
1.0 (double) |
1 |
Converted to integer, loses .0
|
1e10 |
10000000000.0 (double) |
10000000000 |
Scientific notation lost |
The mangling happens INSIDE fnJson, specifically in the FNJSON::getValue() method. The data is already mangled before it's sent back over the wire to the client.
The issue is NOT in:
- The network protocol layer
- The client-side parsing
- The wire transmission
Caution
There is a critical bug in fnjson.cpp:278-286 that could cause random/incorrect values!
bool FNJSON::readValue(uint8_t *rx_buf, unsigned short len)
{
if (_item == nullptr)
return true; // error
memcpy(rx_buf, getValue(_item).data(), len); // ⚠️ DANGEROUS!
return false; // no error.
}The code creates a temporary std::string from getValue(_item), calls .data() on it to get the pointer, then immediately destroys the string after the memcpy. This is undefined behavior because:
-
getValue(_item)returns a temporary string -
.data()returns a pointer to the temporary's internal buffer - The temporary is destroyed at the end of the expression
-
memcpymay be reading from freed memory - The buffer might contain garbage, old data, or corrupted values
When copying from freed memory:
- You might get leftover data from previous calls
- Buffer contents could be partially overwritten
- Values from different JSON fields might bleed through
- Garbage memory could be interpreted as numbers
This explains seeing 56 instead of 57 or 955.9 instead of 974.0 - you're likely getting a mix of old values, garbage, or data from other JSON fields!
bool FNJSON::readValue(uint8_t *rx_buf, unsigned short len)
{
if (_item == nullptr)
return true; // error
std::string value = getValue(_item); // Store in variable first!
memcpy(rx_buf, value.data(), std::min(len, (unsigned short)value.size()));
return false; // no error.
}Modify the code to preserve the original string representation from the JSON input instead of converting through numeric types. This would require:
- Capturing the original string during parsing
- Storing it alongside the cJSON structure
- Returning the original string instead of the converted value
Use cJSON_Print() or cJSON_PrintUnformatted() to get the value as cJSON would serialize it:
char* value_str = cJSON_Print(item);
ss << value_str;
free(value_str);Increase the precision from 10 to a higher value (e.g., 17 for doubles):
ss << std::setprecision(17) << num;This helps but doesn't solve the fundamental issue of large integer precision loss.
Switch to a JSON parser that preserves original string representations, such as:
- RapidJSON
- nlohmann/json
- simdjson
-
Short-term fix: Use
cJSON_Print()to serialize individual values, preserving their original representation - Long-term solution: Consider switching to a JSON parser that preserves numeric precision and original formatting
-
Testing: Add test cases for:
- Large integers (> 2^53)
- High-precision floating-point numbers
- Scientific notation
- Trailing zeros in decimals
- Main issue: fnjson.cpp:166-273 (
FNJSON::getValue()) - Helper function: utils.cpp:1064-1066 (
isApproximatelyInteger()) - Header: fnjson.h
Copyright 2024 Contributors to the FujiNetWIFI project.
Join us on Discord: https://discord.gg/7MfFTvD
- Home
- What is FujiNet?
- The Definition of Done
- Board bring up for FujiNet Platform.IO code
- The Complete Linux CLI Guide
- The Complete macOS CLI Guide
- Development Env for Apps
- FujiNet-Development-Guidelines
- System Quickstarts
- FujiNet Flasher
- Setting up a TNFS Server
- FujiNet Configuration File: fnconfig.ini
- AppKey Registry - SIO Command $DC Open App Key
- CP-M Support
- BBS
- Official Hardware Versions
- Prototype Board Revisions
- FujiNet Development Guidelines
- Atari Programming
- Apple Programming
- C64 Programming
- ADAM Programming
- Testing Plan
- Hacker List
- FujiNet VirtualMachine