| /* |
| * |
| * oFono - Open Source Telephony |
| * |
| * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <string.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| |
| #include <glib.h> |
| #include <ell/ell.h> |
| |
| #include <ofono/types.h> |
| #include "smsutil.h" |
| #include "stkutil.h" |
| #include "simutil.h" |
| #include "util.h" |
| |
| enum stk_data_object_flag { |
| DATAOBJ_FLAG_MANDATORY = 1, |
| DATAOBJ_FLAG_MINIMUM = 2, |
| DATAOBJ_FLAG_CR = 4, |
| DATAOBJ_FLAG_LIST = 8, |
| }; |
| |
| struct stk_file_iter { |
| const uint8_t *start; |
| unsigned int pos; |
| unsigned int max; |
| uint8_t len; |
| const uint8_t *file; |
| }; |
| |
| struct stk_tlv_builder { |
| struct comprehension_tlv_builder ctlv; |
| uint8_t *value; |
| unsigned int len; |
| unsigned int max_len; |
| }; |
| |
| typedef bool (*dataobj_handler)(struct comprehension_tlv_iter *, void *); |
| typedef bool (*dataobj_writer)(struct stk_tlv_builder *, const void *, bool); |
| |
| /* |
| * Defined in TS 102.223 Section 8.13 |
| * The type of gsm sms can be SMS-COMMAND AND SMS-SUBMIT. According to 23.040, |
| * the maximum length is 164 bytes. But for SMS-SUBMIT, sms may be packed by |
| * ME. Thus the maximum length of messsage could be 160 bytes, instead of 140 |
| * bytes. So the total maximum length could be 184 bytes. Refer TS 31.111, |
| * section 6.4.10 for details. |
| */ |
| struct gsm_sms_tpdu { |
| unsigned int len; |
| uint8_t tpdu[184]; |
| }; |
| |
| #define CHECK_TEXT_AND_ICON(text, icon_id) \ |
| if (status != STK_PARSE_RESULT_OK) \ |
| return status; \ |
| \ |
| if ((text == NULL || text[0] == '\0') && icon_id != 0) \ |
| status = STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; \ |
| |
| static char *decode_text(uint8_t dcs, int len, const unsigned char *data) |
| { |
| char *utf8; |
| enum sms_charset charset; |
| |
| if (sms_dcs_decode(dcs, NULL, &charset, NULL, NULL) == FALSE) |
| return NULL; |
| |
| switch (charset) { |
| case SMS_CHARSET_7BIT: |
| { |
| long written; |
| unsigned long max_to_unpack = len * 8 / 7; |
| uint8_t *unpacked = unpack_7bit(data, len, 0, false, |
| max_to_unpack, |
| &written, 0); |
| if (unpacked == NULL) |
| return NULL; |
| |
| utf8 = convert_gsm_to_utf8(unpacked, written, |
| NULL, NULL, 0); |
| l_free(unpacked); |
| break; |
| } |
| case SMS_CHARSET_8BIT: |
| utf8 = convert_gsm_to_utf8(data, len, NULL, NULL, 0); |
| break; |
| case SMS_CHARSET_UCS2: |
| utf8 = l_utf8_from_ucs2be(data, len); |
| break; |
| default: |
| utf8 = NULL; |
| } |
| |
| return utf8; |
| } |
| |
| /* For data object only to indicate its existence */ |
| static bool parse_dataobj_common_bool(struct comprehension_tlv_iter *iter, |
| bool *out) |
| { |
| if (comprehension_tlv_iter_get_length(iter) != 0) |
| return false; |
| |
| *out = true; |
| |
| return true; |
| } |
| |
| /* For data object that only has one byte */ |
| static bool parse_dataobj_common_byte(struct comprehension_tlv_iter *iter, |
| uint8_t *out) |
| { |
| const uint8_t *data; |
| |
| if (comprehension_tlv_iter_get_length(iter) != 1) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| *out = data[0]; |
| |
| return true; |
| } |
| |
| /* For data object that only has text terminated by '\0' */ |
| static bool parse_dataobj_common_text(struct comprehension_tlv_iter *iter, |
| char **text) |
| { |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len < 1) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| *text = l_malloc(len + 1); |
| memcpy(*text, data, len); |
| (*text)[len] = '\0'; |
| |
| return true; |
| } |
| |
| /* For data object that only has a byte array with undetermined length */ |
| static bool parse_dataobj_common_byte_array(struct comprehension_tlv_iter *iter, |
| struct stk_common_byte_array *array) |
| { |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len < 1) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| array->len = len; |
| |
| array->array = l_malloc(len); |
| memcpy(array->array, data, len); |
| |
| return true; |
| } |
| |
| static void stk_file_iter_init(struct stk_file_iter *iter, |
| const uint8_t *start, unsigned int len) |
| { |
| iter->start = start; |
| iter->max = len; |
| iter->pos = 0; |
| } |
| |
| static bool stk_file_iter_next(struct stk_file_iter *iter) |
| { |
| unsigned int pos = iter->pos; |
| const unsigned int max = iter->max; |
| const uint8_t *start = iter->start; |
| unsigned int i; |
| uint8_t last_type; |
| |
| if (pos + 2 >= max) |
| return false; |
| |
| /* SIM EFs always start with ROOT MF, 0x3f */ |
| if (start[iter->pos] != 0x3f) |
| return false; |
| |
| last_type = 0x3f; |
| |
| for (i = pos + 2; i < max; i += 2) { |
| /* |
| * Check the validity of file type. |
| * According to TS 11.11, each file id contains of two bytes, |
| * in which the first byte is the type of file. For GSM is: |
| * 0x3f: master file |
| * 0x7f: 1st level dedicated file |
| * 0x5f: 2nd level dedicated file |
| * 0x2f: elementary file under the master file |
| * 0x6f: elementary file under 1st level dedicated file |
| * 0x4f: elementary file under 2nd level dedicated file |
| */ |
| switch (start[i]) { |
| case 0x2f: |
| if (last_type != 0x3f) |
| return false; |
| break; |
| case 0x6f: |
| if (last_type != 0x7f) |
| return false; |
| break; |
| case 0x4f: |
| if (last_type != 0x5f) |
| return false; |
| break; |
| case 0x7f: |
| if (last_type != 0x3f) |
| return false; |
| break; |
| case 0x5f: |
| if (last_type != 0x7f) |
| return false; |
| break; |
| default: |
| return false; |
| } |
| |
| if ((start[i] == 0x2f) || (start[i] == 0x6f) || |
| (start[i] == 0x4f)) { |
| if (i + 1 >= max) |
| return false; |
| |
| iter->file = start + pos; |
| iter->len = i - pos + 2; |
| iter->pos = i + 2; |
| |
| return true; |
| } |
| |
| last_type = start[i]; |
| } |
| |
| return false; |
| } |
| |
| /* Defined in TS 102.223 Section 8.1 */ |
| static bool parse_dataobj_address(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_address *addr = user; |
| const uint8_t *data; |
| unsigned int len; |
| char *number; |
| |
| len = comprehension_tlv_iter_get_length(iter); |
| if (len < 2) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| number = l_malloc(len * 2 - 1); |
| addr->ton_npi = data[0]; |
| addr->number = number; |
| sim_extract_bcd_number(data + 1, len - 1, addr->number); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.2 */ |
| static bool parse_dataobj_alpha_id(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| char **alpha_id = user; |
| const uint8_t *data; |
| unsigned int len; |
| char *utf8; |
| |
| len = comprehension_tlv_iter_get_length(iter); |
| if (len == 0) { |
| *alpha_id = NULL; |
| return true; |
| } |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| utf8 = sim_string_to_utf8(data, len); |
| |
| if (utf8 == NULL) |
| return false; |
| |
| *alpha_id = utf8; |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.3 */ |
| static bool parse_dataobj_subaddress(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_subaddress *subaddr = user; |
| const uint8_t *data; |
| unsigned int len; |
| |
| len = comprehension_tlv_iter_get_length(iter); |
| if (len < 1) |
| return false; |
| |
| if (len > sizeof(subaddr->subaddr)) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| subaddr->len = len; |
| memcpy(subaddr->subaddr, data, len); |
| |
| subaddr->has_subaddr = true; |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.4 */ |
| static bool parse_dataobj_ccp(struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct stk_ccp *ccp = user; |
| const uint8_t *data; |
| unsigned int len; |
| |
| len = comprehension_tlv_iter_get_length(iter); |
| if (len < 1) |
| return false; |
| |
| if (len > sizeof(ccp->ccp)) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| ccp->len = len; |
| memcpy(ccp->ccp, data, len); |
| |
| return true; |
| } |
| |
| /* Defined in TS 31.111 Section 8.5 */ |
| static bool parse_dataobj_cbs_page(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_cbs_page *cp = user; |
| const uint8_t *data; |
| unsigned int len; |
| |
| len = comprehension_tlv_iter_get_length(iter); |
| if (len < 1) |
| return false; |
| |
| if (len > sizeof(cp->page)) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| cp->len = len; |
| memcpy(cp->page, data, len); |
| |
| return true; |
| } |
| |
| /* Described in TS 102.223 Section 8.8 */ |
| static bool parse_dataobj_duration(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_duration *duration = user; |
| const uint8_t *data; |
| |
| if (comprehension_tlv_iter_get_length(iter) != 2) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| if (data[0] > 0x02) |
| return false; |
| |
| if (data[1] == 0) |
| return false; |
| |
| duration->unit = data[0]; |
| duration->interval = data[1]; |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.9 */ |
| static bool parse_dataobj_item(struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct stk_item *item = user; |
| const uint8_t *data; |
| unsigned int len; |
| char *utf8; |
| |
| len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len == 0) |
| return true; |
| |
| if (len == 1) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| /* The identifier is between 0x01 and 0xFF */ |
| if (data[0] == 0) |
| return false; |
| |
| utf8 = sim_string_to_utf8(data + 1, len - 1); |
| |
| if (utf8 == NULL) |
| return false; |
| |
| item->id = data[0]; |
| item->text = utf8; |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.10 */ |
| static bool parse_dataobj_item_id(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| uint8_t *id = user; |
| const uint8_t *data; |
| |
| if (comprehension_tlv_iter_get_length(iter) != 1) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| *id = data[0]; |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.11 */ |
| static bool parse_dataobj_response_len(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_response_length *response_len = user; |
| const uint8_t *data; |
| |
| if (comprehension_tlv_iter_get_length(iter) != 2) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| response_len->min = data[0]; |
| response_len->max = data[1]; |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.12 */ |
| static bool parse_dataobj_result(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_result *result = user; |
| const uint8_t *data; |
| unsigned int len; |
| uint8_t *additional; |
| |
| len = comprehension_tlv_iter_get_length(iter); |
| if (len < 1) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| if ((len < 2) && ((data[0] == 0x20) || (data[0] == 0x21) || |
| (data[0] == 0x26) || (data[0] == 0x38) || |
| (data[0] == 0x39) || (data[0] == 0x3a) || |
| (data[0] == 0x3c) || (data[0] == 0x3d))) |
| return false; |
| |
| additional = l_malloc(len - 1); |
| result->type = data[0]; |
| result->additional_len = len - 1; |
| result->additional = additional; |
| memcpy(result->additional, data + 1, len - 1); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.13 */ |
| static bool parse_dataobj_gsm_sms_tpdu(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct gsm_sms_tpdu *tpdu = user; |
| const uint8_t *data; |
| unsigned int len; |
| |
| len = comprehension_tlv_iter_get_length(iter); |
| if (len < 1 || len > sizeof(tpdu->tpdu)) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| tpdu->len = len; |
| memcpy(tpdu->tpdu, data, len); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.14 */ |
| static bool parse_dataobj_ss(struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct stk_ss *ss = user; |
| const uint8_t *data; |
| unsigned int len; |
| char *s; |
| |
| len = comprehension_tlv_iter_get_length(iter); |
| if (len < 2) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| s = l_malloc(len * 2 - 1); |
| ss->ton_npi = data[0]; |
| ss->ss = s; |
| sim_extract_bcd_number(data + 1, len - 1, ss->ss); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.15 */ |
| static bool parse_dataobj_text(struct comprehension_tlv_iter *iter, void *user) |
| { |
| char **text = user; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| const uint8_t *data; |
| char *utf8; |
| |
| if (len <= 1) { |
| *text = l_new(char, 1); |
| return true; |
| } |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| utf8 = decode_text(data[0], len - 1, data + 1); |
| |
| if (utf8 == NULL) |
| return false; |
| |
| *text = utf8; |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.16 */ |
| static bool parse_dataobj_tone(struct comprehension_tlv_iter *iter, void *user) |
| { |
| uint8_t *byte = user; |
| return parse_dataobj_common_byte(iter, byte); |
| } |
| |
| /* Defined in TS 102.223 Section 8.17 */ |
| static bool parse_dataobj_ussd(struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct stk_ussd_string *us = user; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| const uint8_t *data = comprehension_tlv_iter_get_data(iter); |
| |
| if (len <= 1 || len > 161) |
| return false; |
| |
| us->dcs = data[0]; |
| us->len = len - 1; |
| memcpy(us->string, data + 1, us->len); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.18 */ |
| static bool parse_dataobj_file_list(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct l_queue **out = user; |
| struct l_queue *fl; |
| const uint8_t *data; |
| unsigned int len; |
| struct stk_file *sf; |
| struct stk_file_iter sf_iter; |
| |
| len = comprehension_tlv_iter_get_length(iter); |
| if (len < 5) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| stk_file_iter_init(&sf_iter, data + 1, len - 1); |
| fl = l_queue_new(); |
| |
| while (stk_file_iter_next(&sf_iter)) { |
| sf = l_new(struct stk_file, 1); |
| sf->len = sf_iter.len; |
| memcpy(sf->file, sf_iter.file, sf_iter.len); |
| l_queue_push_tail(fl, sf); |
| } |
| |
| if (sf_iter.pos != sf_iter.max) |
| goto error; |
| |
| *out = fl; |
| return true; |
| |
| error: |
| l_queue_destroy(fl, l_free); |
| return false; |
| } |
| |
| /* Defined in TS 102.223 Section 8.19 */ |
| static bool parse_dataobj_location_info(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_location_info *li = user; |
| const uint8_t *data; |
| unsigned int len; |
| |
| len = comprehension_tlv_iter_get_length(iter); |
| if ((len != 5) && (len != 7) && (len != 9)) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| sim_parse_mcc_mnc(data, li->mcc, li->mnc); |
| li->lac_tac = (data[3] << 8) + data[4]; |
| |
| if (len >= 7) { |
| li->has_ci = true; |
| li->ci = (data[5] << 8) + data[6]; |
| } |
| |
| if (len == 9) { |
| li->has_ext_ci = true; |
| li->ext_ci = (data[7] << 8) + data[8]; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * Defined in TS 102.223 Section 8.20. |
| * |
| * According to 3GPP TS 24.008, Section 10.5.1.4, IMEI is composed of |
| * 15 digits and totally 8 bytes are used to represent it. |
| * |
| * Bits 1-3 of first byte represent the type of identity, and they |
| * are 0 1 0 separately for IMEI. Bit 4 of first byte is the odd/even |
| * indication, and it's 1 to indicate IMEI has odd number of digits (15). |
| * The rest bytes are coded using BCD coding. |
| * |
| * For example, if the IMEI is "123456789012345", then it's coded as |
| * "1A 32 54 76 98 10 32 54". |
| */ |
| static bool parse_dataobj_imei(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| char *imei = user; |
| const uint8_t *data; |
| unsigned int len; |
| static const char digit_lut[] = "0123456789*#abc\0"; |
| |
| len = comprehension_tlv_iter_get_length(iter); |
| if (len != 8) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| if ((data[0] & 0x0f) != 0x0a) |
| return false; |
| |
| /* Assume imei is at least 16 bytes long (15 for imei + null) */ |
| imei[0] = digit_lut[(data[0] & 0xf0) >> 4]; |
| extract_bcd_number(data + 1, 7, imei + 1); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.21 */ |
| static bool parse_dataobj_help_request(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| bool *ret = user; |
| return parse_dataobj_common_bool(iter, ret); |
| } |
| |
| /* Defined in TS 102.223 Section 8.22 */ |
| static bool parse_dataobj_network_measurement_results( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| uint8_t *nmr = user; |
| const uint8_t *data; |
| unsigned int len; |
| |
| len = comprehension_tlv_iter_get_length(iter); |
| if (len != 0x10) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| /* Assume network measurement result is 16 bytes long */ |
| memcpy(nmr, data, len); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.23 */ |
| static bool parse_dataobj_default_text(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| char **text = user; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| const uint8_t *data = comprehension_tlv_iter_get_data(iter); |
| char *utf8; |
| |
| /* DCS followed by some text, cannot be 1 */ |
| if (len <= 1) |
| return false; |
| |
| utf8 = decode_text(data[0], len - 1, data + 1); |
| |
| if (utf8 == NULL) |
| return false; |
| |
| *text = utf8; |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.24 */ |
| static bool parse_dataobj_items_next_action_indicator( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct stk_items_next_action_indicator *inai = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if ((len < 1) || (len > sizeof(inai->list))) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| inai->len = len; |
| memcpy(inai->list, data, len); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.25 */ |
| static bool parse_dataobj_event_list(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_event_list *el = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len == 0) |
| return true; |
| |
| if (len > sizeof(el->list)) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| el->len = len; |
| memcpy(el->list, data, len); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.26 */ |
| static bool parse_dataobj_cause(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_cause *cause = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if ((len == 1) || (len > sizeof(cause->cause))) |
| return false; |
| |
| cause->has_cause = true; |
| |
| if (len == 0) |
| return true; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| cause->len = len; |
| memcpy(cause->cause, data, len); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.27 */ |
| static bool parse_dataobj_location_status(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| uint8_t *byte = user; |
| |
| return parse_dataobj_common_byte(iter, byte); |
| } |
| |
| /* Defined in TS 102.223 Section 8.28 */ |
| static bool parse_dataobj_transaction_id(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_transaction_id *ti = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if ((len < 1) || (len > sizeof(ti->list))) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| ti->len = len; |
| memcpy(ti->list, data, len); |
| |
| return true; |
| } |
| |
| /* Defined in TS 31.111 Section 8.29 */ |
| static bool parse_dataobj_bcch_channel_list(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_bcch_channel_list *bcl = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| unsigned int i; |
| |
| if (len < 1) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| bcl->num = len * 8 / 10; |
| |
| for (i = 0; i < bcl->num; i++) { |
| unsigned int index = i * 10 / 8; |
| unsigned int occupied = i * 10 % 8; |
| |
| bcl->channels[i] = (data[index] << (2 + occupied)) + |
| (data[index + 1] >> (6 - occupied)); |
| } |
| |
| bcl->has_list = true; |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.30 */ |
| static bool parse_dataobj_call_control_requested_action( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct stk_common_byte_array *array = user; |
| |
| return parse_dataobj_common_byte_array(iter, array); |
| } |
| |
| /* Defined in TS 102.223 Section 8.31 */ |
| static bool parse_dataobj_icon_id(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_icon_id *id = user; |
| const uint8_t *data; |
| |
| if (comprehension_tlv_iter_get_length(iter) != 2) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| id->qualifier = data[0]; |
| id->id = data[1]; |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.32 */ |
| static bool parse_dataobj_item_icon_id_list(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_item_icon_id_list *iiil = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if ((len < 2) || (len > 127)) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| iiil->qualifier = data[0]; |
| iiil->len = len - 1; |
| memcpy(iiil->list, data + 1, iiil->len); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.33 */ |
| static bool parse_dataobj_card_reader_status( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| uint8_t *byte = user; |
| |
| return parse_dataobj_common_byte(iter, byte); |
| } |
| |
| /* Defined in TS 102.223 Section 8.34 */ |
| static bool parse_dataobj_card_atr(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_card_atr *ca = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if ((len < 1) || (len > sizeof(ca->atr))) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| ca->len = len; |
| memcpy(ca->atr, data, len); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.35 */ |
| static bool parse_dataobj_c_apdu(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_c_apdu *ca = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| unsigned int pos; |
| |
| if ((len < 4) || (len > 241)) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| ca->cla = data[0]; |
| ca->ins = data[1]; |
| ca->p1 = data[2]; |
| ca->p2 = data[3]; |
| |
| pos = 4; |
| |
| /* |
| * lc is 0 has the same meaning as lc is absent. But le is 0 means |
| * the maximum number of bytes expected in the response data field |
| * is 256. So we need to rely on has_le to know if it presents. |
| */ |
| if (len > 5) { |
| ca->lc = data[4]; |
| if (ca->lc > sizeof(ca->data)) |
| return false; |
| |
| pos += ca->lc + 1; |
| |
| if (len - pos > 1) |
| return false; |
| |
| memcpy(ca->data, data+5, ca->lc); |
| } |
| |
| if (len - pos > 0) { |
| ca->le = data[len - 1]; |
| ca->has_le = true; |
| } |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.36 */ |
| static bool parse_dataobj_r_apdu(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_r_apdu *ra = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if ((len < 2) || (len > 239)) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| ra->sw1 = data[len-2]; |
| ra->sw2 = data[len-1]; |
| |
| if (len > 2) { |
| ra->len = len - 2; |
| memcpy(ra->data, data, ra->len); |
| } else |
| ra->len = 0; |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.37 */ |
| static bool parse_dataobj_timer_id(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| uint8_t *byte = user; |
| |
| return parse_dataobj_common_byte(iter, byte); |
| } |
| |
| /* Defined in TS 102.223 Section 8.38 */ |
| static bool parse_dataobj_timer_value(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_timer_value *tv = user; |
| const uint8_t *data; |
| |
| if (comprehension_tlv_iter_get_length(iter) != 3) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| tv->hour = sms_decode_semi_octet(data[0]); |
| tv->minute = sms_decode_semi_octet(data[1]); |
| tv->second = sms_decode_semi_octet(data[2]); |
| tv->has_value = true; |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.39 */ |
| static bool parse_dataobj_datetime_timezone( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct sms_scts *scts = user; |
| const uint8_t *data; |
| int offset = 0; |
| |
| if (comprehension_tlv_iter_get_length(iter) != 7) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| sms_decode_scts(data, 7, &offset, scts); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.40 */ |
| static bool parse_dataobj_at_command(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| char **command = user; |
| return parse_dataobj_common_text(iter, command); |
| } |
| |
| /* Defined in TS 102.223 Section 8.41 */ |
| static bool parse_dataobj_at_response(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| char **response = user; |
| return parse_dataobj_common_text(iter, response); |
| } |
| |
| /* Defined in TS 102.223 Section 8.42 */ |
| static bool parse_dataobj_bc_repeat_indicator( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct stk_bc_repeat *bc_repeat = user; |
| |
| if (!parse_dataobj_common_byte(iter, &bc_repeat->value)) |
| return false; |
| |
| bc_repeat->has_bc_repeat = true; |
| return true; |
| } |
| |
| /* Defined in 102.223 Section 8.43 */ |
| static bool parse_dataobj_imm_resp(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| bool *ret = user; |
| return parse_dataobj_common_bool(iter, ret); |
| } |
| |
| /* Defined in 102.223 Section 8.44 */ |
| static bool parse_dataobj_dtmf_string(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| char **dtmf = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len < 1) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| *dtmf = l_malloc(len * 2 + 1); |
| sim_extract_bcd_number(data, len, *dtmf); |
| |
| return true; |
| } |
| |
| /* Defined in 102.223 Section 8.45 */ |
| static bool parse_dataobj_language(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| char *lang = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len != 2) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| /* |
| * This is a 2 character pair as defined in ISO 639, coded using |
| * GSM default 7 bit alphabet with bit 8 set to 0. Since the english |
| * letters have the same mapping in GSM as ASCII, no conversion |
| * is required here |
| */ |
| memcpy(lang, data, len); |
| lang[len] = '\0'; |
| |
| return true; |
| } |
| |
| /* Defined in 31.111 Section 8.46 */ |
| static bool parse_dataobj_timing_advance(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_timing_advance *ta = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len != 2) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| ta->has_value = true; |
| ta->status = data[0]; |
| ta->advance = data[1]; |
| |
| return true; |
| } |
| |
| /* Defined in 102.223 Section 8.47 */ |
| static bool parse_dataobj_browser_id(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| uint8_t *byte = user; |
| |
| if (!parse_dataobj_common_byte(iter, byte) || *byte > 4) |
| return false; |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.48 */ |
| static bool parse_dataobj_url(struct comprehension_tlv_iter *iter, void *user) |
| { |
| char **url = user; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len == 0) { |
| *url = NULL; |
| return true; |
| } |
| |
| return parse_dataobj_common_text(iter, url); |
| } |
| |
| /* Defined in TS 102.223 Section 8.49 */ |
| static bool parse_dataobj_bearer(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_common_byte_array *array = user; |
| return parse_dataobj_common_byte_array(iter, array); |
| } |
| |
| /* Defined in TS 102.223 Section 8.50 */ |
| static bool parse_dataobj_provisioning_file_reference( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct stk_file *f = user; |
| const uint8_t *data; |
| struct stk_file_iter sf_iter; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if ((len < 1) || (len > 8)) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| stk_file_iter_init(&sf_iter, data, len); |
| stk_file_iter_next(&sf_iter); |
| |
| if (sf_iter.pos != sf_iter.max) |
| return false; |
| |
| f->len = len; |
| memcpy(f->file, data, len); |
| |
| return true; |
| } |
| |
| /* Defined in 102.223 Section 8.51 */ |
| static bool parse_dataobj_browser_termination_cause( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| uint8_t *byte = user; |
| return parse_dataobj_common_byte(iter, byte); |
| } |
| |
| /* Defined in TS 102.223 Section 8.52 */ |
| static bool parse_dataobj_bearer_description( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct stk_bearer_description *bd = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len < 1) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| bd->type = data[0]; |
| |
| /* Parse only the packet data service bearer parameters */ |
| if (bd->type != STK_BEARER_TYPE_GPRS_UTRAN) |
| return false; |
| |
| if (len < 7) |
| return false; |
| |
| bd->gprs.precedence = data[1]; |
| bd->gprs.delay = data[2]; |
| bd->gprs.reliability = data[3]; |
| bd->gprs.peak = data[4]; |
| bd->gprs.mean = data[5]; |
| bd->gprs.pdp_type = data[6]; |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.53 */ |
| static bool parse_dataobj_channel_data(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_common_byte_array *array = user; |
| return parse_dataobj_common_byte_array(iter, array); |
| } |
| |
| /* Defined in TS 102.223 Section 8.54 */ |
| static bool parse_dataobj_channel_data_length( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| uint8_t *byte = user; |
| return parse_dataobj_common_byte(iter, byte); |
| } |
| |
| /* Defined in TS 102.223 Section 8.55 */ |
| static bool parse_dataobj_buffer_size(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| uint16_t *size = user; |
| const uint8_t *data; |
| |
| if (comprehension_tlv_iter_get_length(iter) != 2) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| *size = (data[0] << 8) + data[1]; |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.56 */ |
| static bool parse_dataobj_channel_status( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| uint8_t *status = user; |
| const uint8_t *data; |
| |
| if (comprehension_tlv_iter_get_length(iter) != 2) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| /* Assume channel status is 2 bytes long */ |
| memcpy(status, data, 2); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.57 */ |
| static bool parse_dataobj_card_reader_id(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_card_reader_id *cr_id = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len < 1) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| cr_id->len = len; |
| memcpy(cr_id->id, data, len); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.58 */ |
| static bool parse_dataobj_other_address(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_other_address *oa = user; |
| const uint8_t *data; |
| uint8_t len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len == 0) { |
| oa->type = STK_ADDRESS_AUTO; |
| return true; |
| } |
| |
| if ((len != 5) && (len != 17)) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| if (data[0] != STK_ADDRESS_IPV4 && data[0] != STK_ADDRESS_IPV6) |
| return false; |
| |
| oa->type = data[0]; |
| |
| if (oa->type == STK_ADDRESS_IPV4) |
| memcpy(&oa->addr.ipv4, data + 1, 4); |
| else |
| memcpy(&oa->addr.ipv6, data + 1, 16); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.59 */ |
| static bool parse_dataobj_uicc_te_interface(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_uicc_te_interface *uti = user; |
| const uint8_t *data; |
| uint8_t len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len != 3) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| uti->protocol = data[0]; |
| uti->port = (data[1] << 8) + data[2]; |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.60 */ |
| static bool parse_dataobj_aid(struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct stk_aid *aid = user; |
| const uint8_t *data; |
| uint8_t len = comprehension_tlv_iter_get_length(iter); |
| |
| if ((len > 16) || (len < 12)) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| aid->len = len; |
| memcpy(aid->aid, data, len); |
| |
| return true; |
| } |
| |
| /* |
| * Defined in TS 102.223 Section 8.61. According to it, the technology field |
| * can have at most 127 bytes. However, all the defined values are only 1 byte, |
| * so we just use 1 byte to represent it. |
| */ |
| static bool parse_dataobj_access_technology( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| uint8_t *byte = user; |
| return parse_dataobj_common_byte(iter, byte); |
| } |
| |
| /* Defined in TS 102.223 Section 8.62 */ |
| static bool parse_dataobj_display_parameters( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct stk_display_parameters *dp = user; |
| const uint8_t *data; |
| |
| if (comprehension_tlv_iter_get_length(iter) != 3) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| dp->height = data[0]; |
| dp->width = data[1]; |
| dp->effects = data[2]; |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.63 */ |
| static bool parse_dataobj_service_record(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_service_record *sr = user; |
| const uint8_t *data; |
| unsigned int len; |
| |
| len = comprehension_tlv_iter_get_length(iter); |
| if (len < 3) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| sr->tech_id = data[0]; |
| sr->serv_id = data[1]; |
| sr->len = len - 2; |
| |
| sr->serv_rec = l_malloc(sr->len); |
| memcpy(sr->serv_rec, data + 2, sr->len); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.64 */ |
| static bool parse_dataobj_device_filter(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_device_filter *df = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len < 2) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| /* According to TS 102.223, everything except BT & IRDA is RFU */ |
| if (data[0] != STK_TECHNOLOGY_BLUETOOTH && |
| data[0] != STK_TECHNOLOGY_IRDA) |
| return false; |
| |
| df->tech_id = data[0]; |
| df->len = len - 1; |
| |
| df->dev_filter = l_malloc(df->len); |
| memcpy(df->dev_filter, data + 1, df->len); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.65 */ |
| static bool parse_dataobj_service_search( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct stk_service_search *ss = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len < 2) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| /* According to TS 102.223, everything except BT & IRDA is RFU */ |
| if (data[0] != STK_TECHNOLOGY_BLUETOOTH && |
| data[0] != STK_TECHNOLOGY_IRDA) |
| return false; |
| |
| ss->tech_id = data[0]; |
| ss->len = len - 1; |
| |
| ss->ser_search = l_malloc(ss->len); |
| memcpy(ss->ser_search, data + 1, ss->len); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.66 */ |
| static bool parse_dataobj_attribute_info(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_attribute_info *ai = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len < 2) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| /* According to TS 102.223, everything except BT & IRDA is RFU */ |
| if (data[0] != STK_TECHNOLOGY_BLUETOOTH && |
| data[0] != STK_TECHNOLOGY_IRDA) |
| return false; |
| |
| ai->tech_id = data[0]; |
| ai->len = len - 1; |
| |
| ai->attr_info = l_malloc(ai->len); |
| memcpy(ai->attr_info, data + 1, ai->len); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.67 */ |
| static bool parse_dataobj_service_availability( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct stk_common_byte_array *array = user; |
| return parse_dataobj_common_byte_array(iter, array); |
| } |
| |
| /* Defined in TS 102.223 Section 8.68 */ |
| static bool parse_dataobj_remote_entity_address( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct stk_remote_entity_address *rea = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| switch (data[0]) { |
| case 0x00: |
| if (len != 7) |
| return false; |
| break; |
| case 0x01: |
| if (len != 5) |
| return false; |
| break; |
| default: |
| return false; |
| } |
| |
| rea->has_address = true; |
| rea->coding_type = data[0]; |
| memcpy(&rea->addr, data + 1, len - 1); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.69 */ |
| static bool parse_dataobj_esn(struct comprehension_tlv_iter *iter, void *user) |
| { |
| uint8_t *esn = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len != 4) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| /* Assume esn is 4 bytes long */ |
| memcpy(esn, data, len); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.70 */ |
| static bool parse_dataobj_network_access_name( |
| struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| char **apn = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| uint8_t label_size; |
| uint8_t offset = 0; |
| char decoded_apn[100]; |
| |
| if (len == 0 || len > 100) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| /* |
| * As specified in TS 23 003 Section 9 |
| * The APN consists of one or more labels. Each label is coded as |
| * a one octet length field followed by that number of octets coded |
| * as 8 bit ASCII characters |
| */ |
| while (len) { |
| label_size = *data; |
| |
| if (label_size == 0 || label_size > (len - 1)) |
| return false; |
| |
| memcpy(decoded_apn + offset, data + 1, label_size); |
| |
| data += label_size + 1; |
| offset += label_size; |
| len -= label_size + 1; |
| |
| if (len) |
| decoded_apn[offset++] = '.'; |
| } |
| |
| decoded_apn[offset] = '\0'; |
| *apn = l_strdup(decoded_apn); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.71 */ |
| static bool parse_dataobj_cdma_sms_tpdu(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_common_byte_array *array = user; |
| return parse_dataobj_common_byte_array(iter, array); |
| } |
| |
| /* Defined in TS 102.223 Section 8.72 */ |
| static bool parse_dataobj_text_attr(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_text_attribute *attr = user; |
| const uint8_t *data; |
| unsigned int len; |
| |
| len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len > sizeof(attr->attributes)) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| memcpy(attr->attributes, data, len); |
| attr->len = len; |
| |
| return true; |
| } |
| |
| /* Defined in TS 31.111 Section 8.72 */ |
| static bool parse_dataobj_pdp_act_par( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct stk_pdp_act_par *pcap = user; |
| const uint8_t *data; |
| unsigned int len; |
| |
| len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len > sizeof(pcap->par)) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| memcpy(pcap->par, data, len); |
| pcap->len = len; |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.73 */ |
| static bool parse_dataobj_item_text_attribute_list( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct stk_item_text_attribute_list *ital = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if ((len > sizeof(ital->list)) || (len % 4 != 0)) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| memcpy(ital->list, data, len); |
| ital->len = len; |
| |
| return true; |
| } |
| |
| /* Defined in TS 31.111 Section 8.73 */ |
| static bool parse_dataobj_utran_meas_qualifier( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| uint8_t *byte = user; |
| return parse_dataobj_common_byte(iter, byte); |
| } |
| |
| /* |
| * Defined in TS 102.223 Section 8.74. |
| * |
| * According to 3GPP TS 24.008, Section 10.5.1.4, IMEISV is composed of |
| * 16 digits and totally 9 bytes are used to represent it. |
| * |
| * Bits 1-3 of first byte represent the type of identity, and they |
| * are 0 1 1 separately for IMEISV. Bit 4 of first byte is the odd/even |
| * indication, and it's 0 to indicate IMEISV has odd number of digits (16). |
| * The rest bytes are coded using BCD coding. |
| * |
| * For example, if the IMEISV is "1234567890123456", then it's coded as |
| * "13 32 54 76 98 10 32 54 F6". |
| */ |
| static bool parse_dataobj_imeisv(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| char *imeisv = user; |
| const uint8_t *data; |
| unsigned int len; |
| static const char digit_lut[] = "0123456789*#abc\0"; |
| |
| len = comprehension_tlv_iter_get_length(iter); |
| if (len != 9) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| if ((data[0] & 0x0f) != 0x03) |
| return false; |
| |
| if (data[8] >> 4 != 0x0f) |
| return false; |
| |
| /* Assume imeisv is at least 17 bytes long (16 for imeisv + null) */ |
| imeisv[0] = digit_lut[data[0] >> 4]; |
| extract_bcd_number(data + 1, 7, imeisv + 1); |
| imeisv[15] = digit_lut[data[8] & 0x0f]; |
| imeisv[16] = '\0'; |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.75 */ |
| static bool parse_dataobj_network_search_mode( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| uint8_t *byte = user; |
| return parse_dataobj_common_byte(iter, byte); |
| } |
| |
| /* Defined in TS 102.223 Section 8.76 */ |
| static bool parse_dataobj_battery_state(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| uint8_t *byte = user; |
| return parse_dataobj_common_byte(iter, byte); |
| } |
| |
| /* Defined in TS 102.223 Section 8.77 */ |
| static bool parse_dataobj_browsing_status( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct stk_common_byte_array *array = user; |
| return parse_dataobj_common_byte_array(iter, array); |
| } |
| |
| /* Defined in TS 102.223 Section 8.78 */ |
| static bool parse_dataobj_frame_layout(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_frame_layout *fl = user; |
| const uint8_t *data; |
| uint8_t len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len < 2) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| if (data[0] != STK_LAYOUT_HORIZONTAL && |
| data[0] != STK_LAYOUT_VERTICAL) |
| return false; |
| |
| fl->layout = data[0]; |
| fl->len = len - 1; |
| memcpy(fl->size, data + 1, fl->len); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.79 */ |
| static bool parse_dataobj_frames_info(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_frames_info *fi = user; |
| const uint8_t *data; |
| uint8_t len = comprehension_tlv_iter_get_length(iter); |
| unsigned int i; |
| |
| if (len < 1) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| if (data[0] > 0x0f) |
| return false; |
| |
| if ((len == 1 && data[0] != 0) || (len > 1 && data[0] == 0)) |
| return false; |
| |
| if (len % 2 == 0) |
| return false; |
| |
| if (len == 1) |
| return true; |
| |
| fi->id = data[0]; |
| fi->len = (len - 1) / 2; |
| for (i = 0; i < len; i++) { |
| fi->list[i].height = data[i * 2 + 1] & 0x1f; |
| fi->list[i].width = data[i * 2 + 2] & 0x7f; |
| } |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.80 */ |
| static bool parse_dataobj_frame_id(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_frame_id *fi = user; |
| const uint8_t *data; |
| |
| if (comprehension_tlv_iter_get_length(iter) != 1) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| if (data[0] >= 0x10) |
| return false; |
| |
| fi->has_id = true; |
| fi->id = data[0]; |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.81 */ |
| static bool parse_dataobj_meid(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| uint8_t *meid = user; |
| const uint8_t *data; |
| |
| if (comprehension_tlv_iter_get_length(iter) != 8) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| /* Assume meid is 8 bytes long */ |
| memcpy(meid, data, 8); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.82 */ |
| static bool parse_dataobj_mms_reference(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_mms_reference *mr = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len < 1) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| mr->len = len; |
| memcpy(mr->ref, data, len); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.83 */ |
| static bool parse_dataobj_mms_id(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| struct stk_mms_id *mi = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len < 1) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| mi->len = len; |
| memcpy(mi->id, data, len); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.84 */ |
| static bool parse_dataobj_mms_transfer_status( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct stk_mms_transfer_status *mts = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len < 1) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| mts->len = len; |
| memcpy(mts->status, data, len); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.85 */ |
| static bool parse_dataobj_mms_content_id( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct stk_mms_content_id *mci = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len < 1) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| mci->len = len; |
| memcpy(mci->id, data, len); |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.86 */ |
| static bool parse_dataobj_mms_notification( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct stk_common_byte_array *array = user; |
| return parse_dataobj_common_byte_array(iter, array); |
| } |
| |
| /* Defined in TS 102.223 Section 8.87 */ |
| static bool parse_dataobj_last_envelope(struct comprehension_tlv_iter *iter, |
| void *user) |
| { |
| bool *ret = user; |
| return parse_dataobj_common_bool(iter, ret); |
| } |
| |
| /* Defined in TS 102.223 Section 8.88 */ |
| static bool parse_dataobj_registry_application_data( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct stk_registry_application_data *rad = user; |
| const uint8_t *data; |
| char *utf8; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len < 5) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| utf8 = decode_text(data[2], len - 4, data + 4); |
| |
| if (utf8 == NULL) |
| return false; |
| |
| rad->name = utf8; |
| rad->port = (data[0] << 8) + data[1]; |
| rad->type = data[3]; |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.89 */ |
| static bool parse_dataobj_activate_descriptor( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| uint8_t *byte = user; |
| const uint8_t *data; |
| |
| if (comprehension_tlv_iter_get_length(iter) != 1) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| if (data[0] != 0x01) |
| return false; |
| |
| *byte = data[0]; |
| |
| return true; |
| } |
| |
| /* Defined in TS 102.223 Section 8.90 */ |
| static bool parse_dataobj_broadcast_network_info( |
| struct comprehension_tlv_iter *iter, void *user) |
| { |
| struct stk_broadcast_network_information *bni = user; |
| const uint8_t *data; |
| unsigned int len = comprehension_tlv_iter_get_length(iter); |
| |
| if (len < 2) |
| return false; |
| |
| data = comprehension_tlv_iter_get_data(iter); |
| |
| if (data[0] > 0x03) |
| return false; |
| |
| bni->tech = data[0]; |
| bni->len = len - 1; |
| memcpy(bni->loc_info, data + 1, bni->len); |
| |
| return true; |
| } |
| |
| static dataobj_handler handler_for_type(enum stk_data_object_type type) |
| { |
| switch (type) { |
| case STK_DATA_OBJECT_TYPE_ADDRESS: |
| return parse_dataobj_address; |
| case STK_DATA_OBJECT_TYPE_ALPHA_ID: |
| return parse_dataobj_alpha_id; |
| case STK_DATA_OBJECT_TYPE_SUBADDRESS: |
| return parse_dataobj_subaddress; |
| case STK_DATA_OBJECT_TYPE_CCP: |
| return parse_dataobj_ccp; |
| case STK_DATA_OBJECT_TYPE_CBS_PAGE: |
| return parse_dataobj_cbs_page; |
| case STK_DATA_OBJECT_TYPE_DURATION: |
| return parse_dataobj_duration; |
| case STK_DATA_OBJECT_TYPE_ITEM: |
| return parse_dataobj_item; |
| case STK_DATA_OBJECT_TYPE_ITEM_ID: |
| return parse_dataobj_item_id; |
| case STK_DATA_OBJECT_TYPE_RESPONSE_LENGTH: |
| return parse_dataobj_response_len; |
| case STK_DATA_OBJECT_TYPE_RESULT: |
| return parse_dataobj_result; |
| case STK_DATA_OBJECT_TYPE_GSM_SMS_TPDU: |
| return parse_dataobj_gsm_sms_tpdu; |
| case STK_DATA_OBJECT_TYPE_SS_STRING: |
| return parse_dataobj_ss; |
| case STK_DATA_OBJECT_TYPE_TEXT: |
| return parse_dataobj_text; |
| case STK_DATA_OBJECT_TYPE_TONE: |
| return parse_dataobj_tone; |
| case STK_DATA_OBJECT_TYPE_USSD_STRING: |
| return parse_dataobj_ussd; |
| case STK_DATA_OBJECT_TYPE_FILE_LIST: |
| return parse_dataobj_file_list; |
| case STK_DATA_OBJECT_TYPE_LOCATION_INFO: |
| return parse_dataobj_location_info; |
| case STK_DATA_OBJECT_TYPE_IMEI: |
| return parse_dataobj_imei; |
| case STK_DATA_OBJECT_TYPE_HELP_REQUEST: |
| return parse_dataobj_help_request; |
| case STK_DATA_OBJECT_TYPE_NETWORK_MEASUREMENT_RESULTS: |
| return parse_dataobj_network_measurement_results; |
| case STK_DATA_OBJECT_TYPE_DEFAULT_TEXT: |
| return parse_dataobj_default_text; |
| case STK_DATA_OBJECT_TYPE_ITEMS_NEXT_ACTION_INDICATOR: |
| return parse_dataobj_items_next_action_indicator; |
| case STK_DATA_OBJECT_TYPE_EVENT_LIST: |
| return parse_dataobj_event_list; |
| case STK_DATA_OBJECT_TYPE_CAUSE: |
| return parse_dataobj_cause; |
| case STK_DATA_OBJECT_TYPE_LOCATION_STATUS: |
| return parse_dataobj_location_status; |
| case STK_DATA_OBJECT_TYPE_TRANSACTION_ID: |
| return parse_dataobj_transaction_id; |
| case STK_DATA_OBJECT_TYPE_BCCH_CHANNEL_LIST: |
| return parse_dataobj_bcch_channel_list; |
| case STK_DATA_OBJECT_TYPE_CALL_CONTROL_REQUESTED_ACTION: |
| return parse_dataobj_call_control_requested_action; |
| case STK_DATA_OBJECT_TYPE_ICON_ID: |
| return parse_dataobj_icon_id; |
| case STK_DATA_OBJECT_TYPE_ITEM_ICON_ID_LIST: |
| return parse_dataobj_item_icon_id_list; |
| case STK_DATA_OBJECT_TYPE_CARD_READER_STATUS: |
| return parse_dataobj_card_reader_status; |
| case STK_DATA_OBJECT_TYPE_CARD_ATR: |
| return parse_dataobj_card_atr; |
| case STK_DATA_OBJECT_TYPE_C_APDU: |
| return parse_dataobj_c_apdu; |
| case STK_DATA_OBJECT_TYPE_R_APDU: |
| return parse_dataobj_r_apdu; |
| case STK_DATA_OBJECT_TYPE_TIMER_ID: |
| return parse_dataobj_timer_id; |
| case STK_DATA_OBJECT_TYPE_TIMER_VALUE: |
| return parse_dataobj_timer_value; |
| case STK_DATA_OBJECT_TYPE_DATETIME_TIMEZONE: |
| return parse_dataobj_datetime_timezone; |
| case STK_DATA_OBJECT_TYPE_AT_COMMAND: |
| return parse_dataobj_at_command; |
| case STK_DATA_OBJECT_TYPE_AT_RESPONSE: |
| return parse_dataobj_at_response; |
| case STK_DATA_OBJECT_TYPE_BC_REPEAT_INDICATOR: |
| return parse_dataobj_bc_repeat_indicator; |
| case STK_DATA_OBJECT_TYPE_IMMEDIATE_RESPONSE: |
| return parse_dataobj_imm_resp; |
| case STK_DATA_OBJECT_TYPE_DTMF_STRING: |
| return parse_dataobj_dtmf_string; |
| case STK_DATA_OBJECT_TYPE_LANGUAGE: |
| return parse_dataobj_language; |
| case STK_DATA_OBJECT_TYPE_BROWSER_ID: |
| return parse_dataobj_browser_id; |
| case STK_DATA_OBJECT_TYPE_TIMING_ADVANCE: |
| return parse_dataobj_timing_advance; |
| case STK_DATA_OBJECT_TYPE_URL: |
| return parse_dataobj_url; |
| case STK_DATA_OBJECT_TYPE_BEARER: |
| return parse_dataobj_bearer; |
| case STK_DATA_OBJECT_TYPE_PROVISIONING_FILE_REF: |
| return parse_dataobj_provisioning_file_reference; |
| case STK_DATA_OBJECT_TYPE_BROWSER_TERMINATION_CAUSE: |
| return parse_dataobj_browser_termination_cause; |
| case STK_DATA_OBJECT_TYPE_BEARER_DESCRIPTION: |
| return parse_dataobj_bearer_description; |
| case STK_DATA_OBJECT_TYPE_CHANNEL_DATA: |
| return parse_dataobj_channel_data; |
| case STK_DATA_OBJECT_TYPE_CHANNEL_DATA_LENGTH: |
| return parse_dataobj_channel_data_length; |
| case STK_DATA_OBJECT_TYPE_BUFFER_SIZE: |
| return parse_dataobj_buffer_size; |
| case STK_DATA_OBJECT_TYPE_CHANNEL_STATUS: |
| return parse_dataobj_channel_status; |
| case STK_DATA_OBJECT_TYPE_CARD_READER_ID: |
| return parse_dataobj_card_reader_id; |
| case STK_DATA_OBJECT_TYPE_OTHER_ADDRESS: |
| return parse_dataobj_other_address; |
| case STK_DATA_OBJECT_TYPE_UICC_TE_INTERFACE: |
| return parse_dataobj_uicc_te_interface; |
| case STK_DATA_OBJECT_TYPE_AID: |
| return parse_dataobj_aid; |
| case STK_DATA_OBJECT_TYPE_ACCESS_TECHNOLOGY: |
| return parse_dataobj_access_technology; |
| case STK_DATA_OBJECT_TYPE_DISPLAY_PARAMETERS: |
| return parse_dataobj_display_parameters; |
| case STK_DATA_OBJECT_TYPE_SERVICE_RECORD: |
| return parse_dataobj_service_record; |
| case STK_DATA_OBJECT_TYPE_DEVICE_FILTER: |
| return parse_dataobj_device_filter; |
| case STK_DATA_OBJECT_TYPE_SERVICE_SEARCH: |
| return parse_dataobj_service_search; |
| case STK_DATA_OBJECT_TYPE_ATTRIBUTE_INFO: |
| return parse_dataobj_attribute_info; |
| case STK_DATA_OBJECT_TYPE_SERVICE_AVAILABILITY: |
| return parse_dataobj_service_availability; |
| case STK_DATA_OBJECT_TYPE_REMOTE_ENTITY_ADDRESS: |
| return parse_dataobj_remote_entity_address; |
| case STK_DATA_OBJECT_TYPE_ESN: |
| return parse_dataobj_esn; |
| case STK_DATA_OBJECT_TYPE_NETWORK_ACCESS_NAME: |
| return parse_dataobj_network_access_name; |
| case STK_DATA_OBJECT_TYPE_CDMA_SMS_TPDU: |
| return parse_dataobj_cdma_sms_tpdu; |
| case STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE: |
| return parse_dataobj_text_attr; |
| case STK_DATA_OBJECT_TYPE_PDP_ACTIVATION_PARAMETER: |
| return parse_dataobj_pdp_act_par; |
| case STK_DATA_OBJECT_TYPE_ITEM_TEXT_ATTRIBUTE_LIST: |
| return parse_dataobj_item_text_attribute_list; |
| case STK_DATA_OBJECT_TYPE_UTRAN_MEASUREMENT_QUALIFIER: |
| return parse_dataobj_utran_meas_qualifier; |
| case STK_DATA_OBJECT_TYPE_IMEISV: |
| return parse_dataobj_imeisv; |
| case STK_DATA_OBJECT_TYPE_NETWORK_SEARCH_MODE: |
| return parse_dataobj_network_search_mode; |
| case STK_DATA_OBJECT_TYPE_BATTERY_STATE: |
| return parse_dataobj_battery_state; |
| case STK_DATA_OBJECT_TYPE_BROWSING_STATUS: |
| return parse_dataobj_browsing_status; |
| case STK_DATA_OBJECT_TYPE_FRAME_LAYOUT: |
| return parse_dataobj_frame_layout; |
| case STK_DATA_OBJECT_TYPE_FRAMES_INFO: |
| return parse_dataobj_frames_info; |
| case STK_DATA_OBJECT_TYPE_FRAME_ID: |
| return parse_dataobj_frame_id; |
| case STK_DATA_OBJECT_TYPE_MEID: |
| return parse_dataobj_meid; |
| case STK_DATA_OBJECT_TYPE_MMS_REFERENCE: |
| return parse_dataobj_mms_reference; |
| case STK_DATA_OBJECT_TYPE_MMS_ID: |
| return parse_dataobj_mms_id; |
| case STK_DATA_OBJECT_TYPE_MMS_TRANSFER_STATUS: |
| return parse_dataobj_mms_transfer_status; |
| case STK_DATA_OBJECT_TYPE_MMS_CONTENT_ID: |
| return parse_dataobj_mms_content_id; |
| case STK_DATA_OBJECT_TYPE_MMS_NOTIFICATION: |
| return parse_dataobj_mms_notification; |
| case STK_DATA_OBJECT_TYPE_LAST_ENVELOPE: |
| return parse_dataobj_last_envelope; |
| case STK_DATA_OBJECT_TYPE_REGISTRY_APPLICATION_DATA: |
| return parse_dataobj_registry_application_data; |
| case STK_DATA_OBJECT_TYPE_ACTIVATE_DESCRIPTOR: |
| return parse_dataobj_activate_descriptor; |
| case STK_DATA_OBJECT_TYPE_BROADCAST_NETWORK_INFO: |
| return parse_dataobj_broadcast_network_info; |
| default: |
| return NULL; |
| } |
| } |
| |
| static void destroy_stk_item(gpointer pointer) |
| { |
| struct stk_item *item = pointer; |
| l_free(item->text); |
| l_free(item); |
| } |
| |
| static bool parse_item_list(struct comprehension_tlv_iter *iter, void *data) |
| { |
| struct l_queue **out = data; |
| uint16_t tag = STK_DATA_OBJECT_TYPE_ITEM; |
| struct comprehension_tlv_iter iter_old; |
| struct stk_item item; |
| struct l_queue *list = l_queue_new(); |
| unsigned int count = 0; |
| bool has_empty = false; |
| |
| do { |
| comprehension_tlv_iter_copy(iter, &iter_old); |
| memset(&item, 0, sizeof(item)); |
| count++; |
| |
| if (!parse_dataobj_item(iter, &item)) |
| continue; |
| |
| if (item.id == 0) { |
| has_empty = true; |
| continue; |
| } |
| |
| l_queue_push_tail(list, l_memdup(&item, sizeof(item))); |
| } while (comprehension_tlv_iter_next(iter) == TRUE && |
| comprehension_tlv_iter_get_tag(iter) == tag); |
| |
| comprehension_tlv_iter_copy(&iter_old, iter); |
| |
| if (!has_empty || count == 1) { |
| *out = list; |
| return true; |
| } |
| |
| l_queue_destroy(list, destroy_stk_item); |
| return false; |
| } |
| |
| static bool parse_provisioning_list(struct comprehension_tlv_iter *iter, |
| void *data) |
| { |
| struct l_queue **out = data; |
| uint16_t tag = STK_DATA_OBJECT_TYPE_PROVISIONING_FILE_REF; |
| struct comprehension_tlv_iter iter_old; |
| struct stk_file file; |
| struct l_queue *list = l_queue_new(); |
| |
| do { |
| comprehension_tlv_iter_copy(iter, &iter_old); |
| memset(&file, 0, sizeof(file)); |
| |
| if (!parse_dataobj_provisioning_file_reference(iter, &file)) |
| continue; |
| |
| l_queue_push_tail(list, l_memdup(&file, sizeof(file))); |
| } while (comprehension_tlv_iter_next(iter) == TRUE && |
| comprehension_tlv_iter_get_tag(iter) == tag); |
| |
| comprehension_tlv_iter_copy(&iter_old, iter); |
| *out = list; |
| |
| return true; |
| } |
| |
| static dataobj_handler list_handler_for_type(enum stk_data_object_type type) |
| { |
| switch (type) { |
| case STK_DATA_OBJECT_TYPE_ITEM: |
| return parse_item_list; |
| case STK_DATA_OBJECT_TYPE_PROVISIONING_FILE_REF: |
| return parse_provisioning_list; |
| default: |
| return NULL; |
| } |
| } |
| |
| struct dataobj_handler_entry { |
| enum stk_data_object_type type; |
| int flags; |
| void *data; |
| }; |
| |
| static enum stk_command_parse_result parse_dataobj( |
| struct comprehension_tlv_iter *iter, |
| enum stk_data_object_type type, ...) |
| { |
| GSList *entries = NULL; |
| GSList *l; |
| va_list args; |
| bool minimum_set = true; |
| bool parse_error = false; |
| |
| va_start(args, type); |
| |
| while (type != STK_DATA_OBJECT_TYPE_INVALID) { |
| struct dataobj_handler_entry *entry; |
| |
| entry = g_new0(struct dataobj_handler_entry, 1); |
| |
| entry->type = type; |
| entry->flags = va_arg(args, int); |
| entry->data = va_arg(args, void *); |
| |
| type = va_arg(args, enum stk_data_object_type); |
| entries = g_slist_prepend(entries, entry); |
| } |
| |
| va_end(args); |
| |
| entries = g_slist_reverse(entries); |
| |
| l = entries; |
| while (comprehension_tlv_iter_next(iter) == TRUE) { |
| dataobj_handler handler; |
| struct dataobj_handler_entry *entry; |
| GSList *l2; |
| |
| for (l2 = l; l2; l2 = l2->next) { |
| entry = l2->data; |
| |
| if (comprehension_tlv_iter_get_tag(iter) == entry->type) |
| break; |
| |
| /* Can't skip over mandatory objects */ |
| if (entry->flags & DATAOBJ_FLAG_MANDATORY) { |
| l2 = NULL; |
| break; |
| } |
| } |
| |
| if (l2 == NULL) { |
| if (comprehension_tlv_get_cr(iter) == TRUE) |
| parse_error = true; |
| |
| continue; |
| } |
| |
| if (entry->flags & DATAOBJ_FLAG_LIST) |
| handler = list_handler_for_type(entry->type); |
| else |
| handler = handler_for_type(entry->type); |
| |
| if (!handler(iter, entry->data)) |
| parse_error = true; |
| |
| l = l2->next; |
| } |
| |
| for (; l; l = l->next) { |
| struct dataobj_handler_entry *entry = l->data; |
| |
| if (entry->flags & DATAOBJ_FLAG_MANDATORY) |
| minimum_set = false; |
| } |
| |
| g_slist_free_full(entries, g_free); |
| |
| if (!minimum_set) |
| return STK_PARSE_RESULT_MISSING_VALUE; |
| if (parse_error) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| return STK_PARSE_RESULT_OK; |
| } |
| |
| static void destroy_display_text(struct stk_command *command) |
| { |
| l_free(command->display_text.text); |
| } |
| |
| static enum stk_command_parse_result parse_display_text( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_display_text *obj = &command->display_text; |
| enum stk_command_parse_result status; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_DISPLAY) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_display_text; |
| |
| status = parse_dataobj(iter, STK_DATA_OBJECT_TYPE_TEXT, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->text, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id, |
| STK_DATA_OBJECT_TYPE_IMMEDIATE_RESPONSE, 0, |
| &obj->immediate_response, |
| STK_DATA_OBJECT_TYPE_DURATION, 0, |
| &obj->duration, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| |
| CHECK_TEXT_AND_ICON(obj->text, obj->icon_id.id); |
| |
| return status; |
| } |
| |
| static void destroy_get_inkey(struct stk_command *command) |
| { |
| l_free(command->get_inkey.text); |
| } |
| |
| static enum stk_command_parse_result parse_get_inkey( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_get_inkey *obj = &command->get_inkey; |
| enum stk_command_parse_result status; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_get_inkey; |
| |
| status = parse_dataobj(iter, STK_DATA_OBJECT_TYPE_TEXT, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->text, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id, |
| STK_DATA_OBJECT_TYPE_DURATION, 0, |
| &obj->duration, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| |
| CHECK_TEXT_AND_ICON(obj->text, obj->icon_id.id); |
| |
| return status; |
| } |
| |
| static void destroy_get_input(struct stk_command *command) |
| { |
| l_free(command->get_input.text); |
| l_free(command->get_input.default_text); |
| } |
| |
| static enum stk_command_parse_result parse_get_input( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_get_input *obj = &command->get_input; |
| enum stk_command_parse_result status; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_get_input; |
| |
| status = parse_dataobj(iter, STK_DATA_OBJECT_TYPE_TEXT, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->text, |
| STK_DATA_OBJECT_TYPE_RESPONSE_LENGTH, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->resp_len, |
| STK_DATA_OBJECT_TYPE_DEFAULT_TEXT, 0, |
| &obj->default_text, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| |
| CHECK_TEXT_AND_ICON(obj->text, obj->icon_id.id); |
| |
| return status; |
| } |
| |
| static enum stk_command_parse_result parse_more_time( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| return STK_PARSE_RESULT_OK; |
| } |
| |
| static void destroy_play_tone(struct stk_command *command) |
| { |
| l_free(command->play_tone.alpha_id); |
| } |
| |
| static enum stk_command_parse_result parse_play_tone( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_play_tone *obj = &command->play_tone; |
| enum stk_command_parse_result status; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_EARPIECE) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_play_tone; |
| |
| status = parse_dataobj(iter, STK_DATA_OBJECT_TYPE_ALPHA_ID, 0, |
| &obj->alpha_id, |
| STK_DATA_OBJECT_TYPE_TONE, 0, |
| &obj->tone, |
| STK_DATA_OBJECT_TYPE_DURATION, 0, |
| &obj->duration, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| |
| CHECK_TEXT_AND_ICON(obj->alpha_id, obj->icon_id.id); |
| |
| return status; |
| } |
| |
| static enum stk_command_parse_result parse_poll_interval( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_poll_interval *obj = &command->poll_interval; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| return parse_dataobj(iter, STK_DATA_OBJECT_TYPE_DURATION, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->duration, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| } |
| |
| static void destroy_setup_menu(struct stk_command *command) |
| { |
| l_free(command->setup_menu.alpha_id); |
| l_queue_destroy(command->setup_menu.items, destroy_stk_item); |
| } |
| |
| static enum stk_command_parse_result parse_setup_menu( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_setup_menu *obj = &command->setup_menu; |
| enum stk_command_parse_result status; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_setup_menu; |
| |
| status = parse_dataobj(iter, |
| STK_DATA_OBJECT_TYPE_ALPHA_ID, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->alpha_id, |
| STK_DATA_OBJECT_TYPE_ITEM, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM | |
| DATAOBJ_FLAG_LIST, &obj->items, |
| STK_DATA_OBJECT_TYPE_ITEMS_NEXT_ACTION_INDICATOR, 0, |
| &obj->next_act, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id, |
| STK_DATA_OBJECT_TYPE_ITEM_ICON_ID_LIST, 0, |
| &obj->item_icon_id_list, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr, |
| STK_DATA_OBJECT_TYPE_ITEM_TEXT_ATTRIBUTE_LIST, 0, |
| &obj->item_text_attr_list, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| |
| CHECK_TEXT_AND_ICON(obj->alpha_id, obj->icon_id.id); |
| |
| return status; |
| } |
| |
| static void destroy_select_item(struct stk_command *command) |
| { |
| l_free(command->select_item.alpha_id); |
| l_queue_destroy(command->select_item.items, destroy_stk_item); |
| } |
| |
| static enum stk_command_parse_result parse_select_item( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_select_item *obj = &command->select_item; |
| enum stk_command_parse_result status; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| status = parse_dataobj(iter, |
| STK_DATA_OBJECT_TYPE_ALPHA_ID, 0, |
| &obj->alpha_id, |
| STK_DATA_OBJECT_TYPE_ITEM, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM | |
| DATAOBJ_FLAG_LIST, &obj->items, |
| STK_DATA_OBJECT_TYPE_ITEMS_NEXT_ACTION_INDICATOR, 0, |
| &obj->next_act, |
| STK_DATA_OBJECT_TYPE_ITEM_ID, 0, |
| &obj->item_id, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id, |
| STK_DATA_OBJECT_TYPE_ITEM_ICON_ID_LIST, 0, |
| &obj->item_icon_id_list, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr, |
| STK_DATA_OBJECT_TYPE_ITEM_TEXT_ATTRIBUTE_LIST, 0, |
| &obj->item_text_attr_list, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| |
| command->destructor = destroy_select_item; |
| |
| if (status == STK_PARSE_RESULT_OK && l_queue_isempty(obj->items)) |
| status = STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| CHECK_TEXT_AND_ICON(obj->alpha_id, obj->icon_id.id); |
| |
| return status; |
| } |
| |
| static void destroy_send_sms(struct stk_command *command) |
| { |
| l_free(command->send_sms.alpha_id); |
| l_free(command->send_sms.cdma_sms.array); |
| } |
| |
| static enum stk_command_parse_result parse_send_sms( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_send_sms *obj = &command->send_sms; |
| enum stk_command_parse_result status; |
| struct gsm_sms_tpdu gsm_tpdu; |
| struct stk_address sc_address = { 0, NULL }; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_NETWORK) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| memset(&gsm_tpdu, 0, sizeof(gsm_tpdu)); |
| status = parse_dataobj(iter, STK_DATA_OBJECT_TYPE_ALPHA_ID, 0, |
| &obj->alpha_id, |
| STK_DATA_OBJECT_TYPE_ADDRESS, 0, |
| &sc_address, |
| STK_DATA_OBJECT_TYPE_GSM_SMS_TPDU, 0, |
| &gsm_tpdu, |
| STK_DATA_OBJECT_TYPE_CDMA_SMS_TPDU, 0, |
| &obj->cdma_sms, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| |
| command->destructor = destroy_send_sms; |
| |
| if (status != STK_PARSE_RESULT_OK) |
| goto out; |
| |
| CHECK_TEXT_AND_ICON(obj->alpha_id, obj->icon_id.id); |
| |
| if (status != STK_PARSE_RESULT_OK) |
| goto out; |
| |
| if (gsm_tpdu.len == 0 && obj->cdma_sms.len == 0) { |
| status = STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| goto out; |
| } |
| |
| if (gsm_tpdu.len > 0 && obj->cdma_sms.len > 0) { |
| status = STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| goto out; |
| } |
| |
| /* We don't process CDMA pdus for now */ |
| if (obj->cdma_sms.len > 0) |
| goto out; |
| |
| /* packing is needed */ |
| if (command->qualifier & 0x01) { |
| if (!sms_decode_unpacked_stk_pdu(gsm_tpdu.tpdu, gsm_tpdu.len, |
| &obj->gsm_sms)) { |
| status = STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| goto out; |
| } |
| |
| goto set_addr; |
| } |
| |
| if (sms_decode(gsm_tpdu.tpdu, gsm_tpdu.len, TRUE, |
| gsm_tpdu.len, &obj->gsm_sms) == FALSE) { |
| status = STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| goto out; |
| } |
| |
| if (obj->gsm_sms.type != SMS_TYPE_SUBMIT && |
| obj->gsm_sms.type != SMS_TYPE_COMMAND) { |
| status = STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| goto out; |
| } |
| |
| set_addr: |
| if (sc_address.number == NULL) |
| goto out; |
| |
| if (strlen(sc_address.number) > 20) { |
| status = STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| goto out; |
| } |
| |
| strcpy(obj->gsm_sms.sc_addr.address, sc_address.number); |
| obj->gsm_sms.sc_addr.numbering_plan = sc_address.ton_npi & 15; |
| obj->gsm_sms.sc_addr.number_type = (sc_address.ton_npi >> 4) & 7; |
| |
| out: |
| l_free(sc_address.number); |
| |
| return status; |
| } |
| |
| static void destroy_send_ss(struct stk_command *command) |
| { |
| l_free(command->send_ss.alpha_id); |
| l_free(command->send_ss.ss.ss); |
| } |
| |
| static enum stk_command_parse_result parse_send_ss(struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_send_ss *obj = &command->send_ss; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_NETWORK) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_send_ss; |
| |
| return parse_dataobj(iter, STK_DATA_OBJECT_TYPE_ALPHA_ID, 0, |
| &obj->alpha_id, |
| STK_DATA_OBJECT_TYPE_SS_STRING, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->ss, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| } |
| |
| static void destroy_send_ussd(struct stk_command *command) |
| { |
| l_free(command->send_ussd.alpha_id); |
| } |
| |
| static enum stk_command_parse_result parse_send_ussd( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_send_ussd *obj = &command->send_ussd; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_NETWORK) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_send_ussd; |
| |
| return parse_dataobj(iter, STK_DATA_OBJECT_TYPE_ALPHA_ID, 0, |
| &obj->alpha_id, |
| STK_DATA_OBJECT_TYPE_USSD_STRING, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->ussd_string, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| } |
| |
| static void destroy_setup_call(struct stk_command *command) |
| { |
| l_free(command->setup_call.alpha_id_usr_cfm); |
| l_free(command->setup_call.addr.number); |
| l_free(command->setup_call.alpha_id_call_setup); |
| } |
| |
| static enum stk_command_parse_result parse_setup_call( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_setup_call *obj = &command->setup_call; |
| enum stk_command_parse_result status; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_NETWORK) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_setup_call; |
| |
| status = parse_dataobj(iter, STK_DATA_OBJECT_TYPE_ALPHA_ID, 0, |
| &obj->alpha_id_usr_cfm, |
| STK_DATA_OBJECT_TYPE_ADDRESS, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->addr, |
| STK_DATA_OBJECT_TYPE_CCP, 0, |
| &obj->ccp, |
| STK_DATA_OBJECT_TYPE_SUBADDRESS, 0, |
| &obj->subaddr, |
| STK_DATA_OBJECT_TYPE_DURATION, 0, |
| &obj->duration, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id_usr_cfm, |
| STK_DATA_OBJECT_TYPE_ALPHA_ID, 0, |
| &obj->alpha_id_call_setup, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id_call_setup, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr_usr_cfm, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr_call_setup, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| |
| CHECK_TEXT_AND_ICON(obj->alpha_id_usr_cfm, obj->icon_id_usr_cfm.id); |
| CHECK_TEXT_AND_ICON(obj->alpha_id_call_setup, |
| obj->icon_id_call_setup.id); |
| |
| return status; |
| } |
| |
| static void destroy_refresh(struct stk_command *command) |
| { |
| l_queue_destroy(command->refresh.file_list, l_free); |
| l_free(command->refresh.alpha_id); |
| } |
| |
| static enum stk_command_parse_result parse_refresh( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_refresh *obj = &command->refresh; |
| enum stk_command_parse_result status; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_refresh; |
| |
| status = parse_dataobj(iter, STK_DATA_OBJECT_TYPE_FILE_LIST, 0, |
| &obj->file_list, |
| STK_DATA_OBJECT_TYPE_AID, 0, |
| &obj->aid, |
| STK_DATA_OBJECT_TYPE_ALPHA_ID, 0, |
| &obj->alpha_id, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| |
| CHECK_TEXT_AND_ICON(obj->alpha_id, obj->icon_id.id); |
| |
| return status; |
| } |
| |
| static enum stk_command_parse_result parse_polling_off( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| return STK_PARSE_RESULT_OK; |
| } |
| |
| static enum stk_command_parse_result parse_provide_local_info( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| return STK_PARSE_RESULT_OK; |
| } |
| |
| static enum stk_command_parse_result parse_setup_event_list( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_setup_event_list *obj = &command->setup_event_list; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| return parse_dataobj(iter, STK_DATA_OBJECT_TYPE_EVENT_LIST, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->event_list, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| } |
| |
| static enum stk_command_parse_result parse_perform_card_apdu( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_perform_card_apdu *obj = &command->perform_card_apdu; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if ((command->dst < STK_DEVICE_IDENTITY_TYPE_CARD_READER_0) || |
| (command->dst > STK_DEVICE_IDENTITY_TYPE_CARD_READER_7)) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| return parse_dataobj(iter, STK_DATA_OBJECT_TYPE_C_APDU, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->c_apdu, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| } |
| |
| static enum stk_command_parse_result parse_power_off_card( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if ((command->dst < STK_DEVICE_IDENTITY_TYPE_CARD_READER_0) || |
| (command->dst > STK_DEVICE_IDENTITY_TYPE_CARD_READER_7)) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| return STK_PARSE_RESULT_OK; |
| } |
| |
| static enum stk_command_parse_result parse_power_on_card( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if ((command->dst < STK_DEVICE_IDENTITY_TYPE_CARD_READER_0) || |
| (command->dst > STK_DEVICE_IDENTITY_TYPE_CARD_READER_7)) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| return STK_PARSE_RESULT_OK; |
| } |
| |
| static enum stk_command_parse_result parse_get_reader_status( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| switch (command->qualifier) { |
| case STK_QUALIFIER_TYPE_CARD_READER_STATUS: |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| break; |
| case STK_QUALIFIER_TYPE_CARD_READER_ID: |
| if ((command->dst < STK_DEVICE_IDENTITY_TYPE_CARD_READER_0) || |
| (command->dst > |
| STK_DEVICE_IDENTITY_TYPE_CARD_READER_7)) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| break; |
| default: |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| } |
| |
| return STK_PARSE_RESULT_OK; |
| } |
| |
| static enum stk_command_parse_result parse_timer_mgmt( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_timer_mgmt *obj = &command->timer_mgmt; |
| enum stk_data_object_flag value_flags = 0; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if ((command->qualifier & 3) == 0) /* Start a timer */ |
| value_flags = DATAOBJ_FLAG_MANDATORY; |
| |
| return parse_dataobj(iter, STK_DATA_OBJECT_TYPE_TIMER_ID, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->timer_id, |
| STK_DATA_OBJECT_TYPE_TIMER_VALUE, value_flags, |
| &obj->timer_value, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| } |
| |
| static void destroy_setup_idle_mode_text(struct stk_command *command) |
| { |
| l_free(command->setup_idle_mode_text.text); |
| } |
| |
| static enum stk_command_parse_result parse_setup_idle_mode_text( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_setup_idle_mode_text *obj = |
| &command->setup_idle_mode_text; |
| enum stk_command_parse_result status; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_setup_idle_mode_text; |
| |
| status = parse_dataobj(iter, STK_DATA_OBJECT_TYPE_TEXT, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->text, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| |
| CHECK_TEXT_AND_ICON(obj->text, obj->icon_id.id); |
| |
| return status; |
| } |
| |
| static void destroy_run_at_command(struct stk_command *command) |
| { |
| l_free(command->run_at_command.alpha_id); |
| l_free(command->run_at_command.at_command); |
| } |
| |
| static enum stk_command_parse_result parse_run_at_command( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_run_at_command *obj = &command->run_at_command; |
| enum stk_command_parse_result status; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_run_at_command; |
| |
| status = parse_dataobj(iter, STK_DATA_OBJECT_TYPE_ALPHA_ID, 0, |
| &obj->alpha_id, |
| STK_DATA_OBJECT_TYPE_AT_COMMAND, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->at_command, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| |
| CHECK_TEXT_AND_ICON(obj->alpha_id, obj->icon_id.id); |
| |
| return status; |
| } |
| |
| static void destroy_send_dtmf(struct stk_command *command) |
| { |
| l_free(command->send_dtmf.alpha_id); |
| l_free(command->send_dtmf.dtmf); |
| } |
| |
| static enum stk_command_parse_result parse_send_dtmf( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_send_dtmf *obj = &command->send_dtmf; |
| enum stk_command_parse_result status; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_NETWORK) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_send_dtmf; |
| |
| status = parse_dataobj(iter, STK_DATA_OBJECT_TYPE_ALPHA_ID, 0, |
| &obj->alpha_id, |
| STK_DATA_OBJECT_TYPE_DTMF_STRING, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->dtmf, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| |
| CHECK_TEXT_AND_ICON(obj->alpha_id, obj->icon_id.id); |
| |
| return status; |
| } |
| |
| static enum stk_command_parse_result parse_language_notification( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_language_notification *obj = |
| &command->language_notification; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| return parse_dataobj(iter, STK_DATA_OBJECT_TYPE_LANGUAGE, 0, |
| &obj->language, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| } |
| |
| static void destroy_launch_browser(struct stk_command *command) |
| { |
| l_free(command->launch_browser.url); |
| l_free(command->launch_browser.bearer.array); |
| l_queue_destroy(command->launch_browser.prov_file_refs, l_free); |
| l_free(command->launch_browser.text_gateway_proxy_id); |
| l_free(command->launch_browser.alpha_id); |
| l_free(command->launch_browser.network_name.array); |
| l_free(command->launch_browser.text_usr); |
| l_free(command->launch_browser.text_passwd); |
| } |
| |
| static enum stk_command_parse_result parse_launch_browser( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_launch_browser *obj = &command->launch_browser; |
| |
| if (command->qualifier > 3 || command->qualifier == 1) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_launch_browser; |
| |
| return parse_dataobj(iter, |
| STK_DATA_OBJECT_TYPE_BROWSER_ID, 0, |
| &obj->browser_id, |
| STK_DATA_OBJECT_TYPE_URL, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->url, |
| STK_DATA_OBJECT_TYPE_BEARER, 0, |
| &obj->bearer, |
| STK_DATA_OBJECT_TYPE_PROVISIONING_FILE_REF, |
| DATAOBJ_FLAG_LIST, |
| &obj->prov_file_refs, |
| STK_DATA_OBJECT_TYPE_TEXT, 0, |
| &obj->text_gateway_proxy_id, |
| STK_DATA_OBJECT_TYPE_ALPHA_ID, 0, |
| &obj->alpha_id, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_NETWORK_ACCESS_NAME, 0, |
| &obj->network_name, |
| STK_DATA_OBJECT_TYPE_TEXT, 0, |
| &obj->text_usr, |
| STK_DATA_OBJECT_TYPE_TEXT, 0, |
| &obj->text_passwd, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| } |
| |
| static void destroy_open_channel(struct stk_command *command) |
| { |
| l_free(command->open_channel.alpha_id); |
| l_free(command->open_channel.apn); |
| l_free(command->open_channel.text_usr); |
| l_free(command->open_channel.text_passwd); |
| } |
| |
| static enum stk_command_parse_result parse_open_channel( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_open_channel *obj = &command->open_channel; |
| enum stk_command_parse_result status; |
| |
| if (command->qualifier >= 0x08) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_open_channel; |
| |
| /* |
| * parse the Open Channel data objects related to packet data service |
| * bearer |
| */ |
| status = parse_dataobj(iter, |
| STK_DATA_OBJECT_TYPE_ALPHA_ID, 0, |
| &obj->alpha_id, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id, |
| STK_DATA_OBJECT_TYPE_BEARER_DESCRIPTION, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->bearer_desc, |
| STK_DATA_OBJECT_TYPE_BUFFER_SIZE, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->buf_size, |
| STK_DATA_OBJECT_TYPE_NETWORK_ACCESS_NAME, 0, |
| &obj->apn, |
| STK_DATA_OBJECT_TYPE_OTHER_ADDRESS, 0, |
| &obj->local_addr, |
| STK_DATA_OBJECT_TYPE_TEXT, 0, |
| &obj->text_usr, |
| STK_DATA_OBJECT_TYPE_TEXT, 0, |
| &obj->text_passwd, |
| STK_DATA_OBJECT_TYPE_UICC_TE_INTERFACE, 0, |
| &obj->uti, |
| STK_DATA_OBJECT_TYPE_OTHER_ADDRESS, 0, |
| &obj->data_dest_addr, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| |
| CHECK_TEXT_AND_ICON(obj->alpha_id, obj->icon_id.id); |
| |
| return status; |
| } |
| |
| static void destroy_close_channel(struct stk_command *command) |
| { |
| l_free(command->close_channel.alpha_id); |
| } |
| |
| static enum stk_command_parse_result parse_close_channel( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_close_channel *obj = &command->close_channel; |
| enum stk_command_parse_result status; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if ((command->dst < STK_DEVICE_IDENTITY_TYPE_CHANNEL_1) || |
| (command->dst > STK_DEVICE_IDENTITY_TYPE_CHANNEL_7)) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_close_channel; |
| |
| status = parse_dataobj(iter, STK_DATA_OBJECT_TYPE_ALPHA_ID, 0, |
| &obj->alpha_id, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| |
| CHECK_TEXT_AND_ICON(obj->alpha_id, obj->icon_id.id); |
| |
| return status; |
| } |
| |
| static void destroy_receive_data(struct stk_command *command) |
| { |
| l_free(command->receive_data.alpha_id); |
| } |
| |
| static enum stk_command_parse_result parse_receive_data( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_receive_data *obj = &command->receive_data; |
| enum stk_command_parse_result status; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if ((command->dst < STK_DEVICE_IDENTITY_TYPE_CHANNEL_1) || |
| (command->dst > STK_DEVICE_IDENTITY_TYPE_CHANNEL_7)) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_receive_data; |
| |
| status = parse_dataobj(iter, STK_DATA_OBJECT_TYPE_ALPHA_ID, 0, |
| &obj->alpha_id, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id, |
| STK_DATA_OBJECT_TYPE_CHANNEL_DATA_LENGTH, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->data_len, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| |
| CHECK_TEXT_AND_ICON(obj->alpha_id, obj->icon_id.id); |
| |
| return status; |
| } |
| |
| static void destroy_send_data(struct stk_command *command) |
| { |
| l_free(command->send_data.alpha_id); |
| l_free(command->send_data.data.array); |
| } |
| |
| static enum stk_command_parse_result parse_send_data( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_send_data *obj = &command->send_data; |
| enum stk_command_parse_result status; |
| |
| if (command->qualifier > STK_SEND_DATA_IMMEDIATELY) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if ((command->dst < STK_DEVICE_IDENTITY_TYPE_CHANNEL_1) || |
| (command->dst > STK_DEVICE_IDENTITY_TYPE_CHANNEL_7)) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_send_data; |
| |
| status = parse_dataobj(iter, STK_DATA_OBJECT_TYPE_ALPHA_ID, 0, |
| &obj->alpha_id, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id, |
| STK_DATA_OBJECT_TYPE_CHANNEL_DATA, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->data, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| |
| CHECK_TEXT_AND_ICON(obj->alpha_id, obj->icon_id.id); |
| |
| return status; |
| } |
| |
| static enum stk_command_parse_result parse_get_channel_status( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| return STK_PARSE_RESULT_OK; |
| } |
| |
| static void destroy_service_search(struct stk_command *command) |
| { |
| l_free(command->service_search.alpha_id); |
| l_free(command->service_search.serv_search.ser_search); |
| l_free(command->service_search.dev_filter.dev_filter); |
| } |
| |
| static enum stk_command_parse_result parse_service_search( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_service_search *obj = &command->service_search; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_service_search; |
| |
| return parse_dataobj(iter, STK_DATA_OBJECT_TYPE_ALPHA_ID, 0, |
| &obj->alpha_id, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id, |
| STK_DATA_OBJECT_TYPE_SERVICE_SEARCH, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->serv_search, |
| STK_DATA_OBJECT_TYPE_DEVICE_FILTER, 0, |
| &obj->dev_filter, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| } |
| |
| static void destroy_get_service_info(struct stk_command *command) |
| { |
| l_free(command->get_service_info.alpha_id); |
| l_free(command->get_service_info.attr_info.attr_info); |
| } |
| |
| static enum stk_command_parse_result parse_get_service_info( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_get_service_info *obj = &command->get_service_info; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_get_service_info; |
| |
| return parse_dataobj(iter, STK_DATA_OBJECT_TYPE_ALPHA_ID, 0, |
| &obj->alpha_id, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id, |
| STK_DATA_OBJECT_TYPE_ATTRIBUTE_INFO, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->attr_info, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| } |
| |
| static void destroy_declare_service(struct stk_command *command) |
| { |
| l_free(command->declare_service.serv_rec.serv_rec); |
| } |
| |
| static enum stk_command_parse_result parse_declare_service( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_declare_service *obj = &command->declare_service; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_declare_service; |
| |
| return parse_dataobj(iter, STK_DATA_OBJECT_TYPE_SERVICE_RECORD, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->serv_rec, |
| STK_DATA_OBJECT_TYPE_UICC_TE_INTERFACE, 0, |
| &obj->intf, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| } |
| |
| static enum stk_command_parse_result parse_set_frames( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_set_frames *obj = &command->set_frames; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| return parse_dataobj(iter, STK_DATA_OBJECT_TYPE_FRAME_ID, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_FRAME_LAYOUT, 0, |
| &obj->frame_layout, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id_default, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| } |
| |
| static enum stk_command_parse_result parse_get_frames_status( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| return STK_PARSE_RESULT_OK; |
| } |
| |
| static void destroy_retrieve_mms(struct stk_command *command) |
| { |
| l_free(command->retrieve_mms.alpha_id); |
| l_queue_destroy(command->retrieve_mms.mms_rec_files, l_free); |
| } |
| |
| static enum stk_command_parse_result parse_retrieve_mms( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_retrieve_mms *obj = &command->retrieve_mms; |
| enum stk_command_parse_result status; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_NETWORK) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_retrieve_mms; |
| |
| status = parse_dataobj(iter, STK_DATA_OBJECT_TYPE_ALPHA_ID, 0, |
| &obj->alpha_id, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id, |
| STK_DATA_OBJECT_TYPE_MMS_REFERENCE, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->mms_ref, |
| STK_DATA_OBJECT_TYPE_FILE_LIST, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->mms_rec_files, |
| STK_DATA_OBJECT_TYPE_MMS_CONTENT_ID, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->mms_content_id, |
| STK_DATA_OBJECT_TYPE_MMS_ID, 0, |
| &obj->mms_id, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| |
| CHECK_TEXT_AND_ICON(obj->alpha_id, obj->icon_id.id); |
| |
| return status; |
| } |
| |
| static void destroy_submit_mms(struct stk_command *command) |
| { |
| l_free(command->submit_mms.alpha_id); |
| l_queue_destroy(command->submit_mms.mms_subm_files, l_free); |
| } |
| |
| static enum stk_command_parse_result parse_submit_mms( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_submit_mms *obj = &command->submit_mms; |
| enum stk_command_parse_result status; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_NETWORK) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_submit_mms; |
| |
| status = parse_dataobj(iter, STK_DATA_OBJECT_TYPE_ALPHA_ID, 0, |
| &obj->alpha_id, |
| STK_DATA_OBJECT_TYPE_ICON_ID, 0, |
| &obj->icon_id, |
| STK_DATA_OBJECT_TYPE_FILE_LIST, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->mms_subm_files, |
| STK_DATA_OBJECT_TYPE_MMS_ID, 0, |
| &obj->mms_id, |
| STK_DATA_OBJECT_TYPE_TEXT_ATTRIBUTE, 0, |
| &obj->text_attr, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| |
| CHECK_TEXT_AND_ICON(obj->alpha_id, obj->icon_id.id); |
| |
| return status; |
| } |
| |
| static void destroy_display_mms(struct stk_command *command) |
| { |
| l_queue_destroy(command->display_mms.mms_subm_files, l_free); |
| } |
| |
| static enum stk_command_parse_result parse_display_mms( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_display_mms *obj = &command->display_mms; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| command->destructor = destroy_display_mms; |
| |
| return parse_dataobj(iter, STK_DATA_OBJECT_TYPE_FILE_LIST, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->mms_subm_files, |
| STK_DATA_OBJECT_TYPE_MMS_ID, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->mms_id, |
| STK_DATA_OBJECT_TYPE_IMMEDIATE_RESPONSE, 0, |
| &obj->imd_resp, |
| STK_DATA_OBJECT_TYPE_FRAME_ID, 0, |
| &obj->frame_id, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| } |
| |
| static enum stk_command_parse_result parse_activate( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| struct stk_command_activate *obj = &command->activate; |
| |
| if (command->src != STK_DEVICE_IDENTITY_TYPE_UICC) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| if (command->dst != STK_DEVICE_IDENTITY_TYPE_TERMINAL) |
| return STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| |
| return parse_dataobj(iter, STK_DATA_OBJECT_TYPE_ACTIVATE_DESCRIPTOR, |
| DATAOBJ_FLAG_MANDATORY | DATAOBJ_FLAG_MINIMUM, |
| &obj->actv_desc, |
| STK_DATA_OBJECT_TYPE_INVALID); |
| } |
| |
| static enum stk_command_parse_result parse_command_body( |
| struct stk_command *command, |
| struct comprehension_tlv_iter *iter) |
| { |
| switch (command->type) { |
| case STK_COMMAND_TYPE_DISPLAY_TEXT: |
| return parse_display_text(command, iter); |
| case STK_COMMAND_TYPE_GET_INKEY: |
| return parse_get_inkey(command, iter); |
| case STK_COMMAND_TYPE_GET_INPUT: |
| return parse_get_input(command, iter); |
| case STK_COMMAND_TYPE_MORE_TIME: |
| return parse_more_time(command, iter); |
| case STK_COMMAND_TYPE_PLAY_TONE: |
| return parse_play_tone(command, iter); |
| case STK_COMMAND_TYPE_POLL_INTERVAL: |
| return parse_poll_interval(command, iter); |
| case STK_COMMAND_TYPE_SETUP_MENU: |
| return parse_setup_menu(command, iter); |
| case STK_COMMAND_TYPE_SELECT_ITEM: |
| return parse_select_item(command, iter); |
| case STK_COMMAND_TYPE_SEND_SMS: |
| return parse_send_sms(command, iter); |
| case STK_COMMAND_TYPE_SEND_SS: |
| return parse_send_ss(command, iter); |
| case STK_COMMAND_TYPE_SEND_USSD: |
| return parse_send_ussd(command, iter); |
| case STK_COMMAND_TYPE_SETUP_CALL: |
| return parse_setup_call(command, iter); |
| case STK_COMMAND_TYPE_REFRESH: |
| return parse_refresh(command, iter); |
| case STK_COMMAND_TYPE_POLLING_OFF: |
| return parse_polling_off(command, iter); |
| case STK_COMMAND_TYPE_PROVIDE_LOCAL_INFO: |
| return parse_provide_local_info(command, iter); |
| case STK_COMMAND_TYPE_SETUP_EVENT_LIST: |
| return parse_setup_event_list(command, iter); |
| case STK_COMMAND_TYPE_PERFORM_CARD_APDU: |
| return parse_perform_card_apdu(command, iter); |
| case STK_COMMAND_TYPE_POWER_OFF_CARD: |
| return parse_power_off_card(command, iter); |
| case STK_COMMAND_TYPE_POWER_ON_CARD: |
| return parse_power_on_card(command, iter); |
| case STK_COMMAND_TYPE_GET_READER_STATUS: |
| return parse_get_reader_status(command, iter); |
| case STK_COMMAND_TYPE_TIMER_MANAGEMENT: |
| return parse_timer_mgmt(command, iter); |
| case STK_COMMAND_TYPE_SETUP_IDLE_MODE_TEXT: |
| return parse_setup_idle_mode_text(command, iter); |
| case STK_COMMAND_TYPE_RUN_AT_COMMAND: |
| return parse_run_at_command(command, iter); |
| case STK_COMMAND_TYPE_SEND_DTMF: |
| return parse_send_dtmf(command, iter); |
| case STK_COMMAND_TYPE_LANGUAGE_NOTIFICATION: |
| return parse_language_notification(command, iter); |
| case STK_COMMAND_TYPE_LAUNCH_BROWSER: |
| return parse_launch_browser(command, iter); |
| case STK_COMMAND_TYPE_OPEN_CHANNEL: |
| return parse_open_channel(command, iter); |
| case STK_COMMAND_TYPE_CLOSE_CHANNEL: |
| return parse_close_channel(command, iter); |
| case STK_COMMAND_TYPE_RECEIVE_DATA: |
| return parse_receive_data(command, iter); |
| case STK_COMMAND_TYPE_SEND_DATA: |
| return parse_send_data(command, iter); |
| case STK_COMMAND_TYPE_GET_CHANNEL_STATUS: |
| return parse_get_channel_status(command, iter); |
| case STK_COMMAND_TYPE_SERVICE_SEARCH: |
| return parse_service_search(command, iter); |
| case STK_COMMAND_TYPE_GET_SERVICE_INFO: |
| return parse_get_service_info(command, iter); |
| case STK_COMMAND_TYPE_DECLARE_SERVICE: |
| return parse_declare_service(command, iter); |
| case STK_COMMAND_TYPE_SET_FRAMES: |
| return parse_set_frames(command, iter); |
| case STK_COMMAND_TYPE_GET_FRAMES_STATUS: |
| return parse_get_frames_status(command, iter); |
| case STK_COMMAND_TYPE_RETRIEVE_MMS: |
| return parse_retrieve_mms(command, iter); |
| case STK_COMMAND_TYPE_SUBMIT_MMS: |
| return parse_submit_mms(command, iter); |
| case STK_COMMAND_TYPE_DISPLAY_MMS: |
| return parse_display_mms(command, iter); |
| case STK_COMMAND_TYPE_ACTIVATE: |
| return parse_activate(command, iter); |
| default: |
| return STK_PARSE_RESULT_TYPE_NOT_UNDERSTOOD; |
| }; |
| } |
| |
| struct stk_command *stk_command_new_from_pdu(const uint8_t *pdu, |
| unsigned int len) |
| { |
| struct ber_tlv_iter ber; |
| struct comprehension_tlv_iter iter; |
| const uint8_t *data; |
| struct stk_command *command; |
| |
| ber_tlv_iter_init(&ber, pdu, len); |
| |
| if (ber_tlv_iter_next(&ber) != TRUE) |
| return NULL; |
| |
| /* We should be wrapped in a Proactive UICC Command Tag 0xD0 */ |
| if (ber_tlv_iter_get_short_tag(&ber) != 0xD0) |
| return NULL; |
| |
| ber_tlv_iter_recurse_comprehension(&ber, &iter); |
| |
| /* |
| * Now parse actual command details, they come in order with |
| * Command Details TLV first, followed by Device Identities TLV |
| */ |
| if (comprehension_tlv_iter_next(&iter) != TRUE) |
| return NULL; |
| |
| if (comprehension_tlv_iter_get_tag(&iter) != |
| STK_DATA_OBJECT_TYPE_COMMAND_DETAILS) |
| return NULL; |
| |
| if (comprehension_tlv_iter_get_length(&iter) != 0x03) |
| return NULL; |
| |
| data = comprehension_tlv_iter_get_data(&iter); |
| |
| command = g_new0(struct stk_command, 1); |
| |
| command->number = data[0]; |
| command->type = data[1]; |
| command->qualifier = data[2]; |
| |
| if (comprehension_tlv_iter_next(&iter) != TRUE) { |
| command->status = STK_PARSE_RESULT_MISSING_VALUE; |
| goto out; |
| } |
| |
| if (comprehension_tlv_iter_get_tag(&iter) != |
| STK_DATA_OBJECT_TYPE_DEVICE_IDENTITIES) { |
| command->status = STK_PARSE_RESULT_MISSING_VALUE; |
| goto out; |
| } |
| |
| if (comprehension_tlv_iter_get_length(&iter) != 0x02) { |
| command->status = STK_PARSE_RESULT_DATA_NOT_UNDERSTOOD; |
| goto out; |
| } |
| |
| data = comprehension_tlv_iter_get_data(&iter); |
| |
| command->src = data[0]; |
| command->dst = data[1]; |
| |
| command->status = parse_command_body(command, &iter); |
| |
| out: |
| return command; |
| } |
| |
| void stk_command_free(struct stk_command *command) |
| { |
| if (command->destructor) |
| command->destructor(command); |
| |
| g_free(command); |
| } |
| |
| static bool stk_tlv_builder_init(struct stk_tlv_builder *iter, |
| uint8_t *pdu, unsigned int size) |
| { |
| iter->value = NULL; |
| iter->len = 0; |
| |
| return comprehension_tlv_builder_init(&iter->ctlv, pdu, size); |
| } |
| |
| static bool stk_tlv_builder_recurse(struct stk_tlv_builder *iter, |
| struct ber_tlv_builder *btlv, |
| uint8_t tag) |
| { |
| iter->value = NULL; |
| iter->len = 0; |
| |
| if (ber_tlv_builder_next(btlv, tag >> 6, (tag >> 5) & 1, |
| tag & 0x1f) != TRUE) |
| return false; |
| |
| return ber_tlv_builder_recurse_comprehension(btlv, &iter->ctlv); |
| } |
| |
| static bool stk_tlv_builder_open_container(struct stk_tlv_builder *iter, |
| bool cr, |
| uint8_t shorttag, |
| bool relocatable) |
| { |
| if (comprehension_tlv_builder_next(&iter->ctlv, cr, shorttag) != TRUE) |
| return false; |
| |
| iter->len = 0; |
| iter->max_len = relocatable ? 0xff : 0x7f; |
| if (comprehension_tlv_builder_set_length(&iter->ctlv, iter->max_len) != |
| TRUE) |
| return false; |
| |
| iter->value = comprehension_tlv_builder_get_data(&iter->ctlv); |
| |
| return true; |
| } |
| |
| static bool stk_tlv_builder_close_container(struct stk_tlv_builder *iter) |
| { |
| return comprehension_tlv_builder_set_length(&iter->ctlv, iter->len); |
| } |
| |
| static unsigned int stk_tlv_builder_get_length(struct stk_tlv_builder *iter) |
| { |
| return comprehension_tlv_builder_get_data(&iter->ctlv) - |
| iter->ctlv.pdu + iter->len; |
| } |
| |
| static bool stk_tlv_builder_append_byte(struct stk_tlv_builder *iter, |
| uint8_t num) |
| { |
| if (iter->len >= iter->max_len) |
| return false; |
| |
| iter->value[iter->len++] = num; |
| return true; |
| } |
| |
| static bool stk_tlv_builder_append_short(struct stk_tlv_builder *iter, |
| uint16_t num) |
| { |
| if (iter->len + 2 > iter->max_len) |
| return false; |
| |
| iter->value[iter->len++] = num >> 8; |
| iter->value[iter->len++] = num & 0xff; |
| return true; |
| } |
| |
| static bool stk_tlv_builder_append_gsm_packed(struct stk_tlv_builder *iter, |
| const char *text) |
| { |
| unsigned int len; |
| uint8_t *gsm; |
| long written = 0; |
| |
| if (text == NULL) |
| return true; |
| |
| len = strlen(text); |
| |
| gsm = convert_utf8_to_gsm(text, len, NULL, &written, 0); |
| if (gsm == NULL && len > 0) |
| return false; |
| |
| if (iter->len + (written * 7 + 7) / 8 >= iter->max_len) { |
| l_free(gsm); |
| return false; |
| } |
| |
| pack_7bit_own_buf(gsm, len, 0, false, &written, 0, |
| iter->value + iter->len + 1); |
| l_free(gsm); |
| |
| if (written < 1 && len > 0) |
| return false; |
| |
| iter->value[iter->len++] = 0x00; |
| iter->len += written; |
| |
| return true; |
| } |
| |
| static bool stk_tlv_builder_append_gsm_unpacked(struct stk_tlv_builder *iter, |
| const char *text) |
| { |
| unsigned int len; |
| uint8_t *gsm; |
| long written = 0; |
| |
| if (text == NULL) |
| return true; |
| |
| len = strlen(text); |
| |
| gsm = convert_utf8_to_gsm(text, len, NULL, &written, 0); |
| if (gsm == NULL && len > 0) |
| return false; |
| |
| if (iter->len + written >= iter->max_len) { |
| l_free(gsm); |
| return false; |
| } |
| |
| iter->value[iter->len++] = 0x04; |
| memcpy(iter->value + iter->len, gsm, written); |
| iter->len += written; |
| |
| l_free(gsm); |
| |
| return true; |
| } |
| |
| static bool stk_tlv_builder_append_ucs2(struct stk_tlv_builder *iter, |
| const char *text) |
| { |
| L_AUTO_FREE_VAR(uint8_t *, ucs2); |
| size_t ucs2_len; |
| |
| ucs2 = l_utf8_to_ucs2be(text, &ucs2_len); |
| if (!ucs2) |
| return false; |
| |
| /* Don't include terminating NULL */ |
| ucs2_len -= 2; |
| |
| if (iter->len + ucs2_len >= iter->max_len) |
| return false; |
| |
| iter->value[iter->len++] = 0x08; |
| |
| memcpy(iter->value + iter->len, ucs2, ucs2_len); |
| iter->len += ucs2_len; |
| |
| return true; |
| } |
| |
| static bool stk_tlv_builder_append_text(struct stk_tlv_builder *iter, |
| int dcs, const char *text) |
| { |
| bool ret; |
| |
| switch (dcs) { |
| case 0x00: |
| return stk_tlv_builder_append_gsm_packed(iter, text); |
| case 0x04: |
| return stk_tlv_builder_append_gsm_unpacked(iter, text); |
| case 0x08: |
| return stk_tlv_builder_append_ucs2(iter, text); |
| case -1: |
| ret = stk_tlv_builder_append_gsm_unpacked(iter, text); |
| |
| if (ret) |
| return ret; |
| |
| return stk_tlv_builder_append_ucs2(iter, text); |
| } |
| |
| return false; |
| } |
| |
| static inline bool stk_tlv_builder_append_bytes(struct stk_tlv_builder *iter, |
| const uint8_t *data, |
| unsigned int length) |
| { |
| if (iter->len + length > iter->max_len) |
| return false; |
| |
| memcpy(iter->value + iter->len, data, length); |
| iter->len += length; |
| |
| return true; |
| } |
| |
| /* Described in TS 102.223 Section 8.1 */ |
| static bool build_dataobj_address(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_address *addr = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_ADDRESS; |
| unsigned int len; |
| uint8_t number[128]; |
| |
| if (addr->number == NULL) |
| return true; |
| |
| len = (strlen(addr->number) + 1) / 2; |
| sim_encode_bcd_number(addr->number, number); |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, addr->ton_npi) && |
| stk_tlv_builder_append_bytes(tlv, number, len) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.2 */ |
| static bool build_dataobj_alpha_id(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| uint8_t tag = STK_DATA_OBJECT_TYPE_ALPHA_ID; |
| int len; |
| uint8_t *string; |
| |
| if (data == NULL) |
| return true; |
| |
| if (strlen(data) == 0) |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_close_container(tlv); |
| |
| string = utf8_to_sim_string(data, -1, &len); |
| if (string == NULL) |
| return false; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, true) && |
| stk_tlv_builder_append_bytes(tlv, string, len) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.3 */ |
| static bool build_dataobj_subaddress(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_subaddress *sa = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_SUBADDRESS; |
| |
| if (!sa->has_subaddr) |
| return true; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_bytes(tlv, sa->subaddr, sa->len) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 131.111 Section 8.4 */ |
| static bool build_dataobj_ccp(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_ccp *ccp = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_CCP; |
| |
| if (ccp->len == 0) |
| return true; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, ccp->len) && |
| stk_tlv_builder_append_bytes(tlv, ccp->ccp, ccp->len) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 131.111 Section 8.5 */ |
| static bool build_dataobj_cbs_page(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct cbs *page = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_CBS_PAGE; |
| uint8_t pdu[88]; |
| |
| if (cbs_encode(page, NULL, pdu) == FALSE) |
| return false; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, true) && |
| stk_tlv_builder_append_bytes(tlv, pdu, 88) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.6 */ |
| static bool build_dataobj_item_id(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const uint8_t *item_id = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_ITEM_ID; |
| |
| if (*item_id == 0) |
| return true; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, *item_id) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.8 */ |
| static bool build_dataobj_duration(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_duration *duration = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_DURATION; |
| |
| if (duration->interval == 0x00) |
| return true; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, duration->unit) && |
| stk_tlv_builder_append_byte(tlv, duration->interval) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.12 */ |
| static bool build_dataobj_result(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_result *result = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_RESULT; |
| |
| if (!stk_tlv_builder_open_container(tlv, cr, tag, false)) |
| return false; |
| |
| if (!stk_tlv_builder_append_byte(tlv, result->type)) |
| return false; |
| |
| if (result->additional_len > 0) |
| if (!stk_tlv_builder_append_bytes(tlv, result->additional, |
| result->additional_len)) |
| return false; |
| |
| return stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 131.111 Section 8.13 */ |
| static bool build_dataobj_gsm_sms_tpdu(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct sms_deliver *msg = data; |
| struct sms sms; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_GSM_SMS_TPDU; |
| uint8_t tpdu[165]; |
| int tpdu_len; |
| |
| sms.type = SMS_TYPE_DELIVER; |
| memset(&sms.sc_addr, 0, sizeof(sms.sc_addr)); |
| memcpy(&sms.deliver, msg, sizeof(sms.deliver)); |
| |
| if (sms_encode(&sms, NULL, &tpdu_len, tpdu) == FALSE) |
| return false; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, true) && |
| stk_tlv_builder_append_bytes(tlv, tpdu + 1, tpdu_len) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 131.111 Section 8.14 */ |
| static bool build_dataobj_ss_string(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_address *addr = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_SS_STRING; |
| unsigned int len; |
| uint8_t number[128]; |
| |
| if (addr->number == NULL) |
| return true; |
| |
| len = (strlen(addr->number) + 1) / 2; |
| sim_encode_bcd_number(addr->number, number); |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, addr->ton_npi) && |
| stk_tlv_builder_append_bytes(tlv, number, len) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Defined in TS 102.223 Section 8.15 */ |
| static bool build_dataobj_text(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_answer_text *text = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_TEXT; |
| bool ret; |
| |
| if (text->text == NULL && !text->yesno) |
| return true; |
| |
| if (!stk_tlv_builder_open_container(tlv, cr, tag, true)) |
| return false; |
| |
| if (text->yesno) { |
| /* |
| * Section 6.8.5: |
| * When the terminal issues [...] command qualifier set |
| * to "Yes/No", it shall supply the value "01" when the |
| * answer is "positive" and the value '00' when the |
| * answer is "negative" in the text string data object. |
| */ |
| if (!stk_tlv_builder_append_byte(tlv, 0x04)) |
| return false; |
| |
| ret = stk_tlv_builder_append_byte(tlv, |
| text->text ? 0x01 : 0x00); |
| } else if (text->packed) |
| ret = stk_tlv_builder_append_gsm_packed(tlv, text->text); |
| else |
| ret = stk_tlv_builder_append_text(tlv, -1, text->text); |
| |
| if (!ret) |
| return ret; |
| |
| return stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Defined in TS 102.223 Section 8.15 - USSD specific case*/ |
| static bool build_dataobj_ussd_text(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_ussd_text *text = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_TEXT; |
| |
| if (!text->has_text) |
| return true; |
| |
| if (!stk_tlv_builder_open_container(tlv, cr, tag, true)) |
| return false; |
| |
| if (text->len > 0) { |
| if (!stk_tlv_builder_append_byte(tlv, text->dcs)) |
| return false; |
| |
| if (!stk_tlv_builder_append_bytes(tlv, text->text, text->len)) |
| return false; |
| } |
| |
| return stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 131.111 Section 8.17 */ |
| static bool build_dataobj_ussd_string(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_ussd_string *ussd = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_USSD_STRING; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, ussd->dcs) && |
| stk_tlv_builder_append_bytes(tlv, ussd->string, ussd->len) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.18 */ |
| static bool build_dataobj_file_list(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| struct l_queue *fl = (void *) data; |
| const struct l_queue_entry *entry = l_queue_get_entries(fl); |
| const struct stk_file *file; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_FILE_LIST; |
| |
| if (!stk_tlv_builder_open_container(tlv, cr, tag, true)) |
| return false; |
| |
| if (!stk_tlv_builder_append_byte(tlv, l_queue_length(fl))) |
| return false; |
| |
| for (; entry; entry = entry->next) { |
| file = entry->data; |
| |
| if (!stk_tlv_builder_append_bytes(tlv, file->file, file->len)) |
| return false; |
| } |
| |
| return stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Shortcut for a single File element */ |
| static bool build_dataobj_file(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| struct l_queue *fl = l_queue_new(); |
| bool ret; |
| |
| l_queue_push_tail(fl, (void *) data); |
| ret = build_dataobj_file_list(tlv, fl, cr); |
| |
| l_queue_destroy(fl, NULL); |
| return ret; |
| } |
| |
| /* Described in TS 102.223 Section 8.19 */ |
| static bool build_dataobj_location_info(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_location_info *li = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_LOCATION_INFO; |
| uint8_t mccmnc[3]; |
| |
| if (li->mcc[0] == '\0') |
| return true; |
| |
| sim_encode_mcc_mnc(mccmnc, li->mcc, li->mnc); |
| |
| if (!stk_tlv_builder_open_container(tlv, cr, tag, false)) |
| return false; |
| |
| if (!stk_tlv_builder_append_bytes(tlv, mccmnc, 3)) |
| return false; |
| |
| if (!stk_tlv_builder_append_short(tlv, li->lac_tac)) |
| return false; |
| |
| if (li->has_ci && !stk_tlv_builder_append_short(tlv, li->ci)) |
| return false; |
| |
| if (li->has_ext_ci && !stk_tlv_builder_append_short(tlv, li->ext_ci)) |
| return false; |
| |
| if (li->has_eutran_ci) { |
| if (!stk_tlv_builder_append_short(tlv, li->eutran_ci >> 12)) |
| return false; |
| |
| if (!stk_tlv_builder_append_short(tlv, |
| (li->eutran_ci << 4) | 0xf)) |
| return false; |
| } |
| |
| return stk_tlv_builder_close_container(tlv); |
| } |
| |
| static bool build_empty_dataobj_location_info(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| uint8_t tag = STK_DATA_OBJECT_TYPE_LOCATION_INFO; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* |
| * Described in TS 102.223 Section 8.20 |
| * |
| * See format note in parse_dataobj_imei. |
| */ |
| static bool build_dataobj_imei(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| char byte0[3]; |
| const char *imei = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_IMEI; |
| uint8_t value[8]; |
| |
| if (imei == NULL) |
| return true; |
| |
| if (strlen(imei) != 15) |
| return false; |
| |
| byte0[0] = '*'; |
| byte0[1] = imei[0]; |
| byte0[2] = '\0'; |
| sim_encode_bcd_number(byte0, value); |
| sim_encode_bcd_number(imei + 1, value + 1); |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_bytes(tlv, value, 8) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.21 */ |
| static bool build_dataobj_help_request(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const bool *help = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_HELP_REQUEST; |
| |
| if (*help != true) |
| return true; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.22 */ |
| static bool build_dataobj_network_measurement_results( |
| struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_common_byte_array *nmr = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_NETWORK_MEASUREMENT_RESULTS; |
| |
| if (!stk_tlv_builder_open_container(tlv, cr, tag, false)) |
| return false; |
| |
| if (nmr->len > 0 && !stk_tlv_builder_append_bytes(tlv, |
| nmr->array, nmr->len)) |
| return false; |
| |
| return stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.25 */ |
| static bool build_dataobj_event_list(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_event_list *list = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_EVENT_LIST; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_bytes(tlv, list->list, list->len) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Shortcut for a single Event type */ |
| static bool build_dataobj_event_type(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_event_list list = { |
| .list = { *(enum stk_event_type *) data }, |
| .len = 1, |
| }; |
| |
| return build_dataobj_event_list(tlv, &list, cr); |
| } |
| |
| /* Described in TS 102.223 Section 8.26 */ |
| static bool build_dataobj_cause(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_cause *cause = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_CAUSE; |
| |
| if (!cause->has_cause) |
| return true; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_bytes(tlv, cause->cause, cause->len) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.27 */ |
| static bool build_dataobj_location_status(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const enum stk_service_state *state = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_LOCATION_STATUS; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, *state) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 131.111 Section 8.28 */ |
| static bool build_dataobj_transaction_ids(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_transaction_id *id = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_TRANSACTION_ID; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_bytes(tlv, id->list, id->len) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Shortcut for a single Transaction ID */ |
| static bool build_dataobj_transaction_id(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_transaction_id ids = { |
| .list = { *(uint8_t *) data }, |
| .len = 1, |
| }; |
| |
| return build_dataobj_transaction_ids(tlv, &ids, cr); |
| } |
| |
| /* Described in 3GPP 31.111 Section 8.29 */ |
| static bool build_dataobj_bcch_channel_list(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_bcch_channel_list *list = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_BCCH_CHANNEL_LIST; |
| unsigned int i, bytes, pos, shift; |
| uint8_t value; |
| |
| if (!list->has_list) |
| return true; |
| |
| if (!stk_tlv_builder_open_container(tlv, cr, tag, true)) |
| return false; |
| |
| bytes = (list->num * 10 + 7) / 8; |
| for (i = 0; i < bytes; i++) { |
| pos = (i * 8 + 7) / 10; |
| shift = pos * 10 + 10 - i * 8 - 8; |
| |
| value = 0; |
| if (pos < list->num) |
| value |= list->channels[pos] >> shift; |
| if (shift > 2) |
| value |= list->channels[pos - 1] << (10 - shift); |
| |
| if (!stk_tlv_builder_append_byte(tlv, value)) |
| return false; |
| } |
| |
| return stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.30 */ |
| static bool build_dataobj_cc_requested_action(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_common_byte_array *action = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_CALL_CONTROL_REQUESTED_ACTION; |
| |
| if (action->array == NULL) |
| return true; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_bytes(tlv, action->array, action->len) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.33 */ |
| static bool build_dataobj_card_reader_status(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_reader_status *status = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_CARD_READER_STATUS; |
| uint8_t byte; |
| |
| byte = status->id | |
| (status->removable << 3) | |
| (status->present << 4) | |
| (status->id1_size << 5) | |
| (status->card_present << 6) | |
| (status->card_powered << 7); |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, byte) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.37 */ |
| static bool build_dataobj_timer_id(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const uint8_t *id = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_TIMER_ID; |
| |
| if (id[0] == 0) |
| return true; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, id[0]) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.38 */ |
| static bool build_dataobj_timer_value(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_timer_value *value = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_TIMER_VALUE; |
| |
| if (!value->has_value) |
| return true; |
| |
| #define TO_BCD(bin) ((((bin) / 10) & 0xf) | (((bin) % 10) << 4)) |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, TO_BCD(value->hour)) && |
| stk_tlv_builder_append_byte(tlv, TO_BCD(value->minute)) && |
| stk_tlv_builder_append_byte(tlv, TO_BCD(value->second)) && |
| stk_tlv_builder_close_container(tlv); |
| #undef TO_BCD |
| } |
| |
| /* Described in TS 102.223 Section 8.39 */ |
| static bool build_dataobj_datetime_timezone(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct sms_scts *scts = data; |
| uint8_t value[7]; |
| int offset = 0; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_DATETIME_TIMEZONE; |
| |
| if (scts->month == 0 && scts->day == 0) |
| return true; |
| |
| if (sms_encode_scts(scts, value, &offset) != TRUE) |
| return false; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_bytes(tlv, value, 7) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.41 */ |
| static bool build_dataobj_at_response(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| uint8_t tag = STK_DATA_OBJECT_TYPE_AT_RESPONSE; |
| int len; |
| |
| if (data == NULL) |
| return true; |
| |
| /* |
| * "If the AT Response string is longer than the maximum length |
| * capable of being transmitted to the UICC then the AT Response |
| * string shall be truncated to this length by the terminal." |
| */ |
| len = strlen(data); |
| if (len > 240) /* Safe pick */ |
| len = 240; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, true) && |
| stk_tlv_builder_append_bytes(tlv, data, len) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 131.111 Section 8.42 */ |
| static bool build_dataobj_bc_repeat(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| uint8_t tag = STK_DATA_OBJECT_TYPE_BC_REPEAT_INDICATOR; |
| const struct stk_bc_repeat *bcr = data; |
| |
| if (!bcr->has_bc_repeat) |
| return true; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, true) && |
| stk_tlv_builder_append_byte(tlv, bcr->value) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.45 */ |
| static bool build_dataobj_language(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| uint8_t tag = STK_DATA_OBJECT_TYPE_LANGUAGE; |
| |
| if (data == NULL) |
| return true; |
| |
| /* |
| * Coded as two GSM 7-bit characters with eighth bit clear. Since |
| * ISO 639-2 codes use only english alphabet letters, no conversion |
| * from UTF-8 to GSM is needed. |
| */ |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_bytes(tlv, data, 2) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in 3GPP TS 31.111 Section 8.46 */ |
| static bool build_dataobj_timing_advance(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_timing_advance *tadv = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_TIMING_ADVANCE; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, tadv->status) && |
| stk_tlv_builder_append_byte(tlv, tadv->advance) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.51 */ |
| static bool build_dataobj_browser_termination_cause( |
| struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const enum stk_browser_termination_cause *cause = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_BROWSER_TERMINATION_CAUSE; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, *cause) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.52 */ |
| static bool build_dataobj_bearer_description(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_bearer_description *bd = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_BEARER_DESCRIPTION; |
| |
| if (bd->type != STK_BEARER_TYPE_GPRS_UTRAN) |
| return true; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, bd->type) && |
| stk_tlv_builder_append_byte(tlv, |
| bd->gprs.precedence) && |
| stk_tlv_builder_append_byte(tlv, |
| bd->gprs.delay) && |
| stk_tlv_builder_append_byte(tlv, |
| bd->gprs.reliability) && |
| stk_tlv_builder_append_byte(tlv, |
| bd->gprs.peak) && |
| stk_tlv_builder_append_byte(tlv, |
| bd->gprs.mean) && |
| stk_tlv_builder_append_byte(tlv, |
| bd->gprs.pdp_type) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.53 */ |
| static bool build_dataobj_channel_data(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_common_byte_array *cd = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_CHANNEL_DATA; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, true) && |
| stk_tlv_builder_append_bytes(tlv, cd->array, cd->len) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.54 */ |
| static bool build_dataobj_channel_data_length( |
| struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const uint16_t *length = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_CHANNEL_DATA_LENGTH; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, MIN(*length, 255)) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.55 */ |
| static bool build_dataobj_buffer_size(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const uint16_t *buf_size = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_BUFFER_SIZE; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_short(tlv, *buf_size) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.56 */ |
| static bool build_dataobj_channel_status(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_channel *channel = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_CHANNEL_STATUS; |
| uint8_t byte[2]; |
| |
| switch (channel->status) { |
| case STK_CHANNEL_PACKET_DATA_SERVICE_NOT_ACTIVATED: |
| case STK_CHANNEL_TCP_IN_CLOSED_STATE: |
| byte[0] = channel->id; |
| byte[1] = 0x00; |
| break; |
| case STK_CHANNEL_PACKET_DATA_SERVICE_ACTIVATED: |
| case STK_CHANNEL_TCP_IN_ESTABLISHED_STATE: |
| byte[0] = channel->id | 0x80; |
| byte[1] = 0x00; |
| break; |
| case STK_CHANNEL_TCP_IN_LISTEN_STATE: |
| byte[0] = channel->id | 0x40; |
| byte[1] = 0x00; |
| break; |
| case STK_CHANNEL_LINK_DROPPED: |
| byte[0] = channel->id; |
| byte[1] = 0x05; |
| break; |
| } |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_bytes(tlv, byte, 2) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.58 */ |
| static bool build_dataobj_other_address(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_other_address *addr = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_OTHER_ADDRESS; |
| bool ok = false; |
| |
| if (!addr->type) |
| return true; |
| |
| if (!stk_tlv_builder_open_container(tlv, cr, tag, false)) |
| return false; |
| |
| switch (addr->type) { |
| case STK_ADDRESS_AUTO: |
| ok = true; |
| break; |
| case STK_ADDRESS_IPV4: |
| ok = stk_tlv_builder_append_byte(tlv, addr->type) && |
| stk_tlv_builder_append_bytes(tlv, |
| (const uint8_t *) &addr->addr.ipv4, 4); |
| break; |
| case STK_ADDRESS_IPV6: |
| ok = stk_tlv_builder_append_byte(tlv, addr->type) && |
| stk_tlv_builder_append_bytes(tlv, addr->addr.ipv6, 16); |
| break; |
| } |
| |
| if (!ok) |
| return false; |
| |
| return stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.59 */ |
| static bool build_dataobj_uicc_te_interface(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_uicc_te_interface *iface = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_UICC_TE_INTERFACE; |
| |
| if (iface->protocol == 0 && iface->port == 0) |
| return true; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, iface->protocol) && |
| stk_tlv_builder_append_short(tlv, iface->port) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.61 */ |
| static bool build_dataobj_access_technologies(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_access_technologies *techs = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_ACCESS_TECHNOLOGY; |
| int i; |
| |
| if (!stk_tlv_builder_open_container(tlv, cr, tag, false)) |
| return false; |
| |
| for (i = 0; i < techs->length; i++) |
| if (!stk_tlv_builder_append_byte(tlv, techs->techs[i])) |
| return false; |
| |
| return stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Shortcut for a single Access Technology */ |
| static bool build_dataobj_access_technology(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_access_technologies techs = { |
| .techs = data, |
| .length = 1, |
| }; |
| |
| return build_dataobj_access_technologies(tlv, &techs, cr); |
| } |
| |
| /* Described in TS 102.223 Section 8.62 */ |
| static bool build_dataobj_display_parameters(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_display_parameters *params = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_DISPLAY_PARAMETERS; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, params->height) && |
| stk_tlv_builder_append_byte(tlv, params->width) && |
| stk_tlv_builder_append_byte(tlv, params->effects) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.63 */ |
| static bool build_dataobj_service_record(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_service_record *rec = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_SERVICE_RECORD; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, true) && |
| stk_tlv_builder_append_byte(tlv, rec->tech_id) && |
| stk_tlv_builder_append_byte(tlv, rec->serv_id) && |
| stk_tlv_builder_append_bytes(tlv, rec->serv_rec, rec->len) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.68 */ |
| static bool build_dataobj_remote_entity_address(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_remote_entity_address *addr = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_REMOTE_ENTITY_ADDRESS; |
| bool ok = false; |
| |
| if (!addr->has_address) |
| return true; |
| |
| if (!stk_tlv_builder_open_container(tlv, cr, tag, true)) |
| return false; |
| |
| if (!stk_tlv_builder_append_byte(tlv, addr->coding_type)) |
| return false; |
| |
| switch (addr->coding_type) { |
| case 0x00: |
| ok = stk_tlv_builder_append_bytes(tlv, addr->addr.ieee802, 6); |
| break; |
| case 0x01: |
| ok = stk_tlv_builder_append_bytes(tlv, addr->addr.irda, 4); |
| break; |
| } |
| |
| if (!ok) |
| return false; |
| |
| return stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.69 */ |
| static bool build_dataobj_esn(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const uint32_t *esn = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_ESN; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_short(tlv, *esn >> 16) && |
| stk_tlv_builder_append_short(tlv, *esn >> 0) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 131.111 Section 8.72, 3GPP 24.008 Section 9.5.7 */ |
| static bool build_dataobj_pdp_context_params(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_common_byte_array *params = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_PDP_ACTIVATION_PARAMETER; |
| |
| if (params->len < 1) |
| return true; |
| |
| if (params->len > 0x7f) |
| return false; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_bytes(tlv, params->array, params->len) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* |
| * Described in TS 102.223 Section 8.74 |
| * |
| * See format note in parse_dataobj_imeisv. |
| */ |
| static bool build_dataobj_imeisv(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| char byte0[3]; |
| const char *imeisv = data; |
| uint8_t value[9]; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_IMEISV; |
| |
| if (imeisv == NULL) |
| return true; |
| |
| if (strlen(imeisv) != 16) |
| return false; |
| |
| byte0[0] = '3'; |
| byte0[1] = imeisv[0]; |
| byte0[2] = '\0'; |
| sim_encode_bcd_number(byte0, value); |
| sim_encode_bcd_number(imeisv + 1, value + 1); |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_bytes(tlv, value, 9) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.75 */ |
| static bool build_dataobj_network_search_mode(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const enum stk_network_search_mode *mode = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_NETWORK_SEARCH_MODE; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, *mode) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.76 */ |
| static bool build_dataobj_battery_state(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const enum stk_battery_state *state = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_BATTERY_STATE; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, *state) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.77 */ |
| static bool build_dataobj_browsing_status(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_common_byte_array *bs = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_BROWSING_STATUS; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, true) && |
| stk_tlv_builder_append_bytes(tlv, bs->array, bs->len) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.79 */ |
| static bool build_dataobj_frames_information(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_frames_info *info = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_FRAMES_INFO; |
| unsigned int i; |
| |
| if (!stk_tlv_builder_open_container(tlv, cr, tag, false)) |
| return false; |
| |
| if (!stk_tlv_builder_append_byte(tlv, info->id)) |
| return false; |
| |
| for (i = 0; i < info->len; i++) { |
| if (!stk_tlv_builder_append_byte(tlv, info->list[i].height)) |
| return false; |
| if (!stk_tlv_builder_append_byte(tlv, info->list[i].width)) |
| return false; |
| } |
| |
| return stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.81 */ |
| static bool build_dataobj_meid(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const char *meid = data; |
| uint8_t value[8]; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_MEID; |
| |
| if (meid == NULL) |
| return true; |
| |
| if (strlen(meid) != 16) |
| return false; |
| |
| sim_encode_bcd_number(meid, value); |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_bytes(tlv, value, 8) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.83 */ |
| static bool build_dataobj_mms_id(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_mms_id *id = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_MMS_ID; |
| |
| /* Assume the length is never 0 for a valid ID, however the whole |
| * data object's presence is conditional. */ |
| if (id->len == 0) |
| return true; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_bytes(tlv, id->id, id->len) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.84 */ |
| static bool build_dataobj_mms_transfer_status(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_mms_transfer_status *mts = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_MMS_TRANSFER_STATUS; |
| |
| /* |
| * Assume the length is never 0 for a valid Result message, however |
| * the whole data object's presence is conditional. |
| */ |
| if (mts->len == 0) |
| return true; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_bytes(tlv, mts->status, mts->len) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 131.111 Section 8.84 */ |
| static bool build_dataobj_i_wlan_access_status(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const enum stk_i_wlan_access_status *status = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_I_WLAN_ACCESS_STATUS; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, *status) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.86 */ |
| static bool build_dataobj_mms_notification(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_common_byte_array *msg = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_MMS_NOTIFICATION; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, true) && |
| stk_tlv_builder_append_bytes(tlv, msg->array, msg->len) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.87 */ |
| static bool build_dataobj_last_envelope(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const ofono_bool_t *last = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_LAST_ENVELOPE; |
| |
| if (!*last) |
| return true; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.88 */ |
| static bool build_dataobj_registry_application_data( |
| struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_registry_application_data *rad = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_REGISTRY_APPLICATION_DATA; |
| uint8_t dcs; |
| L_AUTO_FREE_VAR(uint8_t *, name); |
| size_t len; |
| long gsmlen; |
| |
| name = convert_utf8_to_gsm(rad->name, -1, NULL, &gsmlen, 0); |
| len = gsmlen; |
| dcs = 0x04; |
| if (!name) { |
| name = l_utf8_to_ucs2be(rad->name, &len); |
| if (!name) |
| return false; |
| |
| /* len includes null terminator, so strip it */ |
| len -= 2; |
| dcs = 0x08; |
| } |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, true) && |
| stk_tlv_builder_append_short(tlv, rad->port) && |
| stk_tlv_builder_append_byte(tlv, dcs) && |
| stk_tlv_builder_append_byte(tlv, rad->type) && |
| stk_tlv_builder_append_bytes(tlv, name, len) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 102.223 Section 8.90 */ |
| static bool build_dataobj_broadcast_network_information( |
| struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_broadcast_network_information *bni = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_BROADCAST_NETWORK_INFO; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, bni->tech) && |
| stk_tlv_builder_append_bytes(tlv, bni->loc_info, bni->len) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 131.111 Section 8.91 / 3GPP 24.008 Section 10.5.5.15 */ |
| static bool build_dataobj_routing_area_id(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_routing_area_info *rai = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_ROUTING_AREA_INFO; |
| uint8_t mccmnc[3]; |
| |
| if (rai->mcc[0] == 0) |
| return true; |
| |
| sim_encode_mcc_mnc(mccmnc, rai->mcc, rai->mnc); |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_bytes(tlv, mccmnc, 3) && |
| stk_tlv_builder_append_short(tlv, rai->lac) && |
| stk_tlv_builder_append_byte(tlv, rai->rac) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 131.111 Section 8.92 */ |
| static bool build_dataobj_update_attach_type(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const enum stk_update_attach_type *type = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_UPDATE_ATTACH_TYPE; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, *type) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 131.111 Section 8.93 */ |
| static bool build_dataobj_rejection_cause_code(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const enum stk_rejection_cause_code *cause = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_REJECTION_CAUSE_CODE; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, *cause) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 131.111 Section 8.98, 3GPP 24.301 Section 6.5.1 */ |
| static bool build_dataobj_eps_pdn_conn_params(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_common_byte_array *params = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_EPS_PDN_CONN_ACTIVATION_REQ; |
| |
| if (params->len < 1) |
| return true; |
| |
| if (params->len > 0x7f) |
| return false; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_bytes(tlv, params->array, params->len) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| /* Described in TS 131.111 Section 8.99 / 3GPP 24.301 Section 9.9.3.32 */ |
| static bool build_dataobj_tracking_area_id(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_tracking_area_id *tai = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_TRACKING_AREA_ID; |
| uint8_t mccmnc[3]; |
| |
| if (tai->mcc[0] == 0) |
| return true; |
| |
| sim_encode_mcc_mnc(mccmnc, tai->mcc, tai->mnc); |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_bytes(tlv, mccmnc, 3) && |
| stk_tlv_builder_append_short(tlv, tai->tac) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| static bool build_dataobj(struct stk_tlv_builder *tlv, |
| dataobj_writer builder_func, ...) |
| { |
| va_list args; |
| |
| va_start(args, builder_func); |
| |
| while (builder_func) { |
| unsigned int flags = va_arg(args, enum stk_data_object_flag); |
| const void *data = va_arg(args, const void *); |
| bool cr = (flags & DATAOBJ_FLAG_CR) ? true : false; |
| |
| if (!builder_func(tlv, data, cr)) { |
| va_end(args); |
| return false; |
| } |
| |
| builder_func = va_arg(args, dataobj_writer); |
| } |
| |
| va_end(args); |
| |
| return true; |
| } |
| |
| static bool build_setup_call(struct stk_tlv_builder *builder, |
| const struct stk_response *response) |
| { |
| if (response->set_up_call.modified_result.cc_modified) |
| return build_dataobj(builder, |
| build_dataobj_cc_requested_action, |
| DATAOBJ_FLAG_CR, |
| &response->set_up_call.cc_requested_action, |
| build_dataobj_result, |
| DATAOBJ_FLAG_CR, |
| &response->set_up_call.modified_result.result, |
| NULL); |
| else |
| return build_dataobj(builder, |
| build_dataobj_cc_requested_action, |
| DATAOBJ_FLAG_CR, |
| &response->set_up_call.cc_requested_action, |
| NULL); |
| } |
| |
| static bool build_local_info(struct stk_tlv_builder *builder, |
| const struct stk_response *response) |
| { |
| const struct stk_response_local_info *info = |
| &response->provide_local_info; |
| int i; |
| |
| switch (response->qualifier) { |
| case 0x00: /* Location Information according to current NAA */ |
| return build_dataobj(builder, |
| build_dataobj_location_info, |
| DATAOBJ_FLAG_CR, &info->location, |
| NULL); |
| |
| case 0x01: /* IMEI of the terminal */ |
| return build_dataobj(builder, |
| build_dataobj_imei, |
| DATAOBJ_FLAG_CR, info->imei, |
| NULL); |
| |
| case 0x02: /* Network Measurement results according to current NAA */ |
| return build_dataobj(builder, |
| build_dataobj_network_measurement_results, |
| DATAOBJ_FLAG_CR, &info->nmr.nmr, |
| build_dataobj_bcch_channel_list, |
| DATAOBJ_FLAG_CR, &info->nmr.bcch_ch_list, |
| NULL); |
| |
| case 0x03: /* Date, time and time zone */ |
| return build_dataobj(builder, |
| build_dataobj_datetime_timezone, |
| DATAOBJ_FLAG_CR, &info->datetime, |
| NULL); |
| |
| case 0x04: /* Language setting */ |
| return build_dataobj(builder, |
| build_dataobj_language, |
| DATAOBJ_FLAG_CR, info->language, |
| NULL); |
| |
| case 0x05: /* Timing Advance */ |
| return build_dataobj(builder, |
| build_dataobj_timing_advance, |
| DATAOBJ_FLAG_CR, &info->tadv, |
| NULL); |
| |
| case 0x06: /* Access Technology (single access technology) */ |
| return build_dataobj(builder, |
| build_dataobj_access_technology, |
| 0, &info->access_technology, |
| NULL); |
| |
| case 0x07: /* ESN of the terminal */ |
| return build_dataobj(builder, |
| build_dataobj_esn, |
| DATAOBJ_FLAG_CR, &info->esn, |
| NULL); |
| |
| case 0x08: /* IMEISV of the terminal */ |
| return build_dataobj(builder, |
| build_dataobj_imeisv, |
| DATAOBJ_FLAG_CR, info->imeisv, |
| NULL); |
| |
| case 0x09: /* Search Mode */ |
| return build_dataobj(builder, |
| build_dataobj_network_search_mode, |
| 0, &info->search_mode, |
| NULL); |
| |
| case 0x0a: /* Charge State of the Battery */ |
| return build_dataobj(builder, |
| build_dataobj_battery_state, |
| DATAOBJ_FLAG_CR, &info->battery_charge, |
| NULL); |
| |
| case 0x0b: /* MEID of the terminal */ |
| return build_dataobj(builder, |
| build_dataobj_meid, |
| 0, info->meid, |
| NULL); |
| |
| case 0x0d: /* Broadcast Network Information according to current tech */ |
| return build_dataobj(builder, |
| build_dataobj_broadcast_network_information, |
| 0, &info->broadcast_network_info, |
| NULL); |
| |
| case 0x0e: /* Multiple Access Technologies */ |
| return build_dataobj(builder, |
| build_dataobj_access_technologies, |
| 0, &info->access_technologies, |
| NULL); |
| |
| case 0x0f: /* Location Information for multiple NAAs */ |
| if (!build_dataobj(builder, |
| build_dataobj_access_technologies, |
| 0, &info->location_infos.access_techs, |
| NULL)) |
| return false; |
| |
| for (i = 0; i < info->location_infos.access_techs.length; i++) { |
| dataobj_writer location = build_dataobj_location_info; |
| /* |
| * "If no location information is available for an |
| * access technology, the respective data object |
| * shall have length zero." |
| */ |
| if (info->location_infos.locations[i].mcc[0] == '\0') |
| location = build_empty_dataobj_location_info; |
| |
| if (!build_dataobj(builder, |
| location, |
| 0, &info->location_infos.locations[i], |
| NULL)) |
| return false; |
| } |
| |
| return true; |
| |
| case 0x10: /* Network Measurement results for multiple NAAs */ |
| if (!build_dataobj(builder, |
| build_dataobj_access_technologies, |
| 0, &info->nmrs.access_techs, |
| NULL)) |
| return false; |
| |
| for (i = 0; i < info->nmrs.access_techs.length; i++) |
| if (!build_dataobj(builder, |
| build_dataobj_network_measurement_results, |
| 0, &info->nmrs.nmrs[i].nmr, |
| build_dataobj_bcch_channel_list, |
| 0, &info->nmrs.nmrs[i].bcch_ch_list, |
| NULL)) |
| return false; |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static bool build_open_channel(struct stk_tlv_builder *builder, |
| const struct stk_response *response) |
| { |
| const struct stk_response_open_channel *open_channel = |
| &response->open_channel; |
| |
| /* insert channel identifier only in case of success */ |
| if (response->result.type == STK_RESULT_TYPE_SUCCESS) { |
| if (!build_dataobj(builder, |
| build_dataobj_channel_status, |
| 0, &open_channel->channel, |
| NULL)) |
| return false; |
| } |
| |
| return build_dataobj(builder, |
| build_dataobj_bearer_description, |
| 0, &open_channel->bearer_desc, |
| build_dataobj_buffer_size, |
| 0, &open_channel->buf_size, |
| NULL); |
| } |
| |
| static bool build_receive_data(struct stk_tlv_builder *builder, |
| const struct stk_response *response) |
| { |
| const struct stk_response_receive_data *receive_data = |
| &response->receive_data; |
| |
| if (response->result.type != STK_RESULT_TYPE_SUCCESS && |
| response->result.type != STK_RESULT_TYPE_MISSING_INFO) |
| return true; |
| |
| if (receive_data->rx_data.len) { |
| if (!build_dataobj(builder, |
| build_dataobj_channel_data, |
| DATAOBJ_FLAG_CR, |
| &response->receive_data.rx_data, |
| NULL)) |
| return false; |
| } |
| |
| return build_dataobj(builder, |
| build_dataobj_channel_data_length, |
| DATAOBJ_FLAG_CR, |
| &response->receive_data.rx_remaining, |
| NULL); |
| } |
| |
| static bool build_send_data(struct stk_tlv_builder *builder, |
| const struct stk_response *response) |
| { |
| if (response->result.type != STK_RESULT_TYPE_SUCCESS) |
| return true; |
| |
| return build_dataobj(builder, |
| build_dataobj_channel_data_length, |
| DATAOBJ_FLAG_CR, |
| &response->send_data.tx_avail, |
| NULL); |
| } |
| |
| const uint8_t *stk_pdu_from_response(const struct stk_response *response, |
| unsigned int *out_length) |
| { |
| struct stk_tlv_builder builder; |
| bool ok = true; |
| uint8_t tag; |
| static uint8_t pdu[512]; |
| |
| stk_tlv_builder_init(&builder, pdu, sizeof(pdu)); |
| |
| /* |
| * Encode command details, they come in order with |
| * Command Details TLV first, followed by Device Identities TLV |
| * and the Result TLV. Comprehension required everywhere. |
| */ |
| tag = STK_DATA_OBJECT_TYPE_COMMAND_DETAILS; |
| if (!stk_tlv_builder_open_container(&builder, true, tag, false)) |
| return NULL; |
| |
| if (!stk_tlv_builder_append_byte(&builder, response->number)) |
| return NULL; |
| |
| if (!stk_tlv_builder_append_byte(&builder, response->type)) |
| return NULL; |
| |
| if (!stk_tlv_builder_append_byte(&builder, response->qualifier)) |
| return NULL; |
| |
| if (!stk_tlv_builder_close_container(&builder)) |
| return NULL; |
| |
| /* |
| * TS 102 223 section 6.8 states: |
| * "For all COMPREHENSION-TLV objects with Min = N, the terminal |
| * should set the CR flag to comprehension not required." |
| * All the data objects except "Command Details" and "Result" have |
| * Min = N. |
| * |
| * However comprehension required is set for many of the TLVs in |
| * TS 102 384 conformance tests so we set it per command and per |
| * data object type. |
| */ |
| tag = STK_DATA_OBJECT_TYPE_DEVICE_IDENTITIES; |
| if (!stk_tlv_builder_open_container(&builder, true, tag, false)) |
| return NULL; |
| |
| if (!stk_tlv_builder_append_byte(&builder, response->src)) |
| return NULL; |
| |
| if (!stk_tlv_builder_append_byte(&builder, response->dst)) |
| return NULL; |
| |
| if (!stk_tlv_builder_close_container(&builder)) |
| return NULL; |
| |
| if (!build_dataobj_result(&builder, &response->result, true)) |
| return NULL; |
| |
| switch (response->type) { |
| case STK_COMMAND_TYPE_DISPLAY_TEXT: |
| break; |
| case STK_COMMAND_TYPE_GET_INKEY: |
| ok = build_dataobj(&builder, |
| build_dataobj_text, DATAOBJ_FLAG_CR, |
| &response->get_inkey.text, |
| build_dataobj_duration, 0, |
| &response->get_inkey.duration, |
| NULL); |
| break; |
| case STK_COMMAND_TYPE_GET_INPUT: |
| ok = build_dataobj(&builder, |
| build_dataobj_text, DATAOBJ_FLAG_CR, |
| &response->get_input.text, |
| NULL); |
| break; |
| case STK_COMMAND_TYPE_MORE_TIME: |
| case STK_COMMAND_TYPE_SEND_SMS: |
| case STK_COMMAND_TYPE_PLAY_TONE: |
| break; |
| case STK_COMMAND_TYPE_POLL_INTERVAL: |
| ok = build_dataobj(&builder, |
| build_dataobj_duration, DATAOBJ_FLAG_CR, |
| &response->poll_interval.max_interval, |
| NULL); |
| break; |
| case STK_COMMAND_TYPE_REFRESH: |
| case STK_COMMAND_TYPE_SETUP_MENU: |
| break; |
| case STK_COMMAND_TYPE_SELECT_ITEM: |
| ok = build_dataobj(&builder, |
| build_dataobj_item_id, DATAOBJ_FLAG_CR, |
| &response->select_item.item_id, |
| NULL); |
| break; |
| case STK_COMMAND_TYPE_SEND_SS: |
| break; |
| case STK_COMMAND_TYPE_SETUP_CALL: |
| ok = build_setup_call(&builder, response); |
| break; |
| case STK_COMMAND_TYPE_POLLING_OFF: |
| break; |
| case STK_COMMAND_TYPE_PROVIDE_LOCAL_INFO: |
| ok = build_local_info(&builder, response); |
| break; |
| case STK_COMMAND_TYPE_SETUP_EVENT_LIST: |
| break; |
| case STK_COMMAND_TYPE_TIMER_MANAGEMENT: |
| ok = build_dataobj(&builder, |
| build_dataobj_timer_id, |
| DATAOBJ_FLAG_CR, |
| &response->timer_mgmt.id, |
| build_dataobj_timer_value, |
| DATAOBJ_FLAG_CR, |
| &response->timer_mgmt.value, |
| NULL); |
| break; |
| case STK_COMMAND_TYPE_SETUP_IDLE_MODE_TEXT: |
| break; |
| case STK_COMMAND_TYPE_RUN_AT_COMMAND: |
| ok = build_dataobj(&builder, |
| build_dataobj_at_response, |
| DATAOBJ_FLAG_CR, |
| response->run_at_command.at_response, |
| NULL); |
| break; |
| case STK_COMMAND_TYPE_SEND_DTMF: |
| case STK_COMMAND_TYPE_LANGUAGE_NOTIFICATION: |
| case STK_COMMAND_TYPE_LAUNCH_BROWSER: |
| case STK_COMMAND_TYPE_CLOSE_CHANNEL: |
| break; |
| case STK_COMMAND_TYPE_SEND_USSD: |
| ok = build_dataobj(&builder, |
| build_dataobj_ussd_text, |
| DATAOBJ_FLAG_CR, |
| &response->send_ussd.text, |
| NULL); |
| break; |
| case STK_COMMAND_TYPE_OPEN_CHANNEL: |
| ok = build_open_channel(&builder, response); |
| break; |
| case STK_COMMAND_TYPE_RECEIVE_DATA: |
| ok = build_receive_data(&builder, response); |
| break; |
| case STK_COMMAND_TYPE_SEND_DATA: |
| ok = build_send_data(&builder, response); |
| break; |
| case STK_COMMAND_TYPE_GET_CHANNEL_STATUS: |
| ok = build_dataobj(&builder, |
| build_dataobj_channel_status, |
| DATAOBJ_FLAG_CR, |
| &response->channel_status.channel, |
| NULL); |
| break; |
| default: |
| return NULL; |
| }; |
| |
| if (!ok) |
| return NULL; |
| |
| if (out_length) |
| *out_length = stk_tlv_builder_get_length(&builder); |
| |
| return pdu; |
| } |
| |
| /* Described in TS 102.223 Section 8.7 */ |
| static bool build_envelope_dataobj_device_ids(struct stk_tlv_builder *tlv, |
| const void *data, bool cr) |
| { |
| const struct stk_envelope *envelope = data; |
| uint8_t tag = STK_DATA_OBJECT_TYPE_DEVICE_IDENTITIES; |
| |
| return stk_tlv_builder_open_container(tlv, cr, tag, false) && |
| stk_tlv_builder_append_byte(tlv, envelope->src) && |
| stk_tlv_builder_append_byte(tlv, envelope->dst) && |
| stk_tlv_builder_close_container(tlv); |
| } |
| |
| static bool build_envelope_call_control( |
| struct stk_tlv_builder *builder, |
| const struct stk_envelope *envelope) |
| { |
| const struct stk_envelope_call_control *cc = &envelope->call_control; |
| bool ok = false; |
| |
| if (!build_dataobj(builder, |
| build_envelope_dataobj_device_ids, |
| DATAOBJ_FLAG_CR, envelope, NULL)) |
| return false; |
| |
| switch (cc->type) { |
| case STK_CC_TYPE_CALL_SETUP: |
| ok = build_dataobj(builder, |
| build_dataobj_address, |
| DATAOBJ_FLAG_CR, &cc->address, NULL); |
| break; |
| case STK_CC_TYPE_SUPPLEMENTARY_SERVICE: |
| ok = build_dataobj(builder, |
| build_dataobj_ss_string, |
| DATAOBJ_FLAG_CR, &cc->ss_string, NULL); |
| break; |
| case STK_CC_TYPE_USSD_OP: |
| ok = build_dataobj(builder, |
| build_dataobj_ussd_string, |
| DATAOBJ_FLAG_CR, &cc->ussd_string, |
| NULL); |
| break; |
| case STK_CC_TYPE_PDP_CTX_ACTIVATION: |
| ok = build_dataobj(builder, |
| build_dataobj_pdp_context_params, |
| DATAOBJ_FLAG_CR, &cc->pdp_ctx_params, |
| NULL); |
| break; |
| case STK_CC_TYPE_EPS_PDN_CONNECTION_ACTIVATION: |
| ok = build_dataobj(builder, |
| build_dataobj_eps_pdn_conn_params, |
| DATAOBJ_FLAG_CR, &cc->eps_pdn_params, |
| NULL); |
| break; |
| } |
| |
| if (!ok) |
| return false; |
| |
| return build_dataobj(builder, |
| build_dataobj_ccp, 0, &cc->ccp1, |
| build_dataobj_subaddress, 0, &cc->subaddress, |
| build_dataobj_location_info, 0, &cc->location, |
| build_dataobj_ccp, 0, &cc->ccp2, |
| build_dataobj_alpha_id, 0, cc->alpha_id, |
| build_dataobj_bc_repeat, 0, &cc->bc_repeat, |
| NULL); |
| } |
| |
| static bool build_envelope_event_download(struct stk_tlv_builder *builder, |
| const struct stk_envelope *envelope) |
| { |
| const struct stk_envelope_event_download *evt = |
| &envelope->event_download; |
| |
| if (!build_dataobj(builder, |
| build_dataobj_event_type, DATAOBJ_FLAG_CR, |
| &evt->type, |
| build_envelope_dataobj_device_ids, |
| DATAOBJ_FLAG_CR, |
| envelope, |
| NULL)) |
| return false; |
| |
| switch (evt->type) { |
| case STK_EVENT_TYPE_MT_CALL: |
| return build_dataobj(builder, |
| build_dataobj_transaction_id, |
| DATAOBJ_FLAG_CR, |
| &evt->mt_call.transaction_id, |
| build_dataobj_address, 0, |
| &evt->mt_call.caller_address, |
| build_dataobj_subaddress, 0, |
| &evt->mt_call.caller_subaddress, |
| NULL); |
| case STK_EVENT_TYPE_CALL_CONNECTED: |
| return build_dataobj(builder, |
| build_dataobj_transaction_id, |
| DATAOBJ_FLAG_CR, |
| &evt->call_connected.transaction_id, |
| NULL); |
| case STK_EVENT_TYPE_CALL_DISCONNECTED: |
| return build_dataobj(builder, |
| build_dataobj_transaction_ids, |
| DATAOBJ_FLAG_CR, |
| &evt->call_disconnected.transaction_ids, |
| build_dataobj_cause, 0, |
| &evt->call_disconnected.cause, |
| NULL); |
| case STK_EVENT_TYPE_LOCATION_STATUS: |
| return build_dataobj(builder, |
| build_dataobj_location_status, |
| DATAOBJ_FLAG_CR, |
| &evt->location_status.state, |
| build_dataobj_location_info, 0, |
| &evt->location_status.info, |
| NULL); |
| case STK_EVENT_TYPE_USER_ACTIVITY: |
| case STK_EVENT_TYPE_IDLE_SCREEN_AVAILABLE: |
| return true; |
| case STK_EVENT_TYPE_CARD_READER_STATUS: |
| return build_dataobj(builder, |
| build_dataobj_card_reader_status, |
| DATAOBJ_FLAG_CR, |
| &evt->card_reader_status, |
| NULL); |
| case STK_EVENT_TYPE_LANGUAGE_SELECTION: |
| return build_dataobj(builder, |
| build_dataobj_language, DATAOBJ_FLAG_CR, |
| evt->language_selection, |
| NULL); |
| case STK_EVENT_TYPE_BROWSER_TERMINATION: |
| return build_dataobj(builder, |
| build_dataobj_browser_termination_cause, |
| DATAOBJ_FLAG_CR, |
| &evt->browser_termination.cause, |
| NULL); |
| case STK_EVENT_TYPE_DATA_AVAILABLE: |
| return build_dataobj(builder, |
| build_dataobj_channel_status, |
| DATAOBJ_FLAG_CR, |
| &evt->data_available.channel, |
| build_dataobj_channel_data_length, |
| DATAOBJ_FLAG_CR, |
| &evt->data_available.channel_data_len, |
| NULL); |
| case STK_EVENT_TYPE_CHANNEL_STATUS: |
| return build_dataobj(builder, |
| build_dataobj_channel_status, |
| DATAOBJ_FLAG_CR, |
| &evt->channel_status.channel, |
| build_dataobj_bearer_description, |
| DATAOBJ_FLAG_CR, |
| &evt->channel_status.bearer_desc, |
| build_dataobj_other_address, |
| DATAOBJ_FLAG_CR, |
| &evt->channel_status.address, |
| NULL); |
| case STK_EVENT_TYPE_SINGLE_ACCESS_TECHNOLOGY_CHANGE: |
| return build_dataobj(builder, |
| build_dataobj_access_technology, |
| DATAOBJ_FLAG_CR, |
| &evt->access_technology_change, |
| NULL); |
| case STK_EVENT_TYPE_DISPLAY_PARAMETERS_CHANGED: |
| return build_dataobj(builder, |
| build_dataobj_display_parameters, |
| DATAOBJ_FLAG_CR, |
| &evt->display_params_changed, |
| NULL); |
| case STK_EVENT_TYPE_LOCAL_CONNECTION: |
| return build_dataobj(builder, |
| build_dataobj_service_record, |
| DATAOBJ_FLAG_CR, |
| &evt->local_connection.service_record, |
| build_dataobj_remote_entity_address, 0, |
| &evt->local_connection.remote_addr, |
| build_dataobj_uicc_te_interface, 0, |
| &evt->local_connection.transport_level, |
| build_dataobj_other_address, |
| 0, |
| &evt->local_connection.transport_addr, |
| NULL); |
| case STK_EVENT_TYPE_NETWORK_SEARCH_MODE_CHANGE: |
| return build_dataobj(builder, |
| build_dataobj_network_search_mode, |
| DATAOBJ_FLAG_CR, |
| &evt->network_search_mode_change, |
| NULL); |
| case STK_EVENT_TYPE_BROWSING_STATUS: |
| return build_dataobj(builder, |
| build_dataobj_browsing_status, |
| DATAOBJ_FLAG_CR, |
| &evt->browsing_status, |
| NULL); |
| case STK_EVENT_TYPE_FRAMES_INFORMATION_CHANGE: |
| return build_dataobj(builder, |
| build_dataobj_frames_information, |
| DATAOBJ_FLAG_CR, |
| &evt->frames_information_change, |
| NULL); |
| case STK_EVENT_TYPE_I_WLAN_ACCESS_STATUS: |
| return build_dataobj(builder, |
| build_dataobj_i_wlan_access_status, |
| DATAOBJ_FLAG_CR, |
| &evt->i_wlan_access_status, |
| NULL); |
| case STK_EVENT_TYPE_NETWORK_REJECTION: |
| return build_dataobj(builder, |
| build_dataobj_location_info, 0, |
| &evt->network_rejection.location, |
| build_dataobj_routing_area_id, 0, |
| &evt->network_rejection.rai, |
| build_dataobj_tracking_area_id, 0, |
| &evt->network_rejection.tai, |
| build_dataobj_access_technology, |
| DATAOBJ_FLAG_CR, |
| &evt->network_rejection.access_tech, |
| build_dataobj_update_attach_type, |
| DATAOBJ_FLAG_CR, |
| &evt->network_rejection.update_attach, |
| build_dataobj_rejection_cause_code, |
| DATAOBJ_FLAG_CR, |
| &evt->network_rejection.cause, |
| NULL); |
| case STK_EVENT_TYPE_HCI_CONNECTIVITY_EVENT: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static bool build_envelope_terminal_apps(struct stk_tlv_builder *builder, |
| const struct stk_envelope *envelope) |
| { |
| const struct stk_envelope_terminal_apps *ta = &envelope->terminal_apps; |
| int i; |
| |
| if (!build_dataobj(builder, |
| build_envelope_dataobj_device_ids, |
| DATAOBJ_FLAG_CR, envelope, NULL)) |
| return false; |
| |
| for (i = 0; i < ta->count; i++) |
| if (!build_dataobj(builder, |
| build_dataobj_registry_application_data, |
| 0, &ta->list[i], NULL)) |
| return false; |
| |
| return build_dataobj(builder, |
| build_dataobj_last_envelope, |
| 0, &ta->last, NULL); |
| } |
| |
| const uint8_t *stk_pdu_from_envelope(const struct stk_envelope *envelope, |
| unsigned int *out_length) |
| { |
| struct ber_tlv_builder btlv; |
| struct stk_tlv_builder builder; |
| bool ok = true; |
| static uint8_t buffer[512]; |
| uint8_t *pdu; |
| |
| if (ber_tlv_builder_init(&btlv, buffer, sizeof(buffer)) != TRUE) |
| return NULL; |
| |
| if (!stk_tlv_builder_recurse(&builder, &btlv, envelope->type)) |
| return NULL; |
| |
| switch (envelope->type) { |
| case STK_ENVELOPE_TYPE_SMS_PP_DOWNLOAD: |
| ok = build_dataobj(&builder, |
| build_envelope_dataobj_device_ids, |
| DATAOBJ_FLAG_CR, |
| envelope, |
| build_dataobj_address, 0, |
| &envelope->sms_pp_download.address, |
| build_dataobj_gsm_sms_tpdu, |
| DATAOBJ_FLAG_CR, |
| &envelope->sms_pp_download.message, |
| NULL); |
| break; |
| case STK_ENVELOPE_TYPE_CBS_PP_DOWNLOAD: |
| ok = build_dataobj(&builder, |
| build_envelope_dataobj_device_ids, |
| DATAOBJ_FLAG_CR, |
| envelope, |
| build_dataobj_cbs_page, |
| DATAOBJ_FLAG_CR, |
| &envelope->cbs_pp_download.page, |
| NULL); |
| break; |
| case STK_ENVELOPE_TYPE_MENU_SELECTION: |
| ok = build_dataobj(&builder, |
| build_envelope_dataobj_device_ids, |
| DATAOBJ_FLAG_CR, |
| envelope, |
| build_dataobj_item_id, DATAOBJ_FLAG_CR, |
| &envelope->menu_selection.item_id, |
| build_dataobj_help_request, 0, |
| &envelope->menu_selection.help_request, |
| NULL); |
| break; |
| case STK_ENVELOPE_TYPE_CALL_CONTROL: |
| ok = build_envelope_call_control(&builder, envelope); |
| break; |
| case STK_ENVELOPE_TYPE_MO_SMS_CONTROL: |
| /* |
| * Comprehension Required according to the specs but not |
| * enabled in conformance tests in 3GPP 31.124. |
| */ |
| ok = build_dataobj(&builder, |
| build_envelope_dataobj_device_ids, 0, |
| envelope, |
| build_dataobj_address, 0, |
| &envelope->sms_mo_control.sc_address, |
| build_dataobj_address, 0, |
| &envelope->sms_mo_control.dest_address, |
| build_dataobj_location_info, 0, |
| &envelope->sms_mo_control.location, |
| NULL); |
| break; |
| case STK_ENVELOPE_TYPE_EVENT_DOWNLOAD: |
| ok = build_envelope_event_download(&builder, envelope); |
| break; |
| case STK_ENVELOPE_TYPE_TIMER_EXPIRATION: |
| ok = build_dataobj(&builder, |
| build_envelope_dataobj_device_ids, |
| DATAOBJ_FLAG_CR, |
| envelope, |
| build_dataobj_timer_id, |
| DATAOBJ_FLAG_CR, |
| &envelope->timer_expiration.id, |
| build_dataobj_timer_value, |
| DATAOBJ_FLAG_CR, |
| &envelope->timer_expiration.value, |
| NULL); |
| break; |
| case STK_ENVELOPE_TYPE_USSD_DOWNLOAD: |
| ok = build_dataobj(&builder, |
| build_envelope_dataobj_device_ids, |
| DATAOBJ_FLAG_CR, |
| envelope, |
| build_dataobj_ussd_string, |
| DATAOBJ_FLAG_CR, |
| &envelope->ussd_data_download.string, |
| NULL); |
| break; |
| case STK_ENVELOPE_TYPE_MMS_TRANSFER_STATUS: |
| ok = build_dataobj(&builder, |
| build_envelope_dataobj_device_ids, |
| DATAOBJ_FLAG_CR, |
| envelope, |
| build_dataobj_file, DATAOBJ_FLAG_CR, |
| &envelope->mms_status.transfer_file, |
| build_dataobj_mms_id, 0, |
| &envelope->mms_status.id, |
| build_dataobj_mms_transfer_status, 0, |
| &envelope->mms_status.transfer_status, |
| NULL); |
| break; |
| case STK_ENVELOPE_TYPE_MMS_NOTIFICATION: |
| ok = build_dataobj(&builder, |
| build_envelope_dataobj_device_ids, |
| DATAOBJ_FLAG_CR, |
| envelope, |
| build_dataobj_mms_notification, |
| DATAOBJ_FLAG_CR, |
| &envelope->mms_notification.msg, |
| build_dataobj_last_envelope, 0, |
| &envelope->mms_notification.last, |
| NULL); |
| break; |
| case STK_ENVELOPE_TYPE_TERMINAL_APP: |
| ok = build_envelope_terminal_apps(&builder, envelope); |
| break; |
| default: |
| return NULL; |
| }; |
| |
| if (!ok) |
| return NULL; |
| |
| ber_tlv_builder_optimize(&btlv, &pdu, out_length); |
| |
| return pdu; |
| } |
| |
| static const char *html_colors[] = { |
| "#000000", /* Black */ |
| "#808080", /* Dark Grey */ |
| "#C11B17", /* Dark Red */ |
| "#FBB117", /* Dark Yellow */ |
| "#347235", /* Dark Green */ |
| "#307D7E", /* Dark Cyan */ |
| "#0000A0", /* Dark Blue */ |
| "#C031C7", /* Dark Magenta */ |
| "#C0C0C0", /* Grey */ |
| "#FFFFFF", /* White */ |
| "#FF0000", /* Bright Red */ |
| "#FFFF00", /* Bright Yellow */ |
| "#00FF00", /* Bright Green */ |
| "#00FFFF", /* Bright Cyan */ |
| "#0000FF", /* Bright Blue */ |
| "#FF00FF", /* Bright Magenta */ |
| }; |
| |
| #define STK_TEXT_FORMAT_ALIGN_MASK 0x03 |
| #define STK_TEXT_FORMAT_FONT_MASK 0x0C |
| #define STK_TEXT_FORMAT_STYLE_MASK 0xF0 |
| #define STK_DEFAULT_TEXT_ALIGNMENT 0x00 |
| #define STK_TEXT_FORMAT_INIT 0x9003 |
| |
| /* Defined in ETSI 123 40 9.2.3.24.10.1.1 */ |
| enum stk_text_format_code { |
| STK_TEXT_FORMAT_LEFT_ALIGN = 0x00, |
| STK_TEXT_FORMAT_CENTER_ALIGN = 0x01, |
| STK_TEXT_FORMAT_RIGHT_ALIGN = 0x02, |
| STK_TEXT_FORMAT_NO_ALIGN = 0x03, |
| STK_TEXT_FORMAT_FONT_SIZE_LARGE = 0x04, |
| STK_TEXT_FORMAT_FONT_SIZE_SMALL = 0x08, |
| STK_TEXT_FORMAT_FONT_SIZE_RESERVED = 0x0c, |
| STK_TEXT_FORMAT_STYLE_BOLD = 0x10, |
| STK_TEXT_FORMAT_STYLE_ITALIC = 0x20, |
| STK_TEXT_FORMAT_STYLE_UNDERLINED = 0x40, |
| STK_TEXT_FORMAT_STYLE_STRIKETHROUGH = 0x80, |
| }; |
| |
| static void end_format(struct l_string *string, uint16_t attr) |
| { |
| uint8_t code = attr & 0xFF; |
| uint8_t color = (attr >> 8) & 0xFF; |
| |
| if ((code & ~STK_TEXT_FORMAT_ALIGN_MASK) || color) |
| l_string_append(string, "</span>"); |
| |
| if ((code & STK_TEXT_FORMAT_ALIGN_MASK) != STK_TEXT_FORMAT_NO_ALIGN) |
| l_string_append(string, "</div>"); |
| } |
| |
| static void start_format(struct l_string *string, uint16_t attr) |
| { |
| uint8_t code = attr & 0xFF; |
| uint8_t color = (attr >> 8) & 0xFF; |
| uint8_t align = code & STK_TEXT_FORMAT_ALIGN_MASK; |
| uint8_t font = code & STK_TEXT_FORMAT_FONT_MASK; |
| uint8_t style = code & STK_TEXT_FORMAT_STYLE_MASK; |
| int fg = color & 0x0f; |
| int bg = (color >> 4) & 0x0f; |
| |
| /* align formatting applies to a block of text */ |
| if (align != STK_TEXT_FORMAT_NO_ALIGN) |
| l_string_append(string, "<div style=\""); |
| |
| switch (align) { |
| case STK_TEXT_FORMAT_RIGHT_ALIGN: |
| l_string_append(string, "text-align: right;\">"); |
| break; |
| case STK_TEXT_FORMAT_CENTER_ALIGN: |
| l_string_append(string, "text-align: center;\">"); |
| break; |
| case STK_TEXT_FORMAT_LEFT_ALIGN: |
| l_string_append(string, "text-align: left;\">"); |
| break; |
| } |
| |
| if ((font == 0) && (style == 0) && (color == 0)) |
| return; |
| |
| /* font, style, and color are inline */ |
| l_string_append(string, "<span style=\""); |
| |
| switch (font) { |
| case STK_TEXT_FORMAT_FONT_SIZE_LARGE: |
| l_string_append(string, "font-size: big;"); |
| break; |
| case STK_TEXT_FORMAT_FONT_SIZE_SMALL: |
| l_string_append(string, "font-size: small;"); |
| break; |
| } |
| |
| if (style & STK_TEXT_FORMAT_STYLE_BOLD) |
| l_string_append(string, "font-weight: bold;"); |
| if (style & STK_TEXT_FORMAT_STYLE_ITALIC) |
| l_string_append(string, "font-style: italic;"); |
| if (style & STK_TEXT_FORMAT_STYLE_UNDERLINED) |
| l_string_append(string, "text-decoration: underline;"); |
| if (style & STK_TEXT_FORMAT_STYLE_STRIKETHROUGH) |
| l_string_append(string, "text-decoration: line-through;"); |
| |
| /* add any color */ |
| l_string_append_printf(string, "color: %s;", html_colors[fg]); |
| l_string_append_printf(string, "background-color: %s;", |
| html_colors[bg]); |
| l_string_append(string, "\">"); |
| } |
| |
| char *stk_text_to_html(const char *utf8, |
| const uint16_t *attrs, int num_attrs) |
| { |
| long text_len = l_utf8_strlen(utf8); |
| struct l_string *string = l_string_new(strlen(utf8) + 1); |
| short *formats; |
| int pos, i, j; |
| uint16_t start, end, len, attr, prev_attr; |
| uint8_t code, color, align; |
| const char *text = utf8; |
| int attrs_len = num_attrs * 4; |
| |
| formats = l_new(int16_t, text_len + 1); |
| |
| /* we will need formatting at the position beyond the last char */ |
| for (i = 0; i <= text_len; i++) |
| formats[i] = STK_TEXT_FORMAT_INIT; |
| |
| for (i = 0; i < attrs_len; i += 4) { |
| start = attrs[i]; |
| len = attrs[i + 1]; |
| code = attrs[i + 2] & 0xFF; |
| color = attrs[i + 3] & 0xFF; |
| |
| if (len == 0) |
| end = text_len; |
| else |
| end = start + len; |
| |
| /* sanity check values */ |
| if (start > end || end > text_len) |
| continue; |
| |
| /* |
| * if the alignment is the same as either the default |
| * or the last alignment used, don't set any alignment |
| * value. |
| */ |
| if (start == 0) |
| align = STK_TEXT_FORMAT_NO_ALIGN; |
| else { |
| align = formats[start - 1] & |
| STK_TEXT_FORMAT_ALIGN_MASK; |
| } |
| |
| if ((code & STK_TEXT_FORMAT_ALIGN_MASK) == align) |
| code |= STK_TEXT_FORMAT_NO_ALIGN; |
| |
| attr = code | (color << 8); |
| |
| for (j = start; j < end; j++) |
| formats[j] = attr; |
| } |
| |
| prev_attr = STK_TEXT_FORMAT_INIT; |
| |
| for (pos = 0; pos <= text_len; pos++) { |
| wchar_t c; |
| int len; |
| |
| attr = formats[pos]; |
| if (attr != prev_attr) { |
| if (prev_attr != STK_TEXT_FORMAT_INIT) |
| end_format(string, prev_attr); |
| |
| if (attr != STK_TEXT_FORMAT_INIT) |
| start_format(string, attr); |
| |
| prev_attr = attr; |
| } |
| |
| if (pos == text_len) |
| break; |
| |
| len = l_utf8_get_codepoint(text, 4, &c); |
| switch (c) { |
| case '\n': |
| l_string_append(string, "<br/>"); |
| break; |
| case '\r': |
| /* If the next character is a newline, consume it */ |
| if ((pos + 1 < text_len) && (text[len] == '\n')) { |
| pos += 1; |
| len += 1; |
| } |
| |
| l_string_append(string, "<br/>"); |
| break; |
| case '<': |
| l_string_append(string, "<"); |
| break; |
| case '>': |
| l_string_append(string, ">"); |
| break; |
| case '&': |
| l_string_append(string, "&"); |
| break; |
| default: |
| l_string_append_fixed(string, text, len); |
| } |
| |
| text += len; |
| } |
| |
| l_free(formats); |
| |
| /* return characters from string. Caller must free char data */ |
| return l_string_unwrap(string); |
| } |
| |
| static const char chars_table[] = { |
| '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', |
| 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', |
| 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', |
| 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', |
| 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '+', '.' }; |
| |
| char *stk_image_to_xpm(const uint8_t *img, unsigned int len, |
| enum stk_img_scheme scheme, const uint8_t *clut, |
| uint16_t clut_len) |
| { |
| uint8_t width, height; |
| unsigned int ncolors, nbits, entry, cpp; |
| unsigned int i, j; |
| int bit, k; |
| struct l_string *xpm; |
| unsigned int pos = 0; |
| const char xpm_header[] = "/* XPM */\n"; |
| const char declaration[] = "static char *xpm[] = {\n"; |
| char c[3]; |
| |
| if (img == NULL) |
| return NULL; |
| |
| /* sanity check length */ |
| if (len < 3) |
| return NULL; |
| |
| width = img[pos++]; |
| height = img[pos++]; |
| |
| if (scheme == STK_IMG_SCHEME_BASIC) { |
| nbits = 1; |
| ncolors = 2; |
| } else { |
| /* sanity check length */ |
| if ((pos + 4 > len) || (clut == NULL)) |
| return NULL; |
| |
| nbits = img[pos++]; |
| ncolors = img[pos++]; |
| |
| /* the value of zero should be interpreted as 256 */ |
| if (ncolors == 0) |
| ncolors = 256; |
| |
| /* skip clut offset bytes */ |
| pos += 2; |
| |
| if ((ncolors * 3) > clut_len) |
| return NULL; |
| } |
| |
| if (pos + ((width * height + 7) / 8) > len) |
| return NULL; |
| |
| /* determine the number of chars need to represent the pixel */ |
| cpp = ncolors > 64 ? 2 : 1; |
| |
| /* |
| * space needed: |
| * header line |
| * declaration and beginning of assignment line |
| * values - max length of 19 |
| * colors - ncolors * (cpp + whitespace + deliminators + color) |
| * pixels - width * height * cpp + height deliminators "",\n |
| * end of assignment - 2 chars "};" |
| */ |
| xpm = l_string_new(strlen(xpm_header) + strlen(declaration) + |
| 19 + ((cpp + 14) * ncolors) + |
| (width * height * cpp) + (4 * height) + 2); |
| |
| /* add header, declaration, values */ |
| l_string_append(xpm, xpm_header); |
| l_string_append(xpm, declaration); |
| l_string_append_printf(xpm, "\"%d %d %d %d\",\n", width, height, |
| ncolors, cpp); |
| |
| /* create colors */ |
| if (scheme == STK_IMG_SCHEME_BASIC) { |
| l_string_append(xpm, "\"0\tc #000000\",\n"); |
| l_string_append(xpm, "\"1\tc #FFFFFF\",\n"); |
| } else { |
| for (i = 0; i < ncolors; i++) { |
| /* lookup char representation of this number */ |
| if (ncolors > 64) { |
| c[0] = chars_table[i / 64]; |
| c[1] = chars_table[i % 64]; |
| c[2] = '\0'; |
| } else { |
| c[0] = chars_table[i % 64]; |
| c[1] = '\0'; |
| } |
| |
| if ((i == (ncolors - 1)) && |
| scheme == STK_IMG_SCHEME_TRANSPARENCY) |
| l_string_append_printf(xpm, |
| "\"%s\tc None\",\n", c); |
| else |
| l_string_append_printf(xpm, |
| "\"%s\tc #%02hhX%02hhX%02hhX\",\n", |
| c, clut[0], clut[1], clut[2]); |
| clut += 3; |
| } |
| } |
| |
| /* height rows of width pixels */ |
| k = 7; |
| for (i = 0; i < height; i++) { |
| l_string_append(xpm, "\""); |
| for (j = 0; j < width; j++) { |
| entry = 0; |
| for (bit = nbits - 1; bit >= 0; bit--) { |
| entry |= (img[pos] >> k & 0x1) << bit; |
| k--; |
| |
| /* see if we crossed a byte boundary */ |
| if (k < 0) { |
| k = 7; |
| pos++; |
| } |
| } |
| |
| /* lookup char representation of this number */ |
| if (ncolors > 64) { |
| c[0] = chars_table[entry / 64]; |
| c[1] = chars_table[entry % 64]; |
| c[2] = '\0'; |
| } else { |
| c[0] = chars_table[entry % 64]; |
| c[1] = '\0'; |
| } |
| |
| l_string_append_printf(xpm, "%s", c); |
| } |
| |
| l_string_append(xpm, "\",\n"); |
| } |
| |
| l_string_append(xpm, "};"); |
| |
| /* Caller must free char data */ |
| return l_string_unwrap(xpm); |
| } |