| /* |
| * |
| * oFono - Open Source Telephony |
| * |
| * Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies). |
| * |
| * 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 <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <errno.h> |
| |
| #include <glib.h> |
| |
| #include <gisi/client.h> |
| #include <gisi/message.h> |
| #include <gisi/iter.h> |
| |
| #include <ofono/log.h> |
| #include <ofono/modem.h> |
| #include <ofono/call-forwarding.h> |
| |
| #include "isimodem.h" |
| #include "isiutil.h" |
| #include "ss.h" |
| #include "debug.h" |
| |
| struct forw_data { |
| GIsiClient *client; |
| }; |
| |
| struct forw_info { |
| uint8_t bsc; /* Basic service code */ |
| uint8_t status; /* SS status */ |
| uint8_t ton; /* Type of number */ |
| uint8_t noreply; /* No reply timeout */ |
| uint8_t forw_opt; /* Forwarding option */ |
| uint8_t numlen; /* Number length */ |
| uint8_t sublen; /* Sub-address length */ |
| uint8_t filler; |
| }; |
| |
| static int forw_type_to_isi_code(int type) |
| { |
| int ss_code; |
| |
| switch (type) { |
| case 0: |
| ss_code = SS_GSM_FORW_UNCONDITIONAL; |
| break; |
| case 1: |
| ss_code = SS_GSM_FORW_BUSY; |
| break; |
| case 2: |
| ss_code = SS_GSM_FORW_NO_REPLY; |
| break; |
| case 3: |
| ss_code = SS_GSM_FORW_NO_REACH; |
| break; |
| case 4: |
| ss_code = SS_GSM_ALL_FORWARDINGS; |
| break; |
| case 5: |
| ss_code = SS_GSM_ALL_COND_FORWARDINGS; |
| break; |
| default: |
| DBG("Unknown forwarding type %d", type); |
| ss_code = -1; |
| break; |
| } |
| return ss_code; |
| } |
| |
| static gboolean check_resp(const GIsiMessage *msg, uint8_t msgid, uint8_t type) |
| { |
| uint8_t service; |
| |
| if (g_isi_msg_error(msg) < 0) { |
| DBG("Error: %s", g_isi_msg_strerror(msg)); |
| return FALSE; |
| } |
| |
| if (g_isi_msg_id(msg) != msgid) { |
| DBG("Unexpected msg: %s", |
| ss_message_id_name(g_isi_msg_id(msg))); |
| return FALSE; |
| } |
| |
| if (!g_isi_msg_data_get_byte(msg, 0, &service) || service != type) { |
| DBG("Unexpected service type: 0x%02X", service); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean decode_gsm_forwarding_info(GIsiSubBlockIter *parent, |
| uint8_t *status, uint8_t *ton, |
| uint8_t *noreply, char **number) |
| { |
| GIsiSubBlockIter iter; |
| struct forw_info *info; |
| size_t len = sizeof(struct forw_info); |
| char *tag = NULL; |
| |
| for (g_isi_sb_subiter_init(parent, &iter, 4); |
| g_isi_sb_iter_is_valid(&iter); |
| g_isi_sb_iter_next(&iter)) { |
| |
| if (g_isi_sb_iter_get_id(&iter) != SS_GSM_FORWARDING_FEATURE) |
| continue; |
| |
| if (!g_isi_sb_iter_get_struct(&iter, (void *) &info, len, 2)) |
| return FALSE; |
| |
| if (info->numlen != 0) { |
| if (!g_isi_sb_iter_get_alpha_tag(&iter, &tag, |
| info->numlen * 2, |
| 2 + len)) |
| return FALSE; |
| |
| if (number) |
| *number = tag; |
| else |
| g_free(tag); |
| } else { |
| if (number) |
| *number = g_strdup(""); |
| } |
| |
| if (status) |
| *status = info->status; |
| |
| if (ton) |
| *ton = info->ton; |
| |
| if (noreply) |
| *noreply = info->noreply; |
| |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| static void registration_resp_cb(const GIsiMessage *msg, void *data) |
| { |
| struct isi_cb_data *cbd = data; |
| ofono_call_forwarding_set_cb_t cb = cbd->cb; |
| GIsiSubBlockIter iter; |
| uint8_t status; |
| |
| if (!check_resp(msg, SS_SERVICE_COMPLETED_RESP, SS_REGISTRATION)) |
| goto error; |
| |
| for (g_isi_sb_iter_init(&iter, msg, 6); |
| g_isi_sb_iter_is_valid(&iter); |
| g_isi_sb_iter_next(&iter)) { |
| |
| if (g_isi_sb_iter_get_id(&iter) != SS_GSM_FORWARDING_INFO) |
| continue; |
| |
| if (!decode_gsm_forwarding_info(&iter, &status, NULL, NULL, |
| NULL)) |
| goto error; |
| |
| if (status & (SS_GSM_ACTIVE | SS_GSM_REGISTERED)) { |
| CALLBACK_WITH_SUCCESS(cb, cbd->data); |
| return; |
| } |
| } |
| |
| error: |
| CALLBACK_WITH_FAILURE(cb, cbd->data); |
| } |
| |
| static void isi_registration(struct ofono_call_forwarding *cf, int type, |
| int cls, |
| const struct ofono_phone_number *number, |
| int time, ofono_call_forwarding_set_cb_t cb, |
| void *data) |
| { |
| struct forw_data *fd = ofono_call_forwarding_get_data(cf); |
| struct isi_cb_data *cbd = isi_cb_data_new(cf, cb, data); |
| int ss_code = forw_type_to_isi_code(type); |
| |
| char *ucs2 = NULL; |
| |
| size_t numlen = strlen(number->number); |
| size_t sb_len = ALIGN4(6 + 2 * numlen); |
| size_t pad_len = sb_len - (6 + 2 * numlen); |
| |
| uint8_t msg[7 + 6 + 28 * 2 + 3] = { |
| SS_SERVICE_REQ, |
| SS_REGISTRATION, |
| SS_GSM_TELEPHONY, |
| ss_code >> 8, ss_code & 0xFF, |
| SS_SEND_ADDITIONAL_INFO, |
| 1, /* Subblock count */ |
| SS_FORWARDING, |
| sb_len, |
| number->type, |
| ss_code == SS_GSM_FORW_NO_REPLY ? time : SS_UNDEFINED_TIME, |
| numlen, |
| 0, /* Sub address length */ |
| /* |
| * Followed by number in UCS-2 (no NULL termination), |
| * zero sub address bytes, and 0 to 3 bytes of filler |
| */ |
| }; |
| size_t msg_len = 7 + 6 + numlen * 2 + pad_len; |
| |
| if (cbd == NULL || fd == NULL || numlen > 28) |
| goto error; |
| |
| DBG("forwarding type %d class %d number %s", type, cls, number->number); |
| |
| if (ss_code < 0) |
| goto error; |
| |
| ucs2 = g_convert(number->number, numlen, "UCS-2BE", "UTF-8//TRANSLIT", |
| NULL, NULL, NULL); |
| if (ucs2 == NULL) |
| goto error; |
| |
| memcpy(msg + 13, ucs2, numlen * 2); |
| g_free(ucs2); |
| |
| if (g_isi_client_send(fd->client, msg, msg_len, registration_resp_cb, |
| cbd, g_free)) |
| return; |
| |
| error: |
| CALLBACK_WITH_FAILURE(cb, data); |
| g_free(cbd); |
| } |
| |
| static void erasure_resp_cb(const GIsiMessage *msg, void *data) |
| { |
| struct isi_cb_data *cbd = data; |
| ofono_call_forwarding_set_cb_t cb = cbd->cb; |
| GIsiSubBlockIter iter; |
| uint8_t status; |
| |
| if (!check_resp(msg, SS_SERVICE_COMPLETED_RESP, SS_ERASURE)) |
| goto error; |
| |
| for (g_isi_sb_iter_init(&iter, msg, 6); |
| g_isi_sb_iter_is_valid(&iter); |
| g_isi_sb_iter_next(&iter)) { |
| |
| if (g_isi_sb_iter_get_id(&iter) != SS_GSM_FORWARDING_INFO) |
| continue; |
| |
| if (!decode_gsm_forwarding_info(&iter, &status, NULL, NULL, |
| NULL)) |
| goto error; |
| |
| if (status & (SS_GSM_ACTIVE | SS_GSM_REGISTERED)) |
| goto error; |
| |
| } |
| CALLBACK_WITH_SUCCESS(cb, cbd->data); |
| return; |
| |
| error: |
| CALLBACK_WITH_FAILURE(cb, cbd->data); |
| } |
| |
| static void isi_erasure(struct ofono_call_forwarding *cf, int type, int cls, |
| ofono_call_forwarding_set_cb_t cb, void *data) |
| { |
| struct forw_data *fd = ofono_call_forwarding_get_data(cf); |
| struct isi_cb_data *cbd = isi_cb_data_new(cf, cb, data); |
| int ss_code = forw_type_to_isi_code(type); |
| |
| const uint8_t msg[] = { |
| SS_SERVICE_REQ, |
| SS_ERASURE, |
| SS_GSM_TELEPHONY, |
| ss_code >> 8, ss_code & 0xFF, |
| SS_SEND_ADDITIONAL_INFO, |
| 0, /* Subblock count */ |
| }; |
| |
| DBG("forwarding type %d class %d", type, cls); |
| |
| if (cbd == NULL || fd == NULL || ss_code < 0) |
| goto error; |
| |
| if (g_isi_client_send(fd->client, msg, sizeof(msg), |
| erasure_resp_cb, cbd, g_free)) |
| return; |
| |
| error: |
| CALLBACK_WITH_FAILURE(cb, data); |
| g_free(cbd); |
| } |
| |
| static void query_resp_cb(const GIsiMessage *msg, void *data) |
| { |
| struct isi_cb_data *cbd = data; |
| ofono_call_forwarding_query_cb_t cb = cbd->cb; |
| GIsiSubBlockIter iter; |
| |
| struct ofono_call_forwarding_condition list = { |
| .status = 0, |
| .cls = 7, |
| .time = 0, |
| .phone_number = { |
| .number[0] = '\0', |
| .type = 0, |
| }, |
| }; |
| uint8_t status; |
| uint8_t ton; |
| uint8_t noreply; |
| char *number = NULL; |
| |
| if (!check_resp(msg, SS_SERVICE_COMPLETED_RESP, SS_INTERROGATION)) |
| goto error; |
| |
| for (g_isi_sb_iter_init(&iter, msg, 6); |
| g_isi_sb_iter_is_valid(&iter); |
| g_isi_sb_iter_next(&iter)) { |
| |
| DBG("Got %s", ss_subblock_name(g_isi_sb_iter_get_id(&iter))); |
| |
| if (g_isi_sb_iter_get_id(&iter) != SS_GSM_FORWARDING_INFO) |
| continue; |
| |
| if (!decode_gsm_forwarding_info(&iter, &status, &ton, &noreply, |
| &number)) |
| goto error; |
| |
| list.status = status & (SS_GSM_ACTIVE | SS_GSM_REGISTERED | |
| SS_GSM_PROVISIONED); |
| list.time = noreply; |
| list.phone_number.type = ton | 0x80; |
| |
| DBG("Number <%s>", number); |
| |
| strncpy(list.phone_number.number, number, |
| OFONO_MAX_PHONE_NUMBER_LENGTH); |
| list.phone_number.number[OFONO_MAX_PHONE_NUMBER_LENGTH] = '\0'; |
| g_free(number); |
| |
| DBG("forwarding query: %d, %d, %s(%d) - %d sec", |
| list.status, list.cls, list.phone_number.number, |
| list.phone_number.type, list.time); |
| } |
| CALLBACK_WITH_SUCCESS(cb, 1, &list, cbd->data); |
| return; |
| |
| error: |
| CALLBACK_WITH_FAILURE(cb, 0, NULL, cbd->data); |
| } |
| |
| |
| static void isi_query(struct ofono_call_forwarding *cf, int type, int cls, |
| ofono_call_forwarding_query_cb_t cb, |
| void *data) |
| { |
| struct forw_data *fd = ofono_call_forwarding_get_data(cf); |
| struct isi_cb_data *cbd = isi_cb_data_new(cf, cb, data); |
| int ss_code = forw_type_to_isi_code(type); |
| |
| const uint8_t msg[] = { |
| SS_SERVICE_REQ, |
| SS_INTERROGATION, |
| SS_GSM_TELEPHONY, |
| ss_code >> 8, ss_code & 0xFF, |
| SS_SEND_ADDITIONAL_INFO, |
| 0, /* Subblock count */ |
| }; |
| |
| DBG("forwarding type %d class %d", type, cls); |
| |
| if (cbd == NULL || fd == NULL || cls != 7 || ss_code < 0) |
| goto error; |
| |
| if (g_isi_client_send(fd->client, msg, sizeof(msg), query_resp_cb, |
| cbd, g_free)) |
| return; |
| |
| error: |
| CALLBACK_WITH_FAILURE(cb, 0, NULL, data); |
| g_free(cbd); |
| } |
| |
| static void reachable_cb(const GIsiMessage *msg, void *data) |
| { |
| struct ofono_call_forwarding *cf = data; |
| |
| if (g_isi_msg_error(msg) < 0) { |
| ofono_call_forwarding_remove(cf); |
| return; |
| } |
| |
| ISI_RESOURCE_DBG(msg); |
| |
| ofono_call_forwarding_register(cf); |
| } |
| |
| |
| static int isi_call_forwarding_probe(struct ofono_call_forwarding *cf, |
| unsigned int vendor, void *user) |
| { |
| GIsiModem *modem = user; |
| struct forw_data *fd; |
| |
| fd = g_try_new0(struct forw_data, 1); |
| if (fd == NULL) |
| return -ENOMEM; |
| |
| fd->client = g_isi_client_create(modem, PN_SS); |
| if (fd->client == NULL) { |
| g_free(fd); |
| return -ENOMEM; |
| } |
| |
| ofono_call_forwarding_set_data(cf, fd); |
| |
| g_isi_client_verify(fd->client, reachable_cb, cf, NULL); |
| |
| return 0; |
| } |
| |
| static void isi_call_forwarding_remove(struct ofono_call_forwarding *cf) |
| { |
| struct forw_data *data = ofono_call_forwarding_get_data(cf); |
| |
| ofono_call_forwarding_set_data(cf, NULL); |
| |
| if (data == NULL) |
| return; |
| |
| g_isi_client_destroy(data->client); |
| g_free(data); |
| } |
| |
| static const struct ofono_call_forwarding_driver driver = { |
| .name = "isimodem", |
| .probe = isi_call_forwarding_probe, |
| .remove = isi_call_forwarding_remove, |
| .activation = NULL, |
| .registration = isi_registration, |
| .deactivation = NULL, |
| .erasure = isi_erasure, |
| .query = isi_query |
| }; |
| |
| void isi_call_forwarding_init(void) |
| { |
| ofono_call_forwarding_driver_register(&driver); |
| } |
| |
| void isi_call_forwarding_exit(void) |
| { |
| ofono_call_forwarding_driver_unregister(&driver); |
| } |