diff options
author | Ivan Klyuchnikov <kluchnikovi@gmail.com> | 2018-05-21 19:25:30 +0300 |
---|---|---|
committer | Ivan Kluchnikov <kluchnikovi@gmail.com> | 2018-05-21 19:39:33 +0300 |
commit | fd87ede9e9164de79c04e2fdb3a7e4a2c41cc4a4 (patch) | |
tree | 637245e9f2b5a2422cffdd058e553ae8a4f20e58 | |
parent | c2891e441cfdbbc23e302f42a986014d052b7975 (diff) |
sms charging: Implement SMS online ECUR charging based on 3GPP TS 32.274
Event Charging with Unit Reservation (ECUR) principle is used for SMS online charging in osmo-nitb.
Osmo-nitb generates Reserve Units information which is transferred to OCS.
For this purpose, osmo-nitb utilizes the Reserve Units procedure that is specified in TS 32.274 and TS 32.299.
The Reserve Units procedure employs the Reserve Units Request and Reserve Units Response messages.
Change-Id: I12b681f65cdcdb281da04b49a63ceb140be7e643
-rw-r--r-- | openbsc/include/openbsc/gsm_04_11.h | 5 | ||||
-rw-r--r-- | openbsc/include/openbsc/gsm_sup.h | 43 | ||||
-rw-r--r-- | openbsc/src/libmsc/gsm_04_11.c | 115 | ||||
-rw-r--r-- | openbsc/src/libmsc/gsm_sup.c | 247 |
4 files changed, 378 insertions, 32 deletions
diff --git a/openbsc/include/openbsc/gsm_04_11.h b/openbsc/include/openbsc/gsm_04_11.h index 6408b7b5c..1d5acb427 100644 --- a/openbsc/include/openbsc/gsm_04_11.h +++ b/openbsc/include/openbsc/gsm_04_11.h @@ -42,4 +42,9 @@ int gsm411_send_rp_msg_subscr(struct gsm_subscriber *subscr, struct msgb *rp); uint8_t sms_next_rp_msg_ref(uint8_t *next_rp_ref); + +int gsm340_rx_tpdu(struct gsm_trans *trans); + +int gsm411_send_rp_error(struct gsm_trans *trans, uint8_t msg_ref, uint8_t cause); + #endif diff --git a/openbsc/include/openbsc/gsm_sup.h b/openbsc/include/openbsc/gsm_sup.h index 26017365d..2b2315a4d 100644 --- a/openbsc/include/openbsc/gsm_sup.h +++ b/openbsc/include/openbsc/gsm_sup.h @@ -11,6 +11,44 @@ (subscr) ? (subscr)->imsi : "---", \ ## args) +#define LOGGSESSIONP(level, session_id, fmt, args...) \ + LOGP(DSUP, level, "SESSION(%d:%d) " fmt, \ + session_id.h, session_id.l, \ + ## args) + +/* TODO move libosmocore */ +enum osmo_gsup_charging_message_type { + OSMO_GSUP_MSGT_RESERVE_UNITS_REQUEST = 0b00100000, + OSMO_GSUP_MSGT_RESERVE_UNITS_RESPONSE = 0b00100010, +}; +enum osmo_gsup_charging_request_type { + OSMO_GSUP_MSGT_REQUEST_TYPE_INITIAL = 0b00000001, + OSMO_GSUP_MSGT_REQUEST_TYPE_UPDATE = 0b00000010, + OSMO_GSUP_MSGT_REQUEST_TYPE_TERMINATION = 0b00000011, + OSMO_GSUP_MSGT_REQUEST_TYPE_EVENT = 0b00000100, +}; +enum osmo_charging_service_type { + OSMO_CHARGING_SERVICE_TYPE_SMS = 0b00000001, +}; + +enum osmo_charging_result_code { + OSMO_CHARGING_RESULT_CODE_SUCCESS = 2001, + OSMO_CHARGING_RESULT_CODE_CREDIT_LIMIT_REACHED = 4012, +}; + +struct osmo_gsup_reserve_units_response { + /* Message type: [Pres: M] [Format: V] [Length: 1] */ + uint8_t message_type; + /* Session id: [Pres: M] [Format: V] [Length: 8] */ + struct charging_session_id session_id; + /* Request type: [Pres: M] [Format: V] [Length: 1] */ + uint8_t request_type; + /* Result code: [Pres: M] [Format: V] [Length: 4] */ + uint32_t result_code; + /* Service units: [Pres: O] [Format: V] [Length: 4] */ + uint32_t service_units; +}; + /* Callback for both HLR/auth and USSD SUP sockets */ int sup_read_cb(struct gsup_client *sup_client, struct msgb *msg); @@ -26,4 +64,9 @@ int subscr_tx_sms_message(struct gsm_subscriber *subscr, void init_charging_session_id(struct gsm_network *network); struct charging_session_id get_charging_session_id(struct gsm_network *network); +int tx_reserve_units_request(enum osmo_gsup_charging_message_type msg_type, + enum osmo_gsup_charging_request_type request_type, + enum osmo_charging_service_type service_type, + struct gsm_trans *trans, uint32_t service_units); + #endif /* _GSM_SUP_H */ diff --git a/openbsc/src/libmsc/gsm_04_11.c b/openbsc/src/libmsc/gsm_04_11.c index 25be3b456..b8523411d 100644 --- a/openbsc/src/libmsc/gsm_04_11.c +++ b/openbsc/src/libmsc/gsm_04_11.c @@ -393,12 +393,11 @@ static int gsm340_tpdu_dst_addr(struct msgb *msg, struct gsm_sms_addr* dst_addr) return 0; } -/* process an incoming TPDU (called from RP-DATA) - * return value > 0: RP CAUSE for ERROR; < 0: silent error; 0 = success */ -static int gsm340_rx_tpdu(struct gsm_subscriber_connection *conn, struct msgb *msg) +/* Decode an incoming TPDU (called from RP-DATA) + * return value > 0: RP CAUSE for ERROR; 0 = success */ +static int gsm340_decode_tpdu(struct gsm_subscriber_connection *conn, struct msgb *msg, struct gsm_sms *gsms) { uint8_t *smsp = msgb_sms(msg); - struct gsm_sms *gsms; unsigned int sms_alphabet; uint8_t sms_mti, sms_vpf; uint8_t *sms_vp; @@ -406,12 +405,6 @@ static int gsm340_rx_tpdu(struct gsm_subscriber_connection *conn, struct msgb *m uint8_t address_lv[12]; /* according to 03.40 / 9.1.2.5 */ int rc = 0; - rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_SMS_SUBMITTED]); - - gsms = sms_alloc(); - if (!gsms) - return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER; - /* invert those fields where 0 means active/present */ sms_mti = *smsp & 0x03; sms_vpf = (*smsp & 0x18) >> 3; @@ -431,12 +424,10 @@ static int gsm340_rx_tpdu(struct gsm_subscriber_connection *conn, struct msgb *m da_len_bytes = 2 + *smsp/2 + *smsp%2; if (da_len_bytes > 12) { LOGP(DLSMS, LOGL_ERROR, "Destination Address > 12 bytes ?!?\n"); - rc = GSM411_RP_CAUSE_SEMANT_INC_MSG; - goto out; + return GSM411_RP_CAUSE_SEMANT_INC_MSG; } else if (da_len_bytes < 4) { LOGP(DLSMS, LOGL_ERROR, "Destination Address < 4 bytes ?!?\n"); - rc = GSM411_RP_CAUSE_SEMANT_INC_MSG; - goto out; + return GSM411_RP_CAUSE_SEMANT_INC_MSG; } memset(address_lv, 0, sizeof(address_lv)); memcpy(address_lv, smsp, da_len_bytes); @@ -455,8 +446,7 @@ static int gsm340_rx_tpdu(struct gsm_subscriber_connection *conn, struct msgb *m sms_alphabet = gsm338_get_sms_alphabet(gsms->data_coding_scheme); if (sms_alphabet == 0xffffffff) { - rc = GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER; - goto out; + return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER; } switch (sms_vpf) { @@ -477,8 +467,7 @@ static int gsm340_rx_tpdu(struct gsm_subscriber_connection *conn, struct msgb *m default: LOGP(DLSMS, LOGL_NOTICE, "SMS Validity period not implemented: 0x%02x\n", sms_vpf); - rc = GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER; - goto out; + return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER; } gsms->user_data_len = *smsp++; if (gsms->user_data_len) { @@ -510,13 +499,6 @@ static int gsm340_rx_tpdu(struct gsm_subscriber_connection *conn, struct msgb *m gsms->validity_minutes = gsm340_validity_period(sms_vpf, sms_vp); - /* FIXME: This looks very wrong */ - send_signal(0, NULL, gsms, 0); - - rc = sms_route_mt_sms(conn, msg, gsms, sms_mti); -out: - sms_free(gsms); - return rc; } @@ -547,7 +529,7 @@ static int gsm411_send_rp_ack(struct gsm_trans *trans, uint8_t msg_ref) msg_ref, GSM411_SM_RL_REPORT_REQ); } -static int gsm411_send_rp_error(struct gsm_trans *trans, +int gsm411_send_rp_error(struct gsm_trans *trans, uint8_t msg_ref, uint8_t cause) { struct msgb *msg = gsm411_msgb_alloc(); @@ -561,6 +543,46 @@ static int gsm411_send_rp_error(struct gsm_trans *trans, GSM411_MT_RP_ERROR_MT, msg_ref, GSM411_SM_RL_REPORT_REQ); } +int gsm340_rx_tpdu(struct gsm_trans *trans) +{ + int rc, rc1; + uint32_t used_service_units = 0; + struct gsm_sms *gsms = trans->sms.sms; + + /* FIXME: This looks very wrong */ + send_signal(0, NULL, gsms, 0); + + rc = sms_route_mt_sms(trans->conn, NULL, gsms, GSM340_SMS_SUBMIT_MS2SC); + + // SMS Charging + if (trans->net->sms_ctf) { + if (rc == 0) { + used_service_units = 1; + } + rc1 = tx_reserve_units_request(OSMO_GSUP_MSGT_RESERVE_UNITS_REQUEST, + OSMO_GSUP_MSGT_REQUEST_TYPE_TERMINATION, + OSMO_CHARGING_SERVICE_TYPE_SMS, + trans, used_service_units); + if (rc1 < 0) { + trans->sms.sms = NULL; + sms_free(gsms); + return gsm411_send_rp_error(trans, trans->msg_ref, + GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER); + } + } + + trans->sms.sms = NULL; + sms_free(gsms); + + if (rc == 0) + return gsm411_send_rp_ack(trans, trans->msg_ref); + else if (rc > 0) + return gsm411_send_rp_error(trans, trans->msg_ref, rc); + else + return rc; +} + + /* Receive a 04.11 TPDU inside RP-DATA / user data */ static int gsm411_rx_rp_ud(struct msgb *msg, struct gsm_trans *trans, struct gsm411_rp_hdr *rph, @@ -568,6 +590,7 @@ static int gsm411_rx_rp_ud(struct msgb *msg, struct gsm_trans *trans, uint8_t dst_len, uint8_t *dst, uint8_t tpdu_len, uint8_t *tpdu) { + struct gsm_sms *gsms; int rc = 0; if (src_len && src) @@ -602,13 +625,41 @@ static int gsm411_rx_rp_ud(struct msgb *msg, struct gsm_trans *trans, return subscr_tx_sms_message(trans->subscr, rph); } - rc = gsm340_rx_tpdu(trans->conn, msg); - if (rc == 0) - return gsm411_send_rp_ack(trans, rph->msg_ref); - else if (rc > 0) - return gsm411_send_rp_error(trans, rph->msg_ref, rc); - else + rate_ctr_inc(&trans->conn->bts->network->msc_ctrs->ctr[MSC_CTR_SMS_SUBMITTED]); + + gsms = sms_alloc(); + if (!gsms) { + gsm411_send_rp_error(trans, rph->msg_ref, + GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER); + return -ENOMEM; + } + + /* Decode an incoming TPDU (called from RP-DATA) */ + /* return value > 0: RP CAUSE for ERROR; 0 = success */ + rc = gsm340_decode_tpdu(trans->conn, msg, gsms); + if (rc > 0) { + sms_free(gsms); + gsm411_send_rp_error(trans, rph->msg_ref, rc); + return -EIO; + } + trans->msg_ref = rph->msg_ref; + trans->sms.sms = gsms; + + // SMS Charging: Initial Reserve Units Request + if (trans->net->sms_ctf) { + rc = tx_reserve_units_request(OSMO_GSUP_MSGT_RESERVE_UNITS_REQUEST, + OSMO_GSUP_MSGT_REQUEST_TYPE_INITIAL, + OSMO_CHARGING_SERVICE_TYPE_SMS, trans, 1); + if (rc < 0) { + trans->sms.sms = NULL; + sms_free(gsms); + gsm411_send_rp_error(trans, rph->msg_ref, + GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER); + } return rc; + } + + return gsm340_rx_tpdu(trans); } /* Receive a 04.11 RP-DATA message in accordance with Section 7.3.1.2 */ diff --git a/openbsc/src/libmsc/gsm_sup.c b/openbsc/src/libmsc/gsm_sup.c index 21e42adf1..72841960a 100644 --- a/openbsc/src/libmsc/gsm_sup.c +++ b/openbsc/src/libmsc/gsm_sup.c @@ -33,6 +33,7 @@ #include <openbsc/gprs_utils.h> #include <openbsc/ussd.h> #include <openbsc/gsm_04_11.h> +#include <openbsc/transaction.h> #include <osmocom/gsm/protocol/gsm_04_11.h> #include <osmocom/gsm/gsm0411_utils.h> @@ -264,6 +265,247 @@ struct charging_session_id get_charging_session_id(struct gsm_network *network) return id; } +static void encode_sms_charging_info(struct msgb *msg, struct gsm_trans *trans) +{ + uint8_t bcd_buf[32]; + size_t bcd_len; + struct gsm_sms *gsms = trans->sms.sms; + + /* SMS: destination address (MSISDN): [Pres: M] [Format: TLV] [Length: 0-9] */ + bcd_len = gsm48_encode_bcd_number(bcd_buf, sizeof(bcd_buf), 0, gsms->dst.addr); + msgb_tlv_put(msg, OSMO_GSUP_MSISDN_IE, bcd_len - 1, &bcd_buf[1]); + + /* SMS: rp msg ref: [Pres: M] [Format: V] [Length: 1] */ + msgb_put_u8(msg, trans->msg_ref); + + /* SMS: tp msg ref: [Pres: M] [Format: V] [Length: 1] */ + msgb_put_u8(msg, gsms->msg_ref); +} + +int tx_reserve_units_request(enum osmo_gsup_charging_message_type msg_type, + enum osmo_gsup_charging_request_type request_type, + enum osmo_charging_service_type service_type, + struct gsm_trans *trans, uint32_t service_units) +{ + uint8_t bcd_buf[32]; + size_t bcd_len; + struct msgb *msg = gsup_client_msgb_alloc(); + + if (!msg) + return -ENOMEM; + if (!trans->subscr->extension) + return -1; + + if (request_type == OSMO_GSUP_MSGT_REQUEST_TYPE_INITIAL) + trans->session_id = get_charging_session_id(trans->net); + + /* Message type: [Pres: M] [Format: V] [Length: 1] */ + msgb_put_u8(msg, msg_type); + + /* Session id: [Pres: M] [Format: V] [Length: 8] */ + msgb_put_u32(msg, trans->session_id.h); + msgb_put_u32(msg, trans->session_id.l); + + /* Request type: [Pres: M] [Format: V] [Length: 1] */ + msgb_put_u8(msg, request_type); + + /* Service type: [Pres: M] [Format: V] [Length: 1] */ + msgb_put_u8(msg, service_type); + + /* Subscriber Identifier (MSISDN): [Pres: M] [Format: TLV] [Length: 0-9] */ + bcd_len = gsm48_encode_bcd_number(bcd_buf, sizeof(bcd_buf), + 0, trans->subscr->extension); + msgb_tlv_put(msg, OSMO_GSUP_MSISDN_IE, bcd_len - 1, &bcd_buf[1]); + + /* Service units [Pres: M] [Format: V] [Length: 4] */ + msgb_put_u32(msg, service_units); + + /* Encode Service Information */ + switch (service_type) { + case OSMO_CHARGING_SERVICE_TYPE_SMS: + encode_sms_charging_info(msg, trans); + break; + default: + msgb_free(msg); + return -EINVAL; + } + + switch (request_type) { + case OSMO_GSUP_MSGT_REQUEST_TYPE_INITIAL: + LOGGSESSIONP(LOGL_NOTICE, trans->session_id, + "Tx: Reserve Units Request: type = INITIAL, service = %d," + " subscriber_id = %s, requested_units = %d, desr_addr = %s," + " rp_msg_ref = %d, tp_msg_ref = %d\n", + service_type, trans->subscr->extension, service_units, + trans->sms.sms->dst.addr, trans->msg_ref, trans->sms.sms->msg_ref); + break; + case OSMO_GSUP_MSGT_REQUEST_TYPE_TERMINATION: + LOGGSESSIONP(LOGL_NOTICE, trans->session_id, + "Tx: Reserve Units Request: type = TERMINATION, service = %d," + " subscriber_id = %s, used_units = %d, desr_addr = %s," + " rp_msg_ref = %d, tp_msg_ref = %d\n", + service_type, trans->subscr->extension, service_units, + trans->sms.sms->dst.addr, trans->msg_ref, trans->sms.sms->msg_ref); + break; + default: + LOGGSESSIONP(LOGL_NOTICE, trans->session_id, + "Tx: Reserve Units Request with unsupported type = %d\n", request_type); + } + + return gsup_client_send(trans->net->sms_ctf, msg); +} + +static int osmo_gsup_reserve_units_response_decode(const uint8_t *const_data, + size_t data_len, struct osmo_gsup_reserve_units_response *response) +{ + int rc; + uint8_t *data = (uint8_t *)const_data; + uint8_t *value; + + /* Message type: [Pres: M] [Format: V] [Length: 1] */ + rc = osmo_shift_v_fixed(&data, &data_len, 1, &value); + if (rc < 0) + return -GMM_CAUSE_INV_MAND_INFO; + response->message_type = osmo_decode_big_endian(value, 1); + + /* Session id: [Pres: M] [Format: V] [Length: 8] */ + rc = osmo_shift_v_fixed(&data, &data_len, 4, &value); + if (rc < 0) + return -GMM_CAUSE_INV_MAND_INFO; + response->session_id.h = osmo_decode_big_endian(value, 4); + rc = osmo_shift_v_fixed(&data, &data_len, 4, &value); + if (rc < 0) + return -GMM_CAUSE_INV_MAND_INFO; + response->session_id.l = osmo_decode_big_endian(value, 4); + + /* Request type: [Pres: M] [Format: V] [Length: 1] */ + rc = osmo_shift_v_fixed(&data, &data_len, 1, &value); + if (rc < 0) + return -GMM_CAUSE_INV_MAND_INFO; + response->request_type = osmo_decode_big_endian(value, 1); + + /* Result code: [Pres: M] [Format: V] [Length: 4] */ + rc = osmo_shift_v_fixed(&data, &data_len, 4, &value); + if (rc < 0) + return -GMM_CAUSE_INV_MAND_INFO; + response->result_code = osmo_decode_big_endian(value, 4); + + if (response->request_type == OSMO_GSUP_MSGT_REQUEST_TYPE_INITIAL) { + /* Service units: [Pres: O] [Format: V] [Length: 4] */ + rc = osmo_shift_v_fixed(&data, &data_len, 4, &value); + if (rc < 0) + return -GMM_CAUSE_INV_MAND_INFO; + response->service_units = osmo_decode_big_endian(value, 4); + } + + return 0; +} + +static int rx_sms_reserve_units_response_init(struct gsm_network *net, + struct osmo_gsup_reserve_units_response *response) +{ + struct gsm_trans *trans; + struct gsm_sms *gsms; + + trans = trans_find_by_session_id(net, GSM48_PDISC_SMS, response->session_id); + if (!trans) { + LOGGSESSIONP(LOGL_ERROR, response->session_id, + "Can't find transaction for Session Id from Reserve Units Response Initial\n"); + return -EINVAL; + } + + gsms = trans->sms.sms; + + switch (response->result_code) { + case OSMO_CHARGING_RESULT_CODE_SUCCESS: + if (response->service_units == 1) { + return gsm340_rx_tpdu(trans); + } else { + LOGGSESSIONP(LOGL_ERROR, response->session_id, + "Received Service Units = %d in Reserve Units Response Initial\n", + response->result_code); + tx_reserve_units_request(OSMO_GSUP_MSGT_RESERVE_UNITS_REQUEST, + OSMO_GSUP_MSGT_REQUEST_TYPE_TERMINATION, + OSMO_CHARGING_SERVICE_TYPE_SMS, + trans, 0); + trans->sms.sms = NULL; + sms_free(gsms); + return gsm411_send_rp_error(trans, trans->msg_ref, + GSM411_RP_CAUSE_MO_CALL_BARRED); + } + case OSMO_CHARGING_RESULT_CODE_CREDIT_LIMIT_REACHED: + trans->sms.sms = NULL; + sms_free(gsms); + return gsm411_send_rp_error(trans, trans->msg_ref, + GSM411_RP_CAUSE_MO_CALL_BARRED); + default: + LOGGSESSIONP(LOGL_ERROR, response->session_id, + "Received Result Code %d in Reserve Units Response Initial\n", + response->result_code); + trans->sms.sms = NULL; + sms_free(gsms); + return gsm411_send_rp_error(trans, trans->msg_ref, + GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER); + } +} + +static int rx_sms_reserve_units_response_term(struct gsm_network *net, + struct osmo_gsup_reserve_units_response *response) +{ + switch (response->result_code) { + case OSMO_CHARGING_RESULT_CODE_SUCCESS: + break; + default: + LOGGSESSIONP(LOGL_ERROR, response->session_id, + "Received Result Code %d in Reserve Units Response Termination\n", + response->result_code); + } + return 0; +} + +static int rx_sms_reserve_units_response(struct gsm_network *net, + struct osmo_gsup_reserve_units_response *response) +{ + switch (response->request_type) { + case OSMO_GSUP_MSGT_REQUEST_TYPE_INITIAL: + LOGGSESSIONP(LOGL_NOTICE, response->session_id, + "Rx: Reserve Units Response: type = INITIAL, result_code = %d, granted_units = %d\n", + response->result_code, response->service_units); + return rx_sms_reserve_units_response_init(net, response); + case OSMO_GSUP_MSGT_REQUEST_TYPE_TERMINATION: + LOGGSESSIONP(LOGL_NOTICE, response->session_id, + "Rx: Reserve Units Response: type = TERMINATION, result_code = %d\n", + response->result_code); + return rx_sms_reserve_units_response_term(net, response); + case OSMO_GSUP_MSGT_REQUEST_TYPE_UPDATE: + case OSMO_GSUP_MSGT_REQUEST_TYPE_EVENT: + default: + LOGGSESSIONP(LOGL_NOTICE, response->session_id, + "Received unsupported Request Type %d in Reserve Units Response message\n", + response->request_type); + return -EINVAL; + } +} + +static int rx_reserve_units_response(struct gsup_client *sup_client, + const uint8_t* const_data, size_t data_len) +{ + int rc; + struct gsm_network *net = sup_client->net; + struct osmo_gsup_reserve_units_response response = {0}; + + rc = osmo_gsup_reserve_units_response_decode(const_data, data_len, &response); + if (rc < 0) { + LOGGSESSIONP(LOGL_ERROR, response.session_id, + "decoding Reserve Units Response message fails with error '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, -rc), -rc); + return rc; + } + + return rx_sms_reserve_units_response(net, &response); +} + + static int subscr_tx_sup_message(struct gsup_client *sup_client, struct gsm_subscriber *subscr, struct osmo_gsup_message *gsup_msg) @@ -551,6 +793,11 @@ static int subscr_rx_sup_message(struct gsup_client *sup_client, struct msgb *ms if (*data == OSMO_GSUP_MSGT_SMS) { return rx_sms_message(sup_client, data, data_len); } + + if (*data == OSMO_GSUP_MSGT_RESERVE_UNITS_RESPONSE) { + return rx_reserve_units_response(sup_client, data, data_len); + } + rc = osmo_gsup_decode(data, data_len, &gsup_msg); if (rc < 0) { LOGP(DSUP, LOGL_ERROR, |