speedup: Remove ussd atom in case we fail to init
[platform/upstream/ofono.git] / drivers / speedupmodem / ussd.c
1 /*
2  *
3  *  oFono - Open Source Telephony
4  *
5  *  Copyright (C) 2008-2012  Intel Corporation. All rights reserved.
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <stdio.h>
27 #include <errno.h>
28
29 #include <glib.h>
30
31 #include <ofono/log.h>
32 #include <ofono/modem.h>
33 #include <ofono/ussd.h>
34 #include "util.h"
35
36 #include "gatchat.h"
37
38 #include "speedupmodem.h"
39
40 static const char *cusd_prefix[] = { "+CUSD:", NULL };
41 static const char *none_prefix[] = { NULL };
42
43 struct ussd_data {
44         GAtChat *chat;
45 };
46
47 static void cusd_parse(GAtResult *result, struct ofono_ussd *ussd)
48 {
49         GAtResultIter iter;
50         int status, dcs;
51         const char *content;
52         unsigned char msg[160];
53         const unsigned char *msg_ptr = NULL;
54         long msg_len;
55
56         g_at_result_iter_init(&iter, result);
57
58         if (!g_at_result_iter_next(&iter, "+CUSD:"))
59                 return;
60
61         if (!g_at_result_iter_next_number(&iter, &status))
62                 return;
63
64         if (!g_at_result_iter_next_string(&iter, &content))
65                 goto out;
66
67         if (!g_at_result_iter_next_number(&iter, &dcs))
68                 dcs = 0;
69
70         msg_ptr = decode_hex_own_buf(content, -1, &msg_len, 0, msg);
71
72 out:
73         ofono_ussd_notify(ussd, status, dcs, msg_ptr, msg_ptr ? msg_len : 0);
74 }
75
76 static void cusd_request_cb(gboolean ok, GAtResult *result, gpointer user_data)
77 {
78         struct cb_data *cbd = user_data;
79         ofono_ussd_cb_t cb = cbd->cb;
80         struct ofono_ussd *ussd = cbd->user;
81         struct ofono_error error;
82
83         decode_at_error(&error, g_at_result_final_response(result));
84
85         cb(&error, cbd->data);
86
87         cusd_parse(result, ussd);
88 }
89
90 static void speedup_ussd_request(struct ofono_ussd *ussd, int dcs,
91                                 const unsigned char *pdu, int len,
92                                 ofono_ussd_cb_t cb, void *user_data)
93 {
94         struct ussd_data *data = ofono_ussd_get_data(ussd);
95         struct cb_data *cbd = cb_data_new(cb, user_data);
96         char buf[512], coded_buf[182];
97         long written;
98
99         cbd->user = ussd;
100
101         unpack_7bit_own_buf(pdu, len, 0, TRUE, sizeof(coded_buf),
102                                 &written, 0, (unsigned char *)coded_buf);
103         if (written < 1)
104                 goto error;
105
106         snprintf(buf, sizeof(buf), "AT+CUSD=1,\"%.*s\",%d", (int) written,
107                         coded_buf, dcs);
108
109         if (g_at_chat_send(data->chat, buf, cusd_prefix,
110                                 cusd_request_cb, cbd, g_free) > 0)
111                 return;
112
113 error:
114         g_free(cbd);
115
116         CALLBACK_WITH_FAILURE(cb, user_data);
117 }
118
119 static void cusd_cancel_cb(gboolean ok, GAtResult *result, gpointer user_data)
120 {
121         struct cb_data *cbd = user_data;
122         ofono_ussd_cb_t cb = cbd->cb;
123         struct ofono_error error;
124
125         decode_at_error(&error, g_at_result_final_response(result));
126
127         /*
128          * All errors and notifications arrive unexpected and
129          * thus just reset the state here. This is safer than
130          * getting stuck in a dead-lock.
131          */
132         error.type = OFONO_ERROR_TYPE_NO_ERROR;
133         error.error = 0;
134
135         cb(&error, cbd->data);
136 }
137
138 static void speedup_ussd_cancel(struct ofono_ussd *ussd,
139                                 ofono_ussd_cb_t cb, void *user_data)
140 {
141         struct ussd_data *data = ofono_ussd_get_data(ussd);
142         struct cb_data *cbd = cb_data_new(cb, user_data);
143
144         cbd->user = data;
145
146         if (g_at_chat_send(data->chat, "AT+CUSD=2", none_prefix,
147                                 cusd_cancel_cb, cbd, g_free) > 0)
148                 return;
149
150         g_free(cbd);
151
152         CALLBACK_WITH_FAILURE(cb, user_data);
153 }
154
155 static void cusd_notify(GAtResult *result, gpointer user_data)
156 {
157         struct ofono_ussd *ussd = user_data;
158
159         cusd_parse(result, ussd);
160 }
161
162 static void cusd_register(gboolean ok, GAtResult *result, gpointer user_data)
163 {
164         struct ofono_ussd *ussd = user_data;
165         struct ussd_data *data = ofono_ussd_get_data(ussd);
166
167         if (!ok) {
168                 ofono_error("Could not enable CUSD notifications");
169                 ofono_ussd_remove(ussd);
170                 return;
171         }
172
173         g_at_chat_register(data->chat, "+CUSD:", cusd_notify,
174                                                 FALSE, ussd, NULL);
175
176         ofono_ussd_register(ussd);
177 }
178
179 static int speedup_ussd_probe(struct ofono_ussd *ussd,
180                                         unsigned int vendor, void *user)
181 {
182         GAtChat *chat = user;
183         struct ussd_data *data;
184
185         data = g_try_new0(struct ussd_data, 1);
186         if (data == NULL)
187                 return -ENOMEM;
188
189         data->chat = g_at_chat_clone(chat);
190
191         ofono_ussd_set_data(ussd, data);
192
193         g_at_chat_send(data->chat, "AT+CUSD=1", none_prefix,
194                                         cusd_register, ussd, NULL);
195
196         return 0;
197 }
198
199 static void speedup_ussd_remove(struct ofono_ussd *ussd)
200 {
201         struct ussd_data *data = ofono_ussd_get_data(ussd);
202
203         ofono_ussd_set_data(ussd, NULL);
204
205         g_at_chat_unref(data->chat);
206         g_free(data);
207 }
208
209 static struct ofono_ussd_driver driver = {
210         .name           = "speedupmodem",
211         .probe          = speedup_ussd_probe,
212         .remove         = speedup_ussd_remove,
213         .request        = speedup_ussd_request,
214         .cancel         = speedup_ussd_cancel,
215 };
216
217 void speedup_ussd_init(void)
218 {
219         ofono_ussd_driver_register(&driver);
220 }
221
222 void speedup_ussd_exit(void)
223 {
224         ofono_ussd_driver_unregister(&driver);
225 }