diff --git a/main/aoc.c b/main/aoc.c index 8a51509258..d45092b057 100644 --- a/main/aoc.c +++ b/main/aoc.c @@ -36,6 +36,124 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$"); #include "asterisk/_private.h" #include "asterisk/cli.h" #include "asterisk/manager.h" +#include "asterisk/stasis_channels.h" +#include "asterisk/stasis_message_router.h" + +/*** DOCUMENTATION + + + Raised when an Advice of Charge message is sent at the beginning of a call. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Raised when an Advice of Charge message is sent during a call. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Raised when an Advice of Charge message is sent at the end of a call. + + + + + + + + + + +***/ /* Encoded Payload Flags */ #define AST_AOC_ENCODED_TYPE_REQUEST (0 << 0) @@ -1289,13 +1407,8 @@ static void aoc_amount_str(struct ast_str **msg, const char *prefix, unsigned in aoc_multiplier_str(mult)); } -static void aoc_request_event(const struct ast_aoc_decoded *decoded, struct ast_channel *chan, struct ast_str **msg) +static void aoc_request_event(const struct ast_aoc_decoded *decoded, struct ast_str **msg) { - if (chan) { - ast_str_append(msg, 0, "Channel: %s\r\n", ast_channel_name(chan)); - ast_str_append(msg, 0, "UniqueID: %s\r\n", ast_channel_uniqueid(chan)); - } - if (decoded->request_flag) { ast_str_append(msg, 0, "AOCRequest:"); if (decoded->request_flag & AST_AOC_REQUEST_S) { @@ -1314,17 +1427,12 @@ static void aoc_request_event(const struct ast_aoc_decoded *decoded, struct ast_ } } -static void aoc_s_event(const struct ast_aoc_decoded *decoded, struct ast_channel *owner, struct ast_str **msg) +static void aoc_s_event(const struct ast_aoc_decoded *decoded, struct ast_str **msg) { const char *rate_str; char prefix[32]; int idx; - if (owner) { - ast_str_append(msg, 0, "Channel: %s\r\n", ast_channel_name(owner)); - ast_str_append(msg, 0, "UniqueID: %s\r\n", ast_channel_uniqueid(owner)); - } - ast_str_append(msg, 0, "NumberRates: %d\r\n", decoded->aoc_s_count); for (idx = 0; idx < decoded->aoc_s_count; ++idx) { snprintf(prefix, sizeof(prefix), "Rate(%d)", idx); @@ -1387,17 +1495,12 @@ static void aoc_s_event(const struct ast_aoc_decoded *decoded, struct ast_channe } } -static void aoc_d_event(const struct ast_aoc_decoded *decoded, struct ast_channel *chan, struct ast_str **msg) +static void aoc_d_event(const struct ast_aoc_decoded *decoded, struct ast_str **msg) { const char *charge_str; int idx; char prefix[32]; - if (chan) { - ast_str_append(msg, 0, "Channel: %s\r\n", ast_channel_name(chan)); - ast_str_append(msg, 0, "UniqueID: %s\r\n", ast_channel_uniqueid(chan)); - } - charge_str = aoc_charge_type_str(decoded->charge_type); ast_str_append(msg, 0, "Type: %s\r\n", charge_str); @@ -1441,17 +1544,12 @@ static void aoc_d_event(const struct ast_aoc_decoded *decoded, struct ast_channe } } -static void aoc_e_event(const struct ast_aoc_decoded *decoded, struct ast_channel *chan, struct ast_str **msg) +static void aoc_e_event(const struct ast_aoc_decoded *decoded, struct ast_str **msg) { const char *charge_str; int idx; char prefix[32]; - if (chan) { - ast_str_append(msg, 0, "Channel: %s\r\n", ast_channel_name(chan)); - ast_str_append(msg, 0, "UniqueID: %s\r\n", ast_channel_uniqueid(chan)); - } - charge_str = "ChargingAssociation"; switch (decoded->charging_association.charging_type) { @@ -1509,41 +1607,271 @@ static void aoc_e_event(const struct ast_aoc_decoded *decoded, struct ast_channe } } +static struct ast_json *units_to_json(const struct ast_aoc_decoded *decoded) +{ + int i; + struct ast_json *units = ast_json_array_create(); + + if (!units) { + return ast_json_null(); + } + + for (i = 0; i < decoded->unit_count; ++i) { + struct ast_json *unit = ast_json_object_create(); + + if (decoded->unit_list[i].valid_amount) { + ast_json_object_set( + unit, "NumberOf", ast_json_stringf( + "%u", decoded->unit_list[i].amount)); + } + + if (decoded->unit_list[i].valid_type) { + ast_json_object_set( + unit, "TypeOf", ast_json_stringf( + "%d", decoded->unit_list[i].type)); + } + + if (ast_json_array_append(units, unit)) { + break; + } + } + + return units; +} + +static struct ast_json *currency_to_json(const char *name, int cost, + enum ast_aoc_currency_multiplier mult) +{ + return ast_json_pack("{s:s, s:i, s:s}", "Name", name, + "Cost", cost, "Multiplier", aoc_multiplier_str(mult)); +} + +static struct ast_json *charge_to_json(const struct ast_aoc_decoded *decoded) +{ + RAII_VAR(struct ast_json *, obj, NULL, ast_json_unref); + const char *obj_type; + + if (decoded->charge_type != AST_AOC_CHARGE_CURRENCY && + decoded->charge_type != AST_AOC_CHARGE_UNIT) { + return ast_json_pack("{s:s}", "Type", + aoc_charge_type_str(decoded->charge_type)); + } + + if (decoded->charge_type == AST_AOC_CHARGE_CURRENCY) { + obj_type = "Currency"; + obj = currency_to_json(decoded->currency_name, decoded->currency_amount, + decoded->multiplier); + } else { /* decoded->charge_type == AST_AOC_CHARGE_UNIT */ + obj_type = "Units"; + obj = units_to_json(decoded); + } + + return ast_json_pack( + "{s:s, s:s, s:s, s:O}", + "Type", aoc_charge_type_str(decoded->charge_type), + "BillingID", aoc_billingid_str(decoded->billing_id), + "TotalType", aoc_type_of_totaling_str(decoded->total_type), + obj_type, obj); +} + +static struct ast_json *association_to_json(const struct ast_aoc_decoded *decoded) +{ + switch (decoded->charging_association.charging_type) { + case AST_AOC_CHARGING_ASSOCIATION_NUMBER: + return ast_json_pack( + "{s:s, s:i}", + "Number", decoded->charging_association.charge.number.number, + "Plan", decoded->charging_association.charge.number.plan); + case AST_AOC_CHARGING_ASSOCIATION_ID: + return ast_json_pack( + "{s:i}", "ID", decoded->charging_association.charge.id); + case AST_AOC_CHARGING_ASSOCIATION_NA: + default: + return ast_json_null(); + } +} + +static struct ast_json *s_to_json(const struct ast_aoc_decoded *decoded) +{ + int i; + struct ast_json *rates = ast_json_array_create(); + + if (!rates) { + return ast_json_null(); + } + + for (i = 0; i < decoded->aoc_s_count; ++i) { + struct ast_json *rate = ast_json_object_create(); + RAII_VAR(struct ast_json *, type, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, currency, NULL, ast_json_unref); + const char *charge_item = aoc_charged_item_str( + decoded->aoc_s_entries[i].charged_item); + + if (decoded->aoc_s_entries[i].charged_item == AST_AOC_CHARGED_ITEM_NA) { + rate = ast_json_pack("{s:s}", "Chargeable", charge_item); + if (ast_json_array_append(rates, rate)) { + break; + } + continue; + } + + switch (decoded->aoc_s_entries[i].rate_type) { + case AST_AOC_RATE_TYPE_DURATION: + { + RAII_VAR(struct ast_json *, time, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, granularity, NULL, ast_json_unref); + + currency = currency_to_json( + decoded->aoc_s_entries[i].rate.duration.currency_name, + decoded->aoc_s_entries[i].rate.duration.amount, + decoded->aoc_s_entries[i].rate.duration.multiplier); + + time = ast_json_pack( + "{s:i, s:s}", + "Length", decoded->aoc_s_entries[i].rate.duration.time, + "Scale", decoded->aoc_s_entries[i].rate.duration.time_scale); + + if (decoded->aoc_s_entries[i].rate.duration.granularity_time) { + granularity = ast_json_pack( + "{s:i, s:s}", + "Length", decoded->aoc_s_entries[i].rate.duration.granularity_time, + "Scale", decoded->aoc_s_entries[i].rate.duration.granularity_time_scale); + } + + type = ast_json_pack("{s:O, s:s, s:O, s:O}", "Currency", currency, "ChargingType", + decoded->aoc_s_entries[i].rate.duration.charging_type ? + "StepFunction" : "ContinuousCharging", "Time", time, + "Granularity", granularity ? granularity : ast_json_null()); + + break; + } + case AST_AOC_RATE_TYPE_FLAT: + currency = currency_to_json( + decoded->aoc_s_entries[i].rate.flat.currency_name, + decoded->aoc_s_entries[i].rate.flat.amount, + decoded->aoc_s_entries[i].rate.flat.multiplier); + + type = ast_json_pack("{s:O}", "Currency", currency); + break; + case AST_AOC_RATE_TYPE_VOLUME: + currency = currency_to_json( + decoded->aoc_s_entries[i].rate.volume.currency_name, + decoded->aoc_s_entries[i].rate.volume.amount, + decoded->aoc_s_entries[i].rate.volume.multiplier); + + type = ast_json_pack( + "{s:s, s:O}", "Unit", aoc_volume_unit_str( + decoded->aoc_s_entries[i].rate.volume.volume_unit), + "Currency", currency); + break; + case AST_AOC_RATE_TYPE_SPECIAL_CODE: + type = ast_json_pack("{s:i}", "SpecialCode", + decoded->aoc_s_entries[i].rate.special_code); + break; + default: + break; + } + + rate = ast_json_pack("{s:s, s:O}", "Chargeable", charge_item, + aoc_rate_type_str(decoded->aoc_s_entries[i].rate_type), type); + if (ast_json_array_append(rates, rate)) { + break; + } + } + return rates; +} + +static struct ast_json *d_to_json(const struct ast_aoc_decoded *decoded) +{ + return ast_json_pack("{s:o}", "Charge", charge_to_json(decoded)); +} + +static struct ast_json *e_to_json(const struct ast_aoc_decoded *decoded) +{ + return ast_json_pack("{s:o, s:o}", + "ChargingAssociation", association_to_json(decoded), + "Charge", charge_to_json(decoded)); +} + +static struct ast_manager_event_blob *aoc_to_ami(struct stasis_message *message, + const char *event_name) +{ + struct ast_channel_blob *obj = stasis_message_data(message); + RAII_VAR(struct ast_str *, channel, NULL, ast_free); + RAII_VAR(struct ast_str *, aoc, NULL, ast_free); + + if (!(channel = ast_manager_build_channel_state_string( + obj->snapshot))) { + return NULL; + } + + if (!(aoc = ast_manager_str_from_json_object(obj->blob, NULL))) { + return NULL; + } + + return ast_manager_event_blob_create(EVENT_FLAG_AOC, event_name, "%s%s", + AS_OR(channel, ""), ast_str_buffer(aoc)); +} + +static struct ast_manager_event_blob *aoc_s_to_ami(struct stasis_message *message) +{ + return aoc_to_ami(message, "AOC-S"); +} + +static struct ast_manager_event_blob *aoc_d_to_ami(struct stasis_message *message) +{ + return aoc_to_ami(message, "AOC-D"); +} + +static struct ast_manager_event_blob *aoc_e_to_ami(struct stasis_message *message) +{ + return aoc_to_ami(message, "AOC-E"); +} + +struct stasis_message_type *aoc_s_type(void); +struct stasis_message_type *aoc_d_type(void); +struct stasis_message_type *aoc_e_type(void); + +STASIS_MESSAGE_TYPE_DEFN( + aoc_s_type, + .to_ami = aoc_s_to_ami); + +STASIS_MESSAGE_TYPE_DEFN( + aoc_d_type, + .to_ami = aoc_d_to_ami); + +STASIS_MESSAGE_TYPE_DEFN( + aoc_e_type, + .to_ami = aoc_e_to_ami); + int ast_aoc_manager_event(const struct ast_aoc_decoded *decoded, struct ast_channel *chan) { - struct ast_str *msg; + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + struct stasis_message_type *msg_type; - if (!decoded || !(msg = ast_str_create(1024))) { + if (!decoded) { return -1; } switch (decoded->msg_type) { case AST_AOC_S: - if (chan) { - aoc_s_event(decoded, chan, &msg); - ast_manager_event(chan, EVENT_FLAG_AOC, "AOC-S", "%s", ast_str_buffer(msg)); - } + blob = s_to_json(decoded); + msg_type = aoc_s_type(); break; case AST_AOC_D: - if (chan) { - aoc_d_event(decoded, chan, &msg); - ast_manager_event(chan, EVENT_FLAG_AOC, "AOC-D", "%s", ast_str_buffer(msg)); - } + blob = d_to_json(decoded); + msg_type = aoc_d_type(); break; case AST_AOC_E: - { - struct ast_channel *chans[1]; - aoc_e_event(decoded, chan, &msg); - chans[0] = chan; - ast_manager_event_multichan(EVENT_FLAG_AOC, "AOC-E", chan ? 1 : 0, chans, "%s", ast_str_buffer(msg)); - } + blob = e_to_json(decoded); + msg_type = aoc_e_type(); break; default: /* events for AST_AOC_REQUEST are not generated here */ - break; + return 0; } - ast_free(msg); + ast_channel_publish_blob(chan, msg_type, blob); return 0; } @@ -1556,19 +1884,19 @@ int ast_aoc_decoded2str(const struct ast_aoc_decoded *decoded, struct ast_str ** switch (decoded->msg_type) { case AST_AOC_S: ast_str_append(msg, 0, "AOC-S\r\n"); - aoc_s_event(decoded, NULL, msg); + aoc_s_event(decoded, msg); break; case AST_AOC_D: ast_str_append(msg, 0, "AOC-D\r\n"); - aoc_d_event(decoded, NULL, msg); + aoc_d_event(decoded, msg); break; case AST_AOC_E: ast_str_append(msg, 0, "AOC-E\r\n"); - aoc_e_event(decoded, NULL, msg); + aoc_e_event(decoded, msg); break; case AST_AOC_REQUEST: ast_str_append(msg, 0, "AOC-Request\r\n"); - aoc_request_event(decoded, NULL, msg); + aoc_request_event(decoded, msg); break; } @@ -1607,10 +1935,18 @@ static struct ast_cli_entry aoc_cli[] = { static void aoc_shutdown(void) { + STASIS_MESSAGE_TYPE_CLEANUP(aoc_s_type); + STASIS_MESSAGE_TYPE_CLEANUP(aoc_d_type); + STASIS_MESSAGE_TYPE_CLEANUP(aoc_e_type); + ast_cli_unregister_multiple(aoc_cli, ARRAY_LEN(aoc_cli)); } int ast_aoc_cli_init(void) { + STASIS_MESSAGE_TYPE_INIT(aoc_s_type); + STASIS_MESSAGE_TYPE_INIT(aoc_d_type); + STASIS_MESSAGE_TYPE_INIT(aoc_e_type); + ast_register_atexit(aoc_shutdown); return ast_cli_register_multiple(aoc_cli, ARRAY_LEN(aoc_cli)); } diff --git a/main/manager.c b/main/manager.c index ec031303ba..2af7fc529a 100644 --- a/main/manager.c +++ b/main/manager.c @@ -1274,47 +1274,98 @@ struct stasis_message_router *ast_manager_get_message_router(void) return stasis_router; } +static void manager_json_value_str_append(struct ast_json *value, const char *key, + struct ast_str **res) +{ + switch (ast_json_typeof(value)) { + case AST_JSON_STRING: + ast_str_append(res, 0, "%s: %s\r\n", key, ast_json_string_get(value)); + break; + case AST_JSON_INTEGER: + ast_str_append(res, 0, "%s: %jd\r\n", key, ast_json_integer_get(value)); + break; + case AST_JSON_TRUE: + ast_str_append(res, 0, "%s: True\r\n", key); + break; + case AST_JSON_FALSE: + ast_str_append(res, 0, "%s: False\r\n", key); + break; + default: + ast_str_append(res, 0, "%s: \r\n", key); + break; + } +} + +static void manager_json_to_ast_str(struct ast_json *obj, const char *key, + struct ast_str **res, key_exclusion_cb exclusion_cb); + +static void manager_json_array_with_key(struct ast_json *obj, const char* key, + size_t index, struct ast_str **res, + key_exclusion_cb exclusion_cb) +{ + struct ast_str *key_str = ast_str_alloca(64); + ast_str_set(&key_str, 0, "%s(%zu)", key, index); + manager_json_to_ast_str(obj, ast_str_buffer(key_str), + res, exclusion_cb); +} + +static void manager_json_obj_with_key(struct ast_json *obj, const char* key, + const char *parent_key, struct ast_str **res, + key_exclusion_cb exclusion_cb) +{ + if (parent_key) { + struct ast_str *key_str = ast_str_alloca(64); + ast_str_set(&key_str, 0, "%s/%s", parent_key, key); + manager_json_to_ast_str(obj, ast_str_buffer(key_str), + res, exclusion_cb); + return; + } + + manager_json_to_ast_str(obj, key, res, exclusion_cb); +} + +void manager_json_to_ast_str(struct ast_json *obj, const char *key, + struct ast_str **res, key_exclusion_cb exclusion_cb) +{ + struct ast_json_iter *i; + + if (!obj || (!res && !(*res) && (!(*res = ast_str_create(1024))))) { + return; + } + + if (exclusion_cb && key && exclusion_cb(key)) { + return; + } + + if (ast_json_typeof(obj) != AST_JSON_OBJECT && + ast_json_typeof(obj) != AST_JSON_ARRAY) { + manager_json_value_str_append(obj, key, res); + return; + } + + if (ast_json_typeof(obj) == AST_JSON_ARRAY) { + size_t j; + for (j = 0; j < ast_json_array_size(obj); ++j) { + manager_json_array_with_key(ast_json_array_get(obj, j), + key, j, res, exclusion_cb); + } + return; + } + + for (i = ast_json_object_iter(obj); i; + i = ast_json_object_iter_next(obj, i)) { + manager_json_obj_with_key(ast_json_object_iter_value(i), + ast_json_object_iter_key(i), + key, res, exclusion_cb); + } +} + + struct ast_str *ast_manager_str_from_json_object(struct ast_json *blob, key_exclusion_cb exclusion_cb) { - struct ast_str *output_str = ast_str_create(32); - struct ast_json *value; - struct ast_json_iter *iter; - const char *key; - if (!output_str) { - return NULL; - } - - for (iter = ast_json_object_iter(blob); iter; iter = ast_json_object_iter_next(blob, iter)) { - key = ast_json_object_iter_key(iter); - value = ast_json_object_iter_value(iter); - - if (exclusion_cb && exclusion_cb(key)) { - continue; - } - switch (ast_json_typeof(value)) { - case AST_JSON_STRING: - ast_str_append(&output_str, 0, "%s: %s\r\n", key, ast_json_string_get(value)); - break; - case AST_JSON_INTEGER: - ast_str_append(&output_str, 0, "%s: %jd\r\n", key, ast_json_integer_get(value)); - break; - case AST_JSON_TRUE: - ast_str_append(&output_str, 0, "%s: True\r\n", key); - break; - case AST_JSON_FALSE: - ast_str_append(&output_str, 0, "%s: False\r\n", key); - break; - default: - ast_str_append(&output_str, 0, "%s: \r\n", key); - break; - } - - if (!output_str) { - return NULL; - } - } - - return output_str; + struct ast_str *res = ast_str_create(1024); + manager_json_to_ast_str(blob, NULL, &res, exclusion_cb); + return res; } static void manager_default_msg_cb(void *data, struct stasis_subscription *sub,