From 82d9bd7499973ee75e78108f10cbafabc3ce62f7 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Fri, 10 Feb 2017 21:10:52 -0600 Subject: qmicli: add support for --nas-get-operator-name --- src/qmicli/Makefile.am | 4 +- src/qmicli/qmicli-charsets.c | 231 +++++++++++++++++++++++++++++++++++++++++++ src/qmicli/qmicli-charsets.h | 29 ++++++ src/qmicli/qmicli-nas.c | 155 +++++++++++++++++++++++++++++ 4 files changed, 418 insertions(+), 1 deletion(-) create mode 100644 src/qmicli/qmicli-charsets.c create mode 100644 src/qmicli/qmicli-charsets.h diff --git a/src/qmicli/Makefile.am b/src/qmicli/Makefile.am index eb3c574..1d701ad 100644 --- a/src/qmicli/Makefile.am +++ b/src/qmicli/Makefile.am @@ -25,7 +25,9 @@ qmicli_SOURCES = \ qmicli-uim.c \ qmicli-wms.c \ qmicli-wda.c \ - qmicli-voice.c + qmicli-voice.c \ + qmicli-charsets.c \ + qmicli-charsets.h qmicli_LDADD = \ $(MBIM_LIBS) \ diff --git a/src/qmicli/qmicli-charsets.c b/src/qmicli/qmicli-charsets.c new file mode 100644 index 0000000..7705ba2 --- /dev/null +++ b/src/qmicli/qmicli-charsets.c @@ -0,0 +1,231 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * qmicli -- Command line interface to control QMI devices + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Copyright (C) 2010-2017 Red Hat, Inc. + */ + +#include "config.h" + +#include +#include +#include + +#include + +#include "qmicli-charsets.h" + +/* GSM 03.38 encoding conversion stuff */ + +#define GSM_DEF_ALPHABET_SIZE 128 +#define GSM_EXT_ALPHABET_SIZE 10 + +typedef struct GsmUtf8Mapping { + gchar chars[3]; + guint8 len; + guint8 gsm; /* only used for extended GSM charset */ +} GsmUtf8Mapping; + +#define ONE(a) { {a, 0x00, 0x00}, 1, 0 } +#define TWO(a, b) { {a, b, 0x00}, 2, 0 } + +/** + * gsm_def_utf8_alphabet: + * + * Mapping from GSM default alphabet to UTF-8. + * + * ETSI GSM 03.38, version 6.0.1, section 6.2.1; Default alphabet. Mapping to UCS-2. + * Mapping according to http://unicode.org/Public/MAPPINGS/ETSI/GSM0338.TXT + */ +static const GsmUtf8Mapping gsm_def_utf8_alphabet[GSM_DEF_ALPHABET_SIZE] = { + /* @ £ $ ¥ */ + ONE(0x40), TWO(0xc2, 0xa3), ONE(0x24), TWO(0xc2, 0xa5), + /* è é ù ì */ + TWO(0xc3, 0xa8), TWO(0xc3, 0xa9), TWO(0xc3, 0xb9), TWO(0xc3, 0xac), + /* ò Ç \n Ø */ + TWO(0xc3, 0xb2), TWO(0xc3, 0x87), ONE(0x0a), TWO(0xc3, 0x98), + /* ø \r Å å */ + TWO(0xc3, 0xb8), ONE(0x0d), TWO(0xc3, 0x85), TWO(0xc3, 0xa5), + /* Δ _ Φ Γ */ + TWO(0xce, 0x94), ONE(0x5f), TWO(0xce, 0xa6), TWO(0xce, 0x93), + /* Λ Ω Π Ψ */ + TWO(0xce, 0x9b), TWO(0xce, 0xa9), TWO(0xce, 0xa0), TWO(0xce, 0xa8), + /* Σ Θ Ξ Escape Code */ + TWO(0xce, 0xa3), TWO(0xce, 0x98), TWO(0xce, 0x9e), ONE(0xa0), + /* Æ æ ß É */ + TWO(0xc3, 0x86), TWO(0xc3, 0xa6), TWO(0xc3, 0x9f), TWO(0xc3, 0x89), + /* ' ' ! " # */ + ONE(0x20), ONE(0x21), ONE(0x22), ONE(0x23), + /* ¤ % & ' */ + TWO(0xc2, 0xa4), ONE(0x25), ONE(0x26), ONE(0x27), + /* ( ) * + */ + ONE(0x28), ONE(0x29), ONE(0x2a), ONE(0x2b), + /* , - . / */ + ONE(0x2c), ONE(0x2d), ONE(0x2e), ONE(0x2f), + /* 0 1 2 3 */ + ONE(0x30), ONE(0x31), ONE(0x32), ONE(0x33), + /* 4 5 6 7 */ + ONE(0x34), ONE(0x35), ONE(0x36), ONE(0x37), + /* 8 9 : ; */ + ONE(0x38), ONE(0x39), ONE(0x3a), ONE(0x3b), + /* < = > ? */ + ONE(0x3c), ONE(0x3d), ONE(0x3e), ONE(0x3f), + /* ¡ A B C */ + TWO(0xc2, 0xa1), ONE(0x41), ONE(0x42), ONE(0x43), + /* D E F G */ + ONE(0x44), ONE(0x45), ONE(0x46), ONE(0x47), + /* H I J K */ + ONE(0x48), ONE(0x49), ONE(0x4a), ONE(0x4b), + /* L M N O */ + ONE(0x4c), ONE(0x4d), ONE(0x4e), ONE(0x4f), + /* P Q R S */ + ONE(0x50), ONE(0x51), ONE(0x52), ONE(0x53), + /* T U V W */ + ONE(0x54), ONE(0x55), ONE(0x56), ONE(0x57), + /* X Y Z Ä */ + ONE(0x58), ONE(0x59), ONE(0x5a), TWO(0xc3, 0x84), + /* Ö Ñ Ü § */ + TWO(0xc3, 0x96), TWO(0xc3, 0x91), TWO(0xc3, 0x9c), TWO(0xc2, 0xa7), + /* ¿ a b c */ + TWO(0xc2, 0xbf), ONE(0x61), ONE(0x62), ONE(0x63), + /* d e f g */ + ONE(0x64), ONE(0x65), ONE(0x66), ONE(0x67), + /* h i j k */ + ONE(0x68), ONE(0x69), ONE(0x6a), ONE(0x6b), + /* l m n o */ + ONE(0x6c), ONE(0x6d), ONE(0x6e), ONE(0x6f), + /* p q r s */ + ONE(0x70), ONE(0x71), ONE(0x72), ONE(0x73), + /* t u v w */ + ONE(0x74), ONE(0x75), ONE(0x76), ONE(0x77), + /* x y z ä */ + ONE(0x78), ONE(0x79), ONE(0x7a), TWO(0xc3, 0xa4), + /* ö ñ ü à */ + TWO(0xc3, 0xb6), TWO(0xc3, 0xb1), TWO(0xc3, 0xbc), TWO(0xc3, 0xa0) +}; + +static guint8 +gsm_def_char_to_utf8 (const guint8 gsm, guint8 out_utf8[2]) +{ + g_return_val_if_fail (gsm < GSM_DEF_ALPHABET_SIZE, 0); + memcpy (&out_utf8[0], &gsm_def_utf8_alphabet[gsm].chars[0], gsm_def_utf8_alphabet[gsm].len); + return gsm_def_utf8_alphabet[gsm].len; +} + +#define EONE(a, g) { {a, 0x00, 0x00}, 1, g } +#define ETHR(a, b, c, g) { {a, b, c}, 3, g } + +/** + * gsm_ext_utf8_alphabet: + * + * Mapping from GSM extended alphabet to UTF-8. + * + */ +static const GsmUtf8Mapping gsm_ext_utf8_alphabet[GSM_EXT_ALPHABET_SIZE] = { + /* form feed ^ { } */ + EONE(0x0c, 0x0a), EONE(0x5e, 0x14), EONE(0x7b, 0x28), EONE(0x7d, 0x29), + /* \ [ ~ ] */ + EONE(0x5c, 0x2f), EONE(0x5b, 0x3c), EONE(0x7e, 0x3d), EONE(0x5d, 0x3e), + /* | € */ + EONE(0x7c, 0x40), ETHR(0xe2, 0x82, 0xac, 0x65) +}; + +#define GSM_ESCAPE_CHAR 0x1b + +static guint8 +gsm_ext_char_to_utf8 (const guint8 gsm, guint8 out_utf8[3]) +{ + int i; + + for (i = 0; i < GSM_EXT_ALPHABET_SIZE; i++) { + if (gsm == gsm_ext_utf8_alphabet[i].gsm) { + memcpy (&out_utf8[0], &gsm_ext_utf8_alphabet[i].chars[0], gsm_ext_utf8_alphabet[i].len); + return gsm_ext_utf8_alphabet[i].len; + } + } + return 0; +} + +guint8 * +qmicli_charset_gsm_unpack (const guint8 *gsm, + guint32 num_septets, + guint32 *out_unpacked_len) +{ + GByteArray *unpacked; + int i; + + unpacked = g_byte_array_sized_new (num_septets + 1); + + for (i = 0; i < num_septets; i++) { + guint8 bits_here, bits_in_next, octet, offset, c; + guint32 start_bit; + + start_bit = i * 7; /* Overall bit offset of char in buffer */ + offset = start_bit % 8; /* Offset to start of char in this byte */ + bits_here = offset ? (8 - offset) : 7; + bits_in_next = 7 - bits_here; + + /* Grab bits in the current byte */ + octet = gsm[start_bit / 8]; + c = (octet >> offset) & (0xFF >> (8 - bits_here)); + + /* Grab any bits that spilled over to next byte */ + if (bits_in_next) { + octet = gsm[(start_bit / 8) + 1]; + c |= (octet & (0xFF >> (8 - bits_in_next))) << bits_here; + } + g_byte_array_append (unpacked, &c, 1); + } + + *out_unpacked_len = unpacked->len; + return g_byte_array_free (unpacked, FALSE); +} + +guint8 * +qmicli_charset_gsm_unpacked_to_utf8 (const guint8 *gsm, guint32 len) +{ + int i; + GByteArray *utf8; + + g_return_val_if_fail (gsm != NULL, NULL); + g_return_val_if_fail (len < 4096, NULL); + + /* worst case initial length */ + utf8 = g_byte_array_sized_new (len * 2 + 1); + + for (i = 0; i < len; i++) { + guint8 uchars[4]; + guint8 ulen; + + if (gsm[i] == GSM_ESCAPE_CHAR) { + /* Extended alphabet, decode next char */ + ulen = gsm_ext_char_to_utf8 (gsm[i+1], uchars); + if (ulen) + i += 1; + } else { + /* Default alphabet */ + ulen = gsm_def_char_to_utf8 (gsm[i], uchars); + } + + if (ulen) + g_byte_array_append (utf8, &uchars[0], ulen); + else + g_byte_array_append (utf8, (guint8 *) "?", 1); + } + + g_byte_array_append (utf8, (guint8 *) "\0", 1); /* NULL terminator */ + return g_byte_array_free (utf8, FALSE); +} diff --git a/src/qmicli/qmicli-charsets.h b/src/qmicli/qmicli-charsets.h new file mode 100644 index 0000000..8221145 --- /dev/null +++ b/src/qmicli/qmicli-charsets.h @@ -0,0 +1,29 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * qmicli -- Command line interface to control QMI devices + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Copyright (C) 2010-2017 Red Hat, Inc. + */ + +#include "config.h" + +guint8 * qmicli_charset_gsm_unpack (const guint8 *gsm, + guint32 num_septets, + guint32 *out_unpacked_len); + +guint8 * qmicli_charset_gsm_unpacked_to_utf8 (const guint8 *gsm, + guint32 len); + diff --git a/src/qmicli/qmicli-nas.c b/src/qmicli/qmicli-nas.c index c9a356f..3ee9653 100644 --- a/src/qmicli/qmicli-nas.c +++ b/src/qmicli/qmicli-nas.c @@ -32,6 +32,7 @@ #include "qmicli.h" #include "qmicli-helpers.h" +#include "qmicli-charsets.h" /* Context */ typedef struct { @@ -54,6 +55,7 @@ static gchar *set_system_selection_preference_str; static gboolean network_scan_flag; static gboolean get_cell_location_info_flag; static gboolean force_network_search_flag; +static gboolean get_operator_name_flag; static gboolean get_lte_cphy_ca_info_flag; static gboolean get_rf_band_info_flag; static gboolean get_supported_messages_flag; @@ -109,6 +111,10 @@ static GOptionEntry entries[] = { "Force network search", NULL }, + { "nas-get-operator-name", 0, 0, G_OPTION_ARG_NONE, &get_operator_name_flag, + "Get operator name data", + NULL + }, { "nas-get-lte-cphy-ca-info", 0, 0, G_OPTION_ARG_NONE, &get_lte_cphy_ca_info_flag, "Get LTE Cphy CA Info", NULL @@ -168,6 +174,7 @@ qmicli_nas_options_enabled (void) network_scan_flag + get_cell_location_info_flag + force_network_search_flag + + get_operator_name_flag + get_lte_cphy_ca_info_flag + get_rf_band_info_flag + get_supported_messages_flag + @@ -2837,6 +2844,142 @@ force_network_search_ready (QmiClientNas *client, operation_shutdown (TRUE); } +static gchar * +garray_of_uint8_to_string (GArray *array, + QmiNasPlmnEncodingScheme scheme) +{ + gchar *decoded = NULL; + + if (array->len == 0) + return NULL; + + if (scheme == QMI_NAS_PLMN_ENCODING_SCHEME_GSM) { + guint8 *unpacked; + guint32 unpacked_len = 0; + + /* Unpack the GSM and decode it */ + unpacked = qmicli_charset_gsm_unpack ((const guint8 *) array->data, (array->len * 8) / 7, &unpacked_len); + if (unpacked) { + decoded = (gchar *) qmicli_charset_gsm_unpacked_to_utf8 (unpacked, unpacked_len); + g_free (unpacked); + } + } else if (scheme == QMI_NAS_PLMN_ENCODING_SCHEME_UCS2LE) { + decoded = g_convert ( + array->data, + array->len, + "UTF-8", + "UCS-2LE", + NULL, + NULL, + NULL); + } + + return decoded; +} + +static void +get_operator_name_ready (QmiClientNas *client, + GAsyncResult *res) +{ + QmiMessageNasGetOperatorNameOutput *output; + GError *error = NULL; + QmiNasNetworkNameDisplayCondition spn_display_condition; + const gchar *spn; + const gchar *operator_name; + GArray *array; + + output = qmi_client_nas_get_operator_name_finish (client, res, &error); + if (!output) { + g_printerr ("error: operation failed: %s\n", error->message); + g_error_free (error); + operation_shutdown (FALSE); + return; + } + + if (!qmi_message_nas_get_operator_name_output_get_result (output, &error)) { + g_printerr ("error: couldn't get operator name data: %s\n", error->message); + g_error_free (error); + qmi_message_nas_get_operator_name_output_unref (output); + operation_shutdown (FALSE); + return; + } + + g_print ("[%s] Successfully got operator name data\n", + qmi_device_get_path_display (ctx->device)); + + if (qmi_message_nas_get_operator_name_output_get_service_provider_name ( + output, + &spn_display_condition, + &spn, + NULL)) { + gchar *dc_string = qmi_nas_network_name_display_condition_build_string_from_mask (spn_display_condition); + + g_print ("Service Provider Name\n"); + g_print ("\tDisplay Condition: '%s'\n" + "\tName : '%s'\n", + dc_string, + spn); + g_free (dc_string); + } + + if (qmi_message_nas_get_operator_name_output_get_operator_string_name ( + output, + &operator_name, + NULL)) { + g_print ("Operator Name: '%s'\n", + operator_name); + } + + if (qmi_message_nas_get_operator_name_output_get_operator_plmn_list (output, &array, NULL)) { + guint i; + + g_print ("PLMN List:\n"); + for (i = 0; i < array->len; i++) { + QmiMessageNasGetOperatorNameOutputOperatorPlmnListElement *element; + gchar *mnc; + + element = &g_array_index (array, QmiMessageNasGetOperatorNameOutputOperatorPlmnListElement, i); + mnc = g_strdup (element->mnc); + if (strlen (mnc) >= 3 && (mnc[2] == 'F' || mnc[2] == 'f')) + mnc[2] = '\0'; + g_print ("\tMCC/MNC: '%s-%s'%s LAC Range: %u->%u\tPNN Record: %u\n", + element->mcc, + mnc, + mnc[2] == '\0' ? " " : "", + element->lac1, + element->lac2, + element->plmn_name_record_identifier); + } + } + + if (qmi_message_nas_get_operator_name_output_get_operator_plmn_name (output, &array, NULL)) { + guint i; + + g_print ("PLMN Names:\n"); + for (i = 0; i < array->len; i++) { + QmiMessageNasGetOperatorNameOutputOperatorPlmnNameElement *element; + gchar *long_name; + gchar *short_name; + + element = &g_array_index (array, QmiMessageNasGetOperatorNameOutputOperatorPlmnNameElement, i); + long_name = garray_of_uint8_to_string (element->long_name, element->name_encoding); + short_name = garray_of_uint8_to_string (element->short_name, element->name_encoding); + g_print ("\t%d: '%s'%s%s%s\t\tCountry: '%s'\n", + i, + long_name ?: "", + short_name ? " ('" : "", + short_name ?: "", + short_name ? "')" : "", + qmi_nas_plmn_name_country_initials_get_string (element->short_country_initials)); + g_free (long_name); + g_free (short_name); + } + } + + qmi_message_nas_get_operator_name_output_unref (output); + operation_shutdown (TRUE); +} + static void get_lte_cphy_ca_info_ready (QmiClientNas *client, GAsyncResult *res) @@ -3236,6 +3379,18 @@ qmicli_nas_run (QmiDevice *device, return; } + /* Request to get operator name data */ + if (get_operator_name_flag) { + g_debug ("Asynchronously getting operator name data..."); + qmi_client_nas_get_operator_name (ctx->client, + NULL, + 10, + ctx->cancellable, + (GAsyncReadyCallback)get_operator_name_ready, + NULL); + return; + } + /* Request to get carrier aggregation info? */ if (get_lte_cphy_ca_info_flag) { g_debug ("Asynchronously getting carrier aggregation info ..."); -- cgit v1.1