/* $Id$ G N O K I I A Linux/Unix toolset and driver for Nokia mobile phones. Copyright (C) 1999, 2000 Hugh Blemings & Pavel Janík ml. Released under the terms of the GNU GPL, see file COPYING for more details. $Log$ Revision 1.1.1.2 2002/04/03 00:07:55 short Found in "gnokii-working" directory, some November-patches version Revision 1.6 2001/09/14 12:15:28 pkot Cleanups from 0.3.3 (part1) Revision 1.5 2001/03/22 16:17:05 chris Tidy-ups and fixed gnokii/Makefile and gnokii/ChangeLog which I somehow corrupted. Revision 1.4 2001/03/21 23:36:04 chris Added the statemachine This will break gnokii --identify and --monitor except for 6210/7110 Revision 1.3 2001/02/28 21:26:51 machek Added StrToMemoryType utility function Revision 1.2 2001/02/03 23:56:15 chris Start of work on irda support (now we just need fbus-irda.c!) Proper unicode support in 7110 code (from pkot) Revision 1.1 2001/01/12 14:28:39 pkot Forgot to add this file. ;-) */ #include #include #include /* for rand() */ #include /* Coding functions */ #define NUMBER_OF_7_BIT_ALPHABET_ELEMENTS 128 static unsigned char GSM_DefaultAlphabet[NUMBER_OF_7_BIT_ALPHABET_ELEMENTS] = { /* ETSI GSM 03.38, version 6.0.1, section 6.2.1; Default alphabet */ /* Characters in hex position 10, [12 to 1a] and 24 are not present on latin1 charset, so we cannot reproduce on the screen, however they are greek symbol not present even on my Nokia */ '@', 0xa3, '$', 0xa5, 0xe8, 0xe9, 0xf9, 0xec, 0xf2, 0xc7, '\n', 0xd8, 0xf8, '\r', 0xc5, 0xe5, '?', '_', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', 0xc6, 0xe6, 0xdf, 0xc9, ' ', '!', '\"', '#', 0xa4, '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', 0xa1, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 0xc4, 0xd6, 0xd1, 0xdc, 0xa7, 0xbf, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0xe4, 0xf6, 0xf1, 0xfc, 0xe0 }; unsigned char EncodeWithDefaultAlphabet(unsigned char value) { unsigned char i; if (value == '?') return 0x3f; for (i = 0; i < NUMBER_OF_7_BIT_ALPHABET_ELEMENTS; i++) if (GSM_DefaultAlphabet[i] == value) return i; return '?'; } unsigned char DecodeWithDefaultAlphabet(unsigned char value) { return GSM_DefaultAlphabet[value]; } wchar_t EncodeWithUnicodeAlphabet(unsigned char value) { wchar_t retval; if (mbtowc(&retval, &value, 1) == -1) return '?'; else return retval; } unsigned char DecodeWithUnicodeAlphabet(wchar_t value) { unsigned char retval; if (wctomb(&retval, value) == -1) return '?'; else return retval; } GSM_Error Unimplemented(void) { return GE_NOTIMPLEMENTED; } GSM_MemoryType StrToMemoryType(const char *s) { #define X(a) if (!strcmp(s, #a)) return GMT_##a; X(ME); X(SM); X(FD); X(ON); X(EN); X(DC); X(RC); X(MC); X(LD); X(MT); return GMT_XX; #undef X } /* This very small function is just to make it */ /* easier to clear the data struct every time one is created */ inline void GSM_DataClear(GSM_Data *data) { memset(data, 0, sizeof(GSM_Data)); } /* TP-Validity Period handling */ /* TP-Validity Period handling */ unsigned char SMS_Validity_to_VP(int Validity) { /* FIXME: error-checking for correct Validity - it should not be bigger then 63 weeks and smaller then 5minutes. We should also test intervals because the SMS->Validity to TP-VP is not continuos. I think that the simplest solution will be an array of correct values. We should parse it and if we find the closest TP-VP value we should use it. Or is it good to take closest smaller TP-VP as we do now? I think it is :-) */ /* 5 minutes intervals up to 12 hours = 720 minutes */ if (Validity <= 720) return((unsigned char) (Validity/5)-1); /* 30 minutes intervals up to 1 day */ else if ((Validity > 720) && (Validity <= 1440)) return((unsigned char) ((Validity-720)/30)+143); /* 1 day intervals up to 30 days */ else if ((Validity > 1440) && (Validity <= 43200)) return((unsigned char) (Validity/1440)+166); /* 1 week intervals up to 63 weeks */ else if ((Validity > 43200) && (Validity <= 635040)) return((unsigned char) (Validity/10080)+192); /* Validity too big, strip it to the highest possible value */ return((unsigned char)0xFF); } int SMS_VP_to_Validity(GSM_SMSMessageValidity Validity) { if (Validity<=GSMV_1_Hour) return(1*60); if (Validity<=GSMV_6_Hours) return(6*60); if (Validity<=GSMV_24_Hours) return(24*60); if (Validity<=GSMV_72_Hours) return(72*60); if (Validity<=GSMV_1_Week) return(7*24*60); if (Validity<=GSMV_Max_Time) return(63*7*24*60); /* Validity too big, strip it to the highest possible value */ return(63*7*24*60); } /* This function implements packing of numbers (SMS Center number and * destination number) for SMS sending function. * RETURN: strlen(Number+(*Number=='+')), that means the number of INPUT * digits (without '+' marker) which is equal to the number of written * digit nibbles (without possible trailing 0xF filler) */ int SemiOctetPack(char *Number, unsigned char *Output) { unsigned char *IN = Number; /* Pointer to the input number */ unsigned char *OUT = Output; /* Pointer to the output */ int count = 0; /* This variable is used to notify us about count of already packed numbers. */ /* The first byte in the Semi-octet representation of the address field is the Type-of-Address. This field is described in the official GSM specification 03.40 version 5.3.0, section 9.1.2.5, page 33. We support only international and unknown number. */ if (*IN == '+') { *OUT++ = GNT_INTERNATIONAL; /* International number */ IN++; } else *OUT++ = GNT_UNKNOWN; /* Unknown number */ /* The next field is the number. It is in semi-octet representation - see GSM scpecification 03.40 version 5.3.0, section 9.1.2.3, page 31. */ while (*IN) { if (count & 0x01) { *OUT = *OUT | ((*IN - '0') << 4); OUT++; } else *OUT = *IN - '0'; count++; IN++; } /* We should also fill in the most significant bits of the last byte with 0x0f (1111 binary) if the number is represented with odd number of digits. */ if (count & 0x01) { *OUT=*OUT | 0xf0; OUT++; return (2 * (OUT - Output - 1) - 1); } return (2 * (OUT - Output - 1)); } /* This function implements unpacking of numbers (SMS Center number and * destination number) for SMS receiving function. * INPUT: Number of NIBBLES which is * equal to the number of written OUTPUT characters (without optional '+' * marker), may be +1 as it is hard to predict possible trailing 0xF filler * RETURN: Number with skipped number or NULL for error */ char *SemiOctetUnpack(char *dest,size_t destlen,u8 *Number,size_t nibbles) { int i; char *d; u8 *s,Digit; if (destlen<2) return(NULL); /* buffer overflow */ d=dest; s=Number; if (*s++ == GNT_INTERNATIONAL) *d++='+'; for (i=0;idest+destlen) return(NULL); /* buffer overflow */ /* Here we advance "s" during first nibble read as when we finish * now we must return the pointer AFTER it */ if (!(i&1)) Digit= (*s++ ) & 0x0F; else Digit=((s[-1])>>4) & 0x0F; if (i+1==nibbles && Digit==0xF) break; /* nibbles+1 is allowed */ if (Digit>9) return(NULL); /* digit=A..F */ *d++=Digit+'0'; } *d='\0'; return(s); } /* When you are so poor that you even don't have your own buffer... * RETURN: local static buffer address or NULL for error * Note: address of Number with skipped number is lost! * Note: be sure that this function is called only once before processing the result! */ char *SemiOctetUnpack_static(u8 *Number,size_t nibbles) { static char Buffer[GNOKII_MAX( GNOKII_MAX(GSM_MAX_SMS_CENTER_LENGTH,GSM_MAX_SENDER_LENGTH) ,GSM_MAX_DESTINATION_LENGTH) ] = ""; char *r; if (!(r=SemiOctetUnpack(Buffer,sizeof(Buffer),Number,nibbles))) return(NULL); return(Buffer); } unsigned char GSM_Default_Alphabet[] = { /* ETSI GSM 03.38, version 6.0.1, section 6.2.1; Default alphabet */ /* Characters in hex position 10, [12 to 1a] and 24 are not present on latin1 charset, so we cannot reproduce on the screen, however they are greek symbol not present even on my Nokia */ '@', 0xa3, '$', 0xa5, 0xe8, 0xe9, 0xf9, 0xec, 0xf2, 0xc7, '\n', 0xd8, 0xf8, '\r', 0xc5, 0xe5, '?', '_', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', 0xc6, 0xe6, 0xdf, 0xc9, ' ', '!', '\"', '#', 0xa4, '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', 0xa1, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 0xc4, 0xd6, 0xd1, 0xdc, 0xa7, 0xbf, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0xe4, 0xf6, 0xf1, 0xfc, 0xe0 }; static unsigned char GetAlphabetValue(unsigned char value) { unsigned char i; if (value == '?') return 0x3f; for (i = 0 ; i < 128 ; i++) if (GSM_Default_Alphabet[i] == value) return i; return 0x3f; /* '?' */ } /* Function packs the '\0'-terminated 7-bit "input" string to the "output" 8-bit stream. * UsedBits specifies the number of bottom bits already used in (*output) byte. * RETURNS: Number of bytes written, at least partially */ int PackSevenBitsToEight(int UsedBits, unsigned char *input, unsigned char *output) { unsigned char *OUT = output; /* Current pointer to the output buffer */ unsigned char *IN = input; /* Current pointer to the input buffer */ UsedBits %= 8; /* just sanity, (0<=x<8) assert would be appropriate */ while (*IN) { unsigned char Byte = GetAlphabetValue(*IN); /* UsedBits specifies the number of already allocated bits in OUT[-1] * byte. If there were no bits used, we just don't advance the destination * pointer and we gain the full 8 bits of free space in the current byte. */ if (UsedBits == 0) UsedBits = 8; else OUT++; /* Write upper (UsedBits) of Byte to the bottom of (*OUT) */ *OUT = Byte >> (8 - UsedBits); /* If we don't write at 0th bit of the octet, we should write * a second part of the previous octet * Write lower (8-UsedBits) to the top of OUT[-1] */ if (UsedBits != 8) OUT[-1] = (OUT[-1] & ((1<> Bits; /* If we don't start from 0th bit, we shouldn't go to the next char. Under *OUT we have now 0 and under Rest - _first_ part of the char. */ if ((IN != input) || (Bits == 7)) OUT++; IN++; if ((OUT - output) >= out_length) break; /* After reading 7 octets we have read 7 full characters but we have 7 bits as well. This is the next character */ if (Bits == 1) { *OUT = Rest; OUT++; Bits = 7; Rest = 0x00; } else { Bits--; } } return OUT - output; } GSM_Error SMS_SetupUDH(GSM_SMSMessage *sms,GSM_UDH UDHType) { unsigned short srcport,dstport; sms->UDHPresent=true; switch (UDHType) { case GSM_RingtoneUDH: /* FALLTHRU */ case GSM_OpLogo: /* FALLTHRU */ case GSM_CallerIDLogo: sms->UDH[1+0]=0x05; /* logos (16-bit ports) */ sms->UDH[1+1]=4; /* sizeof(dstport)+sizeof(srcport) */ sms->UDH[0]=2+sms->UDH[1+1]; /* total UserDataHeader size */ switch (UDHType) { case GSM_RingtoneUDH: dstport=0x1581; break; case GSM_OpLogo: dstport=0x1582; break; case GSM_CallerIDLogo: dstport=0x1583; break; default: dstport=0; /* double shut up GCC */ } /* I have tested on Nokia 9000i on GSM_RingtoneUDH that it will silently * ignore Multi-SMS incoming ringtone with srcport==0 . * Despite this Single-SMS incoming ringtone is accepted even with srcport==0 ! * When both ports were equal to 0x1581 everything was fine. */ srcport=dstport; sms->UDH[1+2+0+0]=dstport>>8; sms->UDH[1+2+0+1]=dstport>>0; sms->UDH[1+2+2+0]=srcport>>8; sms->UDH[1+2+2+1]=srcport>>0; return(GE_NONE); default: break; } sms->UDHPresent=false; return(GE_NOTIMPLEMENTED); /* error */ } GSM_UDH SMS_DetectUDH(GSM_SMSMessage *sms) { if (!sms->UDHPresent) return(GSM_NoUDH); if (sms->UDH[0]< 1/*type*/ +1/*sub-length*/) return(GSM_NoUDH); /* error */ switch (sms->UDH[1]) { case 0x00: /* concatenated messages (8-bit reference) */ dprintf(_("Concatenated message!!!\n")); if (sms->UDH[2]<3) return(GSM_NoUDH); /* error */ return(GSM_ConcatenatedMessages); break; case 0x08: /* concatenated messages (16-bit reference) */ dprintf(_("Concatenated message!!!\n")); if (sms->UDH[2]<4) return(GSM_NoUDH); /* error */ return(GSM_ConcatenatedMessages); break; case 0x05: /* logos (16-bit ports) */ if (sms->UDH[2]<4) return(GSM_NoUDH); /* error */ switch ((sms->UDH[3]<<8) | (sms->UDH[4]<<0)) { case 0x1582: return(GSM_OpLogo); case 0x1583: return(GSM_CallerIDLogo); } break; default: break; } return(GSM_NoUDH); /* error - not recognized */ } /* RETURNS: Total number of SMSes, sms->MessageTextLength will contain cargoitems */ static int SMS_Deconcatenate_calcparts(GSM_SMSMessage *sms,size_t *cargoitemsp,size_t buflen,size_t messagetextpreitems) { int bits=(sms->EightBit ? 8 : 7); int UDHbytes=(!sms->UDHPresent ? 0 : 1+sms->UDH[0]); int UDHitems=(UDHbytes*8 +bits-1/*round-up*/)/bits; int lengthitems=(sms->EightBit ? GSM_MAX_SMS_8BIT_LENGTH : GSM_MAX_SMS_LENGTH); int cargoitems=lengthitems-UDHitems-messagetextpreitems; if (cargoitemsp) *cargoitemsp=cargoitems; return((buflen +cargoitems-1/*round-up*/)/cargoitems); } /* RETURNS: Whether we should send the prepared (*sms) */ bool SMS_Deconcatenate(GSM_Deconcatenate_state *state,GSM_SMSMessage *sms,char *buf,size_t buflen,bool useudh) { size_t offset_start,offset_end,messagetextpreitems; #define CONCAT_TEXT_HEADER "%2d/%-2d: " /* This length has to match 7bit-to-8bit length of single concat UDH * due to broken assumptions at least in xgnokii_sms.c * =round-up((UDHlength+IEItype+IEIlength+reference+total+sequence)*8/7) */ #define CONCAT_TEXT_HEADER_LENGTH 7 if (state->first) { /* INIT */ state->first=false; if ((state->singleshot=(1>=SMS_Deconcatenate_calcparts(sms,NULL,buflen,0)))) { memcpy(sms->MessageText,buf,buflen); sms->MessageText[sms->MessageTextLength=buflen]='\0'; return(true); /* send it */ } state->sequence=0; state->sequencep=NULL; } if (state->singleshot) return(false); /* done */ if (!state->sequence && useudh) { u8 *d; if (!sms->UDHPresent) { sms->UDHPresent=true; sms->UDH[0]=0; } if (1/*sizeof(UDH[0])*/ +sms->UDH[0]/*current UDH len*/ +6/*concatenation UDH len*/ > sizeof(sms->UDH)) return(false); /* error - UDH buffer overflow - shouldn't happen */ d=(sms->UDH+1)+sms->UDH[0]; *d++=0x00; /* concatenated messages (IEI) (8-bit reference number) */ *d++=0x03; /* IEI data length */ *d++=rand(); /* 8-bit reference number */ *d++=0; /* number of messages - not yet known, filled by d[-2] below! */ state->sequencep=d; *d++=0; /* message sequence number - not yet filled */ sms->UDH[0]=d-(sms->UDH+1); d[-2]=SMS_Deconcatenate_calcparts(sms,&state->cargoitems,buflen,0); /* UDH[0] must be already set */ } if (!state->sequence && !useudh) { gsprintf(sms->MessageText,sizeof(sms->MessageText),CONCAT_TEXT_HEADER, 0, SMS_Deconcatenate_calcparts(sms,&state->cargoitems,buflen,CONCAT_TEXT_HEADER_LENGTH)); } /* It is important that "lastsequence" is not "u8" and so it will not overflow * on the 255th part of SMS */ state->sequence++; if (useudh) *state->sequencep=state->sequence; else { gsprintf(sms->MessageText,3,"%2d",state->sequence); sms->MessageText[2]='/'; /* it may got overwritten by '\0' */ } offset_start=(state->sequence-1)*state->cargoitems; offset_end=GNOKII_MIN(buflen,(state->sequence-1+1)*state->cargoitems); if (offset_start>=offset_end) return(false); /* no more data available */ messagetextpreitems=(useudh ? 0 : CONCAT_TEXT_HEADER_LENGTH); memcpy(sms->MessageText+messagetextpreitems,buf+offset_start,offset_end-offset_start); sms->MessageText[sms->MessageTextLength=messagetextpreitems+offset_end-offset_start]='\0'; dprintf(_("Fragment %d: %d..%d (buflen=%d)\n"),state->sequence,offset_start,offset_end,buflen); return(true); /* send it! */ } static inline u8 SMS_CharFromHex(unsigned u) { u&=0xDF; return(u<'A'?u-('0'&0xDF):(u-('A'&0xDF))+0xA); } /* RETURN: end of "d" block or NULL if error * Note: s==d on input is permitted */ u8 *SMS_BlockFromHex(u8 *d,const char *s,size_t len) { while (len>=2) { if (!isxdigit(s[0]) || !isxdigit(s[1])) break; /* error */ *d++=(SMS_CharFromHex(s[0])<<4)|SMS_CharFromHex(s[1]); s+=2; len-=2; } if (len) return(NULL); /* error */ return(d); } static inline char SMS_CharToHex(u8 x) { x&=0x0F; if (x<10) return(x +'0'); else return(x-10+'A'); } /* RETURN: end of "d" block (no errors generated) */ char *SMS_BlockToHex(char *d,const u8 *s,size_t len) { while (len--) { *d++=SMS_CharToHex(*s>>4U); *d++=SMS_CharToHex(*s ); s++; } return(d); }