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