3f460414015bf38a5a19dad4953dc7923acc8330
[gnokii.git] / common / phones / atgen.c
1 /*
2
3   $Id$
4
5   G N O K I I
6
7   A Linux/Unix toolset and driver for mobile phones.
8
9   Copyright 2001 Manfred Jonsson <manfred.jonsson@gmx.de>
10
11   Released under the terms of the GNU GPL, see file COPYING for more details.
12
13   This file provides functions specific to generic at command compatible
14   phones. See README for more details on supported mobile phones.
15
16   $Log$
17   Revision 1.1.1.1.6.1  2001/11/25 23:04:51  short
18   * new common/misc.c/
19     * g{,v}asprintf() - "asprintf()" compatibility emulation
20     * ARRAY_LEN() - sizeof(x)/sizeof(*x)
21     * SAFE_STRNCPY{,_SIZEOF}() - strncpy with variable-size autodetection
22     * G_GNUC_PRINTF - GCC attribute from glib
23     * N_(x) - missing in localization macros
24
25   Revision 1.1.1.1  2001/11/25 21:59:11  short
26   :pserver:cvs@pserver.samba.org:/cvsroot - gnokii - Sun Nov 25 22:56 CET 2001
27
28   Revision 1.6  2001/11/19 13:03:18  pkot
29   nk3110.c cleanup
30
31   Revision 1.5  2001/11/08 16:49:19  pkot
32   Cleanups
33
34   Revision 1.4  2001/08/20 23:36:27  pkot
35   More cleanup in AT code (Manfred Jonsson)
36
37   Revision 1.3  2001/08/20 23:27:37  pkot
38   Add hardware shakehand to the link layer (Manfred Jonsson)
39
40   Revision 1.2  2001/08/09 11:51:39  pkot
41   Generic AT support updates and cleanup (Manfred Jonsson)
42
43   Revision 1.1  2001/07/27 00:02:21  pkot
44   Generic AT support for the new structure (Manfred Jonsson)
45
46 */
47
48 #include <string.h>
49 #include <stdlib.h>
50 #include <ctype.h>
51
52 #include "misc.h"
53 #include "gsm-common.h"
54 #include "gsm-statemachine.h"
55 #include "gsm-encoding.h"
56 #include "phones/generic.h"
57 #include "phones/atgen.h"
58 #include "phones/ateric.h"
59 #include "phones/atsie.h"
60 #include "phones/atnok.h"
61 #include "links/atbus.h"
62 #include "links/cbus.h"
63
64
65 static GSM_Error Initialise(GSM_Data *setupdata, GSM_Statemachine *state);
66 static GSM_Error Functions(GSM_Operation op, GSM_Data *data, GSM_Statemachine *state);
67 static GSM_Error Reply(int messagetype, unsigned char *buffer, int length, GSM_Data *data);
68 static GSM_Error ReplyIdentify(int messagetype, unsigned char *buffer, int length, GSM_Data *data);
69 static GSM_Error ReplyGetRFLevel(int messagetype, unsigned char *buffer, int length, GSM_Data *data);
70 static GSM_Error ReplyGetBattery(int messagetype, unsigned char *buffer, int length, GSM_Data *data);
71 static GSM_Error ReplyReadPhonebook(int messagetype, unsigned char *buffer, int length, GSM_Data *data);
72 static GSM_Error ReplyMemoryStatus(int messagetype, unsigned char *buffer, int length, GSM_Data *data);
73
74 static GSM_Error AT_Identify(GSM_Data *data, GSM_Statemachine *state);
75 static GSM_Error AT_GetModel(GSM_Data *data, GSM_Statemachine *state);
76 static GSM_Error AT_GetRevision(GSM_Data *data, GSM_Statemachine *state);
77 static GSM_Error AT_GetIMEI(GSM_Data *data, GSM_Statemachine *state);
78 static GSM_Error AT_GetManufacturer(GSM_Data *data, GSM_Statemachine *state);
79 static GSM_Error AT_GetBattery(GSM_Data *data,  GSM_Statemachine *state);
80 static GSM_Error AT_GetRFLevel(GSM_Data *data,  GSM_Statemachine *state);
81 static GSM_Error AT_GetMemoryStatus(GSM_Data *data,  GSM_Statemachine *state);
82 static GSM_Error AT_ReadPhonebook(GSM_Data *data,  GSM_Statemachine *state);
83
84 typedef struct {
85         int gop;
86         AT_SendFunctionType sfunc;
87         GSM_RecvFunctionType rfunc;
88 } AT_FunctionInitType;
89
90
91 /* Mobile phone information */
92 static AT_SendFunctionType AT_Functions[GOP_Max];
93 static GSM_IncomingFunctionType IncomingFunctions[GOP_Max];
94 static AT_FunctionInitType AT_FunctionInit[] = {
95         { GOP_Init, NULL, Reply },
96         { GOP_GetModel, AT_GetModel, ReplyIdentify },
97         { GOP_GetRevision, AT_GetRevision, ReplyIdentify },
98         { GOP_GetImei, AT_GetIMEI, ReplyIdentify },
99         { GOP_GetManufacturer, AT_GetManufacturer, ReplyIdentify },
100         { GOP_Identify, AT_Identify, ReplyIdentify },
101         { GOP_GetBatteryLevel, AT_GetBattery, ReplyGetBattery },
102         { GOP_GetPowersource, AT_GetBattery, ReplyGetBattery },
103         { GOP_GetRFLevel, AT_GetRFLevel, ReplyGetRFLevel },
104         { GOP_GetMemoryStatus, AT_GetMemoryStatus, ReplyMemoryStatus },
105         { GOP_ReadPhonebook, AT_ReadPhonebook, ReplyReadPhonebook },
106 };
107
108
109 #define REPLY_SIMPLETEXT(l1, l2, c, t) \
110         if ((0 == strcmp(l1, c)) && (NULL != t)) strcpy(t, l2)
111
112
113 GSM_Phone phone_at = {
114         IncomingFunctions,
115         PGEN_IncomingDefault,
116         {
117                 "AT|AT-HW|dancall",                     /* Supported models */
118                 99,                     /* Max RF Level */
119                 0,                      /* Min RF Level */
120                 GRF_CSQ,                /* RF level units */
121                 100,                    /* Max Battery Level */
122                 0,                      /* Min Battery Level */
123                 GBU_Percentage,         /* Battery level units */
124                 0,                      /* Have date/time support */
125                 0,                      /* Alarm supports time only */
126                 0,                      /* Alarms available - FIXME */
127                 0, 0,                   /* Startup logo size - FIXME */
128                 0, 0,                   /* Op logo size */
129                 0, 0                    /* Caller logo size */
130         },
131         Functions
132 };
133
134
135 static GSM_MemoryType memorytype = GMT_XX;
136 static int atcharset = 0;
137
138 static char *memorynames[] = {
139         "ME", /* Internal memory of the mobile equipment */
140         "SM", /* SIM card memory */
141         "FD", /* Fixed dial numbers */
142         "ON", /* Own numbers */
143         "EN", /* Emergency numbers */
144         "DC", /* Dialled numbers */
145         "RC", /* Received numbers */
146         "MC", /* Missed numbers */
147         "LD", /* Last dialed */
148         "MT", /* combined ME and SIM phonebook */
149         "TA", /* for compatibility only: TA=computer memory */
150         "CB", /* Currently selected memory */
151 };
152
153 /* LinkOK is always true for now... */
154 bool ATGEN_LinkOK = true;
155
156
157 GSM_RecvFunctionType AT_InsertRecvFunction(int type, GSM_RecvFunctionType func)
158 {
159         static int pos = 0;
160         int i;
161         GSM_RecvFunctionType oldfunc;
162
163         if (type >= GOP_Max) {
164                 return (GSM_RecvFunctionType) -1;
165         }
166         if (pos == 0) {
167                 IncomingFunctions[pos].MessageType = type;
168                 IncomingFunctions[pos].Functions = func;
169                 pos++;
170                 return NULL;
171         }
172         for (i=0; i < pos; i++) {
173                 if (IncomingFunctions[i].MessageType == type) {
174                         oldfunc = IncomingFunctions[i].Functions;
175                         IncomingFunctions[i].Functions = func;
176                         return oldfunc;
177                 }
178         }
179         if (pos < GOP_Max-1) {
180                 IncomingFunctions[pos].MessageType = type;
181                 IncomingFunctions[pos].Functions = func;
182                 pos++;
183         }
184         return NULL;
185 }
186
187
188 AT_SendFunctionType AT_InsertSendFunction(int type, AT_SendFunctionType func)
189 {
190         AT_SendFunctionType f;
191
192         f = AT_Functions[type];
193         AT_Functions[type] = func;
194         return f;
195 }
196
197
198 static GSM_Error SetEcho(GSM_Data *data, GSM_Statemachine *state)
199 {
200         char req[128];
201
202         sprintf(req, "ATE1\r\n");
203         if (SM_SendMessage(state, 6, GOP_Init, req) != GE_NONE) return GE_NOTREADY;
204         return SM_Block(state, data, GOP_Init);
205 }
206
207
208 GSM_Error AT_SetMemoryType(GSM_MemoryType mt, GSM_Statemachine *state)
209 {
210         char req[128];
211         GSM_Error ret = GE_NONE;
212         GSM_Data data;
213
214         if (mt != memorytype) {
215                 sprintf(req, "AT+CPBS=\"%s\"\r\n", memorynames[mt]);
216                 ret = SM_SendMessage(state, 14, GOP_Init, req);
217                 if (ret != GE_NONE)
218                         return GE_NOTREADY;
219                 GSM_DataClear(&data);
220                 ret = SM_Block(state, &data, GOP_Init);
221                 if (ret == GE_NONE)
222                         memorytype = mt;
223         }
224         return ret;
225 }
226
227
228 static GSM_Error SetCharset(GSM_Statemachine *state)
229 {
230         char req[128];
231         GSM_Error ret = GE_NONE;
232         GSM_Data data;
233
234         if (atcharset == 0) {
235                 sprintf(req, "AT+CSCS=\"GSM\"\r\n");
236                 ret = SM_SendMessage(state, 15, GOP_Init, req);
237                 if (ret != GE_NONE)
238                         return GE_NOTREADY;
239                 GSM_DataClear(&data);
240                 ret = SM_Block(state, &data, GOP_Init);
241                 if (ret == GE_NONE)
242                         atcharset = 1;
243         }
244         return ret;
245 }
246
247
248 static GSM_Error AT_Identify(GSM_Data *data, GSM_Statemachine *state)
249 {
250         GSM_Error ret;
251
252         if ((ret = Functions(GOP_GetModel, data, state)) != GE_NONE)
253                 return ret;
254         if ((ret = Functions(GOP_GetManufacturer, data, state)) != GE_NONE)
255                 return ret;
256         if ((ret = Functions(GOP_GetRevision, data, state)) != GE_NONE)
257                 return ret;
258         return Functions(GOP_GetImei, data, state);
259 }
260
261
262 static GSM_Error AT_GetModel(GSM_Data *data, GSM_Statemachine *state)
263 {
264         char req[128];
265
266         sprintf(req, "AT+CGMM\r\n");
267         if (SM_SendMessage(state, 9, GOP_Identify, req) != GE_NONE)
268                 return GE_NOTREADY;
269         return SM_Block(state, data, GOP_Identify);
270 }
271
272
273 static GSM_Error AT_GetManufacturer(GSM_Data *data, GSM_Statemachine *state)
274 {
275         char req[128];
276
277         sprintf(req, "AT+CGMI\r\n");
278         if (SM_SendMessage(state, 9, GOP_Identify, req) != GE_NONE)
279                 return GE_NOTREADY;
280         return SM_Block(state, data, GOP_Identify);
281 }
282
283
284 static GSM_Error AT_GetRevision(GSM_Data *data, GSM_Statemachine *state)
285 {
286         char req[128];
287
288         sprintf(req, "AT+CGMR\r\n");
289         if (SM_SendMessage(state, 9, GOP_Identify, req) != GE_NONE)
290                 return GE_NOTREADY;
291         return SM_Block(state, data, GOP_Identify);
292 }
293
294
295 static GSM_Error AT_GetIMEI(GSM_Data *data, GSM_Statemachine *state)
296 {
297         char req[128];
298
299         sprintf(req, "AT+CGSN\r\n");
300         if (SM_SendMessage(state, 9, GOP_Identify, req) != GE_NONE)
301                 return GE_NOTREADY;
302         return SM_Block(state, data, GOP_Identify);
303 }
304
305
306 /* gets battery level and power source */
307
308 static GSM_Error AT_GetBattery(GSM_Data *data,  GSM_Statemachine *state)
309 {
310         char req[128];
311
312         sprintf(req, "AT+CBC\r\n");
313         if (SM_SendMessage(state, 8, GOP_GetBatteryLevel, req) != GE_NONE)
314                 return GE_NOTREADY;
315         return SM_Block(state, data, GOP_GetBatteryLevel);
316 }
317
318
319 static GSM_Error AT_GetRFLevel(GSM_Data *data,  GSM_Statemachine *state)
320 {
321         char req[128];
322  
323         sprintf(req, "AT+CSQ\r\n");
324         if (SM_SendMessage(state, 8, GOP_GetRFLevel, req) != GE_NONE)
325                 return GE_NOTREADY;
326         return SM_Block(state, data, GOP_GetRFLevel);
327 }
328  
329  
330 static GSM_Error AT_GetMemoryStatus(GSM_Data *data,  GSM_Statemachine *state)
331 {
332         char req[128];
333         GSM_Error ret;
334
335         ret = AT_SetMemoryType(data->MemoryStatus->MemoryType,  state);
336         if (ret != GE_NONE)
337                 return ret;
338         sprintf(req, "AT+CPBS?\r\n");
339         if (SM_SendMessage(state, 10, GOP_GetMemoryStatus, req) != GE_NONE)
340                 return GE_NOTREADY;
341         ret = SM_Block(state, data, GOP_GetMemoryStatus);
342         if (ret != GE_UNKNOWN)
343                 return ret;
344         sprintf(req, "AT+CPBR=?\r\n");
345         if (SM_SendMessage(state, 11, GOP_GetMemoryStatus, req) != GE_NONE)
346                 return GE_NOTREADY;
347         ret = SM_Block(state, data, GOP_GetMemoryStatus);
348         return ret;
349 }
350
351
352 static GSM_Error AT_ReadPhonebook(GSM_Data *data,  GSM_Statemachine *state)
353 {
354         char req[128];
355         GSM_Error ret;
356
357         ret = SetCharset(state);
358         if (ret != GE_NONE)
359                 return ret;
360         ret = AT_SetMemoryType(data->PhonebookEntry->MemoryType,  state);
361         if (ret != GE_NONE)
362                 return ret;
363         sprintf(req, "AT+CPBR=%d\r\n", data->PhonebookEntry->Location);
364         if (SM_SendMessage(state, strlen(req), GOP_ReadPhonebook, req) != GE_NONE)
365                 return GE_NOTREADY;
366         ret = SM_Block(state, data, GOP_ReadPhonebook);
367         return ret;
368 }
369
370
371 static GSM_Error Functions(GSM_Operation op, GSM_Data *data, GSM_Statemachine *state)
372 {
373         if (op == GOP_Init)
374                 return Initialise(data, state);
375         if ((op > GOP_Init) && (op < GOP_Max))
376                 if (AT_Functions[op])
377                         return (*AT_Functions[op])(data, state);
378         return GE_NOTIMPLEMENTED;
379 }
380
381
382 static GSM_Error ReplyReadPhonebook(int messagetype, unsigned char *buffer, int length, GSM_Data *data)
383 {
384         AT_LineBuffer buf;
385         char *pos, *endpos;
386         int l;
387
388         buf.line1 = buffer;
389         buf.length= length;
390         splitlines(&buf);
391         if (buf.line1 == NULL)
392                 return GE_INVALIDPHBOOKLOCATION;
393
394         if (strncmp(buffer, "AT+CPBR", 7)) {
395                 return GE_NONE; /*FIXME*/
396         }
397
398         if (!strncmp(buf.line2, "OK", 2)) {
399                 if (data->PhonebookEntry) {
400                         *(data->PhonebookEntry->Number) = '\0';
401                         *(data->PhonebookEntry->Name) = '\0';
402                         data->PhonebookEntry->Group = 0;
403                         data->PhonebookEntry->SubEntriesCount = 0;
404                 }
405                 return GE_NONE;
406         }
407         if (data->PhonebookEntry) {
408                 data->PhonebookEntry->Group = 0;
409                 data->PhonebookEntry->SubEntriesCount = 0;
410                 pos = strchr(buf.line2, '\"');
411                 endpos = NULL;
412                 if (pos)
413                         endpos = strchr(++pos, '\"');
414                 if (endpos) {
415                         *endpos = '\0';
416                         strcpy(data->PhonebookEntry->Number, pos);
417                 }
418                 pos = NULL;
419                 if (endpos)
420                         pos = strchr(++endpos, '\"');
421                 endpos = NULL;
422                 if (pos) {
423                         pos++;
424                         l = pos - (char *)buffer;
425                         endpos = memchr(pos, '\"', length - l);
426                 }
427                 if (endpos) {
428                         l = endpos - pos;
429                         DecodeAscii(data->PhonebookEntry->Name, pos, l);
430                         *(data->PhonebookEntry->Name + l) = '\0';
431                 }
432         }
433         return GE_NONE;
434 }
435
436
437 static GSM_Error ReplyMemoryStatus(int messagetype, unsigned char *buffer, int length, GSM_Data *data)
438 {
439         AT_LineBuffer buf;
440         char *pos;
441
442         buf.line1 = buffer;
443         buf.length= length;
444         splitlines(&buf);
445         if (buf.line1 == NULL)
446                 return GE_INVALIDMEMORYTYPE;
447
448         if (data->MemoryStatus) {
449                 if (strstr(buf.line2,"+CPBS")) {
450                         pos = strchr(buf.line2, ',');
451                         if (pos) {
452                                 data->MemoryStatus->Used = atoi(++pos);
453                         } else {
454                                 data->MemoryStatus->Used = 100;
455                                 data->MemoryStatus->Free = 0;
456                                 return GE_UNKNOWN;
457                         }
458                         pos = strchr(pos, ',');
459                         if (pos) {
460                                 data->MemoryStatus->Free = atoi(++pos) - data->MemoryStatus->Used;
461                         } else {
462                                 return GE_UNKNOWN;
463                         }
464                 }
465         }
466         return GE_NONE;
467 }
468
469
470 static GSM_Error ReplyGetBattery(int messagetype, unsigned char *buffer, int length, GSM_Data *data)
471 {
472         AT_LineBuffer buf;
473         char *pos;
474
475         buf.line1 = buffer;
476         buf.length= length;
477         splitlines(&buf);
478         if ((buf.line1 == NULL) || (buf.line2 == NULL))
479                 return GE_NONE;
480
481         if (!strncmp(buffer, "AT+CBC", 6)) {
482                 if (data->BatteryLevel) {
483                         *(data->BatteryUnits) = GBU_Percentage;
484                         pos = strchr(buf.line2, ',');
485                         if (pos) {
486                                 pos++;
487                                 *(data->BatteryLevel) = atoi(pos);
488                         } else {
489                                 *(data->BatteryLevel) = 1;
490                         }
491                 }
492                 if (data->PowerSource) {
493                         *(data->PowerSource) = 0;
494                         if (*buf.line2 == '1') *(data->PowerSource) = GPS_ACDC;
495                         if (*buf.line2 == '0') *(data->PowerSource) = GPS_BATTERY;
496                 }
497         }
498         return GE_NONE;
499 }
500
501
502 static GSM_Error ReplyGetRFLevel(int messagetype, unsigned char *buffer, int length, GSM_Data *data)
503 {
504         AT_LineBuffer buf;
505         char *pos1, *pos2;
506
507         buf.line1 = buffer;
508         buf.length= length;
509         splitlines(&buf);
510         if (buf.line1 == NULL)
511                 return GE_NONE;
512
513         if ((!strncmp(buffer, "AT+CSQ", 6)) && (data->RFUnits)) {
514                 *(data->RFUnits) = GRF_CSQ;
515                 pos1 = buf.line2 + 6;
516                 pos2 = strchr(buf.line2, ',');
517                 if (pos1 < pos2) {
518                         *(data->RFLevel) = atoi(pos1);
519                 } else {
520                         *(data->RFLevel) = 1;
521                 }
522         }
523         return GE_NONE;
524 }
525
526
527 static GSM_Error ReplyIdentify(int messagetype, unsigned char *buffer, int length, GSM_Data *data)
528 {
529         AT_LineBuffer buf;
530
531         buf.line1 = buffer;
532         buf.length= length;
533         splitlines(&buf);
534         if (buf.line1 == NULL)
535                 return GE_NONE;         /* Fixme */
536         if (!strncmp(buffer, "AT+CG", 5)) {
537                 REPLY_SIMPLETEXT(buffer+5, buf.line2, "SN", data->Imei);
538                 REPLY_SIMPLETEXT(buffer+5, buf.line2, "MM", data->Model);
539                 REPLY_SIMPLETEXT(buffer+5, buf.line2, "MI", data->Manufacturer);
540                 REPLY_SIMPLETEXT(buffer+5, buf.line2, "MR", data->Revision);
541         }
542         return GE_NONE;
543 }
544
545
546 static GSM_Error Reply(int messagetype, unsigned char *buffer, int length, GSM_Data *data)
547 {
548         AT_LineBuffer buf;
549         int error = 0;
550
551         buf.line1 = buffer;
552         buf.length= length;
553         splitlines(&buf);
554         if (buf.line1 == NULL)
555                 error = 1;
556
557         return GE_NONE;
558 }
559
560
561 static GSM_Error Initialise(GSM_Data *setupdata, GSM_Statemachine *state)
562 {
563         GSM_Data data;
564         GSM_Error ret;
565         char model[20];
566         char manufacturer[20];
567         int i;
568
569         fprintf(stderr, "Initializing AT capable mobile phone ...\n");
570
571         /* Copy in the phone info */
572         memcpy(&(state->Phone), &phone_at, sizeof(GSM_Phone));
573
574         for (i=0; i<GOP_Max; i++) {
575                 AT_Functions[i] = NULL;
576                 IncomingFunctions[i].MessageType = 0;
577                 IncomingFunctions[i].Functions = NULL;
578         }
579         for (i=0; i<ARRAY_LEN(AT_FunctionInit); i++) {
580                 AT_InsertSendFunction(AT_FunctionInit[i].gop, AT_FunctionInit[i].sfunc);
581                 AT_InsertRecvFunction(AT_FunctionInit[i].gop, AT_FunctionInit[i].rfunc);
582         }
583
584         switch (state->Link.ConnectionType) {
585         case GCT_Serial:
586                 if (!strcmp(setupdata->Model, "dancall"))
587                         CBUS_Initialise(state);
588                 else if (!strcmp(setupdata->Model, "AT-HW"))
589                         ATBUS_Initialise(state, true);
590                 else
591                         ATBUS_Initialise(state, false);
592                 break;
593         default:
594                 return GE_NOTSUPPORTED;
595                 break;
596         }
597         SM_Initialise(state);
598
599         SetEcho(&data, state);
600
601         GSM_DataClear(&data);
602         data.Model = model;
603         ret = state->Phone.Functions(GOP_GetModel, &data, state);
604         if (ret != GE_NONE) return ret;
605         GSM_DataClear(&data);
606         data.Manufacturer = manufacturer;
607         ret = state->Phone.Functions(GOP_GetManufacturer, &data, state);
608         if (ret != GE_NONE) return ret;
609
610         if (!strncasecmp(manufacturer, "ericsson", 8))
611                 AT_InitEricsson(state, model, setupdata->Model);
612         if (!strncasecmp(manufacturer, "siemens", 7))
613                 AT_InitSiemens(state, model, setupdata->Model);
614         if (!strncasecmp(manufacturer, "nokia", 5))
615                 AT_InitNokia(state, model, setupdata->Model);
616
617         return GE_NONE;
618 }
619
620  
621 void splitlines(AT_LineBuffer *buf)
622 {
623         char *pos;
624
625         if ((buf->length > 7) && (!strncmp(buf->line1+buf->length-7, "ERROR", 5))) {
626                 buf->line1 = NULL;
627                 return;
628         }
629         pos = findcrlf(buf->line1, 0, buf->length);
630         if (pos) {
631                 *pos = 0;
632                 buf->line2 = skipcrlf(++pos);
633         } else {
634                 buf->line2 = buf->line1;
635         }
636         pos = findcrlf(buf->line2, 1, buf->length);
637         if (pos) {
638                 *pos = 0;
639                 buf->line3 = skipcrlf(++pos);
640         } else {
641                 buf->line3 = buf->line2;
642         }
643 }
644
645
646 /*
647  * increments the argument until a char unequal to
648  * <cr> or <lf> is found. returns the new position.
649  */
650  
651 char *skipcrlf(char *str)
652 {
653         if (str == NULL)
654                 return str;
655         while ((*str == '\n') || (*str == '\r'))
656                 str++;
657         return str;
658 }
659  
660  
661 /*
662  * searches for <cr> or <lf> and returns the first
663  * occurrence. if test is set, the gsm char @ which
664  * is 0x00 is not considered as end of string.
665  * return NULL if no <cr> or <lf> was found in the
666  * range of max bytes.
667  */
668  
669 char *findcrlf(char *str, int test, int max)
670 {
671         if (str == NULL)
672                 return str;
673         while ((*str != '\n') && (*str != '\r') && ((*str != '\0') || test) && (max > 0)) {
674                 str++;
675                 max--;
676         }
677         if ((*str == '\0') || ((max == 0) && (*str != '\n') && (*str != '\r')))
678                 return NULL;
679         return str;
680 }