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