diff options
Diffstat (limited to 'channels')
-rw-r--r-- | channels/Makefile | 2 | ||||
-rw-r--r-- | channels/chan_dect.c | 2384 |
2 files changed, 2386 insertions, 0 deletions
diff --git a/channels/Makefile b/channels/Makefile index f9b5b3ad7..245344e9c 100644 --- a/channels/Makefile +++ b/channels/Makefile @@ -90,6 +90,8 @@ chan_h323.so: chan_h323.o h323/libchanh323.a endif endif +chan_dect.so: LIBS+=-ldect + chan_misdn.o: _ASTCFLAGS+=-Imisdn misdn_config.o: _ASTCFLAGS+=-Imisdn diff --git a/channels/chan_dect.c b/channels/chan_dect.c new file mode 100644 index 000000000..ea29bc7c5 --- /dev/null +++ b/channels/chan_dect.c @@ -0,0 +1,2384 @@ +/* + * Asterisk DECT Channel driver + * + * Copyright (c) 2009-2011 Patrick McHardy <kaber@trash.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision") + +#include <signal.h> +#include <semaphore.h> + +#include "asterisk/lock.h" +#include "asterisk/channel.h" +#include "asterisk/config.h" +#include "asterisk/module.h" +#include "asterisk/pbx.h" +#include "asterisk/sched.h" +#include "asterisk/io.h" +#include "asterisk/cli.h" +#include "asterisk/astdb.h" +#include "asterisk/causes.h" +#include "asterisk/callerid.h" +#include "asterisk/abstract_jb.h" + +#include <linux/dect.h> +#include <linux/dect_netlink.h> +#include <dect/libdect.h> +#include <dect/terminal.h> +#include <dect/debug.h> + +#define CONFIG_FILE "dect.conf" + +#define DECT_LOCATE_TIMEOUT_SLACK 10 /* seconds */ + +static struct ast_channel_tech dect_tech; +static struct dect_handle *dh; +static struct ast_sched_context *sched; +static struct io_context *io; +static int access_rights_timer; + +static const struct ast_jb_conf dect_default_jbconf = { + .max_size = -1, + .resync_threshold = -1, + .impl = "", +}; + +static struct dect_fp_capabilities dect_fpc = { + .hlc = DECT_HLC_ADPCM_G721_VOICE | + DECT_HLC_GAP_PAP_BASIC_SPEECH | + DECT_HLC_CISS_SERVICE | + DECT_HLC_CLMS_SERVICE | + DECT_HLC_LOCATION_REGISTRATION | + DECT_HLC_STANDARD_AUTHENTICATION | + DECT_HLC_STANDARD_CIPHERING, + .ehlc2 = DECT_EHLC2_LIST_ACCESS_FEATURES, +}; + +struct { + char cluster[DECTNAMSIZ]; + struct ast_jb_conf jbconf; + char context[AST_MAX_CONTEXT]; + char language[MAX_LANGUAGE]; + char regcontext[AST_MAX_CONTEXT]; + unsigned int regexten_base; + char pin[sizeof("00000000")]; + unsigned int locate_duration; +} dect_cfg; + +static AST_LIST_HEAD_STATIC(dect_pt_list, dect_pt); + +struct dect_pt { + AST_LIST_ENTRY(dect_pt) list; + char name[64]; + char fullname[64]; + char ipei[DECT_IPEI_STRING_LEN + 1]; + struct dect_ipui ipui; + struct dect_tpui tpui; + + AST_DECLARE_STRING_FIELDS( AST_STRING_FIELD(regexten); + AST_STRING_FIELD(context); + AST_STRING_FIELD(language); + AST_STRING_FIELD(cid_num); + AST_STRING_FIELD(cid_name); + AST_STRING_FIELD(ring_pattern); + ); + + uint8_t uak[DECT_AUTH_KEY_LEN]; + uint8_t dck[DECT_CIPHER_KEY_LEN]; + + struct dect_ie_terminal_capability *terminal_capability; + struct dect_ie_codec_list *codec_list; + + int locate_timer; +}; + +#define dect_pt_log(pt, fmt, args...) \ + ast_log(LOG_NOTICE, "PT '%s': " fmt, (pt)->name, ## args) + +struct dect_pvt { + struct dect_pt *pt; + struct dect_mm_endpoint *mme; + struct dect_call *call; + struct ast_channel *chan; + int timer_id; +}; + +struct dect_mm_pvt { + struct dect_pt *pt; + struct dect_mm_endpoint *mme; + struct dect_call *call; + + struct dect_ie_collection *iec; + + /* authentication */ + enum dect_auth_types { + DECT_AUTH_NONE, + DECT_AUTH_PT, + DECT_AUTH_USER, + DECT_AUTH_KEY_ALLOCATION, + } auth_type; + uint64_t rand; + uint64_t rs; + uint8_t uak[DECT_AUTH_KEY_LEN]; + void (*auth_cfm)(struct dect_mm_pvt *, bool, + struct dect_ie_collection *); + + /* ciphering */ + bool ciphered; + void (*cipher_cfm)(struct dect_mm_pvt *, bool, + struct dect_ie_collection *); + + /* user authentication */ + uint8_t upi[DECT_UPI_LEN]; + sem_t userauth_semaphore; + bool userauth_status; +}; + +#define div_round_up(x, y) (((x) + ((y) - 1)) / (y)) +#define dect_ie_update(pos, ie) \ + do { \ + if (ie == NULL) \ + break; \ + dect_ie_put(dh, pos); \ + pos = dect_ie_hold(ie); \ + } while (0) + +static void dect_authenticate(struct dect_mm_pvt *pvt, enum dect_auth_types type, + void (*auth_cfm)(struct dect_mm_pvt *, bool, + struct dect_ie_collection *), + struct dect_ie_collection *iec); + +static void dect_cipher(struct dect_mm_pvt *pvt, + void (*auth_cfm)(struct dect_mm_pvt *, bool, + struct dect_ie_collection *), + struct dect_ie_collection *iec); + +static uint64_t dect_random(void) +{ + return (uint64_t)ast_random() << 32 | ast_random(); +} + +static int dect_release_reason_to_ast(enum dect_release_reasons reason) +{ + switch (reason) { + case DECT_RELEASE_NORMAL: + return AST_CAUSE_NORMAL_CLEARING; + case DECT_RELEASE_UNEXPECTED_MESSAGE: + case DECT_RELEASE_UNKNOWN_TRANSACTION_IDENTIFIER: + return AST_CAUSE_INVALID_MSG_UNSPECIFIED; + case DECT_RELEASE_MANDATORY_IE_MISSING: + return AST_CAUSE_MANDATORY_IE_MISSING; + case DECT_RELEASE_INVALID_IE_CONTENTS: + return AST_CAUSE_INVALID_IE_CONTENTS; + case DECT_RELEASE_INCOMPATIBLE_SERVICE: + case DECT_RELEASE_SERVICE_NOT_IMPLEMENTED: + case DECT_RELEASE_NEGOTIATION_NOT_SUPPORTED: + case DECT_RELEASE_INVALID_IDENTITY: + case DECT_RELEASE_AUTHENTICATION_FAILED: + case DECT_RELEASE_UNKNOWN_IDENTITY: + case DECT_RELEASE_NEGOTIATION_FAILED: + case DECT_RELEASE_TIMER_EXPIRY: + case DECT_RELEASE_PARTIAL_RELEASE: + case DECT_RELEASE_UNKNOWN: + return AST_CAUSE_NORMAL; + /* user values */ + case DECT_RELEASE_USER_DETACHED: + case DECT_RELEASE_USER_NOT_IN_RANGE: + return AST_CAUSE_SUBSCRIBER_ABSENT; + case DECT_RELEASE_USER_UNKNOWN: + return AST_CAUSE_NO_ROUTE_DESTINATION; + case DECT_RELEASE_USER_ALREADY_ACTIVE: + case DECT_RELEASE_USER_BUSY: + return AST_CAUSE_USER_BUSY; + case DECT_RELEASE_USER_REJECTION: + return AST_CAUSE_CALL_REJECTED; + case DECT_RELEASE_USER_CALL_MODIFY: + /* external handover values */ + case DECT_RELEASE_EXTERNAL_HANDOVER_NOT_SUPPORTED: + case DECT_RELEASE_NETWORK_PARAMETERS_MISSING: + case DECT_RELEASE_EXTERNAL_HANDOVER_RELEASE: + return AST_CAUSE_NORMAL; + /* temporary overload values */ + case DECT_RELEASE_OVERLOAD: + case DECT_RELEASE_INSUFFICIENT_RESOURCES: + case DECT_RELEASE_INSUFFICIENT_BEARERS_AVAILABLE: + return AST_CAUSE_NORMAL_CIRCUIT_CONGESTION; + case DECT_RELEASE_IWU_CONGESTION: + return AST_CAUSE_SWITCH_CONGESTION; + default: + return AST_CAUSE_NORMAL; + } +} + +static enum dect_presentation_indicators dect_ast_presentation_to_dect(int presentation) +{ + presentation &= AST_PRES_NUMBER_TYPE; + + switch (presentation) { + default: + ast_log(LOG_WARNING, "Unknown presentation coding %x\n", presentation); + /* fall through */ + case AST_PRES_ALLOWED: + return DECT_PRESENTATION_ALLOWED; + case AST_PRES_RESTRICTED: + return DECT_PRESENTATION_RESTRICTED; + case AST_PRES_UNAVAILABLE: + return DECT_PRESENTATION_NOT_AVAILABLE; + } +} + +static enum dect_screening_indicators dect_ast_screening_to_dect(int screening) +{ + screening &= AST_PRES_RESTRICTION; + + switch (screening) { + default: + ast_log(LOG_WARNING, "Unknown screening coding %x\n", screening); + /* fall through */ + case AST_PRES_USER_NUMBER_UNSCREENED: + return DECT_SCREENING_USER_PROVIDED_NOT_SCREENED; + case AST_PRES_USER_NUMBER_PASSED_SCREEN: + return DECT_SCREENING_USER_PROVIDED_VERIFIED_PASSED; + case AST_PRES_USER_NUMBER_FAILED_SCREEN: + return DECT_SCREENING_USER_PROVIDED_VERIFIED_FAILED; + case AST_PRES_NETWORK_NUMBER: + return DECT_SCREENING_NETWORK_PROVIDED; + } +} + +static struct dect_pt *dect_pt_get_by_name(const char *name) +{ + struct dect_pt *pt; + + AST_LIST_TRAVERSE(&dect_pt_list, pt, list) { + if (!strcasecmp(pt->name, name)) + return pt; + } + return NULL; +} + +static struct dect_pt *dect_pt_get_by_ipui(const struct dect_ipui *ipui) +{ + struct dect_pt *pt; + + AST_LIST_TRAVERSE(&dect_pt_list, pt, list) { + if (!dect_ipui_cmp(&pt->ipui, ipui)) + return pt; + } + return NULL; +} + +static struct dect_pt *dect_init_portable(const char *name) +{ + struct dect_pt *pt; + + pt = ast_calloc(1, sizeof(*pt)); + if (pt == NULL) + return NULL; + + if (name != NULL) { + ast_copy_string(pt->name, name, sizeof(pt->name)); + snprintf(pt->fullname, sizeof(pt->fullname), "DECT/%s", pt->name); + } + + ast_string_field_init(pt, 512); + ast_string_field_set(pt, context, dect_cfg.context); + ast_string_field_set(pt, regexten, ""); + ast_string_field_set(pt, language, dect_cfg.language); + ast_string_field_set(pt, cid_num, ""); + ast_string_field_set(pt, cid_name, ""); + ast_string_field_set(pt, ring_pattern, "0"); + pt->locate_timer = -1; + + AST_LIST_INSERT_TAIL(&dect_pt_list, pt, list); + return pt; +} + +static int dect_alloc_extension(unsigned int *exten) +{ + const struct dect_pt *pt; + uint32_t bitmap[1024]; + unsigned int pext; + + memset(bitmap, 0, sizeof(bitmap)); + AST_LIST_TRAVERSE(&dect_pt_list, pt, list) { + pext = strtoul(pt->regexten, NULL, 0); + if (pext < dect_cfg.regexten_base || + pext >= 8 * sizeof(bitmap)) + continue; + pext -= dect_cfg.regexten_base; + + bitmap[pext / 32] |= 1 << (pext % 32); + } + + for (pext = 0; pext < 8 * sizeof(bitmap); pext++) { + if (!(bitmap[pext / 32] & (1 << (pext % 32)))) { + *exten = dect_cfg.regexten_base + pext; + return 1; + } + } + + return 0; +} + +static void dect_register_extension(const struct dect_pt *pt, bool onoff) +{ + struct pbx_find_info q = { .stacklen = 0 }; + char *ext, *extenp, *context; + char exten[256]; + + if (ast_strlen_zero(dect_cfg.regcontext)) + return; + + ast_copy_string(exten, S_OR(pt->regexten, pt->name), sizeof(exten)); + extenp = exten; + while ((ext = strsep(&extenp, "&")) != NULL) { + context = strchr(ext, '@'); + if (context != NULL) { + *context++ = '\0'; + if (!ast_context_find(context)) { + ast_log(LOG_WARNING, "Context %s for PT %s does not exist", + context, pt->name); + continue; + } + } else + context = dect_cfg.regcontext; + + if (onoff) { + if (!ast_exists_extension(NULL, context, ext, 1, NULL)) { + ast_add_extension(context, 1, ext, 1, NULL, NULL, "Dial", + (char *)pt->fullname, NULL, "DECT"); + } + } else if (pbx_find_extension(NULL, NULL, &q, context, ext, 1, + NULL, "", E_MATCH)) { + ast_context_remove_extension(context, ext, 1, NULL); + } + } +} + +static void dect_init_tpui(struct dect_tpui *tpui, unsigned int exten) +{ + tpui->ia.digits[0] = exten >= 10000 ? exten / 10000 % 10 : 0xb; + tpui->ia.digits[1] = exten >= 1000 ? exten / 1000 % 10 : 0xb; + tpui->ia.digits[2] = exten >= 100 ? exten / 100 % 10 : 0xb; + tpui->ia.digits[3] = exten >= 10 ? exten / 10 % 10 : 0xb; + tpui->ia.digits[4] = exten >= 1 ? exten / 1 % 10 : 0xb; +} + +static void dect_pt_set_extension(struct dect_pt *pt, unsigned int exten) +{ + ast_string_field_build(pt, cid_num, "%u", exten); + ast_string_field_build(pt, regexten, "%u", exten); + dect_init_tpui(&pt->tpui, exten); +} + +static void dect_pt_set_ipui(struct dect_pt *pt, const struct dect_ipui *ipui) +{ + pt->ipui = *ipui; + dect_format_ipei_string(&ipui->pun.n.ipei, pt->ipei); +} + +/* + * Database functions + */ +static uint64_t dect_parse_num(const char *str) +{ + uint64_t num; + + if (sscanf(str, "%" PRIx64, &num) != 1) + ast_log(LOG_WARNING, "Could not parse numeric value '%s'\n", str); + + return num; +} + +static void dect_parse_base64(const char *str, uint8_t *dst, unsigned int size) +{ + ast_base64decode(dst, str, size); +} + +static void dect_db_remove(const struct dect_pt *pt) +{ + ast_db_deltree("dect", pt->ipei); +} + +static int dect_db_put(const struct dect_pt *pt, const char *key, const char *value) +{ + char ptkey[256]; + + snprintf(ptkey, sizeof(ptkey), "%s/%s", pt->ipei, key); + return ast_db_put("dect", ptkey, value); +} + +static int dect_db_put_numbered(const struct dect_pt *pt, const char *key, + unsigned int index, const char *value) +{ + char nkey[256]; + + snprintf(nkey, sizeof(nkey), key, index); + return dect_db_put(pt, nkey, value); +} + +static int dect_db_put_base64(const struct dect_pt *pt, const char *key, + const void *data, unsigned int len) +{ + char value[4 * len + 4]; + + ast_base64encode(value, data, len, sizeof(value)); + return dect_db_put(pt, key, value); +} + +static int dect_db_put_num(const struct dect_pt *pt, const char *key, + uint64_t num) +{ + char value[sizeof("ffffffffffffffff")]; + + snprintf(value, sizeof(value), "%" PRIx64, num); + return dect_db_put(pt, key, value); +} + +static int dect_db_put_numbered_num(const struct dect_pt *pt, const char *key, + unsigned int index, uint64_t num) +{ + char value[sizeof("ffffffffffffffff")]; + + snprintf(value, sizeof(value), "%" PRIx64, num); + return dect_db_put_numbered(pt, key, index, value); +} + +static int dect_db_parse_codec_list(struct dect_pt *pt, const char *key, + const char *val) +{ + struct dect_ie_codec_list *cl; + unsigned int i; + + if (strncmp(key, "codec_list/", strlen("codec_list/"))) + return -1; + key += strlen("codec_list/"); + + cl = pt->codec_list; + if (cl == NULL) { + pt->codec_list = (void *)dect_ie_alloc(dh, sizeof(*cl)); + cl = pt->codec_list; + } + + if (sscanf(key, "%u/", &i) != 1) + return -1; + key = strchr(key, '/') + 1; + + if (!strcmp(key, "codec")) + cl->entry[i].codec = dect_parse_num(val); + else if (!strcmp(key, "service")) + cl->entry[i].service = dect_parse_num(val); + else if (!strcmp(key, "slot")) + cl->entry[i].slot = dect_parse_num(val); + else if (!strcmp(key, "cplane")) + cl->entry[i].cplane = dect_parse_num(val); + else + return -1; + + if (i + 1 > cl->num) + cl->num = i + 1; + return 0; +} + +static void dect_db_store_codec_list(const struct dect_pt *pt) +{ + const struct dect_ie_codec_list *cl = pt->codec_list; + unsigned int i; + + for (i = 0; i < cl->num; i++) { + dect_db_put_numbered_num(pt, "codec_list/%u/codec", i, cl->entry[i].codec); + dect_db_put_numbered_num(pt, "codec_list/%u/service", i, cl->entry[i].service); + dect_db_put_numbered_num(pt, "codec_list/%u/slot", i, cl->entry[i].slot); + dect_db_put_numbered_num(pt, "codec_list/%u/cplane", i, cl->entry[i].cplane); + } +} + +static int dect_db_parse_termcap(struct dect_pt *pt, const char *key, + const char *val) +{ + struct dect_ie_terminal_capability *tc; + + if (strncmp(key, "capabilities/", strlen("capabilities/"))) + return -1; + key += strlen("capabilities/"); + + tc = pt->terminal_capability; + if (tc == NULL) { + pt->terminal_capability = (void *)dect_ie_alloc(dh, sizeof(*tc)); + tc = pt->terminal_capability; + } + + if (!strcmp(key, "tone_capabilities")) + tc->tone = dect_parse_num(val); + else if (!strcmp(key, "echo_parameters")) + tc->echo = dect_parse_num(val); + else if (!strcmp(key, "noise_rejection")) + tc->noise_rejection = dect_parse_num(val); + else if (!strcmp(key, "volume_ctrl")) + tc->volume_ctrl = dect_parse_num(val); + else if (!strcmp(key, "slot_capabilities")) + tc->slot = dect_parse_num(val); + else if (!strcmp(key, "display_capabilities")) + tc->display = dect_parse_num(val); + else if (!strcmp(key, "display_memory")) + tc->display_memory = dect_parse_num(val); + else if (!strcmp(key, "display_lines")) + tc->display_lines = dect_parse_num(val); + else if (!strcmp(key, "display_columns")) + tc->display_columns = dect_parse_num(val); + else if (!strcmp(key, "display_control")) + tc->display_control = dect_parse_num(val); + else if (!strcmp(key, "display_charsets")) + tc->display_charsets = dect_parse_num(val); + else if (!strcmp(key, "scrolling")) + tc->scrolling = dect_parse_num(val); + else if (!strcmp(key, "profile_indicator")) + tc->profile_indicator = dect_parse_num(val); + else + return -1; + return 0; +} + +static void dect_db_store_termcap(const struct dect_pt *pt) +{ + const struct dect_ie_terminal_capability *tc = pt->terminal_capability; + + dect_db_put_num(pt, "capabilities/tone_capabilities", tc->tone); + dect_db_put_num(pt, "capabilities/echo_parameters", tc->echo); + dect_db_put_num(pt, "capabilities/noise_rejection", tc->noise_rejection); + dect_db_put_num(pt, "capabilities/volume_ctrl", tc->volume_ctrl); + dect_db_put_num(pt, "capabilities/slot_capabilities", tc->slot); + dect_db_put_num(pt, "capabilities/display_capabilities", tc->display); + dect_db_put_num(pt, "capabilities/display_memory", tc->display_memory); + dect_db_put_num(pt, "capabilities/display_lines", tc->display_lines); + dect_db_put_num(pt, "capabilities/display_columns", tc->display_columns); + dect_db_put_num(pt, "capabilities/display_control", tc->display_control); + dect_db_put_num(pt, "capabilities/display_charsets", tc->display_charsets); + dect_db_put_num(pt, "capabilities/scrolling", tc->scrolling); + dect_db_put_num(pt, "capabilities/profile_indicator", tc->profile_indicator); +} + +static int dect_db_parse_global(struct dect_pt *pt, const char *key, + const char *val) +{ + if (!strcmp(key, "name")) { + ast_copy_string(pt->name, val, sizeof(pt->name)); + snprintf(pt->fullname, sizeof(pt->fullname), "DECT/%s", pt->name); + } else if (!strcmp(key, "regexten")) + dect_pt_set_extension(pt, strtoul(val, NULL, 0)); + else if (!strcmp(key, "context")) + ast_string_field_set(pt, context, val); + else if (!strcmp(key, "language")) + ast_string_field_set(pt, language, val); + else if (!strcmp(key, "cid_num")) + ast_string_field_set(pt, cid_num, val); + else if (!strcmp(key, "cid_name")) + ast_string_field_set(pt, cid_name, val); + else if (!strcmp(key, "ring_pattern")) + ast_string_field_set(pt, ring_pattern, val); + else if (!strcmp(key, "uak")) + dect_parse_base64(val, pt->uak, sizeof(pt->uak)); + else + return -1; + return 0; +} + +static void dect_db_store_portable(const struct dect_pt *pt) +{ + dect_db_put(pt, "name", pt->name); + dect_db_put(pt, "regexten", pt->regexten); + dect_db_put(pt, "context", pt->context); + dect_db_put(pt, "language", pt->language); + dect_db_put(pt, "cid_num", pt->cid_num); + dect_db_put(pt, "cid_name", pt->cid_name); + dect_db_put(pt, "ring_pattern", pt->ring_pattern); + dect_db_put_base64(pt, "uak", pt->uak, sizeof(pt->uak)); +} + +static int dect_init_call(struct dect_pvt *pvt, enum ast_channel_state state, + const char *exten) +{ + struct dect_pt *pt = pvt->pt; + struct ast_channel *chan; + + chan = ast_channel_alloc(1, state, pt->cid_num, pt->cid_name, "", + exten, pt->context, 0, 0, + "DECT/%s-%08lx", pt->name, (long)pvt); + if (chan == NULL) + return -1; + + chan->tech = &dect_tech; + ast_format_set(&chan->readformat, AST_FORMAT_G726_AAL2, 0); + ast_format_set(&chan->writeformat, AST_FORMAT_G726_AAL2, 0); + ast_format_cap_add(chan->nativeformats, &chan->readformat); + chan->tech_pvt = pvt; + + if (exten != NULL) + ast_copy_string(chan->exten, exten, sizeof(chan->exten)); + if (!ast_strlen_zero(pt->language)) + ast_string_field_set(chan, language, pt->language); + + ast_jb_configure(chan, &dect_cfg.jbconf); + + ast_module_ref(ast_module_info->self); + pvt->chan = chan; + + if (state != AST_STATE_RESERVED && state != AST_STATE_DIALING) { + if (ast_pbx_start(chan)) { + ast_log(LOG_WARNING, "Unable to start PBX\n"); + ast_hangup(chan); + } + } + return 0; +} + +static int dect_try_to_connect_call(struct dect_pvt *pvt) +{ + struct ast_channel *chan = pvt->chan; + struct dect_pt *pt = pvt->pt; + + if (!ast_exists_extension(NULL, pt->context, chan->exten, 1, NULL)) + return -1; + dect_pt_log(pt, "connecting call\n"); + if (ast_pbx_start(chan)) { + ast_log(LOG_WARNING, "Unable to start PBX\n"); + ast_hangup(chan); + } else + ast_setstate(chan, AST_STATE_RING); + return 0; +} + +static int dect_answer(struct ast_channel *chan) +{ + struct dect_pvt *pvt = chan->tech_pvt; + struct dect_pt *pt = pvt->pt; + struct dect_mncc_connect_param connect = {}; + + dect_pt_log(pt, "answer call state %u\n", chan->_state); + if (chan->_state == AST_STATE_UP || chan->_state == AST_STATE_RINGING) + return 0; + + dect_mncc_connect_req(dh, pvt->call, &connect); + ast_setstate(chan, AST_STATE_UP); + return 0; +} + +static void dect_call_auth_cfm(struct dect_mm_pvt *mmp, bool success, + struct dect_ie_collection *iec) +{ + struct dect_call *call = mmp->call; + struct dect_pvt *pvt = dect_call_priv(call); + struct ast_channel *chan = pvt->chan; + struct dect_ie_basic_service basic_service; + struct dect_ie_calling_party_number calling_party_number; + struct dect_ie_calling_party_name calling_party_name; + struct dect_mncc_setup_param param; + int presentation; + + if (!success) + return; + + basic_service.class = DECT_CALL_CLASS_NORMAL; + basic_service.service = DECT_SERVICE_BASIC_SPEECH_DEFAULT; + + presentation = ast_party_id_presentation(&chan->connected.id); + if (chan->connected.id.number.valid) { + calling_party_number.type = DECT_NUMBER_TYPE_UNKNOWN; + calling_party_number.npi = DECT_NPI_UNKNOWN; + calling_party_number.presentation = dect_ast_presentation_to_dect(presentation); + calling_party_number.screening = dect_ast_screening_to_dect(presentation); + calling_party_number.len = strlen(chan->connected.id.number.str); + memcpy(calling_party_number.address, chan->connected.id.number.str, + calling_party_number.len); + } else + calling_party_number.len = 0; + + if (chan->connected.id.name.valid) { + calling_party_name.presentation = dect_ast_presentation_to_dect(presentation); + calling_party_name.screening = dect_ast_screening_to_dect(presentation); + calling_party_name.alphabet = DECT_ALPHABET_STANDARD; + calling_party_name.len = strlen(chan->connected.id.name.str); + memcpy(calling_party_name.name, chan->connected.id.name.str, + calling_party_name.len); + } else + calling_party_name.len = 0; + + memset(¶m, 0, sizeof(param)); + param.basic_service = &basic_service; + if (calling_party_number.len) + param.calling_party_number = &calling_party_number; + if (calling_party_name.len) + param.calling_party_name = &calling_party_name; + + dect_mncc_setup_req(dh, pvt->call, &pvt->pt->ipui, ¶m); +} + +static int dect_call(struct ast_channel *chan, char *dest, int timeout) +{ + struct dect_pvt *pvt = chan->tech_pvt; + struct dect_mm_pvt *mmp = dect_mm_priv(pvt->mme); + + dect_cipher(mmp, dect_call_auth_cfm, NULL); + + ast_setstate(chan, AST_STATE_DIALING); + chan->hangupcause = AST_CAUSE_NORMAL_CLEARING; + return 0; +} + +static struct ast_channel *dect_request_call(const char *type, struct ast_format_cap *cap, + const struct ast_channel *requestor, + void *data, int *cause) +{ + struct dect_call *call; + struct dect_pvt *pvt; + struct dect_mm_endpoint *mme; + struct dect_mm_pvt *mmp; + struct dect_pt *pt; + const char *name = data; + + pt = dect_pt_get_by_name(name); + if (pt == NULL) { + ast_log(LOG_NOTICE, "Call to unknown PT '%s' requested\n", name); + *cause = AST_CAUSE_UNALLOCATED; + return NULL; + } + dect_pt_log(pt, "outgoing call\n"); + + call = dect_call_alloc(dh); + if (call == NULL) { + *cause = AST_CAUSE_SWITCH_CONGESTION; + return NULL; + } + + mme = dect_mm_endpoint_get(dh, &pt->ipui); + if (mme == NULL) { + *cause = AST_CAUSE_SWITCH_CONGESTION; + return NULL; + } + + mmp = dect_mm_priv(mme); + mmp->pt = pt; + mmp->mme = mme; + mmp->call = call; + + pvt = dect_call_priv(call); + pvt->pt = pt; + pvt->mme = mme; + pvt->call = call; + if (dect_init_call(pvt, AST_STATE_RESERVED, NULL) < 0) { + *cause = AST_CAUSE_SWITCH_CONGESTION; + return NULL; + } + + return pvt->chan; +} + +static int dect_hangup(struct ast_channel *chan) +{ + struct dect_pvt *pvt = chan->tech_pvt; + struct dect_ie_release_reason release_reason; + struct dect_mncc_release_param param; + + if (chan->_state != AST_STATE_DOWN) { + dect_pt_log(pvt->pt, "hangup\n"); + + dect_ie_init(&release_reason); + release_reason.reason = DECT_RELEASE_NORMAL; + + memset(¶m, 0, sizeof(param)); + param.release_reason = &release_reason; + dect_mncc_release_req(dh, pvt->call, ¶m); + } + + chan->tech_pvt = NULL; + ast_setstate(chan, AST_STATE_DOWN); + ast_module_unref(ast_module_info->self); + return 0; +} + +static int dect_indicate(struct ast_channel *chan, int condition, + const void *data, size_t datalen) +{ + struct dect_pvt *pvt = chan->tech_pvt; + struct dect_pt *pt = pvt->pt; + struct dect_mncc_alert_param alert; + struct dect_ie_progress_indicator progress; + struct dect_ie_signal signal; + int res = 0; + + switch (condition) { + case AST_CONTROL_RINGING: + dect_pt_log(pt, "call is ringing\n"); + memset(&alert, 0, sizeof(alert)); + if (0) { + alert.signal = dect_signal_init(&signal, DECT_SIGNAL_RING_BACK_TONE_ON); + } else { + dect_ie_init(&progress); + progress.location = DECT_LOCATION_PRIVATE_NETWORK_SERVING_LOCAL_USER; + progress.progress = DECT_PROGRESS_INBAND_INFORMATION_NOW_AVAILABLE; + dect_ie_list_add(&progress, &alert.progress_indicator); + } + dect_mncc_alert_req(dh, pvt->call, &alert); + ast_setstate(chan, AST_STATE_RINGING); + res = -1; + break; + case AST_CONTROL_BUSY: + case AST_CONTROL_CONGESTION: + case AST_CONTROL_PROCEEDING: + case AST_CONTROL_PROGRESS: + case AST_CONTROL_HOLD: + case AST_CONTROL_UNHOLD: + case AST_CONTROL_VIDUPDATE: + case AST_CONTROL_T38_PARAMETERS: + case AST_CONTROL_SRCUPDATE: + case -1: + res = -1; + dect_pt_log(pt, "indicate condition %d\n", condition); + break; + default: + dect_pt_log(pt, "indicate unknown condition %d\n", condition); + res = -1; + break; + } + return res; +} + +static int dect_write(struct ast_channel *chan, struct ast_frame *f) +{ + struct dect_pvt *pvt = chan->tech_pvt; + struct dect_msg_buf mb; + + mb.data = f->data.ptr; + mb.len = f->datalen; + + dect_dl_u_data_req(dh, pvt->call, &mb); + return 0; +} + +static struct ast_frame *dect_read(struct ast_channel *chan) +{ + ast_log(LOG_NOTICE, "audio underrun?\n"); + return &ast_null_frame; +} + +static int dect_fixup(struct ast_channel *old, struct ast_channel *new) +{ + ast_log(LOG_NOTICE, "fixup\n"); + return 0; +} + +static int dect_send_text(struct ast_channel *chan, const char *text) +{ + struct dect_pvt *pvt = chan->tech_pvt; + struct dect_ie_display display; + struct dect_mncc_info_param info = { .display = &display }; + + dect_display_init(&display); + dect_display_append_char(&display, DECT_C_CLEAR_DISPLAY); + dect_display_append(&display, text, strlen(text)); + + dect_mncc_info_req(dh, pvt->call, &info); + return 0; +} + +static struct ast_channel_tech dect_tech = { + .type = "DECT", + .description = "Digital Enhanced Cordless Telecommunications (DECT)", + .answer = dect_answer, + .requester = dect_request_call, + .call = dect_call, + .hangup = dect_hangup, + .indicate = dect_indicate, + .write = dect_write, + .read = dect_read, + .fixup = dect_fixup, + .send_text = dect_send_text, +}; + +static void dect_mncc_reject(struct dect_call *call, enum dect_release_reasons reason) +{ + struct dect_ie_release_reason release_reason = { .reason = reason }; + struct dect_mncc_release_param release = { .release_reason = &release_reason }; + + dect_mncc_reject_req(dh, call, &release); +} + +static const struct { + const char *name; + unsigned int priority; +} dect_codecs[] = { + [DECT_CODEC_USER_SPECIFIC_32KBIT] = { "user specific 32kbit", 0 }, + [DECT_CODEC_G726_32KBIT] = { "G.726", 1 }, + [DECT_CODEC_G722_64KBIT] = { "G.722", 2 }, + [DECT_CODEC_G711_ALAW_64KBIT] = { "G.711 alaw", 0 }, + [DECT_CODEC_G711_ULAW_64KBIT] = { "G.711 ulaw", 0 }, + [DECT_CODEC_G729_1_32KBIT] = { "G.729.1", 0 }, + [DECT_CODEC_MPEG4_ER_AAC_LD_32KBIT] = { "MPEG-4 ER AAC-LD 32kbit", 0 }, + [DECT_CODEC_MPEG4_ER_AAC_LD_64KBIT] = { "MPEG-4 ER AAC-LD 64kbit", 0 }, + [DECT_CODEC_USER_SPECIFIC_64KBIT] = { "user specific 64kbit", 0 }, +}; + +static char *dect_codecs_merge(char *buf, size_t size, const struct dect_pt *pt) +{ + struct dect_ie_codec_list *codec_list = pt->codec_list; + const char *sep = ""; + unsigned int i; + + buf[0] = '\0'; + if (codec_list == NULL) { + strcat(buf, dect_codecs[DECT_CODEC_G726_32KBIT].name); + return buf; + } + + for (i = 0; i < codec_list->num; i++) { + strcat(buf, sep); + strcat(buf, dect_codecs[codec_list->entry[i].codec].name); + sep = ", "; + } + + return buf; +} + +static struct dect_ie_codec_list *dect_select_codec(const struct dect_pt *pt, + struct dect_ie_codec_list *codec_list) +{ + unsigned int i, prio, best = 0, n = 0; + + if (pt->codec_list == NULL) + return NULL; + + for (i = 0; i < pt->codec_list->num; i++) { + prio = dect_codecs[pt->codec_list->entry[i].codec].priority; + if (prio > best) { + best = prio; + n = i; + } + } + + codec_list->negotiation = DECT_NEGOTIATION_CODEC; + codec_list->num = 1; + codec_list->entry[0] = pt->codec_list->entry[n]; + + return codec_list; +} + +static void dect_mncc_setup_auth_cfm(struct dect_mm_pvt *mmp, bool success, + struct dect_ie_collection *iec) +{ + struct dect_mncc_setup_param *param = (void *)iec; + struct dect_mncc_setup_ack_param setup_ack; + struct dect_mncc_call_proc_param call_proc; + struct dect_ie_delimiter_request delimiter_request; + struct dect_ie_codec_list codec_list; + struct dect_ie_signal signal; + struct dect_pt *pt = mmp->pt; + struct dect_call *call = mmp->call; + struct dect_pvt *pvt = dect_call_priv(call); + enum ast_channel_state state; + const char *exten; + + if (!success) + return dect_mncc_reject(call, DECT_RELEASE_AUTHENTICATION_FAILED); + + /* If the phone supplies the entire number en-bloc, enter call + * proceeding state. A number is considered complete when contained + * in a called party number IE, or a multi-keypad IE and the extension + * exists. When no number is supplied and an 's' extension exists in + * the context, the call is also moved to call proceeding state and + * further decisions are left to the PBX. + * + * Otherwise enter overlap sending and wait for further information. + */ + state = AST_STATE_RING; + exten = NULL; + + if (param->called_party_number != NULL) + ; //ast_copy_string(exten, param->called_party_number.info, sizeof(exten)); + else if (param->keypad != NULL && param->keypad->info[0] < 0x80) + exten = (char *)param->keypad->info; + else { + exten = "s"; + if (!ast_exists_extension(NULL, pt->context, exten, 1, NULL)) { + state = AST_STATE_DIALING; + exten = ""; + } + } + + if (state == AST_STATE_RING) { + memset(&call_proc, 0, sizeof(call_proc)); + call_proc.codec_list = dect_select_codec(pt, &codec_list); + dect_mncc_call_proc_req(dh, call, &call_proc); + } else { + dect_ie_init(&delimiter_request); + dect_signal_init(&signal, DECT_SIGNAL_DIAL_TONE_ON); + + memset(&setup_ack, 0, sizeof(setup_ack)); + setup_ack.delimiter_request = &delimiter_request; + setup_ack.signal = &signal; + dect_mncc_setup_ack_req(dh, call, &setup_ack); + } + + dect_init_call(pvt, state, exten); + + /* WTF? Asterisk eats (actually leaks) the first queued frame when + * answering a channel. Give it something to eat :| */ + if (state == AST_STATE_DIALING) + ast_queue_control(pvt->chan, -1); + +} + +static void dect_mncc_setup_ind(struct dect_handle *dh, struct dect_call *call, + struct dect_mncc_setup_param *param) +{ + struct dect_pvt *pvt = dect_call_priv(call); + struct dect_mm_endpoint *mme; + struct dect_mm_pvt *mmp; + struct dect_pt *pt; + + pt = dect_pt_get_by_ipui(dect_call_portable_identity(call)); + if (pt == NULL) { + ast_log(LOG_NOTICE, "Incoming call from unknown PT\n"); + return dect_mncc_reject(call, DECT_RELEASE_UNKNOWN_IDENTITY); + } + dect_pt_log(pt, "incoming call\n"); + + mme = dect_mm_endpoint_get(dh, &pt->ipui); + if (mme == NULL) + return dect_mncc_reject(call, DECT_RELEASE_INSUFFICIENT_RESOURCES); + + mmp = dect_mm_priv(mme); + mmp->pt = pt; + mmp->mme = mme; + mmp->call = call; + + pvt->pt = pt; + pvt->call = call; + + dect_cipher(mmp, dect_mncc_setup_auth_cfm, ¶m->common); +} + +static void dect_mncc_setup_ack_ind(struct dect_handle *dh, struct dect_call *call, + struct dect_mncc_setup_ack_param *param) +{ +} + +static void dect_mncc_reject_ind(struct dect_handle *dh, struct dect_call *call, + enum dect_causes cause, + struct dect_mncc_release_param *param) +{ + struct dect_pvt *pvt = dect_call_priv(call); + int ast_cause; + + ast_setstate(pvt->chan, AST_STATE_DOWN); + if (cause == DECT_CAUSE_PEER_MESSAGE && + param->release_reason != NULL) + ast_cause = dect_release_reason_to_ast(param->release_reason->reason); + else + ast_cause = AST_CAUSE_FAILURE; + + pvt->chan->hangupcause = ast_cause; + ast_queue_hangup(pvt->chan); +} + +static void dect_mncc_alert_ind(struct dect_handle *dh, struct dect_call *call, + struct dect_mncc_alert_param *param) +{ + struct dect_pvt *pvt = dect_call_priv(call); + struct dect_ie_signal signal; + struct dect_mncc_info_param info = { .signal = &signal, }; + char pattern[16]; + const char *c; + + ast_copy_string(pattern, pvt->pt->ring_pattern, sizeof(pattern)); + c = pbx_builtin_getvar_helper(pvt->chan, "RING_PATTERN"); + if (c != NULL) + ast_copy_string(pattern, c, sizeof(pattern)); + + if (strcasecmp(pattern, "silent")) { + dect_ie_init(&signal); + signal.code = DECT_SIGNAL_ALERTING_BASE | (atoi(pattern) & 0xf); + + dect_mncc_info_req(dh, call, &info); + } + + ast_queue_control(pvt->chan, AST_CONTROL_RINGING); + ast_setstate(pvt->chan, AST_STATE_RINGING); +} + +static void dect_mncc_connect_ind(struct dect_handle *dh, struct dect_call *call, + struct dect_mncc_connect_param *param) +{ + struct dect_pvt *pvt = dect_call_priv(call); + + ast_queue_control(pvt->chan, AST_CONTROL_ANSWER); + dect_mncc_connect_res(dh, call, param); +} + +static void dect_mncc_release_ind(struct dect_handle *dh, struct dect_call *call, + struct dect_mncc_release_param *param) +{ + struct dect_pvt *pvt = dect_call_priv(call); + + ast_setstate(pvt->chan, AST_STATE_DOWN); + pvt->chan->hangupcause = + dect_release_reason_to_ast(param->release_reason->reason); + ast_queue_hangup(pvt->chan); + + dect_mncc_release_res(dh, call, param); +} + +static void dect_mncc_release_cfm(struct dect_handle *dh, struct dect_call *call, + enum dect_causes cause, + struct dect_mncc_release_param *param) +{ +} + +static void dect_dl_u_data_ind(struct dect_handle *dh, struct dect_call *call, + struct dect_msg_buf *mb) +{ + struct dect_pvt *pvt = dect_call_priv(call); + struct ast_frame f; + + if (pvt->chan->_state == AST_STATE_DOWN) + return; + + memset(&f, 0, sizeof(f)); + f.frametype = AST_FRAME_VOICE; + f.subclass.integer = AST_FORMAT_G726_AAL2; + f.samples = mb->len * 2; + f.datalen = mb->len; + f.data.ptr = mb->data; + f.offset = AST_FRIENDLY_OFFSET; + + ast_queue_frame(pvt->chan, &f); +} + +static void dect_keypad_info(struct dect_pvt *pvt, + const struct dect_ie_keypad *keypad) +{ + struct ast_frame f = { .frametype = AST_FRAME_DTMF, }; + struct ast_channel *chan = pvt->chan; + unsigned int i; + + if (chan->_state == AST_STATE_DIALING) { + strncat(chan->exten, (char *)keypad->info, keypad->len); + dect_try_to_connect_call(pvt); + } else { + for (i = 0; i < keypad->len; i++) { + ast_verbose("keypad: '%c' (%x)\n", keypad->info[i], keypad->info[i]); + f.subclass.integer = keypad->info[i]; + ast_queue_frame(chan, &f); + } + } +} + +static void dect_mncc_info_ind(struct dect_handle *dh, struct dect_call *call, + struct dect_mncc_info_param *param) +{ + struct dect_pvt *pvt = dect_call_priv(call); + + if (param->keypad != NULL) + dect_keypad_info(pvt, param->keypad); +} + +static const struct dect_cc_ops dect_cc_ops = { + .priv_size = sizeof(struct dect_pvt), + .mncc_setup_ind = dect_mncc_setup_ind, + .mncc_setup_ack_ind = dect_mncc_setup_ack_ind, + .mncc_reject_ind = dect_mncc_reject_ind, + .mncc_alert_ind = dect_mncc_alert_ind, + .mncc_connect_ind = dect_mncc_connect_ind, + .mncc_release_ind = dect_mncc_release_ind, + .mncc_release_cfm = dect_mncc_release_cfm, + .mncc_facility_ind = NULL, + .mncc_info_ind = dect_mncc_info_ind, + .mncc_modify_ind = NULL, + .mncc_modify_cfm = NULL, + .mncc_hold_ind = NULL, + .mncc_hold_cfm = NULL, + .mncc_retrieve_ind = NULL, + .mncc_retrieve_cfm = NULL, + .mncc_iwu_info_ind = NULL, + .dl_u_data_ind = dect_dl_u_data_ind, +}; + +/* + * Authentication / Key Allocation / Ciphering + */ + +static void dect_mm_authenticate_reject(struct dect_mm_endpoint *mme, + enum dect_reject_reasons reason) +{ + struct dect_ie_reject_reason reject_reason = { .reason = reason }; + struct dect_mm_authenticate_param reply = { .reject_reason = &reject_reason }; + + dect_mm_authenticate_res(dh, mme, false, &reply); +} + +static void dect_mm_authenticate_ind(struct dect_handle *dh, + struct dect_mm_endpoint *mme, + struct dect_mm_authenticate_param *param) +{ + struct dect_mm_pvt *mmp = dect_mm_priv(mme); + typeof(mmp->auth_cfm) auth_cfm = mmp->auth_cfm; + struct dect_ie_collection *iec = mmp->iec; + struct dect_ie_auth_value rs; + struct dect_ie_auth_res res; + struct dect_mm_authenticate_param reply = { + .res = &res, + .rs = &rs, + }; + uint8_t k[DECT_AUTH_KEY_LEN], ks[DECT_AUTH_KEY_LEN]; + uint8_t dck[DECT_CIPHER_KEY_LEN]; + uint8_t ac[DECT_AUTH_CODE_LEN]; + uint32_t res1; + + if (param->auth_type->auth_id != DECT_AUTH_DSAA) { + dect_mm_authenticate_reject(mme, DECT_REJECT_AUTHENTICATION_ALGORITHM_NOT_SUPPORTED); + return; + } + + dect_pin_to_ac(dect_cfg.pin, ac, sizeof(ac)); + dect_auth_b1(ac, sizeof(ac), k); + + if (mmp->auth_type == DECT_AUTH_KEY_ALLOCATION) { + /* Reset state before invoking completion handler since it may invoke + * a new authentication procedure. + */ + mmp->auth_type = DECT_AUTH_NONE; + mmp->auth_cfm = NULL; + mmp->iec = NULL; + + dect_auth_a11(k, mmp->rs, ks); + dect_auth_a12(ks, mmp->rand, dck, &res1); + + if (res1 == param->res->value) { + ast_log(LOG_NOTICE, "PT authentication succeeded\n"); + + rs.value = mmp->rs; + dect_auth_a21(k, rs.value, ks); + dect_auth_a22(ks, param->rand->value, &res.value); + + dect_mm_authenticate_res(dh, mme, true, &reply); + + /* Store KS' as UAK */ + memcpy(mmp->uak, ks, sizeof(mmp->uak)); + auth_cfm(mmp, true, iec); + } else { + ast_log(LOG_NOTICE, "PT authentication failed\n"); + dect_mm_authenticate_reject(mme, DECT_REJECT_AUTHENTICATION_FAILED); + auth_cfm(mmp, false, iec); + } + + if (iec != NULL) + __dect_ie_collection_put(dh, iec); + } else { + ast_log(LOG_NOTICE, "FT authentication\n"); + + rs.value = dect_random(); + dect_auth_a21(k, rs.value, ks); + dect_auth_a22(ks, param->rand->value, &res.value); + + dect_mm_authenticate_res(dh, mme, true, &reply); + } +} + +static void dect_mm_authenticate_cfm(struct dect_handle *dh, + struct dect_mm_endpoint *mme, bool accept, + struct dect_mm_authenticate_param *param) +{ + struct dect_mm_pvt *mmp = dect_mm_priv(mme); + struct dect_pt *pt = mmp->pt; + enum dect_auth_types auth_type = mmp->auth_type; + typeof(mmp->auth_cfm) auth_cfm = mmp->auth_cfm; + struct dect_ie_collection *iec = mmp->iec; + uint8_t k[DECT_AUTH_KEY_LEN], ks[DECT_AUTH_KEY_LEN]; + uint8_t dck[DECT_CIPHER_KEY_LEN]; + uint32_t res1; + + /* Reset state before invoking completion handler since it may invoke + * a new authentication procedure. + */ + mmp->auth_type = DECT_AUTH_NONE; + mmp->auth_cfm = NULL; + mmp->iec = NULL; + + if (!accept) + goto reject; + + if (auth_type == DECT_AUTH_PT) + dect_auth_b1(pt->uak, sizeof(pt->uak), k); + else + dect_auth_b2(pt->uak, sizeof(pt->uak), + mmp->upi, sizeof(mmp->upi), k); + + dect_auth_a11(k, mmp->rs, ks); + dect_auth_a12(ks, mmp->rand, dck, &res1); + + if (res1 == param->res->value) { + dect_pt_log(pt, "authentication succeeded\n"); + + /* Store DCK */ + memcpy(pt->dck, dck, sizeof(pt->dck)); + auth_cfm(mmp, true, iec); + } else { +reject: + dect_pt_log(pt, "authentication failed\n"); + auth_cfm(mmp, false, iec); + } + + if (iec != NULL) + __dect_ie_collection_put(dh, iec); +} + +static void dect_authenticate(struct dect_mm_pvt *mmp, enum dect_auth_types type, + void (*auth_cfm)(struct dect_mm_pvt *, bool, + struct dect_ie_collection *), + struct dect_ie_collection *iec) +{ + struct dect_ie_auth_type auth_type; + struct dect_ie_auth_value rand, rs; + struct dect_mm_authenticate_param req = { + .auth_type = &auth_type, + .rand = &rand, + .rs = &rs, + }; + + mmp->auth_type = type; + mmp->auth_cfm = auth_cfm; + if (iec != NULL) + mmp->iec = __dect_ie_collection_hold(iec); + + mmp->rand = dect_random(); + mmp->rs = dect_random(); + + auth_type.auth_id = DECT_AUTH_DSAA; + if (type == DECT_AUTH_PT) + auth_type.auth_key_type = DECT_KEY_USER_AUTHENTICATION_KEY; + else + auth_type.auth_key_type = DECT_KEY_USER_PERSONAL_IDENTITY; + auth_type.auth_key_num = 0 | DECT_AUTH_KEY_IPUI_PARK; + auth_type.cipher_key_num = 0; + auth_type.flags = DECT_AUTH_FLAG_UPC; + rand.value = mmp->rand; + rs.value = mmp->rs; + + dect_mm_authenticate_req(dh, mmp->mme, &req); +} + +static void dect_mm_key_allocate(struct dect_mm_pvt *mmp, + void (*auth_cfm)(struct dect_mm_pvt *, bool, + struct dect_ie_collection *iec), + struct dect_ie_collection *iec) +{ + struct dect_ie_allocation_type allocation_type; + struct dect_ie_auth_value rand, rs; + struct dect_mm_key_allocate_param req = { + .allocation_type = &allocation_type, + .rand = &rand, + .rs = &rs, + }; + + mmp->auth_type = DECT_AUTH_KEY_ALLOCATION; + mmp->auth_cfm = auth_cfm; + mmp->iec = __dect_ie_collection_hold(iec); + + mmp->rand = dect_random(); + mmp->rs = dect_random(); + + allocation_type.auth_id = DECT_AUTH_DSAA; + allocation_type.auth_key_num = 0 | DECT_AUTH_KEY_IPUI_PARK; + allocation_type.auth_code_num = 0 | DECT_AUTH_KEY_IPUI_PARK; + rand.value = mmp->rand; + rs.value = mmp->rs; + + dect_mm_key_allocate_req(dh, mmp->mme, &req); +} + +static void dect_mm_cipher_ind(struct dect_handle *dh, + struct dect_mm_endpoint *mme, + struct dect_mm_cipher_param *param) +{ + +} + +static void dect_mm_cipher_cfm(struct dect_handle *dh, + struct dect_mm_endpoint *mme, bool accept, + struct dect_mm_cipher_param *param) +{ + struct dect_mm_pvt *mmp = dect_mm_priv(mme); + typeof(mmp->cipher_cfm) cipher_cfm = mmp->cipher_cfm; + struct dect_ie_collection *iec = mmp->iec; + + mmp->ciphered = accept; + mmp->cipher_cfm = NULL; + mmp->iec = NULL; + + cipher_cfm(mmp, accept, iec); + + if (iec != NULL) + __dect_ie_collection_put(dh, iec); +} + +static void dect_cipher_auth_cfm(struct dect_mm_pvt *mmp, bool success, + struct dect_ie_collection *iec) +{ + struct dect_ie_cipher_info cipher_info; + struct dect_mm_cipher_param req = { + .cipher_info = &cipher_info, + }; + + if (iec != NULL) + mmp->iec = __dect_ie_collection_hold(iec); + + if (!success) + return dect_mm_cipher_cfm(dh, mmp->mme, success, NULL); + + cipher_info.enable = true; + cipher_info.cipher_alg_id = DECT_CIPHER_STANDARD_1; + cipher_info.cipher_key_type = DECT_CIPHER_DERIVED_KEY; + cipher_info.cipher_key_num = 0; + + dect_mm_cipher_req(dh, mmp->mme, &req, mmp->pt->dck); +} + +/* Authenticate the PT, thereby establishing a new DCK, then switch to ciphering. */ +static void dect_cipher(struct dect_mm_pvt *mmp, + void (*cipher_cfm)(struct dect_mm_pvt *, bool, + struct dect_ie_collection *), + struct dect_ie_collection *iec) +{ + if (mmp->ciphered) + return cipher_cfm(mmp, true, iec); + + mmp->cipher_cfm = cipher_cfm; + if (iec != NULL) + mmp->iec = __dect_ie_collection_hold(iec); + + dect_authenticate(mmp, DECT_AUTH_PT, dect_cipher_auth_cfm, mmp->iec); +} + +/* + * Access rights procedures + */ + +static int dect_access_rights_timer(const void *data) +{ + ast_log(LOG_NOTICE, "disabling access rights requests\n"); + dect_fpc.hlc &= ~DECT_HLC_ACCESS_RIGHTS_REQUESTS; + dect_fpc.ehlc2 &= ~DECT_EHLC2_EASY_PAIRING; + dect_llme_rfp_preload_req(dh, &dect_fpc); + return 0; +} + +static int dect_access_rights_requests_enable(void) +{ + int id; + + if (dect_fpc.hlc & DECT_HLC_ACCESS_RIGHTS_REQUESTS) + return 0; + + id = ast_sched_add(sched, 120 * 1000, dect_access_rights_timer, NULL); + + dect_fpc.hlc |= DECT_HLC_ACCESS_RIGHTS_REQUESTS; + dect_fpc.ehlc2 |= DECT_EHLC2_EASY_PAIRING; + if (dect_llme_rfp_preload_req(dh, &dect_fpc) < 0) + goto err1; + + access_rights_timer = id; + return 0; + +err1: + AST_SCHED_DEL(sched, id); + return -1; +} + +static int dect_access_rights_requests_disable(void) +{ + if (!(dect_fpc.hlc & DECT_HLC_ACCESS_RIGHTS_REQUESTS)) + return 0; + + dect_fpc.hlc &= ~DECT_HLC_ACCESS_RIGHTS_REQUESTS; + dect_fpc.ehlc2 &= ~DECT_EHLC2_EASY_PAIRING; + if (dect_llme_rfp_preload_req(dh, &dect_fpc) < 0) + return -1; + + AST_SCHED_DEL(sched, access_rights_timer); + return 0; +} + +static void dect_mm_access_rights_reject(struct dect_mm_endpoint *mme, + enum dect_reject_reasons reason) +{ + struct dect_ie_reject_reason reject_reason = { .reason = reason }; + struct dect_mm_access_rights_param reply = { .reject_reason = &reject_reason }; + + dect_mm_access_rights_res(dh, mme, false, &reply); +} + +static void dect_mm_access_rights_auth_cfm(struct dect_mm_pvt *mmp, bool success, + struct dect_ie_collection *iec) +{ + struct dect_pt *pt; + struct dect_mm_endpoint *mme = mmp->mme; + struct dect_mm_access_rights_param *param = (void *)iec, reply = { + .portable_identity = param->portable_identity, + .auth_type = param->auth_type, + .cipher_info = param->cipher_info, + .codec_list = param->codec_list, + }; + + if (!success) { + dect_mm_access_rights_reject(mme, DECT_REJECT_AUTHENTICATION_FAILED); + return; + } + + pt = dect_pt_get_by_ipui(¶m->portable_identity->ipui); + if (pt == NULL) { + unsigned int exten; + char name[64]; + + if (!dect_alloc_extension(&exten)) { + dect_mm_access_rights_reject(mme, DECT_REJECT_INSUFFICIENT_MEMORY); + return; + } + + snprintf(name, sizeof(name), "DECT-PT-%u", exten); + pt = dect_init_portable(name); + if (pt == NULL) { + dect_mm_access_rights_reject(mme, DECT_REJECT_INSUFFICIENT_MEMORY); + return; + } + + dect_pt_set_ipui(pt, ¶m->portable_identity->ipui); + dect_pt_set_extension(pt, exten); + } + + dect_mm_access_rights_res(dh, mme, true, &reply); + dect_access_rights_requests_disable(); + + memcpy(pt->uak, mmp->uak, sizeof(pt->uak)); + dect_db_store_portable(pt); + + if (param->terminal_capability) { + dect_ie_update(pt->terminal_capability, param->terminal_capability); + dect_db_store_termcap(pt); + } + if (param->codec_list) { + dect_ie_update(pt->codec_list, param->codec_list); + dect_db_store_codec_list(pt); + } +} + +static void dect_mm_access_rights_ind(struct dect_handle *dh, + struct dect_mm_endpoint *mme, + struct dect_mm_access_rights_param *param) +{ + struct dect_ie_auth_type *auth_type = param->auth_type; + struct dect_ie_cipher_info *cipher_info = param->cipher_info; + struct dect_mm_pvt *mmp = dect_mm_priv(mme); + + if (!(dect_fpc.hlc & DECT_HLC_ACCESS_RIGHTS_REQUESTS)) + return dect_mm_access_rights_reject(mme, DECT_REJECT_INCOMPATIBLE_SERVICE); + + if (auth_type) { + if (auth_type->auth_id != DECT_AUTH_DSAA) + return dect_mm_access_rights_reject(mme, DECT_REJECT_AUTHENTICATION_ALGORITHM_NOT_SUPPORTED); + if (auth_type->auth_key_type != DECT_KEY_AUTHENTICATION_CODE) + return dect_mm_access_rights_reject(mme, DECT_REJECT_AUTHENTICATION_KEY_NOT_SUPPORTED); + } + + if (cipher_info) { + if (cipher_info->cipher_alg_id != DECT_CIPHER_STANDARD_1) + return dect_mm_access_rights_reject(mme, DECT_REJECT_CIPHER_ALGORITHM_NOT_SUPPORTED); + } + + mmp->mme = mme; + + dect_mm_key_allocate(mmp, dect_mm_access_rights_auth_cfm, ¶m->common); +} + +/* + * Access rights termination + */ + +static void dect_destroy_portable(struct dect_pt *pt) +{ + dect_register_extension(pt, false); + + AST_LIST_REMOVE(&dect_pt_list, pt, list); + AST_SCHED_DEL(sched, pt->locate_timer); + ast_free(pt); +} + +static void dect_mm_access_rights_terminate_reject(struct dect_mm_endpoint *mme, + enum dect_reject_reasons reason) +{ + struct dect_ie_reject_reason reject_reason = { .reason = reason }; + struct dect_mm_access_rights_terminate_param reply = { .reject_reason = &reject_reason }; + + dect_mm_access_rights_terminate_res(dh, mme, false, &reply); +} + +static void dect_mm_access_rights_terminate_auth_cfm(struct dect_mm_pvt *mmp, bool success, + struct dect_ie_collection *iec) +{ + struct dect_pt *pt = mmp->pt; + struct dect_mm_endpoint *mme = mmp->mme; + + if (!success) { + dect_mm_access_rights_terminate_reject(mme, DECT_REJECT_AUTHENTICATION_FAILED); + return; + } + + dect_db_remove(pt); + dect_destroy_portable(pt); +} + +static void dect_mm_access_rights_terminate_ind(struct dect_handle *dh, + struct dect_mm_endpoint *mme, + struct dect_mm_access_rights_terminate_param *param) +{ + struct dect_mm_pvt *mmp = dect_mm_priv(mme); + struct dect_pt *pt; + + pt = dect_pt_get_by_ipui(¶m->portable_identity->ipui); + if (pt == NULL) + return dect_mm_access_rights_terminate_reject(mme, DECT_REJECT_IPUI_UNKNOWN); + + mmp->pt = pt; + mmp->mme = mme; + + dect_cipher(mmp, dect_mm_access_rights_terminate_auth_cfm, ¶m->common); +} + +static void dect_mm_access_rights_terminate_cfm(struct dect_handle *dh, + struct dect_mm_endpoint *mme, bool accept, + struct dect_mm_access_rights_terminate_param *param) +{ + struct dect_mm_pvt *mmp = dect_mm_priv(mme); + struct dect_pt *pt = mmp->pt; + + dect_db_remove(pt); + dect_destroy_portable(pt); +} + +static void dect_access_rights_terminate(struct dect_pt *pt) +{ + struct dect_ie_portable_identity portable_identity; + struct dect_mm_access_rights_terminate_param param = { + .portable_identity = &portable_identity, + }; + struct dect_mm_endpoint *mme; + struct dect_mm_pvt *mmp; + + mme = dect_mm_endpoint_alloc(dh, &pt->ipui); + if (mme == NULL) + return; + mmp = dect_mm_priv(mme); + mmp->mme = mme; + mmp->pt = pt; + + portable_identity.type = DECT_PORTABLE_ID_TYPE_IPUI; + portable_identity.ipui = pt->ipui; + + dect_mm_access_rights_terminate_req(dh, mme, ¶m); +} + +/* + * Location procedures + */ + +static int dect_locate_timer(const void *data) +{ + struct dect_pt *pt = (struct dect_pt *)data;; + + dect_pt_log(pt, "location registation timeout\n"); + dect_register_extension(pt, false); + pt->locate_timer = -1; + return 0; +} + +static void dect_mm_locate_reject(struct dect_mm_endpoint *mme, + enum dect_reject_reasons reason) +{ + struct dect_ie_reject_reason reject_reason = { .reason = reason }; + struct dect_mm_locate_param reply = { .reject_reason = &reject_reason }; + + dect_mm_locate_res(dh, mme, false, &reply); +} + +static void dect_mm_locate_auth_cfm(struct dect_mm_pvt *mmp, bool success, + struct dect_ie_collection *iec) +{ + struct dect_pt *pt = mmp->pt; + struct dect_mm_endpoint *mme = mmp->mme; + struct dect_ie_portable_identity portable_identity; + struct dect_ie_duration duration; + struct dect_mm_locate_param *param = (void *)iec, reply = { + .portable_identity = &portable_identity, + .location_area = param->location_area, + .codec_list = param->codec_list, + .duration = &duration, + }; + unsigned int limit, timeout; + + if (!success) { + dect_mm_locate_reject(mme, DECT_REJECT_AUTHENTICATION_FAILED); + return; + } + + portable_identity.type = DECT_PORTABLE_ID_TYPE_TPUI; + portable_identity.tpui = pt->tpui; + + duration.lock = DECT_LOCK_TEMPORARY_USER_LIMIT_1; + if (dect_cfg.locate_duration * DECT_FRAMES_PER_SECOND <= + 255 * DECT_TIME_LIMIT_UNITS_1) { + limit = div_round_up(dect_cfg.locate_duration * + DECT_FRAMES_PER_SECOND, + DECT_TIME_LIMIT_UNITS_1); + timeout = div_round_up(limit * DECT_TIME_LIMIT_UNITS_1, + DECT_FRAMES_PER_SECOND); + duration.time = DECT_TIME_LIMIT_DEFINED_TIME_LIMIT_1; + } else { + limit = div_round_up(dect_cfg.locate_duration * + DECT_FRAMES_PER_SECOND, + DECT_TIME_LIMIT_UNITS_2); + timeout = div_round_up(limit * DECT_TIME_LIMIT_UNITS_2, + DECT_FRAMES_PER_SECOND); + duration.time = DECT_TIME_LIMIT_DEFINED_TIME_LIMIT_2; + } + duration.duration = limit; + + dect_mm_locate_res(dh, mme, true, &reply); + + if (param->terminal_capability) { + dect_ie_update(pt->terminal_capability, param->terminal_capability); + dect_db_store_termcap(pt); + } + if (param->codec_list) { + dect_ie_update(pt->codec_list, param->codec_list); + dect_db_store_codec_list(pt); + } + + timeout += DECT_LOCATE_TIMEOUT_SLACK; + dect_pt_log(pt, "location registration: timeout: %us\n", timeout); + + dect_register_extension(pt, true); + pt->locate_timer = ast_sched_replace(pt->locate_timer, sched, + timeout * 1000, + dect_locate_timer, pt); +} + +static void dect_mm_locate_ind(struct dect_handle *dh, + struct dect_mm_endpoint *mme, + struct dect_mm_locate_param *param) +{ + struct dect_mm_pvt *mmp = dect_mm_priv(mme); + struct dect_pt *pt; + + pt = dect_pt_get_by_ipui(¶m->portable_identity->ipui); + if (pt == NULL) + return dect_mm_locate_reject(mme, DECT_REJECT_IPUI_UNKNOWN); + + mmp->pt = pt; + mmp->mme = mme; + + dect_cipher(mmp, dect_mm_locate_auth_cfm, ¶m->common); +} + +/* + * Identity assignment procedures + */ + +static void dect_mm_identity_assign_cfm(struct dect_handle *dh, + struct dect_mm_endpoint *mme, bool accept, + struct dect_mm_identity_assign_param *param) +{ +} + +static const struct dect_mm_ops dect_mm_ops = { + .priv_size = sizeof(struct dect_mm_pvt), + .mm_authenticate_ind = dect_mm_authenticate_ind, + .mm_authenticate_cfm = dect_mm_authenticate_cfm, + .mm_cipher_ind = dect_mm_cipher_ind, + .mm_cipher_cfm = dect_mm_cipher_cfm, + .mm_access_rights_ind = dect_mm_access_rights_ind, + .mm_access_rights_terminate_ind = dect_mm_access_rights_terminate_ind, + .mm_access_rights_terminate_cfm = dect_mm_access_rights_terminate_cfm, + .mm_locate_ind = dect_mm_locate_ind, + .mm_identity_assign_cfm = dect_mm_identity_assign_cfm, +}; + +/* + * Supplementary Services + */ + +static void dect_mnss_setup_ind(struct dect_handle *dh, struct dect_ss_endpoint *sse, + struct dect_mnss_param *param) +{ +} + +static void dect_mnss_release_ind(struct dect_handle *dh, struct dect_ss_endpoint *sse, + struct dect_mnss_param *param) +{ +} + +static const struct dect_ss_ops dect_ss_ops = { + .mnss_setup_ind = dect_mnss_setup_ind, + .mnss_release_ind = dect_mnss_release_ind, +}; + +/* + * User authentication App + */ +static void dect_userauth_auth_cfm(struct dect_mm_pvt *mmp, bool success, + struct dect_ie_collection *iec) +{ + mmp->userauth_status = success; + sem_post(&mmp->userauth_semaphore); +} + +static int dect_userauth_exec(struct ast_channel *chan, const char *data) +{ + struct dect_pvt *pvt = chan->tech_pvt; + struct dect_pt *pt = pvt->pt; + struct dect_mm_endpoint *mme; + struct dect_mm_pvt *mmp; + + if (data == NULL) { + ast_log(LOG_WARNING, "DectUserAuth requires an argument (PIN)"); + return -1; + } + if (strlen(data) > 8) { + ast_log(LOG_WARNING, "User personal identity may not exceed 8 digits"); + return -1; + } + + mme = dect_mm_endpoint_get(dh, &pt->ipui); + if (mme == NULL) + return -1; + + mmp = dect_mm_priv(mme); + mmp->pt = pt; + mmp->mme = mme; + mmp->call = pvt->call; + sem_init(&mmp->userauth_semaphore, 0, 0); + + dect_pin_to_ac(data, mmp->upi, sizeof(mmp->upi)); + dect_authenticate(mmp, DECT_AUTH_USER, dect_userauth_auth_cfm, NULL); + sem_wait(&mmp->userauth_semaphore); + + dect_pt_log(pt, "DectUserAuth: status: %d\n", mmp->userauth_status); + pbx_builtin_setvar_helper(chan, "USERAUTHSTATUS", + mmp->userauth_status ? "SUCCESS" : "FAILURE"); + return 0; +} + +static void dect_show_debug(enum dect_debug_subsys subsys, + const char *fmt, va_list ap) +{ + ast_verbose_ap(fmt, ap); +} + +/* + * Asterisk CLI commands + */ + +static char *dect_complete_pt(const char *word, int state) +{ + size_t wordlen = strlen(word); + struct dect_pt *pt; + int which = 0; + char *res; + + AST_LIST_TRAVERSE(&dect_pt_list, pt, list) { + if (!strncasecmp(word, pt->name, wordlen) && ++which > state) { + res = ast_strdup(pt->name); + if (res) + return res; + } + } + return NULL; +} + +static char *dect_cli_debug(struct ast_cli_entry *e, int cmd, + struct ast_cli_args *a) +{ + const char *arg; + + switch (cmd) { + case CLI_INIT: + e->command = "dect set debug {on|off}"; + e->usage = "Usage: dect set debug {on|off}\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + arg = a->argv[e->args - 1]; + if (!strcasecmp(arg, "on")) { + dect_set_debug_hook(dect_show_debug); + ast_cli(a->fd, "DECT debugging enabled\n"); + } else if (!strcasecmp(arg, "off")) { + dect_set_debug_hook(NULL); + ast_cli(a->fd, "DECT debugging disabled\n"); + } else + return CLI_SHOWUSAGE; + + return CLI_SUCCESS; +} + +static char *dect_cli_show_portables(struct ast_cli_entry *e, int cmd, + struct ast_cli_args *a) +{ + struct dect_pt *pt; + + switch (cmd) { + case CLI_INIT: + e->command = "dect show portables"; + e->usage = "Usage: dect show portables\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + ast_cli(a->fd, "Name Extension Registered\n"); + AST_LIST_TRAVERSE(&dect_pt_list, pt, list) + ast_cli(a->fd, "%-16s%-16s%-16s\n", + pt->name, pt->regexten, pt->locate_timer == -1 ? "No" : "Yes"); + + return CLI_SUCCESS; +} + +static char *dect_cli_show_portable(struct ast_cli_entry *e, int cmd, + struct ast_cli_args *a) +{ + struct dect_pt *pt; + char cidbuf[256], codbuf[256]; + + switch (cmd) { + case CLI_INIT: + e->command = "dect show portable"; + e->usage = "Usage: dect show portable <name>\n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 3) + return dect_complete_pt(a->word, a->n); + return NULL; + } + + if (a->argc < 4) + return CLI_SHOWUSAGE; + + pt = dect_pt_get_by_name(a->argv[3]); + if (pt == NULL) { + ast_cli(a->fd, "PT '%s' not found\n", a->argv[3]); + return CLI_FAILURE; + } + + ast_callerid_merge(cidbuf, sizeof(cidbuf), pt->cid_name, pt->cid_num, + "<unspecified>"); + dect_codecs_merge(codbuf, sizeof(codbuf), pt); + + ast_cli(a->fd, "IPEI: %s\n", pt->ipei); + ast_cli(a->fd, "Extension: %s\n", pt->regexten); + ast_cli(a->fd, "Registered: %s\n", pt->locate_timer == -1 ? "No" : "Yes"); + ast_cli(a->fd, "Context: %s\n", pt->context); + ast_cli(a->fd, "Language: %s\n", pt->language); + ast_cli(a->fd, "CallerId: %s\n", cidbuf); + ast_cli(a->fd, "Codecs: %s\n", codbuf); + + return CLI_SUCCESS; +} + +static char *dect_cli_ari(struct ast_cli_entry *e, int cmd, + struct ast_cli_args *a) +{ + struct dect_pt *pt; + const char *arg; + + switch (cmd) { + case CLI_INIT: + e->command = "dect access-rights {enable|disable|terminate}"; + e->usage = "Usage: dect access-rights {enable|disable|terminate}\n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 3 && !strcasecmp(a->argv[2], "terminate")) + return dect_complete_pt(a->word, a->n); + return NULL; + } + + if (a->argc < 3) + return CLI_SHOWUSAGE; + + arg = a->argv[2]; + if (!strcasecmp(arg, "enable")) { + if (dect_access_rights_requests_enable() == 0) + ast_cli(a->fd, "ARI enabled\n"); + } else if (!strcasecmp(arg, "disable")) { + if (dect_access_rights_requests_disable() == 0) + ast_cli(a->fd, "ARI disabled\n"); + } else if (!strcasecmp(arg, "terminate")) { + if (a->argc < 4) + return CLI_SHOWUSAGE; + ast_cli(a->fd, "ARI terminate\n"); + pt = dect_pt_get_by_name(a->argv[3]); + if (pt == NULL) { + ast_cli(a->fd, "PT '%s' not found\n", a->argv[3]); + return CLI_FAILURE; + } + dect_access_rights_terminate(pt); + } else + return CLI_SHOWUSAGE; + + return CLI_SUCCESS; +} + +static int dect_db_read(void); +static char *dect_cli_db_reload(struct ast_cli_entry *e, int cmd, + struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "dect reload database"; + e->usage = "Usage: dect reload database\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc < 3) + return CLI_SHOWUSAGE; + + dect_db_read(); + return CLI_SUCCESS; +} + +static struct ast_cli_entry dect_cli_cmds[] = { + AST_CLI_DEFINE(dect_cli_debug, "Enable/Disable DECT debugging"), + AST_CLI_DEFINE(dect_cli_ari, "Access rights modification"), + AST_CLI_DEFINE(dect_cli_show_portables, "Show list of portables"), + AST_CLI_DEFINE(dect_cli_show_portable, "Show portable information"), + AST_CLI_DEFINE(dect_cli_db_reload, "Reload database"), +}; + +/* + * libdect event ops + */ + +struct dect_fd_priv { + int *id; +}; + +static int dect_io_callback(int *id, int fd, short io_events, void *data) +{ + struct dect_fd *dfd = data; + uint32_t events = 0; + + if (io_events & (AST_IO_IN | AST_IO_ERR | AST_IO_HUP)) + events |= DECT_FD_READ; + if (io_events & AST_IO_OUT) + events |= DECT_FD_WRITE; + dect_fd_process(dh, dfd, events); + return 1; +} + +static int dect_register_fd(const struct dect_handle *dh, struct dect_fd *dfd, + uint32_t events) +{ + struct dect_fd_priv *priv = dect_fd_priv(dfd); + short io_events = 0; + + if (events & DECT_FD_READ) + io_events |= AST_IO_IN; + if (events & DECT_FD_WRITE) + io_events |= AST_IO_OUT; + + priv->id = ast_io_add(io, dect_fd_num(dfd), dect_io_callback, + io_events, dfd);; + return priv->id ? 0 : -1; +} + +static void dect_unregister_fd(const struct dect_handle *dh, struct dect_fd *dfd) +{ + struct dect_fd_priv *priv = dect_fd_priv(dfd); + + ast_io_remove(io, priv->id); +} + +struct dect_timer_priv { + int id; +}; + +static int dect_timer_callback(const void *data) +{ + struct dect_timer *timer = (struct dect_timer *)data;; + + dect_timer_run(dh, timer); + return 0; +} + +static void dect_start_timer(const struct dect_handle *dh, + struct dect_timer *timer, + const struct timeval *tv) +{ + struct dect_timer_priv *priv = dect_timer_priv(timer); + + priv->id = ast_sched_add(sched, tv->tv_sec * 1000 + tv->tv_usec / 1000, + dect_timer_callback, timer); +} + +static void dect_stop_timer(const struct dect_handle *dh, + struct dect_timer *timer) +{ + struct dect_timer_priv *priv = dect_timer_priv(timer); + + AST_SCHED_DEL(sched, priv->id); +} + +static const struct dect_event_ops dect_event_ops = { + .fd_priv_size = sizeof(struct dect_fd_priv), + .register_fd = dect_register_fd, + .unregister_fd = dect_unregister_fd, + .timer_priv_size = sizeof(struct dect_timer_priv), + .start_timer = dect_start_timer, + .stop_timer = dect_stop_timer, +}; + +static struct dect_ops dect_ops = { + .event_ops = &dect_event_ops, + .cc_ops = &dect_cc_ops, + .mm_ops = &dect_mm_ops, + .ss_ops = &dect_ss_ops, +}; + +static pthread_t io_thread = AST_PTHREADT_NULL; + +static void *dect_io_thread(void *ignore) +{ + int timeout; + + while (1) { + pthread_testcancel(); + timeout = ast_sched_wait(sched); + if (timeout < 0 || timeout > 1000) + timeout = 1000; + ast_io_wait(io, timeout); + ast_sched_runq(sched); + } + return NULL; +} + +static int dect_io_thread_start(void) +{ + int err; + + err = ast_pthread_create_background(&io_thread, NULL, dect_io_thread, NULL); + if (err < 0) { + ast_log(LOG_ERROR, "Unable to start IO thread\n"); + return err; + } + return 0; +} + +static void dect_io_thread_stop(void) +{ + pthread_cancel(io_thread); + pthread_kill(io_thread, SIGURG); + pthread_join(io_thread, NULL); +} + +static int dect_db_read(void) +{ + struct ast_db_entry *dbt, *dbe; + struct dect_ipui ipui; + struct dect_pt *pt; + const char *key, *val; + char *tmp, ipei[64]; + + memset(&ipui, 0, sizeof(ipui)); + ipui.put = DECT_IPUI_N; + + dbt = ast_db_gettree("dect", NULL); + for (dbe = dbt; dbe != NULL; dbe = dbe->next) { + key = dbe->key + strlen("/dect/"); + strncpy(ipei, key, sizeof(ipei)); + tmp = strchr(ipei, '/'); + + if (tmp == NULL) + continue; + *tmp = '\0'; + key = tmp + 1; + val = dbe->data; + + if (!dect_parse_ipei_string(&ipui.pun.n.ipei, ipei)) + continue; + + pt = dect_pt_get_by_ipui(&ipui); + if (pt == NULL) { + pt = dect_init_portable(NULL); + if (pt == NULL) + return -1; + dect_pt_set_ipui(pt, &ipui); + } + + if (dect_db_parse_global(pt, key, val) != 0 && + dect_db_parse_termcap(pt, key, val) != 0 && + dect_db_parse_codec_list(pt, key, val) != 0) { + ast_log(LOG_NOTICE, "Unknown database key '%s'\n", key); + return -1; + } + } + + return 0; +} + +static int dect_load_config(void) +{ + struct ast_flags cfg_flags = {}; + struct ast_config *cfg; + struct ast_variable *v; + + memcpy(&dect_cfg.jbconf, &dect_default_jbconf, sizeof(struct ast_jb_conf)); + + cfg = ast_config_load(CONFIG_FILE, cfg_flags); + if (cfg == CONFIG_STATUS_FILEINVALID) { + ast_log(LOG_ERROR, "Config file %s is in an invalid format.\n", + CONFIG_FILE); + return AST_MODULE_LOAD_DECLINE; + } + + if (cfg == NULL) { + ast_log(LOG_ERROR, "Unable to load config %s\n", CONFIG_FILE); + return AST_MODULE_LOAD_DECLINE; + } + + for (v = ast_variable_browse(cfg, "general"); v != NULL; v = v->next) { + /* jitter buffer configuration */ + if (!ast_jb_read_conf(&dect_cfg.jbconf, v->name, v->value)) + continue; + + if (!strcasecmp(v->name, "cluster")) { + ast_copy_string(dect_cfg.cluster, v->value, + sizeof(dect_cfg.cluster)); + } else if (!strcasecmp(v->name, "context")) { + ast_copy_string(dect_cfg.context, v->value, + sizeof(dect_cfg.context)); + } else if (!strcasecmp(v->name, "language")) { + ast_copy_string(dect_cfg.language, v->value, + sizeof(dect_cfg.language)); + } else if (!strcasecmp(v->name, "regcontext")) { + ast_copy_string(dect_cfg.regcontext, v->value, + sizeof(dect_cfg.regcontext)); + /* Create context if it doesn't exist already */ + ast_context_find_or_create(NULL, NULL, dect_cfg.regcontext, "DECT"); + } else if (!strcasecmp(v->name, "regexten_base")) { + dect_cfg.regexten_base = strtoul(v->value, NULL, 0); + } else if (!strcasecmp(v->name, "pin")) { + ast_copy_string(dect_cfg.pin, v->value, + sizeof(dect_cfg.pin)); + } else if (!strcasecmp(v->name, "locate_duration")) { + dect_cfg.locate_duration = strtoul(v->value, NULL, 0); + } + } + + return 0; +} + +static int dect_load_module(void) +{ + struct ast_format tmpfmt; + const char *cluster; + + sched = ast_sched_context_create(); + if (sched == NULL) { + ast_log(LOG_ERROR, "Unable to create scheduler context\n"); + goto err1; + } + + io = io_context_create(); + if (io == NULL) { + ast_log(LOG_ERROR, "Unable to create IO context\n"); + goto err2; + } + + dect_tech.capabilities = ast_format_cap_alloc(); + if (dect_tech.capabilities == NULL) + goto err3; + ast_format_cap_add(dect_tech.capabilities, + ast_format_set(&tmpfmt, AST_FORMAT_G726_AAL2, 0)); + + if (ast_channel_register(&dect_tech)) { + ast_log(LOG_ERROR, "Unable to register 'DECT' channel\n"); + goto err4; + } + + ast_cli_register_multiple(dect_cli_cmds, ARRAY_LEN(dect_cli_cmds)); + ast_register_application_xml("DectUserAuth", dect_userauth_exec); + + dect_load_config(); + + if (dect_io_thread_start() < 0) + goto err5; + + cluster = strlen(dect_cfg.cluster) ? dect_cfg.cluster : NULL; + dh = dect_open_handle(&dect_ops, cluster); + if (dh == NULL) { + ast_log(LOG_ERROR, "Unable to initialize DECT handle\n"); + goto err6; + } + if (dect_llme_rfp_preload_req(dh, &dect_fpc) < 0) { + ast_log(LOG_ERROR, "Unable to set FP capabilities\n"); + goto err7; + } + + if (dect_db_read() < 0) { + ast_log(LOG_ERROR, "Unable to read database\n"); + goto err7; + } + + return AST_MODULE_LOAD_SUCCESS; + +err7: + dect_close_handle(dh); +err6: + dect_io_thread_stop(); +err5: + ast_cli_unregister_multiple(dect_cli_cmds, ARRAY_LEN(dect_cli_cmds)); + ast_channel_unregister(&dect_tech); +err4: + ast_format_cap_destroy(dect_tech.capabilities); +err3: + io_context_destroy(io); +err2: + ast_sched_context_destroy(sched); +err1: + return AST_MODULE_LOAD_FAILURE; +} + +static int dect_unload_module(void) +{ + dect_close_handle(dh); + dect_io_thread_stop(); + ast_unregister_application("DectUserAuth"); + ast_cli_unregister_multiple(dect_cli_cmds, ARRAY_LEN(dect_cli_cmds)); + ast_channel_unregister(&dect_tech); + ast_format_cap_destroy(dect_tech.capabilities); + io_context_destroy(io); + ast_sched_context_destroy(sched); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "DECT", + .load = dect_load_module, + .unload = dect_unload_module, +); |