Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions runtimes/c/include/ads_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ uint32_t ads_bitslice(uint32_t byte, uint8_t bit_start, uint8_t bit_end);
uint32_t ads_concat_bits(const uint32_t *values, size_t count);

/* ─── Formatter helpers (push items onto result.formatted.items) ──────── */
/* Every ads_fmt_* takes ownership of the ads_value_t pointers passed in and
* frees them (matches ads_result_raw_set's transfer semantics). Pass NULL
* to no-op (matches the TS pattern of guarding before calling). */
Comment on lines +63 to +65

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Documentation overstates ownership scope.

The comment says "Every ads_fmt_*" takes ownership of ads_value_t*, but several ads_fmt_* functions declared below don't accept ads_value_t* at all (e.g., ads_fmt_state_change, ads_fmt_door_event, ads_fmt_text, ads_fmt_unknown*, ads_fmt_checksum_algorithm, ads_fmt_push_item). Consider clarifying:

📝 Suggested wording
-/* Every ads_fmt_* takes ownership of the ads_value_t pointers passed in and
+/* Each ads_fmt_* that accepts an ads_value_t* takes ownership of it and
  * frees them (matches ads_result_raw_set's transfer semantics). Pass NULL
  * to no-op (matches the TS pattern of guarding before calling). */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/* Every ads_fmt_* takes ownership of the ads_value_t pointers passed in and
* frees them (matches ads_result_raw_set's transfer semantics). Pass NULL
* to no-op (matches the TS pattern of guarding before calling). */
/* Each ads_fmt_* that accepts an ads_value_t* takes ownership of it and
* frees them (matches ads_result_raw_set's transfer semantics). Pass NULL
* to no-op (matches the TS pattern of guarding before calling). */
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@runtimes/c/include/ads_helpers.h` around lines 63 - 65, The header comment
incorrectly states "Every ads_fmt_* takes ownership of the ads_value_t
pointers", which overstates ownership because several functions in this file
(e.g., ads_fmt_state_change, ads_fmt_door_event, ads_fmt_text, ads_fmt_unknown*,
ads_fmt_checksum_algorithm, ads_fmt_push_item) do not accept ads_value_t* and
thus do not take ownership; update the comment in
runtimes/c/include/ads_helpers.h to explicitly state that only the ads_fmt_*
variants that accept ads_value_t* parameters transfer ownership and free them
(and that callers should pass NULL to no-op), and optionally list or reference
the specific functions that do not take ads_value_t* to avoid confusion.


void ads_fmt_position(ads_decode_result_t *r, ads_value_t *lat, ads_value_t *lon);
void ads_fmt_position_value(ads_decode_result_t *r, ads_value_t *position);
Expand All @@ -75,6 +78,69 @@ void ads_fmt_arrival_airport(ads_decode_result_t *r, ads_value_t *v);
void ads_fmt_fuel(ads_decode_result_t *r, ads_value_t *v);
void ads_fmt_unknown_arr(ads_decode_result_t *r, const char *const *values, size_t count);

/* Time-of-day formatters (value in seconds since midnight). */
void ads_fmt_eta(ads_decode_result_t *r, ads_value_t *v);
void ads_fmt_off(ads_decode_result_t *r, ads_value_t *v);
void ads_fmt_on(ads_decode_result_t *r, ads_value_t *v);
void ads_fmt_in(ads_decode_result_t *r, ads_value_t *v);
void ads_fmt_out(ads_decode_result_t *r, ads_value_t *v);

/* Calendar formatters. */
void ads_fmt_day(ads_decode_result_t *r, ads_value_t *v);
void ads_fmt_departure_day(ads_decode_result_t *r, ads_value_t *v);
void ads_fmt_arrival_day(ads_decode_result_t *r, ads_value_t *v);
void ads_fmt_month(ads_decode_result_t *r, ads_value_t *v);

/* Velocity / atmosphere formatters. */
void ads_fmt_mach(ads_decode_result_t *r, ads_value_t *v);
void ads_fmt_groundspeed(ads_decode_result_t *r, ads_value_t *v);
void ads_fmt_airspeed(ads_decode_result_t *r, ads_value_t *v);
void ads_fmt_temperature(ads_decode_result_t *r, ads_value_t *v);
void ads_fmt_total_air_temp(ads_decode_result_t *r, ads_value_t *v);

/* Fuel formatters. */
void ads_fmt_current_fuel(ads_decode_result_t *r, ads_value_t *v);
void ads_fmt_remaining_fuel(ads_decode_result_t *r, ads_value_t *v);

/* Routing formatters. */
void ads_fmt_alternate_airport(ads_decode_result_t *r, ads_value_t *v);
void ads_fmt_arrival_runway(ads_decode_result_t *r, ads_value_t *v);
void ads_fmt_alternate_runway(ads_decode_result_t *r, ads_value_t *v);

/* Event formatters. */
void ads_fmt_state_change(ads_decode_result_t *r, const char *from, const char *to);
void ads_fmt_door_event(ads_decode_result_t *r, const char *door, const char *state);

/* Free-text formatters. */
void ads_fmt_text(ads_decode_result_t *r, const char *value);
void ads_fmt_unknown(ads_decode_result_t *r, const char *value);
void ads_fmt_unknown_sep(ads_decode_result_t *r, const char *value, const char *sep);
void ads_fmt_unknown_arr_sep(ads_decode_result_t *r, const char *const *values, size_t count, const char *sep);

/* Diagnostic formatters. */
void ads_fmt_checksum(ads_decode_result_t *r, ads_value_t *v);
void ads_fmt_checksum_algorithm(ads_decode_result_t *r, const char *value);

/* Generic structured-item push for plugins that emit custom item shapes
* (OHMA, WRN, SQ, ATIS, etc.). Stores no raw field; just pushes the item. */
void ads_fmt_push_item(ads_decode_result_t *r, const char *type, const char *code,
const char *label, const char *value);

/* Time-of-day helper: seconds since midnight → "HH:MM:SS" (for < 86400);
* larger values stringify as-is. Caller frees the returned malloc'd string. */
char *ads_fmt_time_of_day_str(int64_t seconds);

/* ─── Result mutators ────────────────────────────────────────────────────── */
/* For escape hatches that need to override fields the ads_result_new() call
* already set up (e.g. variant-dependent description, custom remaining text). */

void ads_result_set_description(ads_decode_result_t *r, const char *description);
void ads_result_set_remaining(ads_decode_result_t *r, const char *text);
void ads_result_append_remaining(ads_decode_result_t *r, const char *text, const char *sep);
const char *ads_result_get_remaining(const ads_decode_result_t *r);
void ads_result_set_decode_level(ads_decode_result_t *r, ads_decode_level_t level);
void ads_result_clear_items(ads_decode_result_t *r);

#ifdef __cplusplus
}
#endif
Expand Down
210 changes: 204 additions & 6 deletions runtimes/c/src/result_formatter.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,23 +93,221 @@ void ads_fmt_departure_airport(ads_decode_result_t *r, ads_value_t *v) { push_st
void ads_fmt_arrival_airport(ads_decode_result_t *r, ads_value_t *v) { push_string(r, v, "arrival_icao", "airport_destination", "ARR", "Destination"); }

void ads_fmt_unknown_arr(ads_decode_result_t *r, const char *const *values, size_t count) {
ads_fmt_unknown_arr_sep(r, values, count, ",");
}

void ads_fmt_unknown_arr_sep(ads_decode_result_t *r, const char *const *values, size_t count, const char *sep) {
if (!r || !values || count == 0) return;
if (!sep) sep = ",";
size_t sep_len = strlen(sep);
size_t total = 1;
for (size_t i = 0; i < count; i++) total += strlen(values[i] ? values[i] : "") + 1;
for (size_t i = 0; i < count; i++) total += strlen(values[i] ? values[i] : "") + sep_len;
char *joined = malloc(total);
if (!joined) return;
joined[0] = '\0';
for (size_t i = 0; i < count; i++) {
if (i > 0) strcat(joined, ",");
if (i > 0) strcat(joined, sep);
strcat(joined, values[i] ? values[i] : "");
}
ads_result_append_remaining(r, joined, sep);
free(joined);
}

void ads_fmt_unknown(ads_decode_result_t *r, const char *value) {
ads_result_append_remaining(r, value, ",");
}

void ads_fmt_unknown_sep(ads_decode_result_t *r, const char *value, const char *sep) {
ads_result_append_remaining(r, value, sep);
}

/* ─── Time-of-day helper ─────────────────────────────────────────────────── */

char *ads_fmt_time_of_day_str(int64_t seconds) {
char *out = malloc(16);
if (!out) return NULL;
if (seconds >= 0 && seconds < 86400) {
int h = (int)(seconds / 3600);
int m = (int)((seconds % 3600) / 60);
int s = (int)(seconds % 60);
snprintf(out, 16, "%02d:%02d:%02d", h, m, s);
} else {
snprintf(out, 16, "%lld", (long long)seconds);
}
return out;
}
Comment on lines +126 to +138

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Buffer too small for out-of-range integer values.

When seconds is outside the valid 0–86399 range, snprintf outputs the raw integer. A 64-bit signed integer can require up to 20 characters plus sign and null terminator (e.g., -9223372036854775808). The 16-byte buffer will truncate these values silently.

🔧 Suggested fix
 char *ads_fmt_time_of_day_str(int64_t seconds) {
-    char *out = malloc(16);
+    char *out = malloc(24);  /* Room for longest int64 string + null */
     if (!out) return NULL;
     if (seconds >= 0 && seconds < 86400) {
         int h = (int)(seconds / 3600);
         int m = (int)((seconds % 3600) / 60);
         int s = (int)(seconds % 60);
-        snprintf(out, 16, "%02d:%02d:%02d", h, m, s);
+        snprintf(out, 24, "%02d:%02d:%02d", h, m, s);
     } else {
-        snprintf(out, 16, "%lld", (long long)seconds);
+        snprintf(out, 24, "%lld", (long long)seconds);
     }
     return out;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@runtimes/c/src/result_formatter.c` around lines 126 - 138, The function
ads_fmt_time_of_day_str uses a fixed 16-byte buffer which can overflow/truncate
when printing out-of-range int64_t values; change it to allocate a sufficiently
large buffer only when printing the raw integer: compute the required length for
snprintf of the int64_t (or use snprintf(NULL,0, "%lld", (long long)seconds) +
1), malloc that size, check the allocation, then call snprintf into that buffer;
keep the existing 16-byte formatted HH:MM:SS branch unchanged and ensure all
allocation failures return NULL.


/* ─── Time-of-day formatters (value in seconds since midnight) ───────────── */

static void push_tod(ads_decode_result_t *r, ads_value_t *v, const char *raw_key,
const char *kind, const char *code, const char *label) {
if (!v) return;
int64_t n = 0;
ads_value_as_int(v, &n);
cJSON_AddNumberToObject(r->raw, raw_key, (double)n);
char *display = ads_fmt_time_of_day_str(n);
push_item(r, kind, code, label, display ? display : "");
free(display);
ads_value_free(v);
}

void ads_fmt_eta(ads_decode_result_t *r, ads_value_t *v) {
push_tod(r, v, "eta_time", "time", "ETA", "Estimated Time of Arrival");
}
void ads_fmt_off(ads_decode_result_t *r, ads_value_t *v) {
push_tod(r, v, "off_time", "time", "OFF", "Takeoff Time");
}
void ads_fmt_on(ads_decode_result_t *r, ads_value_t *v) {
push_tod(r, v, "on_time", "time", "ON", "Landing Time");
}
void ads_fmt_in(ads_decode_result_t *r, ads_value_t *v) {
push_tod(r, v, "in_time", "time", "IN", "Arrival at Gate");
}
void ads_fmt_out(ads_decode_result_t *r, ads_value_t *v) {
push_tod(r, v, "out_time", "time", "OUT", "Departure from Gate");
Comment on lines +163 to +167

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match the TS OOOI time labels

For OUT/IN time formatters, the TypeScript reference labels these items Out of Gate Time and In Gate Time; this implementation emits different labels. Ported OOOI hatches that call these new helpers will therefore generate formatted output that does not match the shared corpus even though the raw times are correct.

Useful? React with 👍 / 👎.

Comment on lines +163 to +167
}

/* ─── Calendar formatters ────────────────────────────────────────────────── */

static void push_int_item(ads_decode_result_t *r, ads_value_t *v, const char *raw_key,
const char *kind, const char *code, const char *label) {
if (!v) return;
int64_t n = 0;
ads_value_as_int(v, &n);
cJSON_AddNumberToObject(r->raw, raw_key, (double)n);
char buf[32];
snprintf(buf, sizeof(buf), "%lld", (long long)n);
push_item(r, kind, code, label, buf);
ads_value_free(v);
}

void ads_fmt_day(ads_decode_result_t *r, ads_value_t *v) { push_int_item(r, v, "day", "day", "MSG_DAY", "Day of Month"); }
void ads_fmt_departure_day(ads_decode_result_t *r, ads_value_t *v) { push_int_item(r, v, "departure_day", "day", "DEP_DAY", "Departure Day"); }
void ads_fmt_arrival_day(ads_decode_result_t *r, ads_value_t *v) { push_int_item(r, v, "arrival_day", "day", "ARR_DAY", "Arrival Day"); }
void ads_fmt_month(ads_decode_result_t *r, ads_value_t *v) { push_int_item(r, v, "month", "month", "MSG_MONTH", "Month"); }

/* ─── Velocity / atmosphere formatters ───────────────────────────────────── */

void ads_fmt_mach(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "mach", "mach", "MACH", "Mach", ""); }
void ads_fmt_groundspeed(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "groundspeed", "groundspeed", "GS", "Ground Speed", "knots"); }
void ads_fmt_airspeed(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "airspeed", "airspeed", "IAS", "Indicated Airspeed", "knots"); }
Comment on lines +191 to +193

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve TS velocity item metadata

When hatches use these new velocity formatters, their formatted item shapes differ from ResultFormatter: groundspeed should be aircraft_groundspeed/GSPD/Aircraft Groundspeed, airspeed should use ASPD/True Airspeed, and Mach should be labeled Mach Number with a mach suffix. These mismatches break the intended byte-for-byte cross-language formatted output.

Useful? React with 👍 / 👎.

void ads_fmt_temperature(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "outside_air_temperature", "outside_air_temperature", "OATEMP", "Outside Air Temperature (C)", "degrees"); }
void ads_fmt_total_air_temp(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "total_air_temperature", "total_air_temperature", "TATEMP", "Total Air Temperature (C)", "degrees"); }
Comment on lines +194 to +195

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Parse temperature formatter inputs like TS

The TS temperature/totalAirTemp formatters accept strings such as M05/P03 and convert M/P signs before storing the numeric raw temperature; these C wrappers call push_numeric, so a ported hatch passing the same string value will fail ads_value_as_double and emit 0 degrees. That corrupts ARINC 702 temperature fields rather than matching the reference output.

Useful? React with 👍 / 👎.

Comment on lines +191 to +195

/* ─── Fuel formatters ────────────────────────────────────────────────────── */

void ads_fmt_current_fuel(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "fuel_on_board", "fuel_on_board", "FOB", "Fuel On Board", ""); }
void ads_fmt_remaining_fuel(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "fuel_remaining", "fuel_remaining", "FUEL_REM", "Fuel Remaining", ""); }

/* ─── Routing formatters ─────────────────────────────────────────────────── */

void ads_fmt_alternate_airport(ads_decode_result_t *r, ads_value_t *v) { push_string(r, v, "alternate_icao", "icao", "ALT_DST", "Alternate Destination"); }
void ads_fmt_arrival_runway(ads_decode_result_t *r, ads_value_t *v) { push_string(r, v, "arrival_runway", "runway", "ARWY", "Arrival Runway"); }
void ads_fmt_alternate_runway(ads_decode_result_t *r, ads_value_t *v) { push_string(r, v, "alternate_runway", "runway", "ALT_RWY", "Alternate Runway"); }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the TS alternate runway code

For decoded alternate runways, the TypeScript formatter emits code ALT_ARWY, but this C helper emits ALT_RWY. Any C escape hatch that was unblocked by this helper will produce a different formatted item code from the reference runtime for the same message.

Useful? React with 👍 / 👎.


/* ─── Event formatters ───────────────────────────────────────────────────── */

void ads_fmt_state_change(ads_decode_result_t *r, const char *from, const char *to) {
if (!r || !from || !to) return;
cJSON *obj = cJSON_CreateObject();
cJSON_AddStringToObject(obj, "from", from);
cJSON_AddStringToObject(obj, "to", to);
cJSON_AddItemToObject(r->raw, "state_change", obj);
char buf[128];
snprintf(buf, sizeof(buf), "%s -> %s", from, to);
push_item(r, "state_change", "STATE_CHANGE", "State Change", buf);
}
Comment on lines +210 to +219

void ads_fmt_door_event(ads_decode_result_t *r, const char *door, const char *state) {
if (!r || !door || !state) return;
cJSON *obj = cJSON_CreateObject();
cJSON_AddStringToObject(obj, "door", door);
cJSON_AddStringToObject(obj, "state", state);
cJSON_AddItemToObject(r->raw, "door_event", obj);
char buf[128];
snprintf(buf, sizeof(buf), "%s %s", door, state);
push_item(r, "door_event", "DOOR", "Door Event", buf);
}

/* ─── Free-text formatters ───────────────────────────────────────────────── */

void ads_fmt_text(ads_decode_result_t *r, const char *value) {
if (!r || !value) return;
cJSON_AddStringToObject(r->raw, "text", value);
push_item(r, "text", "TEXT", "Text Message", value);
}

/* ─── Diagnostic formatters ──────────────────────────────────────────────── */

void ads_fmt_checksum(ads_decode_result_t *r, ads_value_t *v) {
if (!v) return;
int64_t n = 0;
ads_value_as_int(v, &n);
cJSON_AddNumberToObject(r->raw, "checksum", (double)n);
char buf[32];
snprintf(buf, sizeof(buf), "%llx", (long long)n);
push_item(r, "checksum", "CKSUM", "Checksum", buf);
Comment on lines +248 to +249

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match TS checksum output formatting

For messages that include a checksum, the TypeScript formatter emits type: 'message_checksum', code: 'CHECKSUM', label: 'Message Checksum', and a zero-padded value like 0x00af; this C formatter emits a bare lowercase hex string with different metadata. Downstream corpus comparisons and consumers of the formatted item shape will see different results for the same decoded message.

Useful? React with 👍 / 👎.

ads_value_free(v);
Comment on lines +242 to +250
}

void ads_fmt_checksum_algorithm(ads_decode_result_t *r, const char *value) {
if (!r || !value) return;
cJSON_AddStringToObject(r->raw, "checksum_algorithm", value);
push_item(r, "checksum_algorithm", "CKSUM_ALGO", "Checksum Algorithm", value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Omit the checksum algorithm formatted item

When ARINC 702-style hatches call ads_fmt_checksum_algorithm, this adds a visible formatted item even though the TypeScript reference only sets raw.checksum_algorithm and leaves the formatted push commented out in runtimes/typescript/utils/result_formatter.ts. That makes every checksum-algorithm result diverge from the byte-identical corpus output this runtime is trying to mirror.

Useful? React with 👍 / 👎.

}
Comment on lines +253 to +257

/* ─── Generic structured-item push ───────────────────────────────────────── */

void ads_fmt_push_item(ads_decode_result_t *r, const char *type, const char *code,
const char *label, const char *value) {
if (!r) return;
push_item(r,
type ? type : "unknown",
code ? code : "",
label ? label : "",
value ? value : "");
}

/* ─── Result mutators (declared in ads_helpers.h) ────────────────────────── */

void ads_result_set_description(ads_decode_result_t *r, const char *description) {
if (!r) return;
free(r->description);
r->description = description ? strdup(description) : NULL;
}
Comment on lines +273 to +277

void ads_result_set_remaining(ads_decode_result_t *r, const char *text) {
if (!r) return;
free(r->remaining);
r->remaining = text ? strdup(text) : NULL;
}

void ads_result_append_remaining(ads_decode_result_t *r, const char *text, const char *sep) {
if (!r || !text) return;
if (!sep) sep = ",";
if (r->remaining) {
size_t newlen = strlen(r->remaining) + 1 + strlen(joined) + 1;
size_t newlen = strlen(r->remaining) + strlen(sep) + strlen(text) + 1;
char *grown = malloc(newlen);
snprintf(grown, newlen, "%s,%s", r->remaining, joined);
if (!grown) return;
snprintf(grown, newlen, "%s%s%s", r->remaining, sep, text);
free(r->remaining);
r->remaining = grown;
} else {
r->remaining = strdup(joined);
r->remaining = strdup(text);
}
Comment on lines +279 to 297
free(joined);
}

const char *ads_result_get_remaining(const ads_decode_result_t *r) {
return r ? r->remaining : NULL;
}

void ads_result_set_decode_level(ads_decode_result_t *r, ads_decode_level_t level) {
if (!r) return;
r->decode_level = level;
}

void ads_result_clear_items(ads_decode_result_t *r) {
if (!r) return;
cJSON_Delete(r->items);
r->items = cJSON_CreateArray();
}
Comment on lines +309 to 313
Loading