9aeaf4dc14f325d9471485238e394b829c125276
[osmocom-bb.git] / src / host / layer23 / src / mobile / mnccms.c
1 /*
2  * (C) 2010 by Andreas Eversberg <jolly@eversberg.eu>
3  *
4  * All Rights Reserved
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
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 along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  *
20  */
21
22 #include <stdint.h>
23 #include <errno.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <stdlib.h>
27
28 #include <osmocore/talloc.h>
29
30 #include <osmocom/bb/common/logging.h>
31 #include <osmocom/bb/common/osmocom_data.h>
32 #include <osmocom/bb/mobile/mncc.h>
33 #include <osmocom/bb/mobile/vty.h>
34
35 void *l23_ctx;
36 static uint32_t new_callref = 1;
37 static LLIST_HEAD(call_list);
38
39 /*
40  * support functions
41  */
42
43 void mncc_set_cause(struct gsm_mncc *data, int loc, int val);
44
45 static void free_call(struct gsm_call *call)
46 {
47         llist_del(&call->entry);
48         DEBUGP(DMNCC, "(call %x) Call removed.\n", call->callref);
49         talloc_free(call);
50 }
51
52
53 struct gsm_call *get_call_ref(uint32_t callref)
54 {
55         struct gsm_call *callt;
56
57         llist_for_each_entry(callt, &call_list, entry) {
58                 if (callt->callref == callref)
59                         return callt;
60         }
61         return NULL;
62 }
63
64 static int8_t mncc_get_bearer(struct gsm_support *sup, uint8_t speech_ver)
65 {
66         switch (speech_ver) {
67         case 4:
68                 if (sup->full_v3)
69                         LOGP(DMNCC, LOGL_INFO, " net suggests full rate v3\n");
70                 else {
71                         LOGP(DMNCC, LOGL_INFO, " full rate v3 not supported\n");
72                         speech_ver = -1;
73                 }
74                 break;
75         case 2:
76                 if (sup->full_v2)
77                         LOGP(DMNCC, LOGL_INFO, " net suggests full rate v2\n");
78                 else {
79                         LOGP(DMNCC, LOGL_INFO, " full rate v2 not supported\n");
80                         speech_ver = -1;
81                 }
82                 break;
83         case 0: /* mandatory */
84                 if (sup->full_v1)
85                         LOGP(DMNCC, LOGL_INFO, " net suggests full rate v1\n");
86                 else {
87                         LOGP(DMNCC, LOGL_INFO, " full rate v1 not supported\n");
88                         speech_ver = -1;
89                 }
90                 break;
91         case 5:
92                 if (sup->half_v3)
93                         LOGP(DMNCC, LOGL_INFO, " net suggests half rate v3\n");
94                 else {
95                         LOGP(DMNCC, LOGL_INFO, " half rate v3 not supported\n");
96                         speech_ver = -1;
97                 }
98                 break;
99         case 1:
100                 if (sup->half_v1)
101                         LOGP(DMNCC, LOGL_INFO, " net suggests half rate v1\n");
102                 else {
103                         LOGP(DMNCC, LOGL_INFO, " half rate v1 not supported\n");
104                         speech_ver = -1;
105                 }
106                 break;
107         default:
108                 LOGP(DMNCC, LOGL_INFO, " net suggests unknown speech version "
109                         "%d\n", speech_ver);
110                 speech_ver = -1;
111         }
112
113         return speech_ver;
114 }
115
116 static void mncc_set_bearer(struct osmocom_ms *ms, int8_t speech_ver,
117         struct gsm_mncc *mncc)
118 {
119         struct gsm_support *sup = &ms->support;
120         struct gsm_settings *set = &ms->settings;
121         int i = 0;
122
123         mncc->fields |= MNCC_F_BEARER_CAP;
124         mncc->bearer_cap.coding = 0;
125         if (sup->ch_cap == GSM_CAP_SDCCH_TCHF_TCHH
126          && (sup->half_v1 || sup->half_v3)) {
127                 mncc->bearer_cap.radio = 3;
128                 LOGP(DMNCC, LOGL_INFO, " support TCH/H also\n");
129         } else {
130                 mncc->bearer_cap.radio = 1;
131                 LOGP(DMNCC, LOGL_INFO, " support TCH/F only\n");
132         }
133         mncc->bearer_cap.speech_ctm = 0;
134         /* if no specific speech_ver is given */
135         if (speech_ver < 0) {
136                 /* if half rate is supported and prefered */
137                 if (sup->half_v3 && set->half && set->half_prefer) {
138                         mncc->bearer_cap.speech_ver[i++] = 5;
139                         LOGP(DMNCC, LOGL_INFO, " support half rate v3\n");
140                 }
141                 if (sup->half_v1 && set->half && set->half_prefer) {
142                         mncc->bearer_cap.speech_ver[i++] = 1;
143                         LOGP(DMNCC, LOGL_INFO, " support half rate v1\n");
144                 }
145                 /* if full rate is supported */
146                 if (sup->full_v3) {
147                         mncc->bearer_cap.speech_ver[i++] = 4;
148                         LOGP(DMNCC, LOGL_INFO, " support full rate v3\n");
149                 }
150                 if (sup->full_v2) {
151                         mncc->bearer_cap.speech_ver[i++] = 2;
152                         LOGP(DMNCC, LOGL_INFO, " support full rate v2\n");
153                 }
154                 if (sup->full_v1) { /* mandatory, so it's always true */
155                         mncc->bearer_cap.speech_ver[i++] = 0;
156                         LOGP(DMNCC, LOGL_INFO, " support full rate v1\n");
157                 }
158                 /* if half rate is supported and not prefered */
159                 if (sup->half_v3 && set->half && !set->half_prefer) {
160                         mncc->bearer_cap.speech_ver[i++] = 5;
161                         LOGP(DMNCC, LOGL_INFO, " support half rate v3\n");
162                 }
163                 if (sup->half_v1 && set->half && !set->half_prefer) {
164                         mncc->bearer_cap.speech_ver[i++] = 1;
165                         LOGP(DMNCC, LOGL_INFO, " support half rate v1\n");
166                 }
167         /* if specific speech_ver is given (it must be supported) */
168         } else 
169                 mncc->bearer_cap.speech_ver[i++] = speech_ver;
170         mncc->bearer_cap.speech_ver[i] = -1; /* end of list */
171         mncc->bearer_cap.transfer = 0;
172         mncc->bearer_cap.mode = 0;
173 }
174
175 /*
176  * MNCCms dummy application
177  */
178
179 /* this is a minimal implementation as required by GSM 04.08 */
180 int mncc_recv_dummy(struct osmocom_ms *ms, int msg_type, void *arg)
181 {
182         struct gsm_mncc *data = arg;
183         uint32_t callref = data->callref;
184         struct gsm_mncc rel;
185
186         if (msg_type == MNCC_REL_IND || msg_type == MNCC_REL_CNF)
187                 return 0;
188
189         LOGP(DMNCC, LOGL_INFO, "Rejecting incomming call\n");
190
191         /* reject, as we don't support Calls */
192         memset(&rel, 0, sizeof(struct gsm_mncc));
193         rel.callref = callref;
194         mncc_set_cause(&rel, GSM48_CAUSE_LOC_USER,
195                 GSM48_CC_CAUSE_INCOMPAT_DEST);
196
197         return mncc_send(ms, MNCC_REL_REQ, &rel);
198 }
199
200 /*
201  * MNCCms basic call application
202  */
203
204 int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg)
205 {
206         struct gsm_settings *set = &ms->settings;
207         struct gsm_support *sup = &ms->support;
208         struct gsm_mncc *data = arg;
209         struct gsm_call *call = get_call_ref(data->callref);
210         struct gsm_mncc mncc;
211         uint8_t cause;
212         int8_t  speech_ver = -1, speech_ver_half = -1, temp;
213         int first_call = 0;
214
215         /* call does not exist */
216         if (!call && msg_type != MNCC_SETUP_IND) {
217                 LOGP(DMNCC, LOGL_INFO, "Rejecting incomming call "
218                         "(callref %x)\n", data->callref);
219                 if (msg_type == MNCC_REL_IND || msg_type == MNCC_REL_CNF)
220                         return 0;
221                 cause = GSM48_CC_CAUSE_INCOMPAT_DEST;
222                 release:
223                 memset(&mncc, 0, sizeof(struct gsm_mncc));
224                 mncc.callref = data->callref;
225                 mncc_set_cause(&mncc, GSM48_CAUSE_LOC_USER, cause);
226                 return mncc_send(ms, MNCC_REL_REQ, &mncc);
227         }
228
229         /* setup without call */
230         if (!call) {
231                 if (llist_empty(&call_list))
232                         first_call = 1;
233                 call = talloc_zero(l23_ctx, struct gsm_call);
234                 if (!call)
235                         return -ENOMEM;
236                 call->callref = data->callref;
237                 llist_add_tail(&call->entry, &call_list);
238         }
239
240         /* not in initiated state anymore */
241         call->init = 0;
242
243         switch (msg_type) {
244         case MNCC_DISC_IND:
245                 vty_notify(ms, NULL);
246                 switch (data->cause.value) {
247                 case GSM48_CC_CAUSE_UNASSIGNED_NR:
248                         vty_notify(ms, "Call: Number not assigned\n");
249                         break;
250                 case GSM48_CC_CAUSE_NO_ROUTE:
251                         vty_notify(ms, "Call: Destination unreachable\n");
252                         break;
253                 case GSM48_CC_CAUSE_NORM_CALL_CLEAR:
254                         vty_notify(ms, "Call: Remote hangs up\n");
255                         break;
256                 case GSM48_CC_CAUSE_USER_BUSY:
257                         vty_notify(ms, "Call: Remote busy\n");
258                         break;
259                 case GSM48_CC_CAUSE_USER_NOTRESPOND:
260                         vty_notify(ms, "Call: Remote not responding\n");
261                         break;
262                 case GSM48_CC_CAUSE_USER_ALERTING_NA:
263                         vty_notify(ms, "Call: Remote not answering\n");
264                         break;
265                 case GSM48_CC_CAUSE_CALL_REJECTED:
266                         vty_notify(ms, "Call has been rejected\n");
267                         break;
268                 case GSM48_CC_CAUSE_NUMBER_CHANGED:
269                         vty_notify(ms, "Call: Number changed\n");
270                         break;
271                 case GSM48_CC_CAUSE_PRE_EMPTION:
272                         vty_notify(ms, "Call: Cleared due to pre-emption\n");
273                         break;
274                 case GSM48_CC_CAUSE_DEST_OOO:
275                         vty_notify(ms, "Call: Remote out of order\n");
276                         break;
277                 case GSM48_CC_CAUSE_INV_NR_FORMAT:
278                         vty_notify(ms, "Call: Number invalid or imcomplete\n");
279                         break;
280                 case GSM48_CC_CAUSE_NO_CIRCUIT_CHAN:
281                         vty_notify(ms, "Call: No channel available\n");
282                         break;
283                 case GSM48_CC_CAUSE_NETWORK_OOO:
284                         vty_notify(ms, "Call: Network out of order\n");
285                         break;
286                 case GSM48_CC_CAUSE_TEMP_FAILURE:
287                         vty_notify(ms, "Call: Temporary failure\n");
288                         break;
289                 case GSM48_CC_CAUSE_SWITCH_CONG:
290                         vty_notify(ms, "Congestion\n");
291                         break;
292                 default:
293                         vty_notify(ms, "Call has been disconnected\n");
294                 }
295                 LOGP(DMNCC, LOGL_INFO, "Call has been disconnected "
296                         "(cause %d)\n", data->cause.value);
297                 if ((data->fields & MNCC_F_PROGRESS)
298                  && data->progress.descr == 8) {
299                         vty_notify(ms, "Please hang up!\n");
300                         break;
301                 }
302                 free_call(call);
303                 cause = GSM48_CC_CAUSE_NORM_CALL_CLEAR;
304                 goto release;
305         case MNCC_REL_IND:
306         case MNCC_REL_CNF:
307                 vty_notify(ms, NULL);
308                 if (data->cause.value == GSM48_CC_CAUSE_CALL_REJECTED)
309                         vty_notify(ms, "Call has been rejected\n");
310                 else
311                         vty_notify(ms, "Call has been released\n");
312                 LOGP(DMNCC, LOGL_INFO, "Call has been released (cause %d)\n",
313                         data->cause.value);
314                 free_call(call);
315                 break;
316         case MNCC_CALL_PROC_IND:
317                 vty_notify(ms, NULL);
318                 vty_notify(ms, "Call is proceeding\n");
319                 LOGP(DMNCC, LOGL_INFO, "Call is proceeding\n");
320                 if ((data->fields & MNCC_F_BEARER_CAP)
321                  && data->bearer_cap.speech_ver[0] >= 0) {
322                         mncc_get_bearer(sup, data->bearer_cap.speech_ver[0]);
323                 }
324                 break;
325         case MNCC_ALERT_IND:
326                 vty_notify(ms, NULL);
327                 vty_notify(ms, "Call is aleriting\n");
328                 LOGP(DMNCC, LOGL_INFO, "Call is alerting\n");
329                 break;
330         case MNCC_SETUP_CNF:
331                 vty_notify(ms, NULL);
332                 vty_notify(ms, "Call is answered\n");
333                 LOGP(DMNCC, LOGL_INFO, "Call is answered\n");
334                 break;
335         case MNCC_SETUP_IND:
336                 vty_notify(ms, NULL);
337                 if (!first_call && !ms->settings.cw) {
338                         vty_notify(ms, "Incomming call rejected while busy\n");
339                         LOGP(DMNCC, LOGL_INFO, "Incomming call but busy\n");
340                         cause = GSM48_CC_CAUSE_USER_BUSY;
341                         goto release;
342                 }
343                 /* select first supported speech_ver */
344                 if ((data->fields & MNCC_F_BEARER_CAP)) {
345                         int i;
346
347                         for (i = 0; data->bearer_cap.speech_ver[i] >= 0; i++) {
348
349                                 temp = mncc_get_bearer(sup,
350                                         data->bearer_cap.speech_ver[i]);
351                                 if (temp < 0)
352                                         continue;
353                                 if (temp == 5 || temp == 1) { /* half */
354                                         /* only the first half rate */
355                                         if (speech_ver_half < 0)
356                                                 speech_ver_half = temp;
357                                 } else {
358                                         /* only the first full rate */
359                                         if (speech_ver < 0)
360                                                 speech_ver = temp;
361                                 }
362                         }
363                         /* half and full given */
364                         if (speech_ver_half >= 0 && speech_ver >= 0) {
365                                 if (set->half_prefer) {
366                                         LOGP(DMNCC, LOGL_INFO, " both supported"
367                                                 " codec rates are given, using "
368                                                 "preferred half rate\n");
369                                         speech_ver = speech_ver_half;
370                                 } else
371                                         LOGP(DMNCC, LOGL_INFO, " both supported"
372                                                 " codec rates are given, using "
373                                                 "preferred full rate\n");
374                         } else if (speech_ver_half < 0 && speech_ver < 0) {
375                                 LOGP(DMNCC, LOGL_INFO, " no supported codec "
376                                         "rate is given\n");
377                         /* only half rate is given, use it */
378                         } else if (speech_ver_half >= 0) {
379                                 LOGP(DMNCC, LOGL_INFO, " only supported half "
380                                         "rate codec is given, using it\n");
381                                 speech_ver = speech_ver_half;
382                         /* only full rate is given, use it */
383                         } else {
384                                 LOGP(DMNCC, LOGL_INFO, " only supported full "
385                                         "rate codec is given, using it\n");
386                         }
387                         if (speech_ver < 0) {
388                                 vty_notify(ms, "Incomming call rejected, no "
389                                         "voice call\n");
390                                 LOGP(DMNCC, LOGL_INFO, "Incomming call "
391                                         "rejected, no voice call\n");
392                                 cause = GSM48_CC_CAUSE_BEARERSERV_UNIMPL;
393                                 goto release;
394                         }
395                 }
396                 /* presentation allowed if present == 0 */
397                 if (data->calling.present || !data->calling.number[0])
398                         vty_notify(ms, "Incomming call (anonymous)\n");
399                 else if (data->calling.type == 1)
400                         vty_notify(ms, "Incomming call (from +%s)\n",
401                                 data->calling.number);
402                 else if (data->calling.type == 2)
403                         vty_notify(ms, "Incomming call (from 0-%s)\n",
404                                 data->calling.number);
405                 else
406                         vty_notify(ms, "Incomming call (from %s)\n",
407                                 data->calling.number);
408                 LOGP(DMNCC, LOGL_INFO, "Incomming call (from %s callref %x)\n",
409                         data->calling.number, call->callref);
410                 memset(&mncc, 0, sizeof(struct gsm_mncc));
411                 mncc.callref = call->callref;
412                 /* only include bearer cap, if not given in setup
413                  * or if multiple codecs are given
414                  * or if not only full rate
415                  * or if given codec is unimplemented
416                  */
417                 if (!(data->fields & MNCC_F_BEARER_CAP) || speech_ver < 0)
418                         mncc_set_bearer(ms, -1, &mncc);
419                 else if (data->bearer_cap.speech_ver[1] >= 0
420                       || speech_ver != 0)
421                         mncc_set_bearer(ms, speech_ver, &mncc);
422                 mncc_send(ms, MNCC_CALL_CONF_REQ, &mncc);
423                 if (first_call)
424                         LOGP(DMNCC, LOGL_INFO, "Ring!\n");
425                 else {
426                         LOGP(DMNCC, LOGL_INFO, "Knock!\n");
427                         call->hold = 1;
428                 }
429                 call->ring = 1;
430                 memset(&mncc, 0, sizeof(struct gsm_mncc));
431                 mncc.callref = call->callref;
432                 mncc_send(ms, MNCC_ALERT_REQ, &mncc);
433                 break;
434         case MNCC_SETUP_COMPL_IND:
435                 vty_notify(ms, NULL);
436                 vty_notify(ms, "Call is connected\n");
437                 LOGP(DMNCC, LOGL_INFO, "Call is connected\n");
438                 break;
439         case MNCC_HOLD_CNF:
440                 vty_notify(ms, NULL);
441                 vty_notify(ms, "Call is on hold\n");
442                 LOGP(DMNCC, LOGL_INFO, "Call is on hold\n");
443                 call->hold = 1;
444                 break;
445         case MNCC_HOLD_REJ:
446                 vty_notify(ms, NULL);
447                 vty_notify(ms, "Call hold was rejected\n");
448                 LOGP(DMNCC, LOGL_INFO, "Call hold was rejected\n");
449                 break;
450         case MNCC_RETRIEVE_CNF:
451                 vty_notify(ms, NULL);
452                 vty_notify(ms, "Call is retrieved\n");
453                 LOGP(DMNCC, LOGL_INFO, "Call is retrieved\n");
454                 call->hold = 0;
455                 break;
456         case MNCC_RETRIEVE_REJ:
457                 vty_notify(ms, NULL);
458                 vty_notify(ms, "Call retrieve was rejected\n");
459                 LOGP(DMNCC, LOGL_INFO, "Call retrieve was rejected\n");
460                 break;
461         default:
462                 LOGP(DMNCC, LOGL_INFO, "Message 0x%02x unsupported\n",
463                         msg_type);
464                 return -EINVAL;
465         }
466
467         return 0;
468 }
469
470 int mncc_call(struct osmocom_ms *ms, char *number)
471 {
472         struct gsm_call *call;
473         struct gsm_mncc setup;
474
475         llist_for_each_entry(call, &call_list, entry) {
476                 if (!call->hold) {
477                         vty_notify(ms, NULL);
478                         vty_notify(ms, "Please put active call on hold "
479                                 "first!\n");
480                         LOGP(DMNCC, LOGL_INFO, "Cannot make a call, busy!\n");
481                         return -EBUSY;
482                 }
483         }
484
485         call = talloc_zero(l23_ctx, struct gsm_call);
486         if (!call)
487                 return -ENOMEM;
488         call->callref = new_callref++;
489         call->init = 1;
490         llist_add_tail(&call->entry, &call_list);
491
492         memset(&setup, 0, sizeof(struct gsm_mncc));
493         setup.callref = call->callref;
494
495         if (!strncasecmp(number, "emerg", 5)) {
496                 LOGP(DMNCC, LOGL_INFO, "Make emergency call\n");
497                 /* emergency */
498                 setup.emergency = 1;
499         } else {
500                 LOGP(DMNCC, LOGL_INFO, "Make call to %s\n", number);
501                 /* called number */
502                 setup.fields |= MNCC_F_CALLED;
503                 if (number[0] == '+') {
504                         number++;
505                         setup.called.type = 1; /* international */
506                 } else
507                         setup.called.type = 0; /* auto/unknown - prefix must be
508                                                   used */
509                 setup.called.plan = 1; /* ISDN */
510                 strncpy(setup.called.number, number,
511                         sizeof(setup.called.number) - 1);
512                 
513                 /* bearer capability (mandatory) */
514                 mncc_set_bearer(ms, -1, &setup);
515                 if (ms->settings.clir)
516                         setup.clir.sup = 1;
517                 else if (ms->settings.clip)
518                         setup.clir.inv = 1;
519         }
520
521         return mncc_send(ms, MNCC_SETUP_REQ, &setup);
522 }
523
524 int mncc_hangup(struct osmocom_ms *ms)
525 {
526         struct gsm_call *call, *found = NULL;
527         struct gsm_mncc disc;
528
529         llist_for_each_entry(call, &call_list, entry) {
530                 if (!call->hold) {
531                         found = call;
532                         break;
533                 }
534         }
535         if (!found) {
536                 LOGP(DMNCC, LOGL_INFO, "No active call to hangup\n");
537                 vty_notify(ms, NULL);
538                 vty_notify(ms, "No active call\n");
539                 return -EINVAL;
540         }
541
542         memset(&disc, 0, sizeof(struct gsm_mncc));
543         disc.callref = found->callref;
544         mncc_set_cause(&disc, GSM48_CAUSE_LOC_USER,
545                 GSM48_CC_CAUSE_NORM_CALL_CLEAR);
546         return mncc_send(ms, (call->init) ? MNCC_REL_REQ : MNCC_DISC_REQ,
547                 &disc);
548 }
549
550 int mncc_answer(struct osmocom_ms *ms)
551 {
552         struct gsm_call *call, *alerting = NULL;
553         struct gsm_mncc rsp;
554         int active = 0;
555
556         llist_for_each_entry(call, &call_list, entry) {
557                 if (call->ring)
558                         alerting = call;
559                 else if (!call->hold)
560                         active = 1;
561         }
562         if (!alerting) {
563                 LOGP(DMNCC, LOGL_INFO, "No call alerting\n");
564                 vty_notify(ms, NULL);
565                 vty_notify(ms, "No alerting call\n");
566                 return -EBUSY;
567         }
568         if (active) {
569                 LOGP(DMNCC, LOGL_INFO, "Answer but we have an active call\n");
570                 vty_notify(ms, NULL);
571                 vty_notify(ms, "Please put active call on hold first!\n");
572                 return -EBUSY;
573         }
574         alerting->ring = 0;
575         alerting->hold = 0;
576
577         memset(&rsp, 0, sizeof(struct gsm_mncc));
578         rsp.callref = alerting->callref;
579         return mncc_send(ms, MNCC_SETUP_RSP, &rsp);
580 }
581
582 int mncc_hold(struct osmocom_ms *ms)
583 {
584         struct gsm_call *call, *found = NULL;
585         struct gsm_mncc hold;
586
587         llist_for_each_entry(call, &call_list, entry) {
588                 if (!call->hold) {
589                         found = call;
590                         break;
591                 }
592         }
593         if (!found) {
594                 LOGP(DMNCC, LOGL_INFO, "No active call to hold\n");
595                 vty_notify(ms, NULL);
596                 vty_notify(ms, "No active call\n");
597                 return -EINVAL;
598         }
599
600         memset(&hold, 0, sizeof(struct gsm_mncc));
601         hold.callref = found->callref;
602         return mncc_send(ms, MNCC_HOLD_REQ, &hold);
603 }
604
605 int mncc_retrieve(struct osmocom_ms *ms, int number)
606 {
607         struct gsm_call *call;
608         struct gsm_mncc retr;
609         int holdnum = 0, active = 0, i = 0;
610
611         llist_for_each_entry(call, &call_list, entry) {
612                 if (call->hold)
613                         holdnum++;
614                 if (!call->hold)
615                         active = 1;
616         }
617         if (active) {
618                 LOGP(DMNCC, LOGL_INFO, "Cannot retrieve during active call\n");
619                 vty_notify(ms, NULL);
620                 vty_notify(ms, "Hold active call first!\n");
621                 return -EINVAL;
622         }
623         if (holdnum == 0) {
624                 vty_notify(ms, NULL);
625                 vty_notify(ms, "No call on hold!\n");
626                 return -EINVAL;
627         }
628         if (holdnum > 1 && number <= 0) {
629                 vty_notify(ms, NULL);
630                 vty_notify(ms, "Select call 1..%d\n", holdnum);
631                 return -EINVAL;
632         }
633         if (holdnum == 1 && number <= 0)
634                 number = 1;
635         if (number > holdnum) {
636                 vty_notify(ms, NULL);
637                 vty_notify(ms, "Given number %d out of range!\n", number);
638                 vty_notify(ms, "Select call 1..%d\n", holdnum);
639                 return -EINVAL;
640         }
641
642         llist_for_each_entry(call, &call_list, entry) {
643                 i++;
644                 if (i == number)
645                         break;
646         }
647
648         memset(&retr, 0, sizeof(struct gsm_mncc));
649         retr.callref = call->callref;
650         return mncc_send(ms, MNCC_RETRIEVE_REQ, &retr);
651 }
652
653
654
655