diff --git a/runtimes/c/include/ads_helpers.h b/runtimes/c/include/ads_helpers.h index c71f3bb..edad682 100644 --- a/runtimes/c/include/ads_helpers.h +++ b/runtimes/c/include/ads_helpers.h @@ -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). */ 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); @@ -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 diff --git a/runtimes/c/src/result_formatter.c b/runtimes/c/src/result_formatter.c index e3acbaa..c2ed7a0 100644 --- a/runtimes/c/src/result_formatter.c +++ b/runtimes/c/src/result_formatter.c @@ -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; +} + +/* ─── 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"); +} + +/* ─── 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"); } +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"); } + +/* ─── 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"); } + +/* ─── 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); +} + +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); + ads_value_free(v); +} + +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); +} + +/* ─── 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; +} + +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); } - 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(); }