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