From 660609c45bc1cc59171ed9467d961d97abf865ec Mon Sep 17 00:00:00 2001 From: David Vossel Date: Fri, 28 May 2010 22:34:24 +0000 Subject: [PATCH] support for sending ETSI advice of charge Review: https://reviewboard.asterisk.org/r/619/ git-svn-id: https://origsvn.digium.com/svn/libpri/branches/1.4@1776 2fbb986a-6c06-0410-b554-c9c1f0a7f128 --- libpri.h | 137 +++++- pri_aoc.c | 1245 +++++++++++++++++++++++++++++++++++++++++++++--- pri_facility.c | 5 +- pri_facility.h | 10 +- pri_internal.h | 4 + q931.c | 69 ++- 6 files changed, 1379 insertions(+), 91 deletions(-) diff --git a/libpri.h b/libpri.h index 35f72b8..ba942dd 100644 --- a/libpri.h +++ b/libpri.h @@ -543,8 +543,8 @@ struct pri_rerouting_data { #define PRI_SUBCMD_AOC_S 18 /*!< Advice Of Charge Start information (Rate list) */ #define PRI_SUBCMD_AOC_D 19 /*!< Advice Of Charge During information */ #define PRI_SUBCMD_AOC_E 20 /*!< Advice Of Charge End information */ -//#define PRI_SUBCMD_AOC_CHARGING_REQ 21 /*!< Advice Of Charge Request information */ -//#define PRI_SUBCMD_AOC_CHARGING_REQ_RSP 22 /*!< Advice Of Charge Request Response information */ +#define PRI_SUBCMD_AOC_CHARGING_REQ 21 /*!< Advice Of Charge Request information */ +#define PRI_SUBCMD_AOC_CHARGING_REQ_RSP 22 /*!< Advice Of Charge Request Response information */ #define PRI_SUBCMD_MCID_REQ 23 /*!< Malicious Call ID Request */ #define PRI_SUBCMD_MCID_RSP 24 /*!< Malicious Call ID Request response */ @@ -877,6 +877,64 @@ struct pri_subcmd_aoc_e { struct pri_aoc_e_charging_association associated; }; +enum PRI_AOC_REQ_RSP { + /* Error Results */ + PRI_AOC_REQ_RSP_ERROR_NOT_IMPLEMENTED, + PRI_AOC_REQ_RSP_ERROR_NOT_AVAILABLE, + PRI_AOC_REQ_RSP_ERROR_TIMEOUT, + PRI_AOC_REQ_RSP_ERROR_REJECT, + /* generic error result all other errors are lumped into */ + PRI_AOC_REQ_RSP_ERROR, + + /* AOC Results */ + PRI_AOC_REQ_RSP_CHARGING_INFO_FOLLOWS, + PRI_AOC_REQ_RSP_CURRENCY_INFO_LIST, + PRI_AOC_REQ_RSP_SPECIAL_ARR, +}; + +enum PRI_AOC_REQUEST { + PRI_AOC_REQUEST_S = (1 << 0), + PRI_AOC_REQUEST_D = (1 << 1), + PRI_AOC_REQUEST_E = (1 << 2), +}; + +struct pri_subcmd_aoc_request_response { + /*! + * \brief aoc_s data from response + */ + struct pri_subcmd_aoc_s aoc_s; + + /*! + * \brief if the aoc_s msg is present, this will be set + */ + int valid_aoc_s; + + /*! + * \brief What type of aoc was requested. + * \see enum PRI_AOC_REQUEST + */ + int charging_request; + + /*! + * \brief response to the charging_request + * \see enum PRI_AOC_REQ_RSP + */ + int charging_response; +}; + +struct pri_subcmd_aoc_request { + /*! + * \brief What types of aoc are being requested. + * \see enum PRI_AOC_REQUEST + */ + int charging_request; + + /*! + * \brief Value given by the initiating request. + */ + int invoke_id; +}; + struct pri_subcmd_mcid_req { /*! * \brief Information libpri knows about the malicious caller. @@ -934,6 +992,8 @@ struct pri_subcommand { struct pri_subcmd_cc_id cc_call; struct pri_subcmd_cc_cancel cc_cancel; struct pri_subcmd_transfer transfer; + struct pri_subcmd_aoc_request aoc_request; + struct pri_subcmd_aoc_request_response aoc_request_response; struct pri_subcmd_aoc_s aoc_s; struct pri_subcmd_aoc_d aoc_d; struct pri_subcmd_aoc_e aoc_e; @@ -1587,6 +1647,79 @@ void pri_set_inbanddisconnect(struct pri *pri, unsigned int enable); (and maybe some timers) */ void pri_enslave(struct pri *master, struct pri *slave); +/*! + * \brief Request AOC on call setup + * + * \param call setup struct to set charging request info on + * \param charging request to set on setup struct + * + * \retval 0 on success + * \retval -1 on failure + */ +int pri_sr_set_aoc_charging_request(struct pri_sr *sr, int charging_request); + +/*! + * \brief Send AOC Request Response to a request for AOC-S + * + * \param ctrl D channel controller. + * \param call Q.931 call leg. + * \param invoke_id for response message + * \param aoc_s message for response + * + * \retval 0 on success + * \retval -1 on failure + */ +int pri_aoc_s_request_response_send(struct pri *ctrl, q931_call *call, int invoke_id, const struct pri_subcmd_aoc_s *aoc_s); + +/*! + * \brief Send AOC Request Response to a request for AOC-D or AOC-E + * + * \param ctrl D channel controller. + * \param call Q.931 call leg. + * \param response in form of enum PRI_AOC_REQ_RSP + * \param invoke_id for response message + * + * \retval 0 on success + * \retval -1 on failure + */ +int pri_aoc_de_request_response_send(struct pri *ctrl, q931_call *call, int response, int invoke_id); + +/*! + * \brief Send AOC-S message. + * + * \param ctrl D channel controller. + * \param call Q.931 call leg. + * \param aoc_s message to send + * + * \retval 0 on success + * \retval -1 on failure + */ +int pri_aoc_s_send(struct pri *ctrl, q931_call *c, const struct pri_subcmd_aoc_s *aoc_s); + +/*! + * \brief Send AOC-D message. + * + * \param ctrl D channel controller. + * \param call Q.931 call leg. + * \param aoc_d message to send + * + * \retval 0 on success + * \retval -1 on failure + */ +int pri_aoc_d_send(struct pri *ctrl, q931_call *c, const struct pri_subcmd_aoc_d *aoc_d); + +/*! + * \brief Send AOC-E message. + * + * \param ctrl D channel controller. + * \param call Q.931 call leg. + * \param aoc_e message to send + * + * \retval 0 on success + * \retval -1 on failure + */ +int pri_aoc_e_send(struct pri *ctrl, q931_call *c, const struct pri_subcmd_aoc_e *aoc_e); + #define PRI_GR303_SUPPORT #define PRI_ENSLAVE_SUPPORT #define PRI_SETUP_CALL diff --git a/pri_aoc.c b/pri_aoc.c index b2059a3..46993dd 100644 --- a/pri_aoc.c +++ b/pri_aoc.c @@ -4,6 +4,7 @@ * Copyright (C) 2010 Digium, Inc. * * Richard Mudgett + * David Vossel * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact @@ -55,6 +56,21 @@ static void aoc_etsi_subcmd_amount(struct pri_aoc_amount *subcmd_amount, const s subcmd_amount->multiplier = etsi_amount->multiplier; } +/*! + * \internal + * \brief Fill in the ETSI amount from the AOC subcmd amount. + * + * \param subcmd_amount AOC subcmd amount. + * \param etsi_amount AOC ETSI amount. + * + * \return Nothing + */ +static void aoc_enc_etsi_subcmd_amount(const struct pri_aoc_amount *subcmd_amount, struct roseEtsiAOCAmount *etsi_amount) +{ + etsi_amount->currency = subcmd_amount->cost; + etsi_amount->multiplier = subcmd_amount->multiplier; +} + /*! * \internal * \brief Fill in the AOC subcmd time from the ETSI time. @@ -70,6 +86,21 @@ static void aoc_etsi_subcmd_time(struct pri_aoc_time *subcmd_time, const struct subcmd_time->scale = etsi_time->scale; } +/*! + * \internal + * \brief Fill in the ETSI Time from the AOC subcmd time. + * + * \param subcmd_time AOC subcmd time. + * \param etsi_time AOC ETSI time. + * + * \return Nothing + */ +static void aoc_enc_etsi_subcmd_time(const struct pri_aoc_time *subcmd_time, struct roseEtsiAOCTime *etsi_time) +{ + etsi_time->length = subcmd_time->length; + etsi_time->scale = subcmd_time->scale; +} + /*! * \internal * \brief Fill in the AOC subcmd recorded currency from the ETSI recorded currency. @@ -86,6 +117,23 @@ static void aoc_etsi_subcmd_recorded_currency(struct pri_aoc_recorded_currency * sizeof(subcmd_recorded->currency)); } +/*! + * \internal + * \brief Fill in the the ETSI recorded currency from the subcmd currency info + * + * \param subcmd_recorded AOC subcmd recorded currency. + * \param etsi_recorded AOC ETSI recorded currency. + * + * \return Nothing + */ +static void aoc_enc_etsi_subcmd_recorded_currency(const struct pri_aoc_recorded_currency *subcmd_recorded, struct roseEtsiAOCRecordedCurrency *etsi_recorded) +{ + aoc_enc_etsi_subcmd_amount(&subcmd_recorded->amount, &etsi_recorded->amount); + libpri_copy_string((char *) etsi_recorded->currency, + subcmd_recorded->currency, + sizeof(etsi_recorded->currency)); +} + /*! * \internal * \brief Fill in the AOC subcmd recorded units from the ETSI recorded units. @@ -116,6 +164,84 @@ static void aoc_etsi_subcmd_recorded_units(struct pri_aoc_recorded_units *subcmd subcmd_recorded->num_items = idx; } +/*! + * \internal + * \brief Fill in the ETSI recorded units from the AOC subcmd recorded units. + * + * \param subcmd_recorded AOC subcmd recorded units list. + * \param etsi_recorded AOC ETSI recorded units list. + * + * \return Nothing + */ +static void aoc_enc_etsi_subcmd_recorded_units(const struct pri_aoc_recorded_units *subcmd_recorded, struct roseEtsiAOCRecordedUnitsList *etsi_recorded) +{ + int i; + + /* Fill in the itemized list of recorded units. */ + for (i = 0; i < subcmd_recorded->num_items; i++) { + if (subcmd_recorded->item[i].number >= 0) { + etsi_recorded->list[i].number_of_units = subcmd_recorded->item[i].number; + } else { + etsi_recorded->list[i].not_available = 1; + } + if (subcmd_recorded->item[i].type > 0) { + etsi_recorded->list[i].type_of_unit = subcmd_recorded->item[i].type; + etsi_recorded->list[i].type_of_unit_present = 1; + } + } + etsi_recorded->num_records = i; + + if (!etsi_recorded->num_records) { + etsi_recorded->list[0].not_available = 1; + etsi_recorded->list[0].type_of_unit_present = 0; + etsi_recorded->num_records = 1; + } +} + +/*! + * \brief Handle the ETSI ChargingRequest. + * + * \param ctrl D channel controller for diagnostic messages or global options. + * \param call Q.931 call leg. + * \param invoke Decoded ROSE invoke message contents. + * + * \return Nothing + */ +void aoc_etsi_aoc_request(struct pri *ctrl, q931_call *call, const struct rose_msg_invoke *invoke) +{ + struct pri_subcommand *subcmd; + int request; + + if (!PRI_MASTER(ctrl)->aoc_support) { + send_facility_error(ctrl, call, invoke->invoke_id, ROSE_ERROR_Gen_NotSubscribed); + return; + } + switch (invoke->args.etsi.ChargingRequest.charging_case) { + case 0:/* chargingInformationAtCallSetup */ + request = PRI_AOC_REQUEST_S; + break; + case 1:/* chargingDuringACall */ + request = PRI_AOC_REQUEST_D; + break; + case 2:/* chargingAtTheEndOfACall */ + request = PRI_AOC_REQUEST_E; + break; + default: + send_facility_error(ctrl, call, invoke->invoke_id, ROSE_ERROR_Gen_NotImplemented); + return; + } + + subcmd = q931_alloc_subcommand(ctrl); + if (!subcmd) { + send_facility_error(ctrl, call, invoke->invoke_id, ROSE_ERROR_Gen_NotAvailable); + return; + } + + subcmd->cmd = PRI_SUBCMD_AOC_CHARGING_REQ; + subcmd->u.aoc_request.invoke_id = invoke->invoke_id; + subcmd->u.aoc_request.charging_request = request; +} + /*! * \internal * \brief Fill in the AOC-S subcmd currency info list of chargeable items. @@ -208,6 +334,99 @@ static void aoc_etsi_subcmd_aoc_s_currency_info(struct pri_subcmd_aoc_s *aoc_s, aoc_s->num_items = idx; } +/*! + * \internal + * \brief Fill in the currency info list of chargeable items from a aoc_s subcmd + * + * \param aoc_s AOC-S info list of chargeable items. + * \param info ETSI info list of chargeable items. + * + * \return Nothing + */ +static void enc_etsi_subcmd_aoc_s_currency_info(const struct pri_subcmd_aoc_s *aoc_s, struct roseEtsiAOCSCurrencyInfoList *info) +{ + int idx; + + for (idx = 0; idx < aoc_s->num_items && idx < ARRAY_LEN(info->list); ++idx) { + /* What is being charged. */ + switch (aoc_s->item[idx].chargeable) { + default: + case PRI_AOC_CHARGED_ITEM_BASIC_COMMUNICATION: + info->list[idx].charged_item = 0;/* basicCommunication */ + break; + case PRI_AOC_CHARGED_ITEM_CALL_ATTEMPT: + info->list[idx].charged_item = 1;/* callAttempt */ + break; + case PRI_AOC_CHARGED_ITEM_CALL_SETUP: + info->list[idx].charged_item = 2;/* callSetup */ + break; + case PRI_AOC_CHARGED_ITEM_USER_USER_INFO: + info->list[idx].charged_item = 3;/* userToUserInfo */ + break; + case PRI_AOC_CHARGED_ITEM_SUPPLEMENTARY_SERVICE: + info->list[idx].charged_item = 4;/* operationOfSupplementaryServ */ + break; + } + + /* Rate method being used. */ + switch (aoc_s->item[idx].rate_type) { + case PRI_AOC_RATE_TYPE_SPECIAL_CODE: + info->list[idx].currency_type = 0;/* specialChargingCode */ + info->list[idx].u.special_charging_code = aoc_s->item[idx].rate.special; + break; + case PRI_AOC_RATE_TYPE_DURATION: + info->list[idx].currency_type = 1;/* durationCurrency */ + aoc_enc_etsi_subcmd_amount(&aoc_s->item[idx].rate.duration.amount, + &info->list[idx].u.duration.amount); + aoc_enc_etsi_subcmd_time(&aoc_s->item[idx].rate.duration.time, + &info->list[idx].u.duration.time); + if (aoc_s->item[idx].rate.duration.granularity.length) { + info->list[idx].u.duration.granularity_present = 1; + aoc_enc_etsi_subcmd_time(&aoc_s->item[idx].rate.duration.granularity, + &info->list[idx].u.duration.granularity); + } else { + info->list[idx].u.duration.granularity_present = 0; + } + info->list[idx].u.duration.charging_type = aoc_s->item[idx].rate.duration.charging_type; + libpri_copy_string((char *) info->list[idx].u.duration.currency, + aoc_s->item[idx].rate.duration.currency, + sizeof((char *) info->list[idx].u.duration.currency)); + break; + case PRI_AOC_RATE_TYPE_FLAT: + info->list[idx].currency_type = 2;/* flatRateCurrency */ + aoc_enc_etsi_subcmd_amount(&aoc_s->item[idx].rate.flat.amount, + &info->list[idx].u.flat_rate.amount); + libpri_copy_string((char *) info->list[idx].u.flat_rate.currency, + aoc_s->item[idx].rate.flat.currency, + sizeof((char *) info->list[idx].u.flat_rate.currency)); + break; + case PRI_AOC_RATE_TYPE_VOLUME: + info->list[idx].currency_type = 3;/* volumeRateCurrency */ + aoc_enc_etsi_subcmd_amount(&aoc_s->item[idx].rate.volume.amount, + &info->list[idx].u.volume_rate.amount); + info->list[idx].u.volume_rate.unit = aoc_s->item[idx].rate.volume.unit; + libpri_copy_string((char *) info->list[idx].u.volume_rate.currency, + aoc_s->item[idx].rate.volume.currency, + sizeof((char *) info->list[idx].u.volume_rate.currency)); + break; + case PRI_AOC_RATE_TYPE_FREE: + info->list[idx].currency_type = 4;/* freeOfCharge */ + break; + default: + case PRI_AOC_RATE_TYPE_NOT_AVAILABLE: + info->list[idx].currency_type = 5;/* currencyInfoNotAvailable */ + break; + } + } + if (!idx) { + /* We cannot send an empty list so create a dummy list element. */ + info->list[idx].charged_item = 0;/* basicCommunication */ + info->list[idx].currency_type = 5;/* currencyInfoNotAvailable */ + ++idx; + } + info->num_records = idx; +} + /*! * \brief Handle the ETSI AOCSCurrency message. * @@ -388,80 +607,6 @@ void aoc_etsi_aoc_d_charging_unit(struct pri *ctrl, const struct rose_msg_invoke } } -/*! - * \internal - * \brief Encode the ETSI AOCEChargingUnit invoke message. - * - * \param ctrl D channel controller for diagnostic messages or global options. - * \param pos Starting position to encode the facility ie contents. - * \param end End of facility ie contents encoding data buffer. - * \param chargedunits Number of units charged to encode. - * - * \retval Start of the next ASN.1 component to encode on success. - * \retval NULL on error. - */ -static unsigned char *enc_etsi_aoce_charging_unit(struct pri *ctrl, unsigned char *pos, - unsigned char *end, long chargedunits) -{ - struct rose_msg_invoke msg; - - pos = facility_encode_header(ctrl, pos, end, NULL); - if (!pos) { - return NULL; - } - - memset(&msg, 0, sizeof(msg)); - msg.operation = ROSE_ETSI_AOCEChargingUnit; - msg.invoke_id = get_invokeid(ctrl); - msg.args.etsi.AOCEChargingUnit.type = 1; /* charging_unit */ - if (chargedunits <= 0) { - msg.args.etsi.AOCEChargingUnit.charging_unit.free_of_charge = 1; - } else { - msg.args.etsi.AOCEChargingUnit.charging_unit.specific.recorded.num_records = 1; - msg.args.etsi.AOCEChargingUnit.charging_unit.specific.recorded.list[0]. - number_of_units = chargedunits; - } - pos = rose_encode_invoke(ctrl, pos, end, &msg); - - return pos; -} - -/*! - * \internal - * \brief Send the ETSI AOCEChargingUnit invoke message. - * - * \param ctrl D channel controller for diagnostic messages or global options. - * \param call Call leg from which to encode AOC. - * \param chargedunits Number of units charged to encode. - * - * \retval 0 on success. - * \retval -1 on error. - */ -static int aoc_aoce_charging_unit_encode(struct pri *ctrl, q931_call *call, - long chargedunits) -{ - unsigned char buffer[255]; - unsigned char *end; - - /* sample data: [ 91 a1 12 02 02 3a 78 02 01 24 30 09 30 07 a1 05 30 03 02 01 01 ] */ - - end = - enc_etsi_aoce_charging_unit(ctrl, buffer, buffer + sizeof(buffer), chargedunits); - if (!end) { - return -1; - } - - /* Remember that if we queue a facility IE for a facility message we - * have to explicitly send the facility message ourselves */ - if (pri_call_apdu_queue(call, Q931_FACILITY, buffer, end - buffer, NULL) - || q931_facility(call->pri, call)) { - pri_message(ctrl, "Could not schedule facility message for call %d\n", call->cr); - return -1; - } - - return 0; -} - /*! * \internal * \brief Fill in the AOC-E subcmd charging association from the ETSI charging association. @@ -542,6 +687,66 @@ static enum PRI_AOC_E_BILLING_ID aoc_etsi_subcmd_aoc_e_billing_id(int billing_id return value; } +/*! + * \internal + * \brief Determine the ETSI AOC-E billing_id value from the subcmd. + * + * \param billing_id from upper layer. + * + * \retval -1 failure + * \retval etsi billing id + */ +static int aoc_subcmd_aoc_e_etsi_billing_id(enum PRI_AOC_E_BILLING_ID billing_id) +{ + switch (billing_id) { + case PRI_AOC_E_BILLING_ID_NORMAL: + return 0;/* normalCharging */ + case PRI_AOC_E_BILLING_ID_REVERSE: + return 1;/* reverseCharging */ + case PRI_AOC_E_BILLING_ID_CREDIT_CARD: + return 2;/* creditCardCharging */ + case PRI_AOC_E_BILLING_ID_CALL_FORWARDING_UNCONDITIONAL: + return 3;/* callForwardingUnconditional */ + case PRI_AOC_E_BILLING_ID_CALL_FORWARDING_BUSY: + return 4;/* callForwardingBusy */ + case PRI_AOC_E_BILLING_ID_CALL_FORWARDING_NO_REPLY: + return 5;/* callForwardingNoReply */ + case PRI_AOC_E_BILLING_ID_CALL_DEFLECTION: + return 6;/* callDeflection */ + case PRI_AOC_E_BILLING_ID_CALL_TRANSFER: + return 7;/* callTransfer */ + case PRI_AOC_E_BILLING_ID_NOT_AVAILABLE: + break; + } + + return -1; +} + +/*! + * \internal + * \brief Determine the ETSI AOC-D billing_id value from the subcmd. + * + * \param billing_id from upper layer. + * + * \retval -1 failure + * \retval etsi billing id + */ +static int aoc_subcmd_aoc_d_etsi_billing_id(enum PRI_AOC_D_BILLING_ID billing_id) +{ + switch (billing_id) { + case PRI_AOC_D_BILLING_ID_NORMAL: + return 0;/* normalCharging */ + case PRI_AOC_D_BILLING_ID_REVERSE: + return 1;/* reverseCharging */ + case PRI_AOC_D_BILLING_ID_CREDIT_CARD: + return 2;/* creditCardCharging */ + case PRI_AOC_D_BILLING_ID_NOT_AVAILABLE: + break; + } + + return -1; +} + /*! * \brief Handle the ETSI AOCECurrency message. * @@ -622,11 +827,6 @@ void aoc_etsi_aoc_e_charging_unit(struct pri *ctrl, q931_call *call, const struc } } } - /* the following function is currently not used - just to make the compiler happy */ - if (0) { - /* use this function to forward the aoc-e on a bridged channel */ - aoc_aoce_charging_unit_encode(ctrl, call, call->aoc_units); - } if (!PRI_MASTER(ctrl)->aoc_support) { return; @@ -674,5 +874,892 @@ void pri_aoc_events_enable(struct pri *ctrl, int enable) } } +/*! + * \internal + * \brief Encode the ETSI AOCECurrency invoke message. + * + * \param ctrl D channel controller for diagnostic messages or global options. + * \param pos Starting position to encode the facility ie contents. + * \param end End of facility ie contents encoding data buffer. + * \param aoc_e the AOC-E data to encode. + * + * \retval Start of the next ASN.1 component to encode on success. + * \retval NULL on error. + */ +static unsigned char *enc_etsi_aoce_currency(struct pri *ctrl, unsigned char *pos, + unsigned char *end, const struct pri_subcmd_aoc_e *aoc_e) +{ + struct rose_msg_invoke msg; + struct q931_party_number q931_number; + + pos = facility_encode_header(ctrl, pos, end, NULL); + if (!pos) { + return NULL; + } + + memset(&msg, 0, sizeof(msg)); + msg.operation = ROSE_ETSI_AOCECurrency; + msg.invoke_id = get_invokeid(ctrl); + + if (aoc_e->charge == PRI_AOC_DE_CHARGE_FREE) { + msg.args.etsi.AOCECurrency.type = 1; /* currency_info */ + msg.args.etsi.AOCECurrency.currency_info.free_of_charge = 1; + } else if ((aoc_e->charge == PRI_AOC_DE_CHARGE_CURRENCY) && (aoc_e->recorded.money.amount.cost >= 0)) { + msg.args.etsi.AOCECurrency.type = 1; /* currency_info */ + aoc_enc_etsi_subcmd_recorded_currency(&aoc_e->recorded.money, + &msg.args.etsi.AOCECurrency.currency_info.specific.recorded); + } else { + msg.args.etsi.AOCECurrency.type = 0; /* charge_not_available */ + } + + if (aoc_subcmd_aoc_e_etsi_billing_id(aoc_e->billing_id) != -1) { + msg.args.etsi.AOCECurrency.currency_info.specific.billing_id_present = 1; + msg.args.etsi.AOCECurrency.currency_info.specific.billing_id = + aoc_subcmd_aoc_e_etsi_billing_id(aoc_e->billing_id); + } + + switch (aoc_e->associated.charging_type) { + case PRI_AOC_E_CHARGING_ASSOCIATION_NUMBER: + msg.args.etsi.AOCECurrency.currency_info.charging_association_present = 1; + msg.args.etsi.AOCECurrency.currency_info.charging_association.type = 1; /* charged_number */ + pri_copy_party_number_to_q931(&q931_number, &aoc_e->associated.charge.number); + q931_copy_number_to_rose(ctrl, + &msg.args.etsi.AOCECurrency.currency_info.charging_association.number, + &q931_number); + break; + case PRI_AOC_E_CHARGING_ASSOCIATION_ID: + msg.args.etsi.AOCECurrency.currency_info.charging_association_present = 1; + msg.args.etsi.AOCECurrency.currency_info.charging_association.type = 0; /* charge_identifier */ + msg.args.etsi.AOCECurrency.currency_info.charging_association.id = + aoc_e->associated.charge.id; + break; + default: + /* do nothing */ + break; + } + + pos = rose_encode_invoke(ctrl, pos, end, &msg); + + return pos; +} + +/*! + * \internal + * \brief Encode the ETSI AOCEChargingUnit invoke message. + * + * \param ctrl D channel controller for diagnostic messages or global options. + * \param pos Starting position to encode the facility ie contents. + * \param end End of facility ie contents encoding data buffer. + * \param aoc_e the AOC-E data to encode. + * + * \retval Start of the next ASN.1 component to encode on success. + * \retval NULL on error. + */ +static unsigned char *enc_etsi_aoce_charging_unit(struct pri *ctrl, unsigned char *pos, + unsigned char *end, const struct pri_subcmd_aoc_e *aoc_e) +{ + struct rose_msg_invoke msg; + struct q931_party_number q931_number; + + pos = facility_encode_header(ctrl, pos, end, NULL); + if (!pos) { + return NULL; + } + + memset(&msg, 0, sizeof(msg)); + msg.operation = ROSE_ETSI_AOCEChargingUnit; + msg.invoke_id = get_invokeid(ctrl); + + if (aoc_e->charge == PRI_AOC_DE_CHARGE_FREE) { + msg.args.etsi.AOCEChargingUnit.type = 1; /* charging_unit */ + msg.args.etsi.AOCEChargingUnit.charging_unit.free_of_charge = 1; + + } else if ((aoc_e->charge == PRI_AOC_DE_CHARGE_UNITS) && (aoc_e->recorded.unit.num_items > 0)) { + msg.args.etsi.AOCEChargingUnit.type = 1; /* charging_unit */ + aoc_enc_etsi_subcmd_recorded_units(&aoc_e->recorded.unit, + &msg.args.etsi.AOCEChargingUnit.charging_unit.specific.recorded); + } else { + msg.args.etsi.AOCEChargingUnit.type = 0; /* charge_not_available */ + } + + if (aoc_subcmd_aoc_e_etsi_billing_id(aoc_e->billing_id) != -1) { + msg.args.etsi.AOCEChargingUnit.charging_unit.specific.billing_id_present = 1; + msg.args.etsi.AOCEChargingUnit.charging_unit.specific.billing_id = + aoc_subcmd_aoc_e_etsi_billing_id(aoc_e->billing_id); + } + + switch (aoc_e->associated.charging_type) { + case PRI_AOC_E_CHARGING_ASSOCIATION_NUMBER: + msg.args.etsi.AOCEChargingUnit.charging_unit.charging_association_present = 1; + msg.args.etsi.AOCEChargingUnit.charging_unit.charging_association.type = 1; /* charged_number */ + pri_copy_party_number_to_q931(&q931_number, &aoc_e->associated.charge.number); + q931_copy_number_to_rose(ctrl, + &msg.args.etsi.AOCEChargingUnit.charging_unit.charging_association.number, + &q931_number); + break; + case PRI_AOC_E_CHARGING_ASSOCIATION_ID: + msg.args.etsi.AOCEChargingUnit.charging_unit.charging_association_present = 1; + msg.args.etsi.AOCEChargingUnit.charging_unit.charging_association.type = 0; /* charge_identifier */ + msg.args.etsi.AOCEChargingUnit.charging_unit.charging_association.id = + aoc_e->associated.charge.id; + break; + default: + /* do nothing */ + break; + } + + pos = rose_encode_invoke(ctrl, pos, end, &msg); + + return pos; +} + +/*! + * \internal + * \brief Encode the ETSI AOCDChargingUnit invoke message. + * + * \param ctrl D channel controller for diagnostic messages or global options. + * \param pos Starting position to encode the facility ie contents. + * \param end End of facility ie contents encoding data buffer. + * \param aoc_d the AOC-D data to encode. + * + * \retval Start of the next ASN.1 component to encode on success. + * \retval NULL on error. + */ +static unsigned char *enc_etsi_aocd_charging_unit(struct pri *ctrl, unsigned char *pos, + unsigned char *end, const struct pri_subcmd_aoc_d *aoc_d) +{ + struct rose_msg_invoke msg; + + pos = facility_encode_header(ctrl, pos, end, NULL); + if (!pos) { + return NULL; + } + + memset(&msg, 0, sizeof(msg)); + msg.operation = ROSE_ETSI_AOCDChargingUnit; + msg.invoke_id = get_invokeid(ctrl); + + if (aoc_d->charge == PRI_AOC_DE_CHARGE_FREE) { + msg.args.etsi.AOCDChargingUnit.type = 1; /* free_of_charge */ + } else if ((aoc_d->charge == PRI_AOC_DE_CHARGE_UNITS) && (aoc_d->recorded.unit.num_items > 0)) { + msg.args.etsi.AOCDChargingUnit.type = 2; /* specific_charging_units */ + aoc_enc_etsi_subcmd_recorded_units(&aoc_d->recorded.unit, + &msg.args.etsi.AOCDChargingUnit.specific.recorded); + } else { + msg.args.etsi.AOCDChargingUnit.type = 0; /* charge_not_available */ + } + + if (aoc_subcmd_aoc_d_etsi_billing_id(aoc_d->billing_id) != -1) { + msg.args.etsi.AOCDChargingUnit.specific.billing_id_present = 1; + msg.args.etsi.AOCDChargingUnit.specific.billing_id = + aoc_subcmd_aoc_d_etsi_billing_id(aoc_d->billing_id); + } + + pos = rose_encode_invoke(ctrl, pos, end, &msg); + + return pos; +} + +/*! + * \internal + * \brief Encode the ETSI AOCDCurrency invoke message. + * + * \param ctrl D channel controller for diagnostic messages or global options. + * \param pos Starting position to encode the facility ie contents. + * \param end End of facility ie contents encoding data buffer. + * \param aoc_d the AOC-D data to encode. + * + * \retval Start of the next ASN.1 component to encode on success. + * \retval NULL on error. + */ +static unsigned char *enc_etsi_aocd_currency(struct pri *ctrl, unsigned char *pos, + unsigned char *end, const struct pri_subcmd_aoc_d *aoc_d) +{ + struct rose_msg_invoke msg; + + pos = facility_encode_header(ctrl, pos, end, NULL); + if (!pos) { + return NULL; + } + + memset(&msg, 0, sizeof(msg)); + msg.operation = ROSE_ETSI_AOCDCurrency; + msg.invoke_id = get_invokeid(ctrl); + + if (aoc_d->charge == PRI_AOC_DE_CHARGE_FREE) { + msg.args.etsi.AOCDCurrency.type = 1; /* free_of_charge */ + } else if ((aoc_d->charge == PRI_AOC_DE_CHARGE_CURRENCY) && (aoc_d->recorded.money.amount.cost >= 0)) { + msg.args.etsi.AOCDCurrency.type = 2; /* specific_currency */ + aoc_enc_etsi_subcmd_recorded_currency(&aoc_d->recorded.money, + &msg.args.etsi.AOCDCurrency.specific.recorded); + } else { + msg.args.etsi.AOCDCurrency.type = 0; /* charge_not_available */ + } + + if (aoc_subcmd_aoc_d_etsi_billing_id(aoc_d->billing_id) != -1) { + msg.args.etsi.AOCDCurrency.specific.billing_id_present = 1; + msg.args.etsi.AOCDCurrency.specific.billing_id = + aoc_subcmd_aoc_d_etsi_billing_id(aoc_d->billing_id); + } + + pos = rose_encode_invoke(ctrl, pos, end, &msg); + + return pos; +} + +/*! + * \internal + * \brief Encode the ETSI AOCSSpecialArr invoke message. + * + * \param ctrl D channel controller for diagnostic messages or global options. + * \param pos Starting position to encode the facility ie contents. + * \param end End of facility ie contents encoding data buffer. + * \param aoc_s the AOC-S data to encode. + * + * \retval Start of the next ASN.1 component to encode on success. + * \retval NULL on error. + */ +static unsigned char *enc_etsi_aocs_special_arrangement(struct pri *ctrl, unsigned char *pos, + unsigned char *end, const struct pri_subcmd_aoc_s *aoc_s) +{ + struct rose_msg_invoke msg; + + pos = facility_encode_header(ctrl, pos, end, NULL); + if (!pos) { + return NULL; + } + + memset(&msg, 0, sizeof(msg)); + msg.operation = ROSE_ETSI_AOCSSpecialArr; + msg.invoke_id = get_invokeid(ctrl); + + if (!aoc_s->num_items || (aoc_s->item[0].rate_type != PRI_AOC_RATE_TYPE_SPECIAL_CODE)) { + msg.args.etsi.AOCSSpecialArr.type = 0;/* charge_not_available */ + } else { + msg.args.etsi.AOCSSpecialArr.type = 1;/* special_arrangement_info */ + msg.args.etsi.AOCSSpecialArr.special_arrangement = aoc_s->item[0].rate.special; + } + + pos = rose_encode_invoke(ctrl, pos, end, &msg); + + return pos; +} + +/*! + * \internal + * \brief Encode the ETSI AOCSCurrency invoke message. + * + * \param ctrl D channel controller for diagnostic messages or global options. + * \param pos Starting position to encode the facility ie contents. + * \param end End of facility ie contents encoding data buffer. + * \param aoc_s the AOC-S data to encode. + * + * \retval Start of the next ASN.1 component to encode on success. + * \retval NULL on error. + */ +static unsigned char *enc_etsi_aocs_currency(struct pri *ctrl, unsigned char *pos, + unsigned char *end, const struct pri_subcmd_aoc_s *aoc_s) +{ + struct rose_msg_invoke msg; + + pos = facility_encode_header(ctrl, pos, end, NULL); + if (!pos) { + return NULL; + } + + memset(&msg, 0, sizeof(msg)); + msg.operation = ROSE_ETSI_AOCSCurrency; + msg.invoke_id = get_invokeid(ctrl); + + if (aoc_s->num_items) { + msg.args.etsi.AOCSCurrency.type = 1; /* currency_info_list */ + enc_etsi_subcmd_aoc_s_currency_info(aoc_s, &msg.args.etsi.AOCSCurrency.currency_info); + } else { + msg.args.etsi.AOCSCurrency.type = 0; /* charge_not_available */ + } + + pos = rose_encode_invoke(ctrl, pos, end, &msg); + + return pos; +} + +/*! + * \internal + * \brief Encode the ETSI ChargingRequest Response message + * + * \param ctrl D channel controller for diagnostic messages or global options. + * \param pos Starting position to encode the facility ie contents. + * \param end End of facility ie contents encoding data buffer. + * \param response the response to the request + * \param invoke_id the request's invoke id + * \param aoc_s the rate list associated with a response to AOC-S request + * Could be NULL. + * + * \retval Start of the next ASN.1 component to encode on success. + * \retval NULL on error. + */ +static unsigned char *enc_etsi_aoc_request_response(struct pri *ctrl, unsigned char *pos, + unsigned char *end, enum PRI_AOC_REQ_RSP response, int invoke_id, const struct pri_subcmd_aoc_s *aoc_s) +{ + struct rose_msg_result msg_result = { 0, }; + struct rose_msg_error msg_error = { 0, }; + int is_error = 0; + + pos = facility_encode_header(ctrl, pos, end, NULL); + if (!pos) { + return NULL; + } + + switch (response) { + case PRI_AOC_REQ_RSP_CURRENCY_INFO_LIST: + if (!aoc_s) { + return NULL; + } + enc_etsi_subcmd_aoc_s_currency_info(aoc_s, &msg_result.args.etsi.ChargingRequest.u.currency_info); + msg_result.args.etsi.ChargingRequest.type = 0;/* currency_info_list */ + break; + case PRI_AOC_REQ_RSP_SPECIAL_ARR: + if (!aoc_s) { + return NULL; + } + msg_result.args.etsi.ChargingRequest.type = 1;/* special_arrangement_info */ + msg_result.args.etsi.ChargingRequest.u.special_arrangement = aoc_s->item[0].rate.special; + break; + case PRI_AOC_REQ_RSP_CHARGING_INFO_FOLLOWS: + msg_result.args.etsi.ChargingRequest.type = 2;/* charging_info_follows */ + break; + case PRI_AOC_REQ_RSP_ERROR_NOT_IMPLEMENTED: + msg_error.code = ROSE_ERROR_Gen_NotImplemented; + is_error = 1; + break; + default: + case PRI_AOC_REQ_RSP_ERROR_NOT_AVAILABLE: + is_error = 1; + msg_error.code = ROSE_ERROR_Gen_NotAvailable; + break; + } + + if (is_error) { + msg_error.invoke_id = invoke_id; + pos = rose_encode_error(ctrl, pos, end, &msg_error); + } else { + msg_result.operation = ROSE_ETSI_ChargingRequest; + msg_result.invoke_id = invoke_id; + pos = rose_encode_result(ctrl, pos, end, &msg_result); + } + + return pos; +} + +/*! + * \internal + * \brief Encode the ETSI ChargingRequest invoke message. + * + * \param ctrl D channel controller for diagnostic messages or global options. + * \param pos Starting position to encode the facility ie contents. + * \param end End of facility ie contents encoding data buffer. + * \param aoc_request the aoc charging request data to encode. + * + * \retval Start of the next ASN.1 component to encode on success. + * \retval NULL on error. + */ +static unsigned char *enc_etsi_aoc_request(struct pri *ctrl, unsigned char *pos, + unsigned char *end, enum PRI_AOC_REQUEST request) +{ + struct rose_msg_invoke msg; + + pos = facility_encode_header(ctrl, pos, end, NULL); + if (!pos) { + return NULL; + } + + memset(&msg, 0, sizeof(msg)); + msg.operation = ROSE_ETSI_ChargingRequest; + msg.invoke_id = get_invokeid(ctrl); + + switch (request) { + case PRI_AOC_REQUEST_S: + msg.args.etsi.ChargingRequest.charging_case = 0;/* chargingInformationAtCallSetup */ + break; + case PRI_AOC_REQUEST_D: + msg.args.etsi.ChargingRequest.charging_case = 1;/* chargingDuringACall */ + break; + case PRI_AOC_REQUEST_E: + msg.args.etsi.ChargingRequest.charging_case = 2;/* chargingAtTheEndOfACall */ + break; + default: + /* no valid request parameters are present */ + return NULL; + } + + pos = rose_encode_invoke(ctrl, pos, end, &msg); + + return pos; +} + +/*! + * \internal + * \brief Send the ETSI AOC Request Response message for an AOC-S request + * + * \param ctrl D channel controller for diagnostic messages or global options. + * \param call Call leg from which to encode AOC. + * \param invoke_id the request's invoke id + * \param aoc_s Optional AOC-S rate list for response + * + * \note if aoc_s is NULL, then a response will be sent back as AOC-S not available. + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int aoc_s_request_response_encode(struct pri *ctrl, q931_call *call, int invoke_id, const struct pri_subcmd_aoc_s *aoc_s) +{ + unsigned char buffer[255]; + unsigned char *end = NULL; + int response; + + if (!aoc_s) { + response = PRI_AOC_REQ_RSP_ERROR_NOT_AVAILABLE; + } else if (aoc_s->num_items + && aoc_s->item[0].chargeable == PRI_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT) { + response = PRI_AOC_REQ_RSP_SPECIAL_ARR; + } else { + response = PRI_AOC_REQ_RSP_CURRENCY_INFO_LIST; + } + + end = enc_etsi_aoc_request_response(ctrl, buffer, buffer + sizeof(buffer), response, invoke_id, aoc_s); + if (!end) { + return -1; + } + + /* Remember that if we queue a facility IE for a facility message we + * have to explicitly send the facility message ourselves */ + if (pri_call_apdu_queue(call, Q931_FACILITY, buffer, end - buffer, NULL) + || q931_facility(call->pri, call)) { + pri_message(ctrl, "Could not schedule aoc request response facility message for call %d\n", call->cr); + return -1; + } + + return 0; +} + +/*! + * \internal + * \brief Send the ETSI AOC Request Response message for AOC-D and AOC-E requests + * + * \param ctrl D channel controller for diagnostic messages or global options. + * \param call Call leg from which to encode AOC. + * \param response the response to the request + * \param invoke_id the request's invoke id + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int aoc_de_request_response_encode(struct pri *ctrl, q931_call *call, enum PRI_AOC_REQ_RSP response, int invoke_id) +{ + unsigned char buffer[255]; + unsigned char *end = NULL; + + end = enc_etsi_aoc_request_response(ctrl, buffer, buffer + sizeof(buffer), response, invoke_id, NULL); + if (!end) { + return -1; + } + + /* Remember that if we queue a facility IE for a facility message we + * have to explicitly send the facility message ourselves */ + if (pri_call_apdu_queue(call, Q931_FACILITY, buffer, end - buffer, NULL) + || q931_facility(call->pri, call)) { + pri_message(ctrl, "Could not schedule aoc request response facility message for call %d\n", call->cr); + return -1; + } + + return 0; +} + +/*! + * \internal + * \brief AOC-Request response callback function. + * + * \param reason Reason callback is called. + * \param ctrl D channel controller. + * \param call Q.931 call leg. + * \param apdu APDU queued entry. Do not change! + * \param msg APDU response message data. (NULL if was not the reason called.) + * + * \return TRUE if no more responses are expected. + */ +static int pri_aoc_request_get_response(enum APDU_CALLBACK_REASON reason, struct pri *ctrl, struct q931_call *call, struct apdu_event *apdu, const struct apdu_msg_data *msg) +{ + struct pri_subcommand *subcmd; + + if ((reason == APDU_CALLBACK_REASON_ERROR) || + (reason == APDU_CALLBACK_REASON_CLEANUP)) { + return 1; + } + + subcmd = q931_alloc_subcommand(ctrl); + if (!subcmd) { + return 1; + } + + memset(&subcmd->u.aoc_request_response, 0, sizeof(subcmd->u.aoc_request_response)); + subcmd->u.aoc_request_response.charging_request = apdu->response.user.value; + subcmd->cmd = PRI_SUBCMD_AOC_CHARGING_REQ_RSP; + + switch (reason) { + case APDU_CALLBACK_REASON_MSG_ERROR: + switch (msg->response.error->code) { + case ROSE_ERROR_Gen_NotImplemented: + subcmd->u.aoc_request_response.charging_response = PRI_AOC_REQ_RSP_ERROR_NOT_IMPLEMENTED; + break; + case ROSE_ERROR_Gen_NotAvailable: + subcmd->u.aoc_request_response.charging_response = PRI_AOC_REQ_RSP_ERROR_NOT_AVAILABLE; + break; + default: + subcmd->u.aoc_request_response.charging_response = PRI_AOC_REQ_RSP_ERROR; + break; + } + break; + case APDU_CALLBACK_REASON_MSG_REJECT: + subcmd->u.aoc_request_response.charging_response = PRI_AOC_REQ_RSP_ERROR_REJECT; + break; + case APDU_CALLBACK_REASON_TIMEOUT: + subcmd->u.aoc_request_response.charging_response = PRI_AOC_REQ_RSP_ERROR_TIMEOUT; + break; + case APDU_CALLBACK_REASON_MSG_RESULT: + switch (msg->response.result->args.etsi.ChargingRequest.type) { + case 0:/* currency_info_list */ + subcmd->u.aoc_request_response.valid_aoc_s = 1; + subcmd->u.aoc_request_response.charging_response = PRI_AOC_REQ_RSP_CURRENCY_INFO_LIST; + aoc_etsi_subcmd_aoc_s_currency_info(&subcmd->u.aoc_request_response.aoc_s, + &msg->response.result->args.etsi.ChargingRequest.u.currency_info); + break; + case 1:/* special_arrangement_info */ + subcmd->u.aoc_request_response.valid_aoc_s = 1; + subcmd->u.aoc_request_response.charging_response = PRI_AOC_REQ_RSP_SPECIAL_ARR; + subcmd->u.aoc_request_response.aoc_s.num_items = 1; + subcmd->u.aoc_request_response.aoc_s.item[0].chargeable = PRI_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT; + subcmd->u.aoc_request_response.aoc_s.item[0].rate_type = PRI_AOC_RATE_TYPE_SPECIAL_CODE; + subcmd->u.aoc_request_response.aoc_s.item[0].rate.special = + msg->response.result->args.etsi.ChargingRequest.u.special_arrangement; + break; + case 2:/* charging_info_follows */ + subcmd->u.aoc_request_response.charging_response = PRI_AOC_REQ_RSP_CHARGING_INFO_FOLLOWS; + break; + default: + subcmd->u.aoc_request_response.charging_response = PRI_AOC_REQ_RSP_ERROR; + break; + } + break; + default: + subcmd->u.aoc_request_response.charging_response = PRI_AOC_REQ_RSP_ERROR; + break; + } + + return 1; +} + +/*! + * \internal + * \brief Send the ETSI AOC Request invoke message. + * + * \param ctrl D channel controller for diagnostic messages or global options. + * \param call Call leg from which to encode AOC. + * \param aoc_request the aoc charging request payload data to encode. + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int aoc_charging_request_encode(struct pri *ctrl, q931_call *call, enum PRI_AOC_REQUEST request) +{ + unsigned char buffer[255]; + unsigned char *end = NULL; + struct apdu_callback_data response; + + end = enc_etsi_aoc_request(ctrl, buffer, buffer + sizeof(buffer), request); + if (!end) { + return -1; + } + + memset(&response, 0, sizeof(response)); + response.invoke_id = ctrl->last_invoke; + response.timeout_time = APDU_TIMEOUT_MSGS_ONLY; + response.num_messages = 1; + response.message_type[0] = Q931_CONNECT; + response.callback = pri_aoc_request_get_response; + response.user.value = request; + + /* in the case of an AOC request message, we queue this on a SETUP message and + * do not have to send it ourselves in this function */ + return pri_call_apdu_queue(call, Q931_SETUP, buffer, end - buffer, &response); +} + +/*! + * \internal + * \brief Send the ETSI AOCS invoke message. + * + * \param ctrl D channel controller for diagnostic messages or global options. + * \param call Call leg from which to encode AOC. + * \param aoc_s the AOC-S payload data to encode. + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int aoc_s_encode(struct pri *ctrl, q931_call *call, const struct pri_subcmd_aoc_s *aoc_s) +{ + unsigned char buffer[255]; + unsigned char *end = NULL; + + if (aoc_s->item[0].chargeable == PRI_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT) { + end = enc_etsi_aocs_special_arrangement(ctrl, buffer, buffer + sizeof(buffer), aoc_s); + } else { + end = enc_etsi_aocs_currency(ctrl, buffer, buffer + sizeof(buffer), aoc_s); + } + if (!end) { + return -1; + } + + /* Remember that if we queue a facility IE for a facility message we + * have to explicitly send the facility message ourselves */ + if (pri_call_apdu_queue(call, Q931_FACILITY, buffer, end - buffer, NULL) + || q931_facility(call->pri, call)) { + pri_message(ctrl, "Could not schedule aoc-s facility message for call %d\n", call->cr); + return -1; + } + + return 0; +} + +/*! + * \internal + * \brief Send the ETSI AOCD invoke message. + * + * \param ctrl D channel controller for diagnostic messages or global options. + * \param call Call leg from which to encode AOC. + * \param aoc_d the AOC-D payload data to encode. + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int aoc_d_encode(struct pri *ctrl, q931_call *call, const struct pri_subcmd_aoc_d *aoc_d) +{ + unsigned char buffer[255]; + unsigned char *end = NULL; + + switch (aoc_d->charge) { + case PRI_AOC_DE_CHARGE_NOT_AVAILABLE: + case PRI_AOC_DE_CHARGE_FREE: + case PRI_AOC_DE_CHARGE_CURRENCY: + end = enc_etsi_aocd_currency(ctrl, buffer, buffer + sizeof(buffer), aoc_d); + break; + case PRI_AOC_DE_CHARGE_UNITS: + end = enc_etsi_aocd_charging_unit(ctrl, buffer, buffer + sizeof(buffer), aoc_d); + break; + } + if (!end) { + return -1; + } + + /* Remember that if we queue a facility IE for a facility message we + * have to explicitly send the facility message ourselves */ + if (pri_call_apdu_queue(call, Q931_FACILITY, buffer, end - buffer, NULL) + || q931_facility(call->pri, call)) { + pri_message(ctrl, "Could not schedule aoc-d facility message for call %d\n", call->cr); + return -1; + } + + return 0; +} + +/*! + * \internal + * \brief Send the ETSI AOCE invoke message. + * + * \param ctrl D channel controller for diagnostic messages or global options. + * \param call Call leg from which to encode AOC. + * \param aoc_e the AOC-E payload data to encode. + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int aoc_e_encode(struct pri *ctrl, q931_call *call, const struct pri_subcmd_aoc_e *aoc_e) +{ + unsigned char buffer[255]; + unsigned char *end = NULL; + + switch (aoc_e->charge) { + case PRI_AOC_DE_CHARGE_NOT_AVAILABLE: + case PRI_AOC_DE_CHARGE_FREE: + case PRI_AOC_DE_CHARGE_CURRENCY: + end = enc_etsi_aoce_currency(ctrl, buffer, buffer + sizeof(buffer), aoc_e); + break; + case PRI_AOC_DE_CHARGE_UNITS: + end = enc_etsi_aoce_charging_unit(ctrl, buffer, buffer + sizeof(buffer), aoc_e); + break; + } + if (!end) { + return -1; + } + + if (pri_call_apdu_queue(call, Q931_ANY_MESSAGE, buffer, end - buffer, NULL)) { + pri_message(ctrl, "Could not schedule aoc-e facility message for call %d\n", call->cr); + return -1; + } + + return 0; +} + +int pri_aoc_de_request_response_send(struct pri *ctrl, q931_call *call, int response, int invoke_id) +{ + if (!ctrl || !call) { + return -1; + } + + switch (ctrl->switchtype) { + case PRI_SWITCH_EUROISDN_E1: + case PRI_SWITCH_EUROISDN_T1: + return aoc_de_request_response_encode(ctrl, call, response, invoke_id); + case PRI_SWITCH_QSIG: + break; + default: + return -1; + } + + return 0; +} + +int pri_aoc_s_request_response_send(struct pri *ctrl, q931_call *call, int invoke_id, const struct pri_subcmd_aoc_s *aoc_s) +{ + if (!ctrl || !call) { + return -1; + } + + switch (ctrl->switchtype) { + case PRI_SWITCH_EUROISDN_E1: + case PRI_SWITCH_EUROISDN_T1: + return aoc_s_request_response_encode(ctrl, call, invoke_id, aoc_s); + case PRI_SWITCH_QSIG: + break; + default: + return -1; + } + + return 0; +} + +/*! + * \brief Send AOC request message. + * + * \param ctrl D channel controller. + * \param call Q.931 call leg. + * \param aoc types to request + * + * \retval 0 on success + * \retval -1 on failure + */ +int aoc_charging_request_send(struct pri *ctrl, q931_call *call, enum PRI_AOC_REQUEST aoc_request_flag) +{ + int res; + + switch (ctrl->switchtype) { + case PRI_SWITCH_EUROISDN_E1: + case PRI_SWITCH_EUROISDN_T1: + if (BRI_NT_PTMP(ctrl)) { + /* + * We are not setup to handle responses from multiple phones. + * Besides, it is silly to ask for AOC from a phone. + */ + return -1; + } + res = 0; + if (aoc_request_flag & PRI_AOC_REQUEST_S) { + res |= aoc_charging_request_encode(ctrl, call, PRI_AOC_REQUEST_S); + } + if (aoc_request_flag & PRI_AOC_REQUEST_D) { + res |= aoc_charging_request_encode(ctrl, call, PRI_AOC_REQUEST_D); + } + if (aoc_request_flag & PRI_AOC_REQUEST_E) { + res |= aoc_charging_request_encode(ctrl, call, PRI_AOC_REQUEST_E); + } + return res; + case PRI_SWITCH_QSIG: + break; + default: + return -1; + } + + return 0; +} + +int pri_aoc_s_send(struct pri *ctrl, q931_call *call, const struct pri_subcmd_aoc_s *aoc_s) +{ + if (!ctrl || !call) { + return -1; + } + + switch (ctrl->switchtype) { + case PRI_SWITCH_EUROISDN_E1: + case PRI_SWITCH_EUROISDN_T1: + return aoc_s_encode(ctrl, call, aoc_s); + case PRI_SWITCH_QSIG: + break; + default: + return -1; + } + + return 0; +} + +int pri_aoc_d_send(struct pri *ctrl, q931_call *call, const struct pri_subcmd_aoc_d *aoc_d) +{ + if (!ctrl || !call) { + return -1; + } + + switch (ctrl->switchtype) { + case PRI_SWITCH_EUROISDN_E1: + case PRI_SWITCH_EUROISDN_T1: + return aoc_d_encode(ctrl, call, aoc_d); + case PRI_SWITCH_QSIG: + break; + default: + return -1; + } + return 0; +} + +int pri_aoc_e_send(struct pri *ctrl, q931_call *call, const struct pri_subcmd_aoc_e *aoc_e) +{ + if (!ctrl || !call) { + return -1; + } + + switch (ctrl->switchtype) { + case PRI_SWITCH_EUROISDN_E1: + case PRI_SWITCH_EUROISDN_T1: + return aoc_e_encode(ctrl, call, aoc_e); + case PRI_SWITCH_QSIG: + break; + default: + return -1; + } + + return 0; +} + +int pri_sr_set_aoc_charging_request(struct pri_sr *sr, int charging_request) +{ + if (charging_request & PRI_AOC_REQUEST_S) { + sr->aoc_charging_request |= PRI_AOC_REQUEST_S; + } + if (charging_request & PRI_AOC_REQUEST_D) { + sr->aoc_charging_request |= PRI_AOC_REQUEST_D; + } + if (charging_request & PRI_AOC_REQUEST_E) { + sr->aoc_charging_request |= PRI_AOC_REQUEST_E; + } + + return 0; +} + /* ------------------------------------------------------------------- */ /* end pri_aoc.c */ diff --git a/pri_facility.c b/pri_facility.c index 572ff0e..e8f805a 100644 --- a/pri_facility.c +++ b/pri_facility.c @@ -3296,6 +3296,9 @@ int pri_call_add_standard_apdus(struct pri *ctrl, q931_call *call) switch (ctrl->switchtype) { case PRI_SWITCH_EUROISDN_E1: case PRI_SWITCH_EUROISDN_T1: + if (call->aoc_charging_request) { + aoc_charging_request_send(ctrl, call, call->aoc_charging_request); + } if (PTMP_MODE(ctrl)) { /* PTMP mode */ break; @@ -4336,7 +4339,7 @@ void rose_handle_invoke(struct pri *ctrl, q931_call *call, int msgtype, q931_ie } break; case ROSE_ETSI_ChargingRequest: - /* Ignore messsage */ + aoc_etsi_aoc_request(ctrl, call, invoke); break; case ROSE_ETSI_AOCSCurrency: aoc_etsi_aoc_s_currency(ctrl, invoke); diff --git a/pri_facility.h b/pri_facility.h index 74c2e27..cad52fd 100644 --- a/pri_facility.h +++ b/pri_facility.h @@ -128,15 +128,21 @@ union apdu_callback_param { /* So calls to pri_call_apdu_find() will not find an aliased event. */ #define APDU_INVALID_INVOKE_ID 0x10000 +#define APDU_TIMEOUT_MSGS_ONLY -1 + struct apdu_callback_data { /*! APDU invoke id to match with any response messages. (Result/Error/Reject) */ int invoke_id; /*! * \brief Time to wait for responses to APDU in ms. * \note Set to 0 if send the message only. - * \note Set to less than 0 for PRI_TIMER_T_RESPONSE time. + * \note Set to APDU_TIMEOUT_MSGS_ONLY to "timeout" with the message_type list only. */ int timeout_time; + /*! Number of Q.931 messages the APDU can "timeout" on. */ + unsigned num_messages; + /*! Q.931 message list to "timeout" on. */ + int message_type[5]; /*! * \brief APDU callback function. * @@ -245,6 +251,8 @@ void pri_cc_qsig_request(struct pri *ctrl, q931_call *call, int msgtype, const s void pri_cc_qsig_cancel(struct pri *ctrl, q931_call *call, int msgtype, const struct rose_msg_invoke *invoke); void pri_cc_qsig_exec_possible(struct pri *ctrl, q931_call *call, int msgtype, const struct rose_msg_invoke *invoke); +int aoc_charging_request_send(struct pri *ctrl, q931_call *c, enum PRI_AOC_REQUEST aoc_request_flag); +void aoc_etsi_aoc_request(struct pri *ctrl, q931_call *call, const struct rose_msg_invoke *invoke); void aoc_etsi_aoc_s_currency(struct pri *ctrl, const struct rose_msg_invoke *invoke); void aoc_etsi_aoc_s_special_arrangement(struct pri *ctrl, const struct rose_msg_invoke *invoke); void aoc_etsi_aoc_d_currency(struct pri *ctrl, const struct rose_msg_invoke *invoke); diff --git a/pri_internal.h b/pri_internal.h index 9a26c6b..e506042 100644 --- a/pri_internal.h +++ b/pri_internal.h @@ -366,6 +366,7 @@ struct pri_sr { const char *keypad_digits; int transferable; int reversecharge; + int aoc_charging_request; }; /* Internal switch types */ @@ -632,6 +633,9 @@ struct q931_call { /*! TRUE if outgoing call was already redirected. */ unsigned char initially_redirected; } cc; + + /* AOC charge requesting on Setup */ + int aoc_charging_request; }; enum CC_STATES { diff --git a/q931.c b/q931.c index 6b138de..29f0609 100644 --- a/q931.c +++ b/q931.c @@ -2437,17 +2437,18 @@ static int transmit_facility(int full_ie, struct pri *ctrl, q931_call *call, int cur->sent = 1; if (cur->response.callback && cur->response.timeout_time) { - int duration; + int failed; if (0 < cur->response.timeout_time) { - /* Sender specified timeout duration. */ - duration = cur->response.timeout_time; + /* Sender specified a timeout duration. */ + cur->timer = pri_schedule_event(ctrl, cur->response.timeout_time, + q931_apdu_timeout, cur); + failed = !cur->timer; } else { - /* Sender wants to use the typical timeout duration. */ - duration = ctrl->timers[PRI_TIMER_T_RESPONSE]; + /* Sender wants to "timeout" only when specified messages are received. */ + failed = !cur->response.num_messages; } - cur->timer = pri_schedule_event(ctrl, duration, q931_apdu_timeout, cur); - if (!cur->timer) { + if (failed) { /* Remove APDU from list. */ *prev = cur->next; @@ -2456,7 +2457,7 @@ static int transmit_facility(int full_ie, struct pri *ctrl, q931_call *call, int free(cur); } - } else if (!cur->timer) { + } else { /* Remove APDU from list. */ *prev = cur->next; free(cur); @@ -2576,6 +2577,52 @@ static void q931_handle_facilities(struct pri *ctrl, q931_call *call, int msgtyp } } +/*! + * \internal + * \brief Check if any APDU responses "timeout" with the current Q.931 message. + * + * \param ctrl D channel controller. + * \param call Q.931 call leg. + * \param msgtype Q.931 message type received. + * + * \return Nothing + */ +static void q931_apdu_msg_expire(struct pri *ctrl, struct q931_call *call, int msgtype) +{ + struct apdu_event **prev; + struct apdu_event **prev_next; + struct apdu_event *cur; + unsigned idx; + + for (prev = &call->apdus; *prev; prev = prev_next) { + cur = *prev; + prev_next = &cur->next; + if (cur->sent) { + for (idx = 0; idx < cur->response.num_messages; ++idx) { + if (cur->response.message_type[idx] == msgtype) { + /* + * APDU response message "timeout". + * + * Extract the APDU from the list so it cannot be + * deleted from under us by the callback. + */ + prev_next = prev; + *prev = cur->next; + + /* Stop any response timeout. */ + pri_schedule_del(ctrl, cur->timer); + cur->timer = 0; + + cur->response.callback(APDU_CALLBACK_REASON_TIMEOUT, ctrl, call, cur, NULL); + + free(cur); + break; + } + } + } + } +} + static int transmit_progress_indicator(int full_ie, struct pri *ctrl, q931_call *call, int msgtype, q931_ie *ie, int len, int order) { int code, mask; @@ -5093,6 +5140,8 @@ int q931_setup(struct pri *ctrl, q931_call *c, struct pri_sr *req) c->reversecharge = req->reversecharge; + c->aoc_charging_request = req->aoc_charging_request; + pri_call_add_standard_apdus(ctrl, c); /* Save the initial cc-parties. */ @@ -6423,6 +6472,7 @@ int q931_receive(struct pri *ctrl, int tei, q931_h *h, int len) /* Now handle the facility ie's after all the other ie's were processed. */ q931_handle_facilities(ctrl, c, mh->msg); } + q931_apdu_msg_expire(ctrl, c, mh->msg); /* Post handling */ switch (h->pd) { @@ -6436,6 +6486,9 @@ int q931_receive(struct pri *ctrl, int tei, q931_h *h, int len) if (c->master_call->outboundbroadcast) { nt_ptmp_handle_q931_message(ctrl, mh, c, &allow_event, &allow_posthandle); + if (allow_event) { + q931_apdu_msg_expire(ctrl, c->master_call, mh->msg); + } } if (allow_posthandle) {