diff options
Diffstat (limited to 'data.c')
-rw-r--r-- | data.c | 605 |
1 files changed, 605 insertions, 0 deletions
@@ -0,0 +1,605 @@ +/* + * This file is part of QMI-RIL. + * + * Copyright (C) 2017 Wolfgang Wiedmeyer <wolfgit@wiedmeyer.de> + * + * QMI-RIL 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 3 of the License, or + * (at your option) any later version. + * + * QMI-RIL 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 QMI-RIL. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> + +#define LOG_TAG "RIL" +#include <utils/Log.h> +#include <netutils/ifc.h> + +#include <qmi-ril.h> + +static int qmi2ril_call_fail_cause(QmiWdsCallEndReason cer) +{ + switch (cer) { + case QMI_WDS_CALL_END_REASON_GSM_WCDMA_OPERATOR_DETERMINED_BARRING: + return PDP_FAIL_OPERATOR_BARRED; + case QMI_WDS_CALL_END_REASON_GSM_WCDMA_INSUFFICIENT_RESOURCES: + return PDP_FAIL_INSUFFICIENT_RESOURCES; + case QMI_WDS_CALL_END_REASON_GSM_WCDMA_APN_TYPE_CONFLICT: + case QMI_WDS_CALL_END_REASON_GSM_WCDMA_UNKNOWN_APN: + return PDP_FAIL_MISSING_UKNOWN_APN; + case QMI_WDS_CALL_END_REASON_GSM_WCDMA_UNKNOWN_PDP: + return PDP_FAIL_UNKNOWN_PDP_ADDRESS_TYPE; + case QMI_WDS_CALL_END_REASON_GENERIC_AUTHENTICATION_FAILED: + return PDP_FAIL_USER_AUTHENTICATION; + case QMI_WDS_CALL_END_REASON_GSM_WCDMA_GGSN_REJECT: + return PDP_FAIL_ACTIVATION_REJECT_GGSN; + case QMI_WDS_CALL_END_REASON_GSM_WCDMA_ACTIVATION_REJECT: + return PDP_FAIL_ACTIVATION_REJECT_UNSPECIFIED; + case QMI_WDS_CALL_END_REASON_GSM_WCDMA_OPTION_NOT_SUPPORTED: + return PDP_FAIL_SERVICE_OPTION_NOT_SUPPORTED; + case QMI_WDS_CALL_END_REASON_GSM_WCDMA_OPTION_UNSUBSCRIBED: + return PDP_FAIL_SERVICE_OPTION_NOT_SUBSCRIBED; + case QMI_WDS_CALL_END_REASON_GENERIC_NO_SERVICE: + case QMI_WDS_CALL_END_REASON_GSM_WCDMA_NO_SERVICE: + case QMI_WDS_CALL_END_REASON_GSM_WCDMA_OPTION_TEMPORARILY_OUT_OF_ORDER: + return PDP_FAIL_SERVICE_OPTION_OUT_OF_ORDER; + case QMI_WDS_CALL_END_REASON_GSM_WCDMA_NSAPI_ALREADY_USED: + return PDP_FAIL_NSAPI_IN_USE; + case QMI_WDS_CALL_END_REASON_GENERIC_CLOSE_IN_PROGRESS: + case QMI_WDS_CALL_END_REASON_GENERIC_RELEASE_NORMAL: + case QMI_WDS_CALL_END_REASON_GSM_WCDMA_REGULAR_DEACTIVATION: + return PDP_FAIL_REGULAR_DEACTIVATION; + case QMI_WDS_CALL_END_REASON_GSM_WCDMA_PROTOCOL_ERROR: + return PDP_FAIL_PROTOCOL_ERRORS; + case QMI_WDS_CALL_END_REASON_GSM_WCDMA_MESSAGE_INCORRECT_SEMANTIC: + return PDP_FAIL_VOICE_REGISTRATION_FAIL; + case QMI_WDS_CALL_END_REASON_GENERIC_ACCESS_FAILURE: + case QMI_WDS_CALL_END_REASON_GSM_WCDMA_NETWORK_FAILURE: + return PDP_FAIL_DATA_REGISTRATION_FAIL; + case QMI_WDS_CALL_END_REASON_GENERIC_FADE: + return PDP_FAIL_SIGNAL_LOST; + case QMI_WDS_CALL_END_REASON_GSM_WCDMA_REATTACH_REQUIRED: + return PDP_FAIL_PREF_RADIO_TECH_CHANGED; + case QMI_WDS_CALL_END_REASON_GENERIC_CLIENT_END: + return PDP_FAIL_RADIO_POWER_OFF; + case QMI_WDS_CALL_END_REASON_GSM_WCDMA_NETWORK_END: + case QMI_WDS_CALL_END_REASON_GENERIC_INTERNAL_ERROR: + case QMI_WDS_CALL_END_REASON_GENERIC_ACCESS_ATTEMPT_IN_PROGRESS: + case QMI_WDS_CALL_END_REASON_GENERIC_UNSPECIFIED: + default: + return PDP_FAIL_ERROR_UNSPECIFIED; + } +} + +static void qmi2ril_addr4_string(guint32 buf, char **addr4) +{ + struct in_addr in_addr_val; + char buf4[INET_ADDRSTRLEN]; + + in_addr_val.s_addr = GUINT32_TO_BE(buf); + memset(buf4, 0, sizeof(buf4)); + inet_ntop(AF_INET, &in_addr_val, buf4, sizeof(buf4)); + + *addr4 = strdup(buf4); +} + +static int qmi2ril_ipv4_netmask_prefix_length(in_addr_t mask) +{ + int prefixLength = 0; + uint32_t m = (uint32_t)ntohl(mask); + while (m & 0x80000000) { + prefixLength++; + m = m << 1; + } + + return prefixLength; +} + +static void ril_setup_data_call_response(RIL_Token token) +{ + RIL_Data_Call_Response_v6 response[3]; + struct ril_data_connection data_connection; + size_t size; + + data_connection = ril_data->data_connection; + size = sizeof(RIL_Data_Call_Response_v6); + memset(&response, 0, sizeof(response)); + + response[0].status = data_connection.status; + // TODO: should this be set? It's not in Samsung-RIL + /* response[0].suggestedRetryTime = -1; */ + response[0].cid = data_connection.packet_data_handle; + response[0].active = data_connection.active; + response[0].type = data_connection.type; + response[0].ifname = (char*) data_connection.iface; + asprintf(&response[0].addresses, "%s/%d", data_connection.ip_addr, + qmi2ril_ipv4_netmask_prefix_length(inet_addr(data_connection.subnet_mask))); + RIL_LOGD("addresses for response: '%s'", response[0].addresses); + if (data_connection.dns1 != NULL && data_connection.dns2 != NULL) + asprintf(&response[0].dnses, "%s %s", data_connection.dns1, + data_connection.dns2); + response[0].gateways = data_connection.gateway; + + if (response[0].status == PDP_FAIL_NONE) + ril_request_unsolicited(RIL_UNSOL_DATA_CALL_LIST_CHANGED, &response, size); + + ril_request_complete(token, RIL_E_SUCCESS, &response, size); +} + +static int ril_setup_data_connection(struct ril_data_connection *data_connection) +{ + in_addr_t ip_addr; + in_addr_t gateway_addr; + in_addr_t subnet_mask_addr; + in_addr_t dns1_addr; + in_addr_t dns2_addr; + int rc; + + if (data_connection == NULL || data_connection->iface == NULL + || data_connection->ip_addr == NULL + || data_connection->gateway == NULL + || data_connection->subnet_mask == NULL) + return -1; + + ip_addr = inet_addr(data_connection->ip_addr); + gateway_addr = inet_addr(data_connection->gateway); + subnet_mask_addr = inet_addr(data_connection->subnet_mask); + + if (data_connection->dns1 != NULL) + dns1_addr = inet_addr(data_connection->dns1); + else + dns1_addr = 0; + + if (data_connection->dns2 != NULL) + dns2_addr = inet_addr(data_connection->dns2); + else + dns2_addr = 0; + + rc = ifc_configure(data_connection->iface, ip_addr, + qmi2ril_ipv4_netmask_prefix_length(subnet_mask_addr), + gateway_addr, dns1_addr, dns2_addr); + if (rc < 0) + return -1; + + RIL_LOGD("%s: Enabled data connection", __func__); + + return 0; +} + +static void get_current_settings_ready(QmiClientWds *client, + GAsyncResult *res, + RIL_Token token) +{ + GError *error = NULL; + QmiMessageWdsGetCurrentSettingsOutput *output; + QmiWdsIpFamily ip_family = QMI_WDS_IP_FAMILY_UNSPECIFIED; + char *type = strdup("IP"); + guint32 addr_buf = 0; + char *ip_addr = NULL; + char *gateway = NULL; + char *subnet_mask = NULL; + char *dns1 = NULL; + char *dns2 = NULL; + int rc; + + output = qmi_client_wds_get_current_settings_finish(client, res, + &error); + if (!output) { + RIL_LOGE("%s: error: operation failed: %s", __func__, + error->message); + goto error; + } + + if (!qmi_message_wds_get_current_settings_output_get_result( + output, &error)) { + RIL_LOGE("%s: error: couldn't get current settings: %s", + __func__, error->message); + goto error; + } + + RIL_LOGD("Current data settings retrieved"); + + if (qmi_message_wds_get_current_settings_output_get_ip_family (output, &ip_family, NULL)) { + if (ip_family == QMI_WDS_IP_FAMILY_IPV6) { + type = strdup("IPV6"); + RIL_LOGE("TODO: IPv6 support is missing"); + goto error; + } + } + + ril_data->data_connection.type = strdup(type); + + if (qmi_message_wds_get_current_settings_output_get_ipv4_address( + output, &addr_buf, NULL)) { + qmi2ril_addr4_string(addr_buf, &ip_addr); + RIL_LOGD("IPv4 address: %s", ip_addr); + ril_data->data_connection.ip_addr = strdup(ip_addr); + } else { + RIL_LOGE("failed to retrieve IPv4 address"); + goto error; + } + + if (qmi_message_wds_get_current_settings_output_get_ipv4_gateway_subnet_mask( + output, &addr_buf, NULL)) { + qmi2ril_addr4_string(addr_buf, &subnet_mask); + RIL_LOGD("IPv4 subnet mask: %s", subnet_mask); + ril_data->data_connection.subnet_mask = strdup(subnet_mask); + } else if (ip_addr != NULL) { + asprintf(&subnet_mask, "255.255.255.255"); + ril_data->data_connection.subnet_mask = strdup(subnet_mask); + } + + if (qmi_message_wds_get_current_settings_output_get_ipv4_gateway_address( + output, &addr_buf, NULL)) { + qmi2ril_addr4_string(addr_buf, &gateway); + RIL_LOGD("IPv4 gateway address: %s", gateway); + ril_data->data_connection.gateway = strdup(gateway); + } else if (ip_addr != NULL) { + RIL_LOGE("failed to retrieve IPv4 gateway"); + goto error; + } + + if (qmi_message_wds_get_current_settings_output_get_primary_ipv4_dns_address( + output, &addr_buf, NULL)) { + qmi2ril_addr4_string(addr_buf, &dns1); + RIL_LOGD("IPv4 primary DNS: %s", dns1); + ril_data->data_connection.dns1 = strdup(dns1); + } + + if (qmi_message_wds_get_current_settings_output_get_secondary_ipv4_dns_address( + output, &addr_buf, NULL)) { + qmi2ril_addr4_string(addr_buf, &dns2); + RIL_LOGD("IPv4 secondary DNS: %s", dns2); + ril_data->data_connection.dns2 = strdup(dns2); + } + + rc = ril_setup_data_connection(&ril_data->data_connection); + if (rc < 0) { + RIL_LOGE("setting up the data connection failed"); + ril_request_complete(token, RIL_E_GENERIC_FAILURE, NULL, 0); + qmi_message_wds_get_current_settings_output_unref( + output); + return; + } + +error: + ril_setup_data_call_response(token); + + if (output) + qmi_message_wds_get_current_settings_output_unref( + output); + if (error) + g_error_free(error); +} + +static void +timeout_get_packet_service_status_ready(QmiClientWds *client, + GAsyncResult *res) +{ + GError *error = NULL; + QmiMessageWdsGetPacketServiceStatusOutput *output; + QmiWdsConnectionStatus status; + + output = qmi_client_wds_get_packet_service_status_finish( + client, res, &error); + if (!output) { + RIL_LOGD("%s: error: operation failed: %s", __func__, + error->message); + g_error_free(error); + return; + } + + if (!qmi_message_wds_get_packet_service_status_output_get_result (output, &error)) { + RIL_LOGD("%s: error: couldn't get packet service status: %s", + __func__, error->message); + g_error_free(error); + qmi_message_wds_get_packet_service_status_output_unref(output); + return; + } + + qmi_message_wds_get_packet_service_status_output_get_connection_status( + output, &status, NULL); + + RIL_LOGD("timout connection status: '%s'", + qmi_wds_connection_status_get_string(status)); + qmi_message_wds_get_packet_service_status_output_unref(output); +} + +static gboolean packet_status_timeout(void) +{ + qmi_client_wds_get_packet_service_status(ctx->WdsClient, NULL, + 10, ctx->cancellable, + (GAsyncReadyCallback)timeout_get_packet_service_status_ready, + NULL); + + return TRUE; +} + +static void get_packet_service_status_ready(QmiClientWds *client, + GAsyncResult *res, + RIL_Token token) +{ + GError *error = NULL; + QmiMessageWdsGetPacketServiceStatusOutput *output; + QmiWdsConnectionStatus status; + QmiMessageWdsGetCurrentSettingsInput *input; + int active = 0; + + output = qmi_client_wds_get_packet_service_status_finish( + client, res, &error); + if (!output) { + RIL_LOGE("%s: error: operation failed: %s", __func__, + error->message); + goto error; + } + + if (!qmi_message_wds_get_packet_service_status_output_get_result(output, &error)) { + RIL_LOGE("%s: error: couldn't get packet service status: %s", + __func__, error->message); + goto error; + } + + qmi_message_wds_get_packet_service_status_output_get_connection_status( + output, &status, NULL); + + RIL_LOGD("Connection status: '%s'", + qmi_wds_connection_status_get_string(status)); + + if (status == QMI_WDS_CONNECTION_STATUS_CONNECTED) + active = 2; + +error: + ril_data->data_connection.active = active; + + input = qmi_message_wds_get_current_settings_input_new(); + qmi_message_wds_get_current_settings_input_set_requested_settings( + input, + (QMI_WDS_GET_CURRENT_SETTINGS_REQUESTED_SETTINGS_DNS_ADDRESS + | QMI_WDS_GET_CURRENT_SETTINGS_REQUESTED_SETTINGS_IP_ADDRESS + | QMI_WDS_GET_CURRENT_SETTINGS_REQUESTED_SETTINGS_GATEWAY_INFO + | QMI_WDS_GET_CURRENT_SETTINGS_REQUESTED_SETTINGS_IP_FAMILY), + NULL); + + qmi_client_wds_get_current_settings(ctx->WdsClient, + input, + 10, + ctx->cancellable, + (GAsyncReadyCallback)get_current_settings_ready, + token); + qmi_message_wds_get_current_settings_input_unref(input); + + if (error) + g_error_free(error); + if (output) + qmi_message_wds_get_packet_service_status_output_unref(output); +} + +static void start_network_ready(QmiClientWds *client, + GAsyncResult *res, + RIL_Token token) +{ + GError *error = NULL; + int status = PDP_FAIL_ERROR_UNSPECIFIED; + QmiMessageWdsStartNetworkOutput *output; + QmiWdsCallEndReason cer; + QmiWdsVerboseCallEndReasonType verbose_cer_type; + gint16 verbose_cer_reason; + + output = qmi_client_wds_start_network_finish(client, res, + &error); + if (!output) { + RIL_LOGE("%s: error: operation failed: %s", __func__, + error->message); + goto error; + } + + if (!qmi_message_wds_start_network_output_get_result( + output, &error)) { + RIL_LOGE("%s: error: couldn't start network: %s", __func__, + error->message); + if (g_error_matches(error, QMI_PROTOCOL_ERROR, + QMI_PROTOCOL_ERROR_CALL_FAILED)) { + if (qmi_message_wds_start_network_output_get_call_end_reason( + output, &cer, NULL)) { + RIL_LOGE("call end reason (%u): %s", + cer, + qmi_wds_call_end_reason_get_string(cer)); + status = qmi2ril_call_fail_cause(cer); + } + if (qmi_message_wds_start_network_output_get_verbose_call_end_reason( + output, &verbose_cer_type, + &verbose_cer_reason, NULL)) + RIL_LOGE("verbose call end reason (%u,%d): [%s] %s", + verbose_cer_type, + verbose_cer_reason, + qmi_wds_verbose_call_end_reason_type_get_string(verbose_cer_type), + qmi_wds_verbose_call_end_reason_get_string(verbose_cer_type, verbose_cer_reason)); + } + + goto error; + } + + status = PDP_FAIL_NONE; + + qmi_message_wds_start_network_output_get_packet_data_handle( + output, &ril_data->data_connection.packet_data_handle, + NULL); + qmi_message_wds_start_network_output_unref(output); + + RIL_LOGD("Network started, packet data handle: '%u'", + (guint)ril_data->data_connection.packet_data_handle); + +error: + ril_data->data_connection.status = status; + ril_data->data_connection.iface = strdup(qmi_device_get_wwan_iface( + ctx->device)); + + qmi_client_wds_get_packet_service_status(ctx->WdsClient, + NULL, + 10, + ctx->cancellable, + (GAsyncReadyCallback)get_packet_service_status_ready, + token); + + // additionally check periodically the connection status + ctx->packet_status_timeout_id = g_timeout_add_seconds( + 10, (GSourceFunc)packet_status_timeout, NULL); + + if (error) + g_error_free(error); +} + +int ril_request_setup_data_call(void *data, size_t size, RIL_Token token) +{ + struct ril_request *request; + char *apn = NULL; + char *username = NULL; + char *password = NULL; + char **values = NULL; + QmiMessageWdsStartNetworkInput *input; + int rc; + + if (data == NULL || size < 6 * sizeof(char *)) + goto error; + + rc = ril_radio_state_check(RADIO_STATE_SIM_READY); + if (rc < 0) { + ril_request_complete(token, RIL_E_OP_NOT_ALLOWED_BEFORE_REG_TO_NW, NULL, 0); + return RIL_REQUEST_COMPLETED; + } + + request = ril_request_find_request_status(RIL_REQUEST_SETUP_DATA_CALL, + RIL_REQUEST_HANDLED); + if (request != NULL) + return RIL_REQUEST_UNHANDLED; + + if (ril_data->data_connection.raw_ip_mode == FALSE) { + RIL_LOGE("error: device is not in raw IP mode!"); + goto error; + } + + values = (char **) data; + + if (values[2] == NULL) { + RIL_LOGE("%s: No APN was provided", __func__); + goto error; + } + + input = qmi_message_wds_start_network_input_new(); + + apn = strdup(values[2]); + qmi_message_wds_start_network_input_set_apn(input, apn, NULL); + + if (values[3] != NULL) { + username = strdup(values[3]); + qmi_message_wds_start_network_input_set_username( + input, username, NULL); + } + + if (values[4] != NULL) { + password = strdup(values[4]); + qmi_message_wds_start_network_input_set_password( + input, password, NULL); + } + + qmi_client_wds_start_network(ctx->WdsClient, + input, + 45, + ctx->cancellable, + (GAsyncReadyCallback)start_network_ready, + token); + + RIL_LOGD("Setting up data connection to APN: %s with username/password: %s/%s", + apn, username, password); + + qmi_message_wds_start_network_input_unref(input); + strings_array_free(values, size); + values = NULL; + + rc = RIL_REQUEST_HANDLED; + goto complete; + +error: + if (values != NULL) + strings_array_free(values, size); + + if (apn != NULL) + free(apn); + + if (username != NULL) + free(username); + + if (password != NULL) + free(password); + + ril_request_complete(token, RIL_E_GENERIC_FAILURE, NULL, 0); + + rc = RIL_REQUEST_COMPLETED; + +complete: + return rc; +} + +static void set_data_format_ready(QmiClientWda *client, + GAsyncResult *res) +{ + QmiMessageWdaSetDataFormatOutput *output; + GError *error = NULL; + QmiWdaLinkLayerProtocol link_layer_protocol; + + output = qmi_client_wda_set_data_format_finish(client, res, &error); + if (!output) { + RIL_LOGE("%s: error: operation failed: %s\n", __func__, + error->message); + g_error_free(error); + return; + } + + if (!qmi_message_wda_set_data_format_output_get_result(output, + &error)) { + RIL_LOGE("%s: error: couldn't set data format: %s", + __func__, error->message); + g_error_free(error); + qmi_message_wda_set_data_format_output_unref(output); + return; + } + + RIL_LOGD("[%s] Successfully set data format", + qmi_device_get_path_display(ctx->device)); + + if (qmi_message_wda_set_data_format_output_get_link_layer_protocol( + output, &link_layer_protocol, NULL)) + RIL_LOGD("Link layer protocol: '%s'", + qmi_wda_link_layer_protocol_get_string(link_layer_protocol)); + + ril_data->data_connection.raw_ip_mode = TRUE; + + qmi_message_wda_set_data_format_output_unref(output); +} + +void qmi_set_raw_ip_mode(void) +{ + QmiMessageWdaSetDataFormatInput *input; + + input = qmi_message_wda_set_data_format_input_new(); + + qmi_message_wda_set_data_format_input_set_link_layer_protocol( + input, + QMI_WDA_LINK_LAYER_PROTOCOL_RAW_IP, + NULL); + + qmi_client_wda_set_data_format(ctx->WdaClient, input, 10, + ctx->cancellable, + (GAsyncReadyCallback)set_data_format_ready, + NULL); + + qmi_message_wda_set_data_format_input_unref(input); +} |