This commit was generated by cvs2svn to compensate for changes in r158,
[gnokii.git] / common / gsm-common.c
1 /*
2
3   $Id$
4
5   G N O K I I
6
7   A Linux/Unix toolset and driver for Nokia mobile phones.
8
9   Copyright (C) 1999, 2000 Hugh Blemings & Pavel Janík ml.
10
11   Released under the terms of the GNU GPL, see file COPYING for more details.
12
13   $Log$
14   Revision 1.1.1.2  2002/04/03 00:07:55  short
15   Found in "gnokii-working" directory, some November-patches version
16
17   Revision 1.6  2001/09/14 12:15:28  pkot
18   Cleanups from 0.3.3 (part1)
19
20   Revision 1.5  2001/03/22 16:17:05  chris
21   Tidy-ups and fixed gnokii/Makefile and gnokii/ChangeLog which I somehow corrupted.
22
23   Revision 1.4  2001/03/21 23:36:04  chris
24   Added the statemachine
25   This will break gnokii --identify and --monitor except for 6210/7110
26
27   Revision 1.3  2001/02/28 21:26:51  machek
28   Added StrToMemoryType utility function
29
30   Revision 1.2  2001/02/03 23:56:15  chris
31   Start of work on irda support (now we just need fbus-irda.c!)
32   Proper unicode support in 7110 code (from pkot)
33
34   Revision 1.1  2001/01/12 14:28:39  pkot
35   Forgot to add this file. ;-)
36
37
38 */
39
40 #include <gsm-common.h>
41 #include <string.h>
42 #include <stdlib.h>     /* for rand() */
43 #include <ctype.h>
44
45 /* Coding functions */
46 #define NUMBER_OF_7_BIT_ALPHABET_ELEMENTS 128
47 static unsigned char GSM_DefaultAlphabet[NUMBER_OF_7_BIT_ALPHABET_ELEMENTS] = {
48
49         /* ETSI GSM 03.38, version 6.0.1, section 6.2.1; Default alphabet */
50         /* Characters in hex position 10, [12 to 1a] and 24 are not present on
51            latin1 charset, so we cannot reproduce on the screen, however they are
52            greek symbol not present even on my Nokia */
53         
54         '@',  0xa3, '$',  0xa5, 0xe8, 0xe9, 0xf9, 0xec, 
55         0xf2, 0xc7, '\n', 0xd8, 0xf8, '\r', 0xc5, 0xe5,
56         '?',  '_',  '?',  '?',  '?',  '?',  '?',  '?',
57         '?',  '?',  '?',  '?',  0xc6, 0xe6, 0xdf, 0xc9,
58         ' ',  '!',  '\"', '#',  0xa4,  '%',  '&',  '\'',
59         '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
60         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
61         '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
62         0xa1, 'A',  'B',  'C',  'D',  'E',  'F',  'G',
63         'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
64         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
65         'X',  'Y',  'Z',  0xc4, 0xd6, 0xd1, 0xdc, 0xa7,
66         0xbf, 'a',  'b',  'c',  'd',  'e',  'f',  'g',
67         'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
68         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
69         'x',  'y',  'z',  0xe4, 0xf6, 0xf1, 0xfc, 0xe0
70 };
71
72 unsigned char EncodeWithDefaultAlphabet(unsigned char value)
73 {
74         unsigned char i;
75         
76         if (value == '?') return  0x3f;
77         
78         for (i = 0; i < NUMBER_OF_7_BIT_ALPHABET_ELEMENTS; i++)
79                 if (GSM_DefaultAlphabet[i] == value)
80                         return i;
81         
82         return '?';
83 }
84
85 unsigned char DecodeWithDefaultAlphabet(unsigned char value)
86 {
87         return GSM_DefaultAlphabet[value];
88 }
89
90 wchar_t EncodeWithUnicodeAlphabet(unsigned char value)
91 {
92         wchar_t retval;
93         
94         if (mbtowc(&retval, &value, 1) == -1) return '?';
95         else return retval;
96 }
97
98 unsigned char DecodeWithUnicodeAlphabet(wchar_t value)
99 {
100         unsigned char retval;
101
102         if (wctomb(&retval, value) == -1) return '?';
103         else return retval;
104 }
105
106
107 GSM_Error Unimplemented(void)
108 {
109         return GE_NOTIMPLEMENTED;
110 }
111
112 GSM_MemoryType StrToMemoryType(const char *s)
113 {
114 #define X(a) if (!strcmp(s, #a)) return GMT_##a;
115         X(ME);
116         X(SM);
117         X(FD);
118         X(ON);
119         X(EN);
120         X(DC);
121         X(RC);
122         X(MC);
123         X(LD);
124         X(MT);
125         return GMT_XX;
126 #undef X
127 }
128
129 /* This very small function is just to make it */
130 /* easier to clear the data struct every time one is created */
131 inline void GSM_DataClear(GSM_Data *data)
132 {
133         memset(data, 0, sizeof(GSM_Data));
134 }
135         /* TP-Validity Period handling */
136
137 /* TP-Validity Period handling */
138 unsigned char SMS_Validity_to_VP(int Validity)
139 {
140         /* FIXME: error-checking for correct Validity - it should not be bigger then
141            63 weeks and smaller then 5minutes. We should also test intervals because
142            the SMS->Validity to TP-VP is not continuos. I think that the simplest
143            solution will be an array of correct values. We should parse it and if we
144            find the closest TP-VP value we should use it. Or is it good to take
145            closest smaller TP-VP as we do now? I think it is :-) */
146
147         /* 5 minutes intervals up to 12 hours = 720 minutes */
148         if (Validity <= 720)
149                 return((unsigned char) (Validity/5)-1);
150
151         /* 30 minutes intervals up to 1 day */
152         else if ((Validity > 720) && (Validity <= 1440))
153                 return((unsigned char) ((Validity-720)/30)+143);
154
155         /* 1 day intervals up to 30 days */
156         else if ((Validity > 1440) && (Validity <= 43200))
157                 return((unsigned char) (Validity/1440)+166);
158
159         /* 1 week intervals up to 63 weeks */
160         else if ((Validity > 43200) && (Validity <= 635040))
161                 return((unsigned char) (Validity/10080)+192);
162
163         /* Validity too big, strip it to the highest possible value */
164         return((unsigned char)0xFF);
165 }
166
167 int SMS_VP_to_Validity(GSM_SMSMessageValidity Validity)
168 {
169         if (Validity<=GSMV_1_Hour)
170                 return(1*60);
171         if (Validity<=GSMV_6_Hours)
172                 return(6*60);
173         if (Validity<=GSMV_24_Hours)
174                 return(24*60);
175         if (Validity<=GSMV_72_Hours)
176                 return(72*60);
177         if (Validity<=GSMV_1_Week)
178                 return(7*24*60);
179         if (Validity<=GSMV_Max_Time)
180                 return(63*7*24*60);
181         /* Validity too big, strip it to the highest possible value */
182         return(63*7*24*60);
183 }
184
185 /* This function implements packing of numbers (SMS Center number and
186  * destination number) for SMS sending function.
187  * RETURN: strlen(Number+(*Number=='+')), that means the number of INPUT
188  * digits (without '+' marker) which is equal to the number of written
189  * digit nibbles (without possible trailing 0xF filler)
190  */
191 int SemiOctetPack(char *Number, unsigned char *Output)
192 {
193         unsigned char *IN = Number;  /* Pointer to the input number */
194         unsigned char *OUT = Output; /* Pointer to the output */
195         int count = 0; /* This variable is used to notify us about count of already
196                           packed numbers. */
197
198         /* The first byte in the Semi-octet representation of the address field is
199            the Type-of-Address. This field is described in the official GSM
200            specification 03.40 version 5.3.0, section 9.1.2.5, page 33. We support
201            only international and unknown number. */
202   
203         if (*IN == '+') {
204                 *OUT++ = GNT_INTERNATIONAL; /* International number */
205                 IN++;
206         } else *OUT++ = GNT_UNKNOWN; /* Unknown number */
207
208         /* The next field is the number. It is in semi-octet representation - see
209            GSM scpecification 03.40 version 5.3.0, section 9.1.2.3, page 31. */
210
211         while (*IN) {
212                 if (count & 0x01) {
213                         *OUT = *OUT | ((*IN - '0') << 4);
214                         OUT++;
215                 }
216                 else
217                         *OUT = *IN - '0';
218                 count++; IN++;
219         }
220
221         /* We should also fill in the most significant bits of the last byte with
222            0x0f (1111 binary) if the number is represented with odd number of
223            digits. */
224
225         if (count & 0x01) {
226                 *OUT=*OUT | 0xf0;
227                 OUT++;
228                 return (2 * (OUT - Output - 1) - 1);
229         }
230
231         return (2 * (OUT - Output - 1));
232 }
233
234 /* This function implements unpacking of numbers (SMS Center number and
235  * destination number) for SMS receiving function.
236  * INPUT: Number of NIBBLES which is
237  * equal to the number of written OUTPUT characters (without optional '+'
238  * marker), may be +1 as it is hard to predict possible trailing 0xF filler
239  * RETURN: Number with skipped number or NULL for error
240  */
241 char *SemiOctetUnpack(char *dest,size_t destlen,u8 *Number,size_t nibbles)
242 {
243         int i;
244         char *d;
245         u8 *s,Digit;
246
247         if (destlen<2)
248                 return(NULL);           /* buffer overflow */
249
250         d=dest;
251         s=Number;
252         if (*s++ == GNT_INTERNATIONAL)
253                 *d++='+';
254
255         for (i=0;i<nibbles;i++) {
256                 if (d +1/*current digit*/ +1/*terminating '\0'*/ >dest+destlen)
257                         return(NULL);           /* buffer overflow */
258                 /* Here we advance "s" during first nibble read as when we finish
259                  * now we must return the pointer AFTER it
260                  */
261                 if (!(i&1))
262                         Digit= (*s++ )     & 0x0F;
263                 else
264                         Digit=((s[-1])>>4) & 0x0F;
265                 if (i+1==nibbles && Digit==0xF)
266                         break;                  /* nibbles+1 is allowed */
267                 if (Digit>9)
268                         return(NULL);           /* digit=A..F */
269                 *d++=Digit+'0';
270                 }
271         *d='\0';
272
273         return(s);
274 }
275
276 /* When you are so poor that you even don't have your own buffer...
277  * RETURN: local static buffer address or NULL for error
278  * Note: address of Number with skipped number is lost!
279  * Note: be sure that this function is called only once before processing the result!
280  */
281 char *SemiOctetUnpack_static(u8 *Number,size_t nibbles)
282 {
283         static char Buffer[GNOKII_MAX(
284                                 GNOKII_MAX(GSM_MAX_SMS_CENTER_LENGTH,GSM_MAX_SENDER_LENGTH)
285                                 ,GSM_MAX_DESTINATION_LENGTH)
286                                 ] = "";
287         char *r;
288
289         if (!(r=SemiOctetUnpack(Buffer,sizeof(Buffer),Number,nibbles)))
290                 return(NULL);
291         return(Buffer);
292 }
293
294 unsigned char GSM_Default_Alphabet[] = {
295
296         /* ETSI GSM 03.38, version 6.0.1, section 6.2.1; Default alphabet */
297         /* Characters in hex position 10, [12 to 1a] and 24 are not present on
298            latin1 charset, so we cannot reproduce on the screen, however they are
299            greek symbol not present even on my Nokia */
300
301         '@',  0xa3, '$',  0xa5, 0xe8, 0xe9, 0xf9, 0xec, 
302         0xf2, 0xc7, '\n', 0xd8, 0xf8, '\r', 0xc5, 0xe5,
303         '?',  '_',  '?',  '?',  '?',  '?',  '?',  '?',
304         '?',  '?',  '?',  '?',  0xc6, 0xe6, 0xdf, 0xc9,
305         ' ',  '!',  '\"', '#',  0xa4,  '%',  '&',  '\'',
306         '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
307         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
308         '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
309         0xa1, 'A',  'B',  'C',  'D',  'E',  'F',  'G',
310         'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
311         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
312         'X',  'Y',  'Z',  0xc4, 0xd6, 0xd1, 0xdc, 0xa7,
313         0xbf, 'a',  'b',  'c',  'd',  'e',  'f',  'g',
314         'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
315         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
316         'x',  'y',  'z',  0xe4, 0xf6, 0xf1, 0xfc, 0xe0
317 };
318
319 static unsigned char GetAlphabetValue(unsigned char value)
320 {
321         unsigned char i;
322   
323         if (value == '?') return 0x3f;
324   
325         for (i = 0 ; i < 128 ; i++)
326                 if (GSM_Default_Alphabet[i] == value)
327                         return i;
328
329         return 0x3f; /* '?' */
330 }
331
332 /* Function packs the '\0'-terminated 7-bit "input" string to the "output" 8-bit stream.
333  * UsedBits specifies the number of bottom bits already used in (*output) byte.
334  * RETURNS: Number of bytes written, at least partially
335  */
336 int PackSevenBitsToEight(int UsedBits, unsigned char *input, unsigned char *output)
337 {
338         unsigned char *OUT = output; /* Current pointer to the output buffer */
339         unsigned char *IN  = input;  /* Current pointer to the input buffer */
340
341         UsedBits %= 8;          /* just sanity, (0<=x<8) assert would be appropriate */
342
343         while (*IN) {
344                 unsigned char Byte = GetAlphabetValue(*IN);
345
346                 /* UsedBits specifies the number of already allocated bits in OUT[-1]
347                  * byte. If there were no bits used, we just don't advance the destination
348                  * pointer and we gain the full 8 bits of free space in the current byte.
349                  */
350                 if (UsedBits == 0)
351                         UsedBits = 8;
352                 else
353                         OUT++;
354
355                 /* Write upper (UsedBits) of Byte to the bottom of (*OUT) */
356                 *OUT = Byte >> (8 - UsedBits);
357                 /* If we don't write at 0th bit of the octet, we should write
358                  * a second part of the previous octet
359                  * Write lower (8-UsedBits) to the top of OUT[-1]
360                  */
361                 if (UsedBits != 8)
362                         OUT[-1] = (OUT[-1] & ((1<<UsedBits)-1)) | (Byte << UsedBits);
363
364                 UsedBits--;
365
366                 IN++;
367         }
368         if (UsedBits!=0)
369                 OUT++;
370
371         return (OUT - output);
372 }
373
374 int UnpackEightBitsToSeven(int offset, int in_length, int out_length,
375                            unsigned char *input, unsigned char *output)
376 {
377         unsigned char *OUT = output; /* Current pointer to the output buffer */
378         unsigned char *IN  = input;  /* Current pointer to the input buffer */
379         unsigned char Rest = 0x00;
380         int Bits;
381 #define ByteMask ((1 << Bits) - 1)
382
383         Bits = offset ? offset : 7;
384
385         while ((IN - input) < in_length) {
386
387                 *OUT = ((*IN & ByteMask) << (7 - Bits)) | Rest;
388                 Rest = *IN >> Bits;
389
390                 /* If we don't start from 0th bit, we shouldn't go to the
391                    next char. Under *OUT we have now 0 and under Rest -
392                    _first_ part of the char. */
393                 if ((IN != input) || (Bits == 7)) OUT++;
394                 IN++;
395
396                 if ((OUT - output) >= out_length) break;
397
398                 /* After reading 7 octets we have read 7 full characters but
399                    we have 7 bits as well. This is the next character */
400                 if (Bits == 1) {
401                         *OUT = Rest;
402                         OUT++;
403                         Bits = 7;
404                         Rest = 0x00;
405                 } else {
406                         Bits--;
407                 }
408         }
409
410         return OUT - output;
411 }
412
413 GSM_Error SMS_SetupUDH(GSM_SMSMessage *sms,GSM_UDH UDHType)
414 {
415 unsigned short srcport,dstport;
416
417         sms->UDHPresent=true;
418         switch (UDHType) {
419         case GSM_RingtoneUDH:  /* FALLTHRU */
420         case GSM_OpLogo:       /* FALLTHRU */
421         case GSM_CallerIDLogo:
422                 sms->UDH[1+0]=0x05;     /* logos (16-bit ports) */
423                 sms->UDH[1+1]=4;        /* sizeof(dstport)+sizeof(srcport) */
424                 sms->UDH[0]=2+sms->UDH[1+1];    /* total UserDataHeader size */
425                 switch (UDHType) {
426                         case GSM_RingtoneUDH:  dstport=0x1581; break;
427                         case GSM_OpLogo:       dstport=0x1582; break;
428                         case GSM_CallerIDLogo: dstport=0x1583; break;
429                         default: dstport=0;     /* double shut up GCC */
430                         }
431                 /* I have tested on Nokia 9000i on GSM_RingtoneUDH that it will silently
432                  * ignore Multi-SMS incoming ringtone with srcport==0 .
433                  * Despite this Single-SMS incoming ringtone is accepted even with srcport==0 !
434                  * When both ports were equal to 0x1581 everything was fine.
435                  */
436                 srcport=dstport;
437                 sms->UDH[1+2+0+0]=dstport>>8;
438                 sms->UDH[1+2+0+1]=dstport>>0;
439                 sms->UDH[1+2+2+0]=srcport>>8;
440                 sms->UDH[1+2+2+1]=srcport>>0;
441                 return(GE_NONE);
442         default:
443                 break;
444         }
445         sms->UDHPresent=false;
446         return(GE_NOTIMPLEMENTED);      /* error  */
447 }
448
449 GSM_UDH SMS_DetectUDH(GSM_SMSMessage *sms)
450 {
451         if (!sms->UDHPresent)
452                 return(GSM_NoUDH);
453         if (sms->UDH[0]< 1/*type*/ +1/*sub-length*/)
454                 return(GSM_NoUDH);      /* error */
455         switch (sms->UDH[1]) {
456
457         case 0x00: /* concatenated messages (8-bit reference) */
458                 dprintf(_("Concatenated message!!!\n"));
459                 if (sms->UDH[2]<3)
460                         return(GSM_NoUDH);      /* error */
461                 return(GSM_ConcatenatedMessages);
462                 break;
463
464         case 0x08: /* concatenated messages (16-bit reference) */
465                 dprintf(_("Concatenated message!!!\n"));
466                 if (sms->UDH[2]<4)
467                         return(GSM_NoUDH);      /* error */
468                 return(GSM_ConcatenatedMessages);
469                 break;
470
471         case 0x05: /* logos (16-bit ports) */
472                 if (sms->UDH[2]<4)
473                         return(GSM_NoUDH);      /* error */
474                 switch ((sms->UDH[3]<<8) | (sms->UDH[4]<<0)) {
475                 case 0x1582:
476                         return(GSM_OpLogo);
477                 case 0x1583:
478                         return(GSM_CallerIDLogo);
479                 }
480                 break;
481         default:
482                 break;
483         }
484         return(GSM_NoUDH);      /* error - not recognized */
485 }
486
487 /* RETURNS: Total number of SMSes, sms->MessageTextLength will contain cargoitems
488  */
489 static int SMS_Deconcatenate_calcparts(GSM_SMSMessage *sms,size_t *cargoitemsp,size_t buflen,size_t messagetextpreitems)
490 {
491 int bits=(sms->EightBit ? 8 : 7);
492 int UDHbytes=(!sms->UDHPresent ? 0 : 1+sms->UDH[0]);
493 int UDHitems=(UDHbytes*8 +bits-1/*round-up*/)/bits;
494 int lengthitems=(sms->EightBit ? GSM_MAX_SMS_8BIT_LENGTH : GSM_MAX_SMS_LENGTH);
495 int cargoitems=lengthitems-UDHitems-messagetextpreitems;
496
497         if (cargoitemsp)
498                 *cargoitemsp=cargoitems;
499
500         return((buflen +cargoitems-1/*round-up*/)/cargoitems);
501 }
502
503 /* RETURNS: Whether we should send the prepared (*sms)
504  */
505 bool SMS_Deconcatenate(GSM_Deconcatenate_state *state,GSM_SMSMessage *sms,char *buf,size_t buflen,bool useudh)
506 {
507 size_t offset_start,offset_end,messagetextpreitems;
508
509 #define CONCAT_TEXT_HEADER "%2d/%-2d: "
510 /* This length has to match 7bit-to-8bit length of single concat UDH
511  * due to broken assumptions at least in xgnokii_sms.c
512  * =round-up((UDHlength+IEItype+IEIlength+reference+total+sequence)*8/7)
513  */
514 #define CONCAT_TEXT_HEADER_LENGTH 7
515
516         if (state->first) {
517                 /* INIT */
518                 state->first=false;
519
520                 if ((state->singleshot=(1>=SMS_Deconcatenate_calcparts(sms,NULL,buflen,0)))) {
521                         memcpy(sms->MessageText,buf,buflen);
522                         sms->MessageText[sms->MessageTextLength=buflen]='\0';
523                         return(true);   /* send it */
524                         }
525                 state->sequence=0;
526                 state->sequencep=NULL;
527                 }
528         if (state->singleshot)
529                 return(false);  /* done */
530
531         if (!state->sequence && useudh) {
532 u8 *d;
533                 if (!sms->UDHPresent) {
534                         sms->UDHPresent=true;
535                         sms->UDH[0]=0;
536                         }
537                 if (1/*sizeof(UDH[0])*/ +sms->UDH[0]/*current UDH len*/ +6/*concatenation UDH len*/
538                                 > sizeof(sms->UDH))
539                         return(false);  /* error - UDH buffer overflow - shouldn't happen */
540                 d=(sms->UDH+1)+sms->UDH[0];
541
542                 *d++=0x00;      /* concatenated messages (IEI) (8-bit reference number) */
543                 *d++=0x03;      /* IEI data length */
544                 *d++=rand();    /* 8-bit reference number */
545                 *d++=0;         /* number of messages - not yet known, filled by d[-2] below! */
546                 state->sequencep=d;
547                 *d++=0;         /* message sequence number - not yet filled */
548
549                 sms->UDH[0]=d-(sms->UDH+1);
550
551                 d[-2]=SMS_Deconcatenate_calcparts(sms,&state->cargoitems,buflen,0);     /* UDH[0] must be already set */
552                 }
553         if (!state->sequence && !useudh) {
554                 gsprintf(sms->MessageText,sizeof(sms->MessageText),CONCAT_TEXT_HEADER,
555                                 0, SMS_Deconcatenate_calcparts(sms,&state->cargoitems,buflen,CONCAT_TEXT_HEADER_LENGTH));
556                 }
557         
558         /* It is important that "lastsequence" is not "u8" and so it will not overflow
559          * on the 255th part of SMS
560          */
561         state->sequence++;
562
563         if (useudh)
564                 *state->sequencep=state->sequence;
565         else {
566                 gsprintf(sms->MessageText,3,"%2d",state->sequence);
567                 sms->MessageText[2]='/';        /* it may got overwritten by '\0' */
568                 }
569
570         offset_start=(state->sequence-1)*state->cargoitems;
571         offset_end=GNOKII_MIN(buflen,(state->sequence-1+1)*state->cargoitems);
572
573         if (offset_start>=offset_end)
574                 return(false);  /* no more data available */
575
576         messagetextpreitems=(useudh ? 0 : CONCAT_TEXT_HEADER_LENGTH);
577         memcpy(sms->MessageText+messagetextpreitems,buf+offset_start,offset_end-offset_start);
578         sms->MessageText[sms->MessageTextLength=messagetextpreitems+offset_end-offset_start]='\0';
579         dprintf(_("Fragment %d: %d..%d (buflen=%d)\n"),state->sequence,offset_start,offset_end,buflen);
580
581         return(true);   /* send it! */
582 }
583
584 static inline u8 SMS_CharFromHex(unsigned u)
585 {
586         u&=0xDF;
587         return(u<'A'?u-('0'&0xDF):(u-('A'&0xDF))+0xA);
588 }
589
590 /* RETURN: end of "d" block or NULL if error
591  * Note: s==d on input is permitted
592  */
593 u8 *SMS_BlockFromHex(u8 *d,const char *s,size_t len)
594 {
595         while (len>=2) {
596                 if (!isxdigit(s[0]) || !isxdigit(s[1])) 
597                         break;          /* error */
598                 *d++=(SMS_CharFromHex(s[0])<<4)|SMS_CharFromHex(s[1]);
599                 s+=2;
600                 len-=2;
601                 }
602         if (len)
603                 return(NULL);           /* error */
604         return(d);
605 }
606
607 static inline char SMS_CharToHex(u8 x)
608 {
609         x&=0x0F;
610         if (x<10) return(x   +'0');
611         else      return(x-10+'A');
612 }
613
614 /* RETURN: end of "d" block (no errors generated)
615  */
616 char *SMS_BlockToHex(char *d,const u8 *s,size_t len)
617 {
618         while (len--) {
619                 *d++=SMS_CharToHex(*s>>4U);
620                 *d++=SMS_CharToHex(*s    );
621                 s++;
622                 }
623         return(d);
624 }