diff --git a/Makefile b/Makefile index b16a252..69cc314 100644 --- a/Makefile +++ b/Makefile @@ -47,6 +47,7 @@ STATIC_OBJS= \ q921.o \ prisched.o \ q931.o \ + pri_aoc.o \ pri_cc.o \ pri_facility.o \ asn1_primitive.o \ @@ -71,6 +72,7 @@ DYNAMIC_OBJS= \ q921.lo \ prisched.lo \ q931.lo \ + pri_aoc.lo \ pri_cc.lo \ pri_facility.lo \ asn1_primitive.lo \ diff --git a/libpri.h b/libpri.h index fca0b0d..0e2282f 100644 --- a/libpri.h +++ b/libpri.h @@ -539,6 +539,11 @@ struct pri_rerouting_data { #define PRI_SUBCMD_CC_CANCEL 15 /*!< Unsolicited indication that CC is canceled */ #define PRI_SUBCMD_CC_STOP_ALERTING 16 /*!< Indicate that someone else has responed to remote user free */ #define PRI_SUBCMD_TRANSFER_CALL 17 /*!< Request to transfer the specified calls together. */ +#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 */ #if defined(STATUS_REQUEST_PLACE_HOLDER) struct pri_subcmd_status_request { @@ -648,6 +653,227 @@ struct pri_subcmd_transfer { int invoke_id; }; +/*! \brief What is being charged. */ +enum PRI_AOC_CHARGED_ITEM { + PRI_AOC_CHARGED_ITEM_NOT_AVAILABLE, + PRI_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT, + PRI_AOC_CHARGED_ITEM_BASIC_COMMUNICATION, + PRI_AOC_CHARGED_ITEM_CALL_ATTEMPT, + PRI_AOC_CHARGED_ITEM_CALL_SETUP, + PRI_AOC_CHARGED_ITEM_USER_USER_INFO, + PRI_AOC_CHARGED_ITEM_SUPPLEMENTARY_SERVICE, +}; + +/*! \brief Rate method being used. */ +enum PRI_AOC_RATE_TYPE { + PRI_AOC_RATE_TYPE_NOT_AVAILABLE, + PRI_AOC_RATE_TYPE_FREE, + PRI_AOC_RATE_TYPE_FREE_FROM_BEGINNING, + PRI_AOC_RATE_TYPE_DURATION, + PRI_AOC_RATE_TYPE_FLAT, + PRI_AOC_RATE_TYPE_VOLUME, + PRI_AOC_RATE_TYPE_SPECIAL_CODE, +}; + +enum PRI_AOC_TIME_SCALE { + PRI_AOC_TIME_SCALE_HUNDREDTH_SECOND, + PRI_AOC_TIME_SCALE_TENTH_SECOND, + PRI_AOC_TIME_SCALE_SECOND, + PRI_AOC_TIME_SCALE_TEN_SECOND, + PRI_AOC_TIME_SCALE_MINUTE, + PRI_AOC_TIME_SCALE_HOUR, + PRI_AOC_TIME_SCALE_DAY, +}; + +struct pri_aoc_time { + /*! LengthOfTimeUnit (Not valid if length is zero.) */ + long length; + /*! \see enum PRI_AOC_TIME_SCALE */ + int scale; +}; + +enum PRI_AOC_MULTIPLIER { + PRI_AOC_MULTIPLIER_THOUSANDTH, + PRI_AOC_MULTIPLIER_HUNDREDTH, + PRI_AOC_MULTIPLIER_TENTH, + PRI_AOC_MULTIPLIER_ONE, + PRI_AOC_MULTIPLIER_TEN, + PRI_AOC_MULTIPLIER_HUNDRED, + PRI_AOC_MULTIPLIER_THOUSAND, +}; + +struct pri_aoc_amount { + long cost; + /*! \see enum PRI_AOC_MULTIPLIER */ + int multiplier; +}; + +struct pri_aoc_duration { + struct pri_aoc_amount amount; + struct pri_aoc_time time; + /*! Not present if the granularity time is zero. */ + struct pri_aoc_time granularity; + /*! + * \brief Charging interval type + * \details + * continuousCharging(0), + * stepFunction(1) + */ + int charging_type; + /*! Name of currency involved. Null terminated. */ + char currency[10 + 1]; +}; + +struct pri_aoc_flat { + struct pri_aoc_amount amount; + /*! Name of currency involved. Null terminated. */ + char currency[10 + 1]; +}; + +enum PRI_AOC_VOLUME_UNIT { + PRI_AOC_VOLUME_UNIT_OCTET, + PRI_AOC_VOLUME_UNIT_SEGMENT, + PRI_AOC_VOLUME_UNIT_MESSAGE, +}; + +struct pri_aoc_volume { + struct pri_aoc_amount amount; + /*! \see enum PRI_AOC_VOLUME_UNIT */ + int unit; + /*! Name of currency involved. Null terminated. */ + char currency[10 + 1]; +}; + +struct pri_aoc_s_element { + /*! + * \brief What is being charged. + * \see enum PRI_AOC_CHARGED_ITEM + */ + int chargeable; + /*! + * \brief Rate method being used. + * \see enum PRI_AOC_RATE_TYPE + */ + int rate_type; + /*! \brief Charge rate being applied. */ + union { + struct pri_aoc_duration duration; + struct pri_aoc_flat flat; + struct pri_aoc_volume volume; + int special; + } rate; +}; + +struct pri_subcmd_aoc_s { + /*! + * \brief Number of items in the rate list. + * \note If the list is empty then the charging information is not available. + */ + int num_items; + struct pri_aoc_s_element item[10]; +}; + +enum PRI_AOC_DE_CHARGE { + PRI_AOC_DE_CHARGE_NOT_AVAILABLE, + PRI_AOC_DE_CHARGE_FREE, + PRI_AOC_DE_CHARGE_CURRENCY, + PRI_AOC_DE_CHARGE_UNITS, +}; + +struct pri_aoc_recorded_currency { + struct pri_aoc_amount amount; + /*! Name of currency involved. Null terminated. */ + char currency[10 + 1]; +}; + +struct pri_aoc_units_element { + /*! Number of units recorded. -1 if not available. */ + long number; + /*! Type of unit recorded. -1 if not available. */ + int type; +}; + +struct pri_aoc_recorded_units { + int num_items; + struct pri_aoc_units_element item[32]; +}; + +enum PRI_AOC_D_BILLING_ID { + PRI_AOC_D_BILLING_ID_NOT_AVAILABLE, + PRI_AOC_D_BILLING_ID_NORMAL, + PRI_AOC_D_BILLING_ID_REVERSE, + PRI_AOC_D_BILLING_ID_CREDIT_CARD, +}; +struct pri_subcmd_aoc_d { + /*! + * \brief What is being charged. + * \see enum PRI_AOC_DE_CHARGE + */ + int charge; + /*! + * \brief Billing accumulation + * \details + * subTotal(0), + * total(1) + */ + int billing_accumulation; + /*! \see enum PRI_AOC_D_BILLING_ID */ + int billing_id; + union { + /*! Recorded currency */ + struct pri_aoc_recorded_currency money; + /*! Recorded units list */ + struct pri_aoc_recorded_units unit; + } recorded; +}; + +enum PRI_AOC_E_BILLING_ID { + PRI_AOC_E_BILLING_ID_NOT_AVAILABLE, + PRI_AOC_E_BILLING_ID_NORMAL, + PRI_AOC_E_BILLING_ID_REVERSE, + PRI_AOC_E_BILLING_ID_CREDIT_CARD, + PRI_AOC_E_BILLING_ID_CALL_FORWARDING_UNCONDITIONAL, + PRI_AOC_E_BILLING_ID_CALL_FORWARDING_BUSY, + PRI_AOC_E_BILLING_ID_CALL_FORWARDING_NO_REPLY, + PRI_AOC_E_BILLING_ID_CALL_DEFLECTION, + PRI_AOC_E_BILLING_ID_CALL_TRANSFER, +}; + +enum PRI_AOC_E_CHARGING_ASSOCIATION { + PRI_AOC_E_CHARGING_ASSOCIATION_NOT_AVAILABLE, + PRI_AOC_E_CHARGING_ASSOCIATION_NUMBER, + PRI_AOC_E_CHARGING_ASSOCIATION_ID, +}; + +struct pri_aoc_e_charging_association { + union { + /*! Charged number */ + struct pri_party_number number; + /*! Charge identifier */ + int id; + } charge; + /*! \see enum PRI_AOC_E_CHARGING_ASSOCIATION */ + int charging_type; +}; + +struct pri_subcmd_aoc_e { + /*! + * \brief What is being charged. + * \see enum PRI_AOC_DE_CHARGE + */ + int charge; + /*! \see enum PRI_AOC_E_BILLING_ID */ + int billing_id; + union { + /*! Recorded currency */ + struct pri_aoc_recorded_currency money; + /*! Recorded units list */ + struct pri_aoc_recorded_units unit; + } recorded; + /*! Charging association. */ + struct pri_aoc_e_charging_association associated; +}; + struct pri_subcommand { /*! PRI_SUBCMD_xxx defined values */ int cmd; @@ -673,6 +899,9 @@ 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_s aoc_s; + struct pri_subcmd_aoc_d aoc_d; + struct pri_subcmd_aoc_e aoc_e; } u; }; @@ -817,7 +1046,7 @@ typedef struct pri_event_hangup { int cause; int cref; q931_call *call; /* Opaque call pointer of call hanging up. */ - long aoc_units; /* Advise of Charge number of charged units */ + long aoc_units; /* Advice of Charge number of charged units */ char useruserinfo[260]; /* User->User info */ struct pri_subcommands *subcmds; /*! @@ -1378,6 +1607,16 @@ void pri_transfer_enable(struct pri *ctrl, int enable); */ int pri_transfer_rsp(struct pri *ctrl, q931_call *call, int invoke_id, int is_successful); +/*! + * \brief Set the advice of charge events feature enable flag. + * + * \param ctrl D channel controller. + * \param enable TRUE to enable AOC events feature. + * + * \return Nothing + */ +void pri_aoc_events_enable(struct pri *ctrl, int enable); + /*! * \brief Set the call hold feature enable flag. * diff --git a/pri_aoc.c b/pri_aoc.c new file mode 100644 index 0000000..b2059a3 --- /dev/null +++ b/pri_aoc.c @@ -0,0 +1,678 @@ +/* + * libpri: An implementation of Primary Rate ISDN + * + * Copyright (C) 2010 Digium, Inc. + * + * Richard Mudgett + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2 as published by the + * Free Software Foundation. See the LICENSE file included with + * this program for more details. + * + * In addition, when this program is distributed with Asterisk in + * any form that would qualify as a 'combined work' or as a + * 'derivative work' (but not mere aggregation), you can redistribute + * and/or modify the combination under the terms of the license + * provided with that copy of Asterisk, instead of the license + * terms granted here. + */ + +/*! + * \file + * \brief Advice Of Charge (AOC) facility support. + * + * \author Richard Mudgett + */ + + +#include "compat.h" +#include "libpri.h" +#include "pri_internal.h" +#include "pri_facility.h" + + +/* ------------------------------------------------------------------- */ + +/*! + * \internal + * \brief Fill in the AOC subcmd amount from the ETSI amount. + * + * \param subcmd_amount AOC subcmd amount. + * \param etsi_amount AOC ETSI amount. + * + * \return Nothing + */ +static void aoc_etsi_subcmd_amount(struct pri_aoc_amount *subcmd_amount, const struct roseEtsiAOCAmount *etsi_amount) +{ + subcmd_amount->cost = etsi_amount->currency; + subcmd_amount->multiplier = etsi_amount->multiplier; +} + +/*! + * \internal + * \brief Fill in the AOC subcmd time from the ETSI time. + * + * \param subcmd_time AOC subcmd time. + * \param etsi_time AOC ETSI time. + * + * \return Nothing + */ +static void aoc_etsi_subcmd_time(struct pri_aoc_time *subcmd_time, const struct roseEtsiAOCTime *etsi_time) +{ + subcmd_time->length = etsi_time->length; + subcmd_time->scale = etsi_time->scale; +} + +/*! + * \internal + * \brief Fill in the AOC subcmd recorded currency from the ETSI recorded currency. + * + * \param subcmd_recorded AOC subcmd recorded currency. + * \param etsi_recorded AOC ETSI recorded currency. + * + * \return Nothing + */ +static void aoc_etsi_subcmd_recorded_currency(struct pri_aoc_recorded_currency *subcmd_recorded, const struct roseEtsiAOCRecordedCurrency *etsi_recorded) +{ + aoc_etsi_subcmd_amount(&subcmd_recorded->amount, &etsi_recorded->amount); + libpri_copy_string(subcmd_recorded->currency, (char *) etsi_recorded->currency, + sizeof(subcmd_recorded->currency)); +} + +/*! + * \internal + * \brief Fill in the AOC subcmd recorded units from the ETSI recorded units. + * + * \param subcmd_recorded AOC subcmd recorded units list. + * \param etsi_recorded AOC ETSI recorded units list. + * + * \return Nothing + */ +static void aoc_etsi_subcmd_recorded_units(struct pri_aoc_recorded_units *subcmd_recorded, const struct roseEtsiAOCRecordedUnitsList *etsi_recorded) +{ + int idx; + + /* Fill in the itemized list of recorded units. */ + for (idx = 0; idx < etsi_recorded->num_records + && idx < ARRAY_LEN(subcmd_recorded->item); ++idx) { + if (etsi_recorded->list[idx].not_available) { + subcmd_recorded->item[idx].number = -1; + } else { + subcmd_recorded->item[idx].number = etsi_recorded->list[idx].number_of_units; + } + if (etsi_recorded->list[idx].type_of_unit_present) { + subcmd_recorded->item[idx].type = etsi_recorded->list[idx].type_of_unit; + } else { + subcmd_recorded->item[idx].type = -1; + } + } + subcmd_recorded->num_items = idx; +} + +/*! + * \internal + * \brief Fill in the AOC-S subcmd currency info list of chargeable items. + * + * \param aoc_s AOC-S info list of chargeable items. + * \param info ETSI info list of chargeable items. + * + * \return Nothing + */ +static void aoc_etsi_subcmd_aoc_s_currency_info(struct pri_subcmd_aoc_s *aoc_s, const struct roseEtsiAOCSCurrencyInfoList *info) +{ + int idx; + + /* Fill in the itemized list of chargeable items. */ + for (idx = 0; idx < info->num_records && idx < ARRAY_LEN(aoc_s->item); ++idx) { + /* What is being charged. */ + switch (info->list[idx].charged_item) { + case 0:/* basicCommunication */ + aoc_s->item[idx].chargeable = PRI_AOC_CHARGED_ITEM_BASIC_COMMUNICATION; + break; + case 1:/* callAttempt */ + aoc_s->item[idx].chargeable = PRI_AOC_CHARGED_ITEM_CALL_ATTEMPT; + break; + case 2:/* callSetup */ + aoc_s->item[idx].chargeable = PRI_AOC_CHARGED_ITEM_CALL_SETUP; + break; + case 3:/* userToUserInfo */ + aoc_s->item[idx].chargeable = PRI_AOC_CHARGED_ITEM_USER_USER_INFO; + break; + case 4:/* operationOfSupplementaryServ */ + aoc_s->item[idx].chargeable = PRI_AOC_CHARGED_ITEM_SUPPLEMENTARY_SERVICE; + break; + default: + aoc_s->item[idx].chargeable = PRI_AOC_CHARGED_ITEM_NOT_AVAILABLE; + break; + } + + /* Rate method being used. */ + switch (info->list[idx].currency_type) { + case 0:/* specialChargingCode */ + aoc_s->item[idx].rate_type = PRI_AOC_RATE_TYPE_SPECIAL_CODE; + aoc_s->item[idx].rate.special = info->list[idx].u.special_charging_code; + break; + case 1:/* durationCurrency */ + aoc_s->item[idx].rate_type = PRI_AOC_RATE_TYPE_DURATION; + aoc_etsi_subcmd_amount(&aoc_s->item[idx].rate.duration.amount, + &info->list[idx].u.duration.amount); + aoc_etsi_subcmd_time(&aoc_s->item[idx].rate.duration.time, + &info->list[idx].u.duration.time); + if (info->list[idx].u.duration.granularity_present) { + aoc_etsi_subcmd_time(&aoc_s->item[idx].rate.duration.granularity, + &info->list[idx].u.duration.granularity); + } else { + aoc_s->item[idx].rate.duration.granularity.length = 0; + aoc_s->item[idx].rate.duration.granularity.scale = + PRI_AOC_TIME_SCALE_HUNDREDTH_SECOND; + } + aoc_s->item[idx].rate.duration.charging_type = + info->list[idx].u.duration.charging_type; + libpri_copy_string(aoc_s->item[idx].rate.duration.currency, + (char *) info->list[idx].u.duration.currency, + sizeof(aoc_s->item[idx].rate.duration.currency)); + break; + case 2:/* flatRateCurrency */ + aoc_s->item[idx].rate_type = PRI_AOC_RATE_TYPE_FLAT; + aoc_etsi_subcmd_amount(&aoc_s->item[idx].rate.flat.amount, + &info->list[idx].u.flat_rate.amount); + libpri_copy_string(aoc_s->item[idx].rate.flat.currency, + (char *) info->list[idx].u.flat_rate.currency, + sizeof(aoc_s->item[idx].rate.flat.currency)); + break; + case 3:/* volumeRateCurrency */ + aoc_s->item[idx].rate_type = PRI_AOC_RATE_TYPE_VOLUME; + aoc_etsi_subcmd_amount(&aoc_s->item[idx].rate.volume.amount, + &info->list[idx].u.volume_rate.amount); + aoc_s->item[idx].rate.volume.unit = info->list[idx].u.volume_rate.unit; + libpri_copy_string(aoc_s->item[idx].rate.volume.currency, + (char *) info->list[idx].u.volume_rate.currency, + sizeof(aoc_s->item[idx].rate.volume.currency)); + break; + case 4:/* freeOfCharge */ + aoc_s->item[idx].rate_type = PRI_AOC_RATE_TYPE_FREE; + break; + default: + case 5:/* currencyInfoNotAvailable */ + aoc_s->item[idx].rate_type = PRI_AOC_RATE_TYPE_NOT_AVAILABLE; + break; + } + } + aoc_s->num_items = idx; +} + +/*! + * \brief Handle the ETSI AOCSCurrency message. + * + * \param ctrl D channel controller for diagnostic messages or global options. + * \param invoke Decoded ROSE invoke message contents. + * + * \return Nothing + */ +void aoc_etsi_aoc_s_currency(struct pri *ctrl, const struct rose_msg_invoke *invoke) +{ + struct pri_subcommand *subcmd; + + if (!PRI_MASTER(ctrl)->aoc_support) { + return; + } + subcmd = q931_alloc_subcommand(ctrl); + if (!subcmd) { + return; + } + + subcmd->cmd = PRI_SUBCMD_AOC_S; + if (!invoke->args.etsi.AOCSCurrency.type) { + subcmd->u.aoc_s.num_items = 0; + return; + } + + /* Fill in the itemized list of chargeable items. */ + aoc_etsi_subcmd_aoc_s_currency_info(&subcmd->u.aoc_s, + &invoke->args.etsi.AOCSCurrency.currency_info); +} + +/*! + * \brief Handle the ETSI AOCSSpecialArr message. + * + * \param ctrl D channel controller for diagnostic messages or global options. + * \param invoke Decoded ROSE invoke message contents. + * + * \return Nothing + */ +void aoc_etsi_aoc_s_special_arrangement(struct pri *ctrl, const struct rose_msg_invoke *invoke) +{ + struct pri_subcommand *subcmd; + + if (!PRI_MASTER(ctrl)->aoc_support) { + return; + } + subcmd = q931_alloc_subcommand(ctrl); + if (!subcmd) { + return; + } + subcmd->cmd = PRI_SUBCMD_AOC_S; + if (!invoke->args.etsi.AOCSSpecialArr.type) { + subcmd->u.aoc_s.num_items = 0; + return; + } + subcmd->u.aoc_s.num_items = 1; + subcmd->u.aoc_s.item[0].chargeable = PRI_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT; + subcmd->u.aoc_s.item[0].rate_type = PRI_AOC_RATE_TYPE_SPECIAL_CODE; + subcmd->u.aoc_s.item[0].rate.special = + invoke->args.etsi.AOCSSpecialArr.special_arrangement; +} + +/*! + * \internal + * \brief Determine the AOC-D subcmd billing_id value. + * + * \param billing_id_present TRUE if billing_id valid. + * \param billing_id ETSI billing id from ROSE. + * + * \return enum PRI_AOC_D_BILLING_ID value + */ +static enum PRI_AOC_D_BILLING_ID aoc_etsi_subcmd_aoc_d_billing_id(int billing_id_present, int billing_id) +{ + enum PRI_AOC_D_BILLING_ID value; + + if (billing_id_present) { + switch (billing_id) { + case 0:/* normalCharging */ + value = PRI_AOC_D_BILLING_ID_NORMAL; + break; + case 1:/* reverseCharging */ + value = PRI_AOC_D_BILLING_ID_REVERSE; + break; + case 2:/* creditCardCharging */ + value = PRI_AOC_D_BILLING_ID_CREDIT_CARD; + break; + default: + value = PRI_AOC_D_BILLING_ID_NOT_AVAILABLE; + break; + } + } else { + value = PRI_AOC_D_BILLING_ID_NOT_AVAILABLE; + } + return value; +} + +/*! + * \brief Handle the ETSI AOCDCurrency message. + * + * \param ctrl D channel controller for diagnostic messages or global options. + * \param invoke Decoded ROSE invoke message contents. + * + * \return Nothing + */ +void aoc_etsi_aoc_d_currency(struct pri *ctrl, const struct rose_msg_invoke *invoke) +{ + struct pri_subcommand *subcmd; + + if (!PRI_MASTER(ctrl)->aoc_support) { + return; + } + subcmd = q931_alloc_subcommand(ctrl); + if (!subcmd) { + return; + } + + subcmd->cmd = PRI_SUBCMD_AOC_D; + switch (invoke->args.etsi.AOCDCurrency.type) { + default: + case 0:/* charge_not_available */ + subcmd->u.aoc_d.charge = PRI_AOC_DE_CHARGE_NOT_AVAILABLE; + break; + case 1:/* free_of_charge */ + subcmd->u.aoc_d.charge = PRI_AOC_DE_CHARGE_FREE; + break; + case 2:/* specific_currency */ + subcmd->u.aoc_d.charge = PRI_AOC_DE_CHARGE_CURRENCY; + aoc_etsi_subcmd_recorded_currency(&subcmd->u.aoc_d.recorded.money, + &invoke->args.etsi.AOCDCurrency.specific.recorded); + subcmd->u.aoc_d.billing_accumulation = + invoke->args.etsi.AOCDCurrency.specific.type_of_charging_info; + subcmd->u.aoc_d.billing_id = aoc_etsi_subcmd_aoc_d_billing_id( + invoke->args.etsi.AOCDCurrency.specific.billing_id_present, + invoke->args.etsi.AOCDCurrency.specific.billing_id); + break; + } +} + +/*! + * \brief Handle the ETSI AOCDChargingUnit message. + * + * \param ctrl D channel controller for diagnostic messages or global options. + * \param invoke Decoded ROSE invoke message contents. + * + * \return Nothing + */ +void aoc_etsi_aoc_d_charging_unit(struct pri *ctrl, const struct rose_msg_invoke *invoke) +{ + struct pri_subcommand *subcmd; + + if (!PRI_MASTER(ctrl)->aoc_support) { + return; + } + subcmd = q931_alloc_subcommand(ctrl); + if (!subcmd) { + return; + } + + subcmd->cmd = PRI_SUBCMD_AOC_D; + switch (invoke->args.etsi.AOCDChargingUnit.type) { + default: + case 0:/* charge_not_available */ + subcmd->u.aoc_d.charge = PRI_AOC_DE_CHARGE_NOT_AVAILABLE; + break; + case 1:/* free_of_charge */ + subcmd->u.aoc_d.charge = PRI_AOC_DE_CHARGE_FREE; + break; + case 2:/* specific_charging_units */ + subcmd->u.aoc_d.charge = PRI_AOC_DE_CHARGE_UNITS; + aoc_etsi_subcmd_recorded_units(&subcmd->u.aoc_d.recorded.unit, + &invoke->args.etsi.AOCDChargingUnit.specific.recorded); + subcmd->u.aoc_d.billing_accumulation = + invoke->args.etsi.AOCDChargingUnit.specific.type_of_charging_info; + subcmd->u.aoc_d.billing_id = aoc_etsi_subcmd_aoc_d_billing_id( + invoke->args.etsi.AOCDChargingUnit.specific.billing_id_present, + invoke->args.etsi.AOCDChargingUnit.specific.billing_id); + break; + } +} + +/*! + * \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. + * + * \param ctrl D channel controller for diagnostic messages or global options. + * \param subcmd_association AOC-E subcmd charging association. + * \param etsi_association AOC-E ETSI charging association. + * + * \return Nothing + */ +static void aoc_etsi_subcmd_aoc_e_charging_association(struct pri *ctrl, struct pri_aoc_e_charging_association *subcmd_association, const struct roseEtsiAOCChargingAssociation *etsi_association) +{ + struct q931_party_number q931_number; + + switch (etsi_association->type) { + case 0:/* charge_identifier */ + subcmd_association->charging_type = PRI_AOC_E_CHARGING_ASSOCIATION_ID; + subcmd_association->charge.id = etsi_association->id; + break; + case 1:/* charged_number */ + subcmd_association->charging_type = PRI_AOC_E_CHARGING_ASSOCIATION_NUMBER; + q931_party_number_init(&q931_number); + rose_copy_number_to_q931(ctrl, &q931_number, &etsi_association->number); + q931_party_number_copy_to_pri(&subcmd_association->charge.number, &q931_number); + break; + default: + subcmd_association->charging_type = PRI_AOC_E_CHARGING_ASSOCIATION_NOT_AVAILABLE; + break; + } +} + +/*! + * \internal + * \brief Determine the AOC-E subcmd billing_id value. + * + * \param billing_id_present TRUE if billing_id valid. + * \param billing_id ETSI billing id from ROSE. + * + * \return enum PRI_AOC_E_BILLING_ID value + */ +static enum PRI_AOC_E_BILLING_ID aoc_etsi_subcmd_aoc_e_billing_id(int billing_id_present, int billing_id) +{ + enum PRI_AOC_E_BILLING_ID value; + + if (billing_id_present) { + switch (billing_id) { + case 0:/* normalCharging */ + value = PRI_AOC_E_BILLING_ID_NORMAL; + break; + case 1:/* reverseCharging */ + value = PRI_AOC_E_BILLING_ID_REVERSE; + break; + case 2:/* creditCardCharging */ + value = PRI_AOC_E_BILLING_ID_CREDIT_CARD; + break; + case 3:/* callForwardingUnconditional */ + value = PRI_AOC_E_BILLING_ID_CALL_FORWARDING_UNCONDITIONAL; + break; + case 4:/* callForwardingBusy */ + value = PRI_AOC_E_BILLING_ID_CALL_FORWARDING_BUSY; + break; + case 5:/* callForwardingNoReply */ + value = PRI_AOC_E_BILLING_ID_CALL_FORWARDING_NO_REPLY; + break; + case 6:/* callDeflection */ + value = PRI_AOC_E_BILLING_ID_CALL_DEFLECTION; + break; + case 7:/* callTransfer */ + value = PRI_AOC_E_BILLING_ID_CALL_TRANSFER; + break; + default: + value = PRI_AOC_E_BILLING_ID_NOT_AVAILABLE; + break; + } + } else { + value = PRI_AOC_E_BILLING_ID_NOT_AVAILABLE; + } + return value; +} + +/*! + * \brief Handle the ETSI AOCECurrency message. + * + * \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_e_currency(struct pri *ctrl, q931_call *call, const struct rose_msg_invoke *invoke) +{ + struct pri_subcommand *subcmd; + + if (!PRI_MASTER(ctrl)->aoc_support) { + return; + } + subcmd = q931_alloc_subcommand(ctrl); + if (!subcmd) { + return; + } + + subcmd->cmd = PRI_SUBCMD_AOC_E; + subcmd->u.aoc_e.associated.charging_type = + PRI_AOC_E_CHARGING_ASSOCIATION_NOT_AVAILABLE; + + if (!invoke->args.etsi.AOCECurrency.type) { + subcmd->u.aoc_e.charge = PRI_AOC_DE_CHARGE_NOT_AVAILABLE; + return; + } + + /* Fill in charging association if present. */ + if (invoke->args.etsi.AOCECurrency.currency_info.charging_association_present) { + aoc_etsi_subcmd_aoc_e_charging_association(ctrl, &subcmd->u.aoc_e.associated, + &invoke->args.etsi.AOCECurrency.currency_info.charging_association); + } + + /* Call was free of charge. */ + if (invoke->args.etsi.AOCECurrency.currency_info.free_of_charge) { + subcmd->u.aoc_e.charge = PRI_AOC_DE_CHARGE_FREE; + return; + } + + /* Fill in currency cost of call. */ + subcmd->u.aoc_e.charge = PRI_AOC_DE_CHARGE_CURRENCY; + aoc_etsi_subcmd_recorded_currency(&subcmd->u.aoc_e.recorded.money, + &invoke->args.etsi.AOCECurrency.currency_info.specific.recorded); + subcmd->u.aoc_e.billing_id = aoc_etsi_subcmd_aoc_e_billing_id( + invoke->args.etsi.AOCECurrency.currency_info.specific.billing_id_present, + invoke->args.etsi.AOCECurrency.currency_info.specific.billing_id); +} + +/*! + * \brief Handle the ETSI AOCEChargingUnit message. + * + * \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_e_charging_unit(struct pri *ctrl, q931_call *call, const struct rose_msg_invoke *invoke) +{ + struct pri_subcommand *subcmd; + unsigned idx; + + /* Fill in legacy stuff. */ + call->aoc_units = 0; + if (invoke->args.etsi.AOCEChargingUnit.type == 1 + && !invoke->args.etsi.AOCEChargingUnit.charging_unit.free_of_charge) { + for (idx = + invoke->args.etsi.AOCEChargingUnit.charging_unit.specific.recorded.num_records; + idx--;) { + if (!invoke->args.etsi.AOCEChargingUnit.charging_unit.specific.recorded. + list[idx].not_available) { + call->aoc_units += + invoke->args.etsi.AOCEChargingUnit.charging_unit.specific. + recorded.list[idx].number_of_units; + } + } + } + /* 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; + } + subcmd = q931_alloc_subcommand(ctrl); + if (!subcmd) { + return; + } + + subcmd->cmd = PRI_SUBCMD_AOC_E; + subcmd->u.aoc_e.associated.charging_type = + PRI_AOC_E_CHARGING_ASSOCIATION_NOT_AVAILABLE; + + if (!invoke->args.etsi.AOCEChargingUnit.type) { + subcmd->u.aoc_e.charge = PRI_AOC_DE_CHARGE_NOT_AVAILABLE; + return; + } + + /* Fill in charging association if present. */ + if (invoke->args.etsi.AOCEChargingUnit.charging_unit.charging_association_present) { + aoc_etsi_subcmd_aoc_e_charging_association(ctrl, &subcmd->u.aoc_e.associated, + &invoke->args.etsi.AOCEChargingUnit.charging_unit.charging_association); + } + + /* Call was free of charge. */ + if (invoke->args.etsi.AOCEChargingUnit.charging_unit.free_of_charge) { + subcmd->u.aoc_e.charge = PRI_AOC_DE_CHARGE_FREE; + return; + } + + /* Fill in unit cost of call. */ + subcmd->u.aoc_e.charge = PRI_AOC_DE_CHARGE_UNITS; + aoc_etsi_subcmd_recorded_units(&subcmd->u.aoc_e.recorded.unit, + &invoke->args.etsi.AOCEChargingUnit.charging_unit.specific.recorded); + subcmd->u.aoc_e.billing_id = aoc_etsi_subcmd_aoc_e_billing_id( + invoke->args.etsi.AOCEChargingUnit.charging_unit.specific.billing_id_present, + invoke->args.etsi.AOCEChargingUnit.charging_unit.specific.billing_id); +} + +void pri_aoc_events_enable(struct pri *ctrl, int enable) +{ + if (ctrl) { + ctrl = PRI_MASTER(ctrl); + ctrl->aoc_support = enable ? 1 : 0; + } +} + +/* ------------------------------------------------------------------- */ +/* end pri_aoc.c */ diff --git a/pri_facility.c b/pri_facility.c index e48fa1d..418a269 100644 --- a/pri_facility.c +++ b/pri_facility.c @@ -2700,82 +2700,6 @@ static enum rose_error_code etsi_explicit_ect_execute_transfer(struct pri *ctrl, return error_code; } -/* AOC */ -/*! - * \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; -} -/* End AOC */ - /* ===== Call Transfer Supplementary Service (ECMA-178) ===== */ /*! @@ -4129,40 +4053,23 @@ void rose_handle_invoke(struct pri *ctrl, q931_call *call, int msgtype, q931_ie case ROSE_ETSI_ChargingRequest: /* Ignore messsage */ break; -#if 0 /* Not handled yet */ case ROSE_ETSI_AOCSCurrency: + aoc_etsi_aoc_s_currency(ctrl, invoke); break; case ROSE_ETSI_AOCSSpecialArr: + aoc_etsi_aoc_s_special_arrangement(ctrl, invoke); break; case ROSE_ETSI_AOCDCurrency: + aoc_etsi_aoc_d_currency(ctrl, invoke); break; case ROSE_ETSI_AOCDChargingUnit: + aoc_etsi_aoc_d_charging_unit(ctrl, invoke); break; case ROSE_ETSI_AOCECurrency: + aoc_etsi_aoc_e_currency(ctrl, call, invoke); break; -#endif /* Not handled yet */ case ROSE_ETSI_AOCEChargingUnit: - call->aoc_units = 0; - if (invoke->args.etsi.AOCEChargingUnit.type == 1 - && !invoke->args.etsi.AOCEChargingUnit.charging_unit.free_of_charge) { - unsigned index; - - for (index = - invoke->args.etsi.AOCEChargingUnit.charging_unit.specific.recorded. - num_records; index--;) { - if (!invoke->args.etsi.AOCEChargingUnit.charging_unit.specific.recorded. - list[index].not_available) { - call->aoc_units += - invoke->args.etsi.AOCEChargingUnit.charging_unit.specific. - recorded.list[index].number_of_units; - } - } - } - /* 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); - } + aoc_etsi_aoc_e_charging_unit(ctrl, call, invoke); break; #if 0 /* Not handled yet */ case ROSE_ITU_IdentificationOfCharge: diff --git a/pri_facility.h b/pri_facility.h index 0180397..74c2e27 100644 --- a/pri_facility.h +++ b/pri_facility.h @@ -245,4 +245,11 @@ 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); +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); +void aoc_etsi_aoc_d_charging_unit(struct pri *ctrl, const struct rose_msg_invoke *invoke); +void aoc_etsi_aoc_e_currency(struct pri *ctrl, q931_call *call, const struct rose_msg_invoke *invoke); +void aoc_etsi_aoc_e_charging_unit(struct pri *ctrl, q931_call *call, const struct rose_msg_invoke *invoke); + #endif /* _PRI_FACILITY_H */ diff --git a/pri_internal.h b/pri_internal.h index f8d2211..58e9a34 100644 --- a/pri_internal.h +++ b/pri_internal.h @@ -107,6 +107,7 @@ struct pri { unsigned int hangup_fix_enabled:1;/* TRUE if should follow Q.931 Section 5.3.2 instead of blindly sending RELEASE_COMPLETE for certain causes */ unsigned int cc_support:1;/* TRUE if upper layer supports call completion. */ unsigned int transfer_support:1;/* TRUE if the upper layer supports ECT */ + unsigned int aoc_support:1;/* TRUE if can send AOC events to the upper layer. */ /* MDL variables */ int mdl_error; diff --git a/rose_etsi_aoc.c b/rose_etsi_aoc.c index 8e387a4..c146911 100644 --- a/rose_etsi_aoc.c +++ b/rose_etsi_aoc.c @@ -623,8 +623,13 @@ unsigned char *rose_enc_etsi_AOCSCurrency_ARG(struct pri *ctrl, unsigned char *p ASN1_CALL(pos, asn1_enc_null(pos, end, ASN1_TYPE_NULL)); break; case 1: /* currency_info_list */ - ASN1_CALL(pos, rose_enc_etsi_AOCSCurrencyInfoList(ctrl, pos, end, - ASN1_TAG_SEQUENCE, &args->etsi.AOCSCurrency.currency_info)); + if (args->etsi.AOCSCurrency.currency_info.num_records) { + ASN1_CALL(pos, rose_enc_etsi_AOCSCurrencyInfoList(ctrl, pos, end, + ASN1_TAG_SEQUENCE, &args->etsi.AOCSCurrency.currency_info)); + } else { + /* There were no records so encode as charge_not_available */ + ASN1_CALL(pos, asn1_enc_null(pos, end, ASN1_TYPE_NULL)); + } break; default: ASN1_ENC_ERROR(ctrl, "Unknown AOCSCurrency type");