/* * * oFono - Open Source Telephony * * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. * Copyright (C) 2018 Gemalto M2M * * 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 #endif #include #include #include #include #include #define OFONO_API_SUBJECT_TO_CHANGE #include #include #include #include "atutil.h" #include "vendor.h" static const char *cpin_prefix[] = { "+CPIN:", NULL }; struct at_util_sim_state_query { GAtChat *chat; guint cpin_poll_source; guint cpin_poll_count; guint interval; guint num_times; at_util_sim_inserted_cb_t cb; void *userdata; GDestroyNotify destroy; }; static gboolean cpin_check(gpointer userdata); void decode_at_error(struct ofono_error *error, const char *final) { if (!strcmp(final, "OK")) { error->type = OFONO_ERROR_TYPE_NO_ERROR; error->error = 0; } else if (g_str_has_prefix(final, "+CMS ERROR:")) { error->type = OFONO_ERROR_TYPE_CMS; error->error = strtol(&final[11], NULL, 0); } else if (g_str_has_prefix(final, "+CME ERROR:")) { error->type = OFONO_ERROR_TYPE_CME; error->error = strtol(&final[11], NULL, 0); } else { error->type = OFONO_ERROR_TYPE_FAILURE; error->error = 0; } } gint at_util_call_compare_by_status(gconstpointer a, gconstpointer b) { const struct ofono_call *call = a; int status = GPOINTER_TO_INT(b); if (status != call->status) return 1; return 0; } gint at_util_call_compare_by_phone_number(gconstpointer a, gconstpointer b) { const struct ofono_call *call = a; const struct ofono_phone_number *pb = b; return memcmp(&call->phone_number, pb, sizeof(struct ofono_phone_number)); } gint at_util_call_compare_by_id(gconstpointer a, gconstpointer b) { const struct ofono_call *call = a; unsigned int id = GPOINTER_TO_UINT(b); if (id < call->id) return -1; if (id > call->id) return 1; return 0; } gint at_util_call_compare(gconstpointer a, gconstpointer b) { const struct ofono_call *ca = a; const struct ofono_call *cb = b; if (ca->id < cb->id) return -1; if (ca->id > cb->id) return 1; return 0; } GSList *at_util_parse_clcc(GAtResult *result, unsigned int *ret_mpty_ids) { GAtResultIter iter; GSList *l = NULL; int id, dir, status, type; ofono_bool_t mpty; struct ofono_call *call; unsigned int mpty_ids = 0; g_at_result_iter_init(&iter, result); while (g_at_result_iter_next(&iter, "+CLCC:")) { const char *str = ""; int number_type = 129; if (!g_at_result_iter_next_number(&iter, &id)) continue; if (id == 0) continue; if (!g_at_result_iter_next_number(&iter, &dir)) continue; if (!g_at_result_iter_next_number(&iter, &status)) continue; if (status > 5) continue; if (!g_at_result_iter_next_number(&iter, &type)) continue; if (!g_at_result_iter_next_number(&iter, &mpty)) continue; if (g_at_result_iter_next_string(&iter, &str)) g_at_result_iter_next_number(&iter, &number_type); call = g_try_new(struct ofono_call, 1); if (call == NULL) break; ofono_call_init(call); call->id = id; call->direction = dir; call->status = status; call->type = type; strncpy(call->phone_number.number, str, OFONO_MAX_PHONE_NUMBER_LENGTH); call->phone_number.type = number_type; if (strlen(call->phone_number.number) > 0) call->clip_validity = 0; else call->clip_validity = 2; l = g_slist_insert_sorted(l, call, at_util_call_compare); if (mpty) mpty_ids |= 1 << id; } if (ret_mpty_ids) *ret_mpty_ids = mpty_ids; return l; } gboolean at_util_parse_reg_unsolicited(GAtResult *result, const char *prefix, int *status, int *lac, int *ci, int *tech, unsigned int vendor) { GAtResultIter iter; int s; int l = -1, c = -1, t = -1; const char *str; g_at_result_iter_init(&iter, result); if (g_at_result_iter_next(&iter, prefix) == FALSE) return FALSE; if (g_at_result_iter_next_number(&iter, &s) == FALSE) return FALSE; /* Some firmware will report bogus lac/ci when unregistered */ if (s != 1 && s != 5) goto out; switch (vendor) { case OFONO_VENDOR_GOBI: case OFONO_VENDOR_ZTE: case OFONO_VENDOR_HUAWEI: case OFONO_VENDOR_NOVATEL: case OFONO_VENDOR_SPEEDUP: if (g_at_result_iter_next_unquoted_string(&iter, &str) == TRUE) l = strtol(str, NULL, 16); else goto out; if (g_at_result_iter_next_unquoted_string(&iter, &str) == TRUE) c = strtol(str, NULL, 16); else goto out; break; default: if (g_at_result_iter_next_string(&iter, &str) == TRUE) l = strtol(str, NULL, 16); else goto out; if (g_at_result_iter_next_string(&iter, &str) == TRUE) c = strtol(str, NULL, 16); else goto out; } g_at_result_iter_next_number(&iter, &t); out: if (status) *status = s; if (lac) *lac = l; if (ci) *ci = c; if (tech) *tech = t; return TRUE; } gboolean at_util_parse_reg(GAtResult *result, const char *prefix, int *mode, int *status, int *lac, int *ci, int *tech, unsigned int vendor) { GAtResultIter iter; int m, s; int l = -1, c = -1, t = -1; const char *str; g_at_result_iter_init(&iter, result); while (g_at_result_iter_next(&iter, prefix)) { gboolean r; g_at_result_iter_next_number(&iter, &m); /* Sometimes we get an unsolicited CREG/CGREG here, skip it */ switch (vendor) { case OFONO_VENDOR_ZTE: case OFONO_VENDOR_HUAWEI: case OFONO_VENDOR_NOVATEL: case OFONO_VENDOR_SPEEDUP: r = g_at_result_iter_next_unquoted_string(&iter, &str); if (r == FALSE || strlen(str) != 1) continue; s = strtol(str, NULL, 10); break; default: if (g_at_result_iter_next_number(&iter, &s) == FALSE) continue; break; } /* Some firmware will report bogus lac/ci when unregistered */ if (s != 1 && s != 5) goto out; switch (vendor) { case OFONO_VENDOR_GOBI: case OFONO_VENDOR_ZTE: case OFONO_VENDOR_HUAWEI: case OFONO_VENDOR_NOVATEL: case OFONO_VENDOR_SPEEDUP: r = g_at_result_iter_next_unquoted_string(&iter, &str); if (r == TRUE) l = strtol(str, NULL, 16); else goto out; r = g_at_result_iter_next_unquoted_string(&iter, &str); if (r == TRUE) c = strtol(str, NULL, 16); else goto out; break; default: if (g_at_result_iter_next_string(&iter, &str) == TRUE) l = strtol(str, NULL, 16); else goto out; if (g_at_result_iter_next_string(&iter, &str) == TRUE) c = strtol(str, NULL, 16); else goto out; } g_at_result_iter_next_number(&iter, &t); out: if (mode) *mode = m; if (status) *status = s; if (lac) *lac = l; if (ci) *ci = c; if (tech) *tech = t; return TRUE; } return FALSE; } gboolean at_util_parse_sms_index_delivery(GAtResult *result, const char *prefix, enum at_util_sms_store *out_st, int *out_index) { GAtResultIter iter; const char *strstore; enum at_util_sms_store st; int index; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, prefix)) return FALSE; if (!g_at_result_iter_next_string(&iter, &strstore)) return FALSE; if (g_str_equal(strstore, "ME")) st = AT_UTIL_SMS_STORE_ME; else if (g_str_equal(strstore, "SM")) st = AT_UTIL_SMS_STORE_SM; else if (g_str_equal(strstore, "SR")) st = AT_UTIL_SMS_STORE_SR; else if (g_str_equal(strstore, "BM")) st = AT_UTIL_SMS_STORE_BM; else return FALSE; if (!g_at_result_iter_next_number(&iter, &index)) return FALSE; if (out_index) *out_index = index; if (out_st) *out_st = st; return TRUE; } static gboolean at_util_charset_string_to_charset(const char *str, enum at_util_charset *charset) { if (!g_strcmp0(str, "GSM")) *charset = AT_UTIL_CHARSET_GSM; else if (!g_strcmp0(str, "HEX")) *charset = AT_UTIL_CHARSET_HEX; else if (!g_strcmp0(str, "IRA")) *charset = AT_UTIL_CHARSET_IRA; else if (!g_strcmp0(str, "PCCP437")) *charset = AT_UTIL_CHARSET_PCCP437; else if (!g_strcmp0(str, "PCDN")) *charset = AT_UTIL_CHARSET_PCDN; else if (!g_strcmp0(str, "UCS2")) *charset = AT_UTIL_CHARSET_UCS2; else if (!g_strcmp0(str, "UTF-8")) *charset = AT_UTIL_CHARSET_UTF8; else if (!g_strcmp0(str, "8859-1")) *charset = AT_UTIL_CHARSET_8859_1; else if (!g_strcmp0(str, "8859-2")) *charset = AT_UTIL_CHARSET_8859_2; else if (!g_strcmp0(str, "8859-3")) *charset = AT_UTIL_CHARSET_8859_3; else if (!g_strcmp0(str, "8859-4")) *charset = AT_UTIL_CHARSET_8859_4; else if (!g_strcmp0(str, "8859-5")) *charset = AT_UTIL_CHARSET_8859_5; else if (!g_strcmp0(str, "8859-6")) *charset = AT_UTIL_CHARSET_8859_6; else if (!g_strcmp0(str, "8859-C")) *charset = AT_UTIL_CHARSET_8859_C; else if (!g_strcmp0(str, "8859-A")) *charset = AT_UTIL_CHARSET_8859_A; else if (!g_strcmp0(str, "8859-G")) *charset = AT_UTIL_CHARSET_8859_G; else if (!g_strcmp0(str, "8859-H")) *charset = AT_UTIL_CHARSET_8859_H; else return FALSE; return TRUE; } gboolean at_util_parse_cscs_supported(GAtResult *result, int *supported) { GAtResultIter iter; const char *str; enum at_util_charset charset; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "+CSCS:")) return FALSE; /* Some modems don't report CSCS in a proper list */ g_at_result_iter_open_list(&iter); while (g_at_result_iter_next_string(&iter, &str)) { if (at_util_charset_string_to_charset(str, &charset)) *supported |= charset; } g_at_result_iter_close_list(&iter); return TRUE; } gboolean at_util_parse_cscs_query(GAtResult *result, enum at_util_charset *charset) { GAtResultIter iter; const char *str; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "+CSCS:")) return FALSE; if (g_at_result_iter_next_string(&iter, &str)) return at_util_charset_string_to_charset(str, charset); return FALSE; } static const char *at_util_fixup_return(const char *line, const char *prefix) { if (g_str_has_prefix(line, prefix) == FALSE) return line; line += strlen(prefix); while (line[0] == ' ') line++; return line; } gboolean at_util_parse_attr(GAtResult *result, const char *prefix, const char **out_attr) { int numlines = g_at_result_num_response_lines(result); GAtResultIter iter; const char *line; int i; if (numlines == 0) return FALSE; g_at_result_iter_init(&iter, result); /* * We have to be careful here, sometimes a stray unsolicited * notification will appear as part of the response and we * cannot rely on having a prefix to recognize the actual * response line. So use the last line only as the response */ for (i = 0; i < numlines; i++) g_at_result_iter_next(&iter, NULL); line = g_at_result_iter_raw_line(&iter); if (out_attr) *out_attr = at_util_fixup_return(line, prefix); return TRUE; } static void cpin_check_cb(gboolean ok, GAtResult *result, gpointer userdata) { struct at_util_sim_state_query *req = userdata; struct ofono_error error; decode_at_error(&error, g_at_result_final_response(result)); if (error.type == OFONO_ERROR_TYPE_NO_ERROR) goto done; /* * If we got a generic error the AT port might not be ready, * try again */ if (error.type == OFONO_ERROR_TYPE_FAILURE) goto tryagain; /* If we got any other error besides CME, fail */ if (error.type != OFONO_ERROR_TYPE_CME) goto done; switch (error.error) { case 10: case 13: goto done; case 14: goto tryagain; default: /* Assume SIM is present */ ok = TRUE; goto done; } tryagain: if (req->cpin_poll_count++ < req->num_times) { req->cpin_poll_source = g_timeout_add_seconds(req->interval, cpin_check, req); return; } done: if (req->cb) req->cb(ok, req->userdata); } static gboolean cpin_check(gpointer userdata) { struct at_util_sim_state_query *req = userdata; req->cpin_poll_source = 0; g_at_chat_send(req->chat, "AT+CPIN?", cpin_prefix, cpin_check_cb, req, NULL); return FALSE; } struct at_util_sim_state_query *at_util_sim_state_query_new(GAtChat *chat, guint interval, guint num_times, at_util_sim_inserted_cb_t cb, void *userdata, GDestroyNotify destroy) { struct at_util_sim_state_query *req; req = g_new0(struct at_util_sim_state_query, 1); req->chat = chat; req->interval = interval; req->num_times = num_times; req->cb = cb; req->userdata = userdata; req->destroy = destroy; cpin_check(req); return req; } void at_util_sim_state_query_free(struct at_util_sim_state_query *req) { if (req == NULL) return; if (req->cpin_poll_source > 0) g_source_remove(req->cpin_poll_source); if (req->destroy) req->destroy(req->userdata); g_free(req); } /* * CGCONTRDP returns addr + netmask in the same string in the form * of "a.b.c.d.m.m.m.m" for IPv4. * address/netmask must be able to hold * 255.255.255.255 + null = 16 characters */ int at_util_get_ipv4_address_and_netmask(const char *addrnetmask, char *address, char *netmask) { const char *s = addrnetmask; const char *net = NULL; int ret = -EINVAL; int i; /* Count 7 dots for ipv4, less or more means error. */ for (i = 0; i < 9; i++, s++) { s = strchr(s, '.'); if (!s) break; if (i == 3) { /* set netmask ptr and break the string */ net = s + 1; } } if (i == 7) { memcpy(address, addrnetmask, net - addrnetmask); address[net - addrnetmask - 1] = '\0'; strcpy(netmask, net); ret = 0; } return ret; } /* * CGCONTRDP returns addr + netmask in the same string in the form * of "a1.a2.a3.a4.a5.a6.a7.a8.a9.a10.a11.a12.a13.a14.a15.a16.m1.m2. * m3.m4.m5.m6.m7.m8.m9.m10.m11.m12.m13.m14.m15.m16" for IPv6. * address/netmask must be able to hold 64 characters. */ int at_util_get_ipv6_address_and_netmask(const char *addrnetmask, char *address, char *netmask) { const char *s = addrnetmask; const char *net = NULL; int ret = -EINVAL; int i; /* Count 31 dots for ipv6, less or more means error. */ for (i = 0; i < 33; i++, s++) { s = strchr(s, '.'); if (!s) break; if (i == 15) { /* set netmask ptr and break the string */ net = s + 1; } } if (i == 31) { memcpy(address, addrnetmask, net - addrnetmask); address[net - addrnetmask - 1] = '\0'; strcpy(netmask, net); ret = 0; } return ret; } int at_util_gprs_auth_method_to_auth_prot( enum ofono_gprs_auth_method auth_method) { switch (auth_method) { case OFONO_GPRS_AUTH_METHOD_PAP: return 1; case OFONO_GPRS_AUTH_METHOD_CHAP: return 2; case OFONO_GPRS_AUTH_METHOD_NONE: return 0; } return 0; } const char *at_util_gprs_proto_to_pdp_type(enum ofono_gprs_proto proto) { switch (proto) { case OFONO_GPRS_PROTO_IPV6: return "IPV6"; case OFONO_GPRS_PROTO_IPV4V6: return "IPV4V6"; break; case OFONO_GPRS_PROTO_IP: return "IP"; } return NULL; } char *at_util_get_cgdcont_command(guint cid, enum ofono_gprs_proto proto, const char *apn) { const char *pdp_type = at_util_gprs_proto_to_pdp_type(proto); if (!apn) return g_strdup_printf("AT+CGDCONT=%u", cid); return g_strdup_printf("AT+CGDCONT=%u,\"%s\",\"%s\"", cid, pdp_type, apn); } GAtChat *at_util_open_device(struct ofono_modem *modem, const char *key, GAtDebugFunc debug_func, char *debug_prefix, char *tty_option, ...) { const char *device; va_list args; GIOChannel *channel; GAtSyntax *syntax; GAtChat *chat; GHashTable *options = NULL; device = ofono_modem_get_string(modem, key); if (device == NULL) return NULL; if (tty_option) { options = g_hash_table_new(g_str_hash, g_str_equal); if (options == NULL) return NULL; va_start(args, tty_option); while (tty_option) { gpointer value = (gpointer) va_arg(args, const char *); g_hash_table_insert(options, tty_option, value); tty_option = (gpointer) va_arg(args, const char *); } } channel = g_at_tty_open(device, options); if (options) g_hash_table_destroy(options); if (channel == NULL) return NULL; syntax = g_at_syntax_new_gsm_permissive(); chat = g_at_chat_new(channel, syntax); g_at_syntax_unref(syntax); g_io_channel_unref(channel); if (chat == NULL) return NULL; if (getenv("OFONO_AT_DEBUG")) g_at_chat_set_debug(chat, debug_func, debug_prefix); return chat; }