You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
asterisk/res/res_cdrel_custom.c

259 lines
8.5 KiB

/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2026, Sangoma Technologies Corporation
*
* George Joseph <gjoseph@sangoma.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
/*!
* \file
* \author George Joseph <gjoseph@sangoma.com>
*
* \brief Common logic for the CDR and CEL Custom Backends
*
* All source files are in the res/cdrel_custom directory.
*
* "config.c": Contains common configuration file parsing the ultimate goal
* of which is to create a vector of cdrel_config structures for each of
* the cdr_custom, cdr_sqlite3_custom, cel_custom and cel_sqlite3_custom
* modules. Each cdrel_config object represents an output file defined in
* their respective config files. Each one contains a vector of cdrel_field
* objects, one for each field in the output record, plus settings like
* the output file name, backend type (text file or database), config
* type ((legacy or advanced), the field separator and quote character to
* use.
*
* Each cdrel_field object contains an abstract field id that points to
* a ast_cdr structure member or CEL event field id along with an input
* type and an output type. The registry of cdrel_fields is located in
* registry.c.
*
* "loggers.c": Contains the common "cdrel_logger" entrypoint that the
* individual modules call to log a record. It takes the module's
* cdrel_configs vector and the record to log it got from the core
* cel.c and cdr.c. It then looks up and runs the logger implementation
* based on the backend type (text file or database) and config type
* (legacy or advanced).
*
* "getters_cdr.c", "getters_cel.c": Contain the getters that retrieve
* values from the ast_cdr or ast_event structures based on the field
* id and input type defined for that field and create a cdrel_value
* wrapper object for it.
*
* "writers.c": Contains common backend writers for the text file and
* database backends.
*
* The load-time flow...
*
* Each of the individual cdr/cel custom modules call the common
* cdrel_load_module function with their backend_type, record_type
* (cdr or cel), config file name, and the logging callback that
* should be registered with the core cdr or cel facility.
*
* cdrel_load_module calls the config load function appropriate for
* the backend type, each of which parses the config file and, if
* successful, registers the calling module with the cdr or cel core
* and creates a vector of cdrel_config objects that is passed
* back to the calling module. That vector contains the context for
* all future operations.
*
* The run-time flow...
*
* The core cdr and cel modules use their registries of backends and call
* the callback function registered by the 4 cdr and cel custom modules.
* No changes there.
*
* Each of those modules call the common cdrel_logger function with their
* cdrel_configs vector and the actual ast_cdr or ast_event structure to log.
* The cdrel_logger function iterates over the cdrel_configs vector and for
* each invokes the logger implementation specific to the backend type
* (text file or database) and config type (legacy or advanced).
*
* For legacy config types, the logger implementation simply calls
* ast_str_substitute_variables() on the whole opaque format and writes
* the result to the text file or database.
*
* For advanced configs, the logger implementation iterates over each field
* in the cdrel_config's fields vector and for each, calls the appropriate
* getter based on the record type (cdr or cel) and field id. Each getter
* call returns a cdrel_value object which is then passed to a field formatter
* looked up based on the field's data type (string, int32, etc). The formatter
* is also passed the cdrel_config object and the desired output type and
* returns the final value in another cdrel_value object formatted with any
* quoting, etc. needed. The logger accumulates the output cdrel_values
* (which are all now strings) in another vector and after all fields have
* been processed, hands the vector over to one of the backend writers.
*
* The backend writer concatenates the cdrel_values into an output record
* using the config's separator setting and writes it to the text file
* or database. For the JSON output format, it creates a simple
* name/value pair output record.
*
* The identification of field data types, field ids, record types and
* backend types is all done at config load time and saved in the
* cdrel_config and cdrel_field objects. The callbacks for getters, formatters
* and writers are also loaded when the res_cdrel_custom module loads
* and stored in arrays indexed by their enum values. The result is that
* at run time, simple array indexing is all that's needed to get the
* proper getter, formatter and writer for any logging request.
*
*/
/*** MODULEINFO
<depend>sqlite3</depend>
<support_level>core</support_level>
***/
#include "asterisk.h"
#include "asterisk/module.h"
#include "cdrel_custom/cdrel.h"
/*
* Populated by cdrel_custom/getters_cdr.c and cdrel_custom/getters_cel.c.
*/
cdrel_field_getter cdrel_field_getters[cdrel_record_type_end][cdrel_data_type_end];
/*
* Populated by cdrel_custom/formatters.c.
*/
cdrel_field_formatter cdrel_field_formatters[cdrel_data_type_end];
/*
* Populated by cdrel_custom/writers.c.
*/
cdrel_backend_writer cdrel_backend_writers[cdrel_format_type_end];
/*
* Populated by cdrel_custom/getters_cdr.c and cdrel_custom/getters_cel.c.
*/
cdrel_dummy_channel_alloc cdrel_dummy_channel_allocators[cdrel_format_type_end];
/*
* You must ensure that there's an entry for every value in the enum.
*/
const char *cdrel_record_type_map[] = {
[cdrel_record_cdr] = "CDR",
[cdrel_record_cel] = "CEL",
[cdrel_record_type_end] = "!!END!!",
};
const char *cdrel_module_type_map[] = {
[cdrel_backend_text] = "Custom ",
[cdrel_backend_db] = "SQLITE3 Custom",
[cdrel_backend_type_end] = "!!END!!",
};
const char *cdrel_data_type_map[] = {
[cdrel_type_string] = "string",
[cdrel_type_timeval] = "timeval",
[cdrel_type_literal] = "literal",
[cdrel_type_amaflags] = "amaflags",
[cdrel_type_disposition] = "disposition",
[cdrel_type_uservar] = "uservar",
[cdrel_type_event_type] = "event_type",
[cdrel_type_event_enum] = "event_enum",
[cdrel_type_cel_timefmt] = "cel_timefmt",
[cdrel_data_type_strings_end] = "!!STRINGS END!!",
[cdrel_type_int32] = "int32",
[cdrel_type_uint32] = "uint32",
[cdrel_type_int64] = "int64",
[cdrel_type_uint64] = "uint64",
[cdrel_type_double] = "double",
[cdrel_data_type_end] = "!!END!!",
};
enum cdrel_data_type cdrel_data_type_from_str(const char *str)
{
enum cdrel_data_type data_type = 0;
for (data_type = 0; data_type < cdrel_data_type_end; data_type++) {
if (strcasecmp(cdrel_data_type_map[data_type], str) == 0) {
return data_type;
}
}
return cdrel_data_type_end;
}
static const char *cdrel_field_flags_map[] = {
[CDREL_FIELD_FLAG_QUOTE] = "quote",
[CDREL_FIELD_FLAG_NOQUOTE] = "noquote",
[CDREL_FIELD_FLAG_TYPE_FORCED] = "type_forced",
[CDREL_FIELD_FLAG_USERVAR] = "uservar",
[CDREL_FIELD_FLAG_LITERAL] = "literal",
[CDREL_FIELD_FLAG_FORMAT_SPEC] = "format_spec",
[CDREL_FIELD_FLAG_LAST] = "LAST",
};
const char *cdrel_get_field_flags(struct ast_flags *flags, struct ast_str **str)
{
int ix = 0;
int res = 0;
int trues = 0;
for (ix = 0; ix < CDREL_FIELD_FLAG_LAST; ix++) {
if (ast_test_flag(flags, (1 << ix))) {
res = ast_str_append(str, -1, "%s%s", trues++ ? "," : "", cdrel_field_flags_map[ix]);
if (res < 0) {
return "";
}
}
}
return ast_str_buffer(*str);
}
const char *cdrel_basename(const char *path)
{
int i = 0;
const char *basename = path;
if (ast_strlen_zero(path)) {
return path;
}
i = strlen(path) - 1;
while(i >= 0) {
if (path[i] == '/') {
basename = &path[i + 1];
break;
}
i--;
}
return basename;
}
static int unload_module(void)
{
return 0;
}
static enum ast_module_load_result load_module(void)
{
load_cdr();
load_cel();
load_formatters();
load_writers();
return AST_MODULE_LOAD_SUCCESS;
}
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER,
"Combined logic for CDR/CEL Custom modules",
.support_level = AST_MODULE_SUPPORT_CORE,
.load = load_module,
.unload = unload_module,
.load_pri = AST_MODPRI_CDR_DRIVER,
);