414 lines
12 KiB
C
414 lines
12 KiB
C
/* TC-User API / Interface between TCAP protocol manager and User Application */
|
|
|
|
/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
|
|
* (C) 2010 by On-Waves
|
|
*
|
|
* All Rights Reserved
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation; either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <errno.h>
|
|
|
|
#include <osmocore/msgb.h>
|
|
#include <osmocore/utils.h>
|
|
#include <osmocore/talloc.h>
|
|
|
|
#include <osmocom/tcap/Parameter.h>
|
|
#include <osmocom/tcap/OPERATION.h>
|
|
|
|
#include "tcap.h"
|
|
#include "tcap_user.h"
|
|
|
|
void *tcap_ind_ctx;
|
|
|
|
static const struct value_string tcap_prim_names[] = {
|
|
/* dialogue handling */
|
|
{ TCAP_PR_TC_UNI, "TC-UNI" },
|
|
{ TCAP_PR_TC_BEGIN, "TC-BEGIN" },
|
|
{ TCAP_PR_TC_CONTINUE, "TC-CONTINUE" },
|
|
{ TCAP_PR_TC_END, "TC-END" },
|
|
{ TCAP_PR_TC_U_ABORT, "TC-U-ABORT" },
|
|
{ TCAP_PR_TC_NOTICE, "TC-NOTICE" },
|
|
/* component handling */
|
|
{ TCAP_PR_TC_INVOKE, "TC-INVOKE" },
|
|
{ TCAP_PR_TC_RESULT_L, "TC-RESULT-L" },
|
|
{ TCAP_PR_TC_RESULT_NL, "TC-RESULT-NL" },
|
|
{ TCAP_PR_TC_U_ERROR, "TC-U-ERROR" },
|
|
{ TCAP_PR_TC_U_REJECT, "TC-U-REJECT" },
|
|
{ TCAP_PR_TC_CANCEL, "TC-CANCEL" },
|
|
{ TCAP_PR_TC_L_REJECT, "TC-L-REJECT" },
|
|
{ TCAP_PR_TC_R_REJECT, "TC-R-REJECT" },
|
|
{ TCAP_PR_TC_P_ABORT, "TC-P-ABORT" },
|
|
{ 0, NULL }
|
|
};
|
|
|
|
LIB_EXPORTED const char *tcap_prim_name(enum tcap_primitive prim)
|
|
{
|
|
return get_value_string(tcap_prim_names, prim);
|
|
}
|
|
|
|
/***********************************************************************/
|
|
/* Dialogue Primitives */
|
|
/***********************************************************************/
|
|
|
|
static struct tcap_dialg_ind *tcap_dialg_ind_alloc(void)
|
|
{
|
|
struct tcap_dialg_ind *tcdi = talloc_zero(tcap_ind_ctx,
|
|
struct tcap_dialg_ind);
|
|
return tcdi;
|
|
}
|
|
|
|
/* fill the application context and user information part of 'tcap_dialg_ind' */
|
|
static int fill_tcap_dialg_ind(struct tcap_dialg_ind *tcdi,
|
|
OBJECT_IDENTIFIER_t *app_ctx_name,
|
|
struct user_information *user_info)
|
|
{
|
|
int rc;
|
|
|
|
if (app_ctx_name) {
|
|
/* Parse the Application Context from an OID into our array of ARCs */
|
|
rc = OBJECT_IDENTIFIER_get_arcs(app_ctx_name, &tcdi->app_ctx_name.arc,
|
|
sizeof(tcdi->app_ctx_name.arc[0]),
|
|
ARRAY_SIZE(tcdi->app_ctx_name.arc));
|
|
if (rc < 0)
|
|
return rc;
|
|
tcdi->app_ctx_name.num_arcs = rc;
|
|
tcdi->app_ctx_present = 1;
|
|
}
|
|
|
|
/* copy the user_info */
|
|
if (user_info) {
|
|
EXTERNAL_t *ext;
|
|
if (user_info->list.count != 1)
|
|
return -EIO;
|
|
ext = user_info->list.array[0];
|
|
if (ext->size > sizeof(tcdi->user_info.data))
|
|
return -EIO;
|
|
memcpy(tcdi->user_info.data, ext->buf, ext->size);
|
|
tcdi->user_info.data_len = ext->size;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Take any TC-Dialogue.ind from the stack and pass it to the user */
|
|
static int _tcap_tcu_dialg_ind(enum tcap_primitive prim, struct tcap_dialogue *td,
|
|
OBJECT_IDENTIFIER_t *app_ctx_name,
|
|
struct user_information *user_info, int comp_present)
|
|
{
|
|
struct tcap_dialg_ind *tcdi = tcap_dialg_ind_alloc();
|
|
int rc;
|
|
|
|
if (!tcdi)
|
|
return -ENOMEM;
|
|
|
|
tcdi->dialg_id = td->dialogue_id;
|
|
rc = fill_tcap_dialg_ind(tcdi, app_ctx_name, user_info);
|
|
if (rc < 0) {
|
|
/* FIXME: reject the dialogue */
|
|
return rc;
|
|
}
|
|
if (comp_present)
|
|
tcdi->components_present = 1;
|
|
|
|
return tcap_user_ind_dialg(prim, tcdi);
|
|
}
|
|
|
|
/* TC-BEGIN.ind from DHA */
|
|
int tcap_tcu_begin_ind(struct tcap_dialogue *td, void *app_ctx_name, void *user_info, int comp_present)
|
|
{
|
|
return _tcap_tcu_dialg_ind(TCAP_PR_TC_BEGIN, td, app_ctx_name, user_info, comp_present);
|
|
}
|
|
|
|
/* TC-UNI.ind from DHA */
|
|
int tcap_tcu_uni_ind(struct tcap_dialogue *td, void *app_ctx_name, void *user_info, int comp_present)
|
|
{
|
|
return _tcap_tcu_dialg_ind(TCAP_PR_TC_UNI, td, app_ctx_name, user_info, comp_present);
|
|
}
|
|
|
|
/* TC-CONT.ind from DHA */
|
|
int tcap_tcu_cont_ind(struct tcap_dialogue *td, void *app_ctx_name, void *user_info, int comp_present)
|
|
{
|
|
return _tcap_tcu_dialg_ind(TCAP_PR_TC_CONTINUE, td, app_ctx_name, user_info, comp_present);
|
|
}
|
|
|
|
/* TC-END.ind from DHA */
|
|
int tcap_tcu_end_ind(struct tcap_dialogue *td, void *app_ctx_name, void *user_info, int comp_present)
|
|
{
|
|
return _tcap_tcu_dialg_ind(TCAP_PR_TC_END, td, app_ctx_name, user_info, comp_present);
|
|
}
|
|
|
|
/* TC-ABORT.ind from DHA */
|
|
int tcap_tcu_abort_ind(struct tcap_dialogue *td, void *app_ctx_name, void *user_info)
|
|
{
|
|
}
|
|
|
|
/* TC-NOTICE.ind from DHA */
|
|
int tcap_tcu_notice_ind(struct tcap_dialogue *td, uint32_t cause)
|
|
{
|
|
}
|
|
|
|
/* DIALOGUE primitive received from TC-User */
|
|
LIB_EXPORTED int tcap_user_req_dialg(enum tcap_primitive prim, struct tcap_dialg_ind *tcdi)
|
|
{
|
|
/* Allocate some structures on the stack */
|
|
OBJECT_IDENTIFIER_t *app_ctx;
|
|
EXTERNAL_t *ext;
|
|
struct user_information *user_info;
|
|
struct tcap_dialogue *td;
|
|
int rc = 0;
|
|
|
|
fprintf(stdout, "<- USER_REQ_DIALG(%s)\n", tcap_prim_name(prim));
|
|
|
|
/* Resolve (or allocate) the dialogue/transaction state */
|
|
td = tcap_dialg_by_dialg_id(tcdi->dialg_id);
|
|
if (!td) {
|
|
switch (prim) {
|
|
case TCAP_PR_TC_BEGIN:
|
|
td = tcap_dialg_alloc(tcdi->dialg_id);
|
|
if (!td)
|
|
return -EINVAL;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/* Parse the tcap_dialg_ind into something that the asn1c code understands */
|
|
if (tcdi->app_ctx_present) {
|
|
app_ctx = talloc_zero(td, OBJECT_IDENTIFIER_t);
|
|
OBJECT_IDENTIFIER_set_arcs(app_ctx, &tcdi->app_ctx_name.arc,
|
|
sizeof(tcdi->app_ctx_name.arc[0]),
|
|
tcdi->app_ctx_name.num_arcs);
|
|
}
|
|
|
|
/* Parse the tcap_dialg_ind into something that the asn1c code understands */
|
|
if (tcdi->user_info_present) {
|
|
ext = talloc_zero(td, EXTERNAL_t);
|
|
ext->buf = tcdi->user_info.data;
|
|
ext->size = tcdi->user_info.data_len;
|
|
user_info = talloc_zero(ext, struct user_information);
|
|
ASN_SEQUENCE_ADD(&user_info->list, ext);
|
|
}
|
|
|
|
switch (prim) {
|
|
case TCAP_PR_TC_BEGIN:
|
|
td->transp_ent = tcdi->transp_ent;
|
|
rc = tcap_csl_tc_begin_req(td, app_ctx, user_info);
|
|
break;
|
|
case TCAP_PR_TC_CONTINUE:
|
|
rc = tcap_csl_tc_cont_req(td, app_ctx, user_info);
|
|
break;
|
|
case TCAP_PR_TC_END:
|
|
rc = tcap_csl_tc_end_req(td, app_ctx, user_info, tcdi->prearranged_end);
|
|
break;
|
|
default:
|
|
fprintf(stderr, "unsupported component primitive %s\n", tcap_prim_name(prim));
|
|
return -EINVAL;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/***********************************************************************/
|
|
/* Component Primitives */
|
|
/***********************************************************************/
|
|
|
|
static struct tcap_component_ind *tcap_comp_ind_alloc(void)
|
|
{
|
|
struct tcap_component_ind *tcci = talloc_zero(tcap_ind_ctx,
|
|
struct tcap_component_ind);
|
|
return tcci;
|
|
}
|
|
|
|
static int _tcu_comp_ind(enum tcap_primitive prim, struct tcap_invocation *ti, struct OPERATION *oper,
|
|
Parameter_t *param, int last)
|
|
{
|
|
struct tcap_component_ind *tcci = tcap_comp_ind_alloc();
|
|
int rc;
|
|
|
|
tcci->dialg_id = ti->dialogue->dialogue_id;
|
|
tcci->invoke_id = ti->invoke_id;
|
|
if (ti->linked_id) {
|
|
tcci->_linked_id = ti->_linked_id;
|
|
tcci->linked_id = &tcci->_linked_id;
|
|
}
|
|
if (oper && oper->present != OPERATION_PR_NOTHING) {
|
|
switch (oper->present) {
|
|
case OPERATION_PR_localValue:
|
|
rc = asn_INTEGER2long(&oper->choice.localValue, &tcci->operation.local);
|
|
if (rc < 0)
|
|
goto out_free;
|
|
break;
|
|
case OPERATION_PR_globalValue:
|
|
rc = OBJECT_IDENTIFIER_get_arcs(&oper->choice.globalValue,
|
|
&tcci->operation.global.arc,
|
|
sizeof(tcci->operation.global.arc[0]),
|
|
ARRAY_SIZE(tcci->operation.global.arc));
|
|
if (rc < 0)
|
|
goto out_free;
|
|
tcci->operation.global.num_arcs = rc;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (param) {
|
|
if (param->size > sizeof(tcci->parameter.data))
|
|
goto out_free;
|
|
memcpy(tcci->parameter.data, param->buf, param->size);
|
|
tcci->parameter.data_len = param->size;
|
|
}
|
|
tcci->last_component = last;
|
|
|
|
return tcap_user_ind_comp(prim, tcci);
|
|
|
|
out_free:
|
|
talloc_free(tcci);
|
|
return rc;
|
|
}
|
|
|
|
/* Table 10 / Q.771 : TC-INVOKE.ind */
|
|
int tcap_tcu_tc_invoke_ind(struct tcap_invocation *ti, struct OPERATION *oper, Parameter_t *param, int last)
|
|
{
|
|
return _tcu_comp_ind(TCAP_PR_TC_INVOKE, ti, oper, param, last);
|
|
}
|
|
|
|
/* TC-L-REJECT.ind */
|
|
int tcap_tcu_tc_l_rej_ind(struct tcap_invocation *ti, int8_t *invoke_id, uint32_t problem)
|
|
{
|
|
/* FIXME */
|
|
}
|
|
|
|
/* TC-R-REJECT.ind */
|
|
int tcap_tcu_tc_r_rej_ind(struct tcap_invocation *ti, int8_t *invoke_id, uint32_t problem,
|
|
int last_component)
|
|
{
|
|
/* FIXME */
|
|
}
|
|
|
|
/* TC-RESULT-L.ind from ISM */
|
|
int tcap_tcu_result_l_ind(struct tcap_invocation *ti, struct OPERATION *oper, Parameter_t *param, int last)
|
|
{
|
|
return _tcu_comp_ind(TCAP_PR_TC_RESULT_L, ti, oper, param, last);
|
|
}
|
|
|
|
int tcap_tcu_result_nl_ind(struct tcap_invocation *ti, struct OPERATION *oper, Parameter_t *param, int last)
|
|
{
|
|
return _tcu_comp_ind(TCAP_PR_TC_RESULT_NL, ti, oper, param, last);
|
|
}
|
|
|
|
/* Allocate and Fill a OPERATION_t from information inside a tcap_component_ind */
|
|
static OPERATION_t *generate_op(struct tcap_dialogue *td, struct tcap_component_ind *tcci)
|
|
{
|
|
OPERATION_t *op;
|
|
|
|
op = talloc_zero(td, OPERATION_t);
|
|
if (!op)
|
|
return NULL;
|
|
|
|
if (tcci->operation.is_global) {
|
|
op->present = OPERATION_PR_globalValue;
|
|
OBJECT_IDENTIFIER_set_arcs(&op->choice.globalValue,
|
|
tcci->operation.global.arc,
|
|
sizeof(tcci->operation.global.arc[0]),
|
|
tcci->operation.global.num_arcs);
|
|
} else {
|
|
op->present = OPERATION_PR_localValue;
|
|
asn_long2INTEGER(&op->choice.localValue, tcci->operation.local);
|
|
}
|
|
|
|
return op;
|
|
}
|
|
|
|
/* Allocate and Fill an ErrorCode_t from information inside a tcap_component_ind */
|
|
static ErrorCode_t *generate_errcode(struct tcap_dialogue *td, struct tcap_component_ind *tcci)
|
|
{
|
|
ErrorCode_t *err;
|
|
|
|
err = talloc_zero(td, ErrorCode_t);
|
|
if (!err)
|
|
return NULL;
|
|
|
|
if (tcci->error.is_private) {
|
|
err->present = ErrorCode_PR_privateer;
|
|
asn_long2INTEGER(&err->choice.privateer, tcci->error.err);
|
|
} else {
|
|
err->present = ErrorCode_PR_nationaler;
|
|
err->choice.nationaler = tcci->error.err;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
LIB_EXPORTED int tcap_user_req_comp(enum tcap_primitive prim, struct tcap_component_ind *tcci)
|
|
{
|
|
struct tcap_dialogue *td;
|
|
OPERATION_t *op = NULL;
|
|
ErrorCode_t *err = NULL;
|
|
int rc = 0;
|
|
|
|
fprintf(stdout, "<- USER_REQ_COMP(%s)\n", tcap_prim_name(prim));
|
|
|
|
/* Resolve (or allocate) the dialogue/transaction state */
|
|
td = tcap_dialg_by_dialg_id(tcci->dialg_id);
|
|
if (!td) {
|
|
switch (prim) {
|
|
case TCAP_PR_TC_INVOKE:
|
|
td = tcap_dialg_alloc(tcci->dialg_id);
|
|
td->trans.tid_local = tcap_trans_id_alloc();
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/* Actually dispatch the primitive */
|
|
switch (prim) {
|
|
case TCAP_PR_TC_INVOKE:
|
|
op = generate_op(td, tcci);
|
|
rc = tcap_cha_tc_invoke_req(td, tcci->op_class, tcci->invoke_id,
|
|
tcci->linked_id, op, tcci->parameter.data,
|
|
tcci->parameter.data_len, tcci->timeout_secs);
|
|
break;
|
|
case TCAP_PR_TC_RESULT_L:
|
|
op = generate_op(td, tcci);
|
|
rc = tcap_cha_tc_result_req(td, tcci->invoke_id, 1, op,
|
|
&tcci->parameter.data, tcci->parameter.data_len);
|
|
break;
|
|
case TCAP_PR_TC_RESULT_NL:
|
|
op = generate_op(td, tcci);
|
|
rc = tcap_cha_tc_result_req(td, tcci->invoke_id, 0, op,
|
|
tcci->parameter.data, tcci->parameter.data_len);
|
|
break;
|
|
case TCAP_PR_TC_U_ERROR:
|
|
rc = tcap_cha_tc_u_error_req(td, tcci->invoke_id, generate_errcode(td, tcci),
|
|
tcci->parameter.data, tcci->parameter.data_len);
|
|
break;
|
|
case TCAP_PR_TC_U_REJECT:
|
|
case TCAP_PR_TC_CANCEL:
|
|
case TCAP_PR_TC_TIMER_RESET:
|
|
default:
|
|
fprintf(stderr, "unsupported dialogue primitive %s\n", tcap_prim_name(prim));
|
|
return -EINVAL;
|
|
}
|
|
|
|
talloc_free(op);
|
|
talloc_free(err);
|
|
|
|
return rc;
|
|
}
|