This commit was generated by cvs2svn to compensate for changes in r158,
[gnokii.git] / common / cimd.c
diff --git a/common/cimd.c b/common/cimd.c
new file mode 100644 (file)
index 0000000..e213170
--- /dev/null
@@ -0,0 +1,1636 @@
+/*
+
+  $Id$
+  
+  G N O K I I
+
+  A Linux/Unix toolset and driver for Nokia mobile phones.
+
+  Copyright (C) 2001 Jan Kratochvil,
+  based on code by Hugh Blemings & Pavel Janík ml.
+
+  Released under the terms of the GNU GPL, see file COPYING for more details.
+
+  This file provides an API for accessing SMS centers by CIMD and related protococols.
+  See README-CIMD for more details on supported protocols.
+
+  The various routines are prefixed by CIMD.
+
+  $Log$
+  Revision 1.1.1.1  2002/04/03 00:08:03  short
+  Found in "gnokii-working" directory, some November-patches version
+
+
+*/
+
+#define CIMD_DEBUG 1
+
+/* System header files */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <limits.h>
+
+#ifdef WIN32
+
+#include <windows.h>
+#include "win32/winserial.h"
+
+#undef IN
+#undef OUT
+
+#define WRITEPHONE(a, b, c) WriteCommBlock(b, c)
+#define sleep(x) Sleep((x) * 1000)
+#define usleep(x) Sleep(((x) < 1000) ? 1 : ((x) / 1000))
+extern HANDLE hPhone;
+
+#else
+
+#define WRITEPHONE(a, b, c) device_write(b, c)
+#include <unistd.h>
+#include <termios.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <pthread.h>
+#include <errno.h>
+#include "device.h"
+#include "devices/unixserial.h"
+
+#endif
+
+/* Various header file */
+
+#include "config.h"
+#include "misc.h"
+#include "gsm-common.h"
+#include "cfgreader.h"
+
+/* Global variables used by code in gsm-api.c to expose the functions
+   supported by this model of phone. */
+
+
+#if __unices__
+/* fd opened in device.c */
+extern int device_portfd;
+#endif
+
+/* Our private defines */
+
+/* Define if plain (non-Bin) Submit is needed for 7bit messages
+ */
+#define CIMD_SUBMIT_7BIT 1
+
+/* When now catchbuffer was provided and must have some space to decode
+ * OK/ERROR/... result codes.
+ */
+#define CIMD_CATCHBUFFER_LENGTH 0x400
+
+/* +1 of maximum position number of CIMD protocol parameter */
+#define CIMD_PARAMSLOTS (0x10)
+
+/* We assume the 'right one' class is 1 (Mobile Equipment specific) */
+#define DEFAULT_CLASS 1
+
+/* CIMD_Param_Nak_Error codes:
+ */
+#define CIMD_NAK_NO_SMS (0x5001)
+#define CIMD_NAK_KEEPALIVE_REPLY (0x9998)
+#define CIMD_NAK_RESEND (0x9999)
+
+/* Local variables */
+
+#ifndef WIN32
+static char PortDevice[GSM_MAX_DEVICE_NAME_LENGTH];
+#endif
+static bool RequestTerminate;
+
+
+static u8 CIMD_CatchBuffer[CIMD_CATCHBUFFER_LENGTH];
+static u8 *CIMD_CatchBufferPtr=CIMD_CatchBuffer;       /* current destination writing ptr */
+static GSM_Error *CIMD_CatchBufferErrorP;
+
+static void CIMD_CatchBufferStart(GSM_Error *errorcodep);
+
+
+#define CIMD_MARK_START (0x02)
+#define CIMD_MARK_STOP  (0x03)
+#define CIMD_MARK_SEP   (0x09)
+#define CIMD_MARK_SEPS  "\x09"
+
+/* ELF=ELement Format */
+enum CIMD_ELF {
+       CIMD_ELF_HexByte,
+       CIMD_ELF_HexWord,
+       CIMD_ELF_Decimal,
+       CIMD_ELF_StringZ,
+       CIMD_ELF_HexBlock,
+       CIMD_ELF_Empty,
+       };
+
+struct CIMD_Param {
+       u16 code;
+       enum CIMD_ELF elf;
+       };
+
+enum CIMD_Param_SAMPLE {               /* we need some universal enum for typecasting all params */
+       CIMD_Param_SAMPLE_dummy,
+       };
+
+/* CIMD_Cmd_Ack:
+ */
+enum CIMD_Param_Ack {
+       CIMD_Param_Ack_Cmd,
+       CIMD_Param_Ack_Error,
+       CIMD_Param_Ack_NULL
+       };
+static const struct CIMD_Param CIMD_Param_BIP_Ack[]={
+       { /* CIMD_Param_Ack_Cmd   */ 1,CIMD_ELF_HexByte },
+       { /* CIMD_Param_Ack_Error */ 2,CIMD_ELF_StringZ },      /* protocol version on Cmd_Login, otherwise 0x0000 */
+       };
+
+/* CIMD_Cmd_Nak:
+ */
+enum CIMD_Param_Nak {
+       CIMD_Param_Nak_Cmd,
+       CIMD_Param_Nak_Error,
+       CIMD_Param_Nak_NULL
+       };
+static const struct CIMD_Param CIMD_Param_BIP_Nak[]={
+       { /* CIMD_Param_Nak_Cmd   */ 1,CIMD_ELF_HexByte },
+       { /* CIMD_Param_Nak_Error */ 2,CIMD_ELF_HexWord },
+       };
+
+/* CIMD_Cmd_Login:
+ */
+enum CIMD_Param_Login {
+       CIMD_Param_Login_ID,
+       CIMD_Param_Login_PWD,
+       CIMD_Param_Login_NULL
+       };
+static const struct CIMD_Param CIMD_Param_BIP_Login[]={
+       { /* CIMD_Param_Login_ID  */ 1,CIMD_ELF_StringZ },
+       { /* CIMD_Param_Login_PWD */ 2,CIMD_ELF_StringZ },
+       };
+
+/* CIMD_Cmd_Logout:
+ */
+enum CIMD_Param_Logout {
+       CIMD_Param_Logout_NULL
+       };
+static const struct CIMD_Param CIMD_Param_BIP_Logout[]={
+       };
+
+/* CIMD_Cmd_Retrieve:
+ */
+enum CIMD_Param_Retrieve {
+       CIMD_Param_Retrieve_NULL
+       };
+static const struct CIMD_Param CIMD_Param_BIP_Retrieve[]={
+       };
+
+/* CIMD_Cmd_RetrieveReply:
+ */
+enum CIMD_Param_RetrieveReply {
+       CIMD_Param_RetrieveReply_Destination,
+       CIMD_Param_RetrieveReply_SourceApplication,     /* ??? */
+       CIMD_Param_RetrieveReply_Text,
+       CIMD_Param_RetrieveReply_Timestamp,
+       CIMD_Param_RetrieveReply_Is8bit,                /* protocol version 1.14+, CIMD_Param_DCSEnable_Type_*DCS* reqd */
+       CIMD_Param_RetrieveReply_PID_DCS,               /* protocol version 1.15+, CIMD_Param_DCSEnable_Type_*PID_DCS* reqd */
+       CIMD_Param_RetrieveReply_SPEC,                  /* protocol version 1.16+, CIMD_Param_DCSEnable_Type_*SPEC* reqd */
+       CIMD_Param_RetrieveReply_NULL
+       };
+static const struct CIMD_Param CIMD_Param_BIP_RetrieveReply[]={
+       { /* CIMD_Param_RetrieveReply_Destination       */ 1,CIMD_ELF_StringZ },
+       { /* CIMD_Param_RetrieveReply_SourceApplication */ 2,CIMD_ELF_StringZ },
+       { /* CIMD_Param_RetrieveReply_Text              */ 3,CIMD_ELF_StringZ },
+       { /* CIMD_Param_RetrieveReply_Timestamp         */ 4,CIMD_ELF_StringZ },
+       { /* CIMD_Param_RetrieveReply_Is8bit            */ 5,CIMD_ELF_Decimal },
+       { /* CIMD_Param_RetrieveReply_PID_DCS           */ 6,CIMD_ELF_Decimal }, /* upper=PID, lower=DCS, FIXME: should be HexWord! */
+       { /* CIMD_Param_RetrieveReply_SPEC              */ 7,CIMD_ELF_Decimal }, /* FIXME: should be HexByte! */
+       };
+
+/* CIMD_Cmd_Count:
+ */
+enum CIMD_Param_Count {
+       CIMD_Param_Count_NULL
+       };
+static const struct CIMD_Param CIMD_Param_BIP_Count[]={
+       };
+
+/* CIMD_Cmd_CountReply:
+ */
+enum CIMD_Param_CountReply {
+       CIMD_Param_CountReply_Used,             /* Used==NotRead==Slots */
+       CIMD_Param_CountReply_NULL
+       };
+static const struct CIMD_Param CIMD_Param_BIP_CountReply[]={
+       { /* CIMD_Param_CountReply_Used */ 1,CIMD_ELF_Decimal },
+       };
+
+/* CIMD_Cmd_Submit:
+ */
+enum CIMD_Param_Submit {
+       CIMD_Param_Submit_Destination,
+       CIMD_Param_Submit_Text,
+       CIMD_Param_Submit_ValidityPeriod,
+       CIMD_Param_Submit_AUX,          /* protocol version 1.13+, empty for BMG<->SMSC link CIMD (just for OIS) */
+       CIMD_Param_Submit_DCS,          /* protocol version 1.14+ */
+       CIMD_Param_Submit_PID,          /* protocol version 1.15+ */
+       CIMD_Param_Submit_SPEC,         /* protocol version 1.16+ */
+       CIMD_Param_Submit_NULL
+       };
+static const struct CIMD_Param CIMD_Param_BIP_Submit[]={
+       { /* CIMD_Param_Submit_Destination    */ 1,CIMD_ELF_StringZ },
+       { /* CIMD_Param_Submit_Text           */ 2,CIMD_ELF_StringZ },
+       { /* CIMD_Param_Submit_ValidityPeriod */ 3,CIMD_ELF_HexByte },
+       { /* CIMD_Param_Submit_AUX            */ 4,CIMD_ELF_Empty   },
+       { /* CIMD_Param_Submit_DCS            */ 5,CIMD_ELF_Decimal },
+       { /* CIMD_Param_Submit_PID            */ 6,CIMD_ELF_HexByte },
+       { /* CIMD_Param_Submit_SPEC           */ 7,CIMD_ELF_HexByte },
+       };
+
+/* CIMD_Cmd_SubmitBin:         protocol version 1.12+
+ */
+enum CIMD_Param_SubmitBin {
+       CIMD_Param_SubmitBin_Destination,
+       CIMD_Param_SubmitBin_Text,
+       CIMD_Param_SubmitBin_ValidityPeriod,
+       CIMD_Param_SubmitBin_AUX,               /* protocol version 1.13+, empty for BMG<->SMSC link CIMD (just for OIS) */
+       CIMD_Param_SubmitBin_DCS,               /* protocol version 1.14+ */
+       CIMD_Param_SubmitBin_PID,               /* protocol version 1.15+ */
+       CIMD_Param_SubmitBin_SPEC,              /* protocol version 1.16+ */
+       CIMD_Param_SubmitBin_NULL
+       };
+static const struct CIMD_Param CIMD_Param_BIP_SubmitBin[]={
+       { /* CIMD_Param_SubmitBin_Destination    */ 1,CIMD_ELF_StringZ },
+       { /* CIMD_Param_SubmitBin_Text           */ 2,CIMD_ELF_StringZ },
+       { /* CIMD_Param_SubmitBin_ValidityPeriod */ 3,CIMD_ELF_HexByte },
+       { /* CIMD_Param_SubmitBin_AUX            */ 4,CIMD_ELF_Empty   },
+       { /* CIMD_Param_SubmitBin_DCS            */ 5,CIMD_ELF_Decimal },
+       { /* CIMD_Param_SubmitBin_PID            */ 6,CIMD_ELF_HexByte },
+       { /* CIMD_Param_SubmitBin_SPEC           */ 7,CIMD_ELF_HexByte },
+       };
+
+/* CIMD_Cmd_DCSEnable:         protocol version 1.14+
+ */
+enum CIMD_Param_DCSEnable {
+       CIMD_Param_DCSEnable_Type,
+       CIMD_Param_DCSEnable_NULL
+       };
+static const struct CIMD_Param CIMD_Param_BIP_DCSEnable[]={
+       { /* CIMD_Param_DCSEnable_Type */ 1,CIMD_ELF_HexWord },
+       };
+
+enum CIMD_Param_DCSEnable_Type {
+       CIMD_Param_DCSEnable_Type_None        =0x0000,  /* protocol version 1.14+ */
+       CIMD_Param_DCSEnable_Type_DCS         =0x0001,  /* protocol version 1.14+ */
+       CIMD_Param_DCSEnable_Type_PID_DCS     =0x0002,  /* protocol version 1.15+ */
+       CIMD_Param_DCSEnable_Type_PID_DCS_SPEC=0x0003,  /* protocol version 1.16+ */
+       };
+
+#define CIMD_PARAM_ENTRY(param) (param),ARRAY_LEN((param))
+
+struct CIMD_Cmd {
+       u8 code;
+       const struct CIMD_Param *param;
+       unsigned paramcnt;
+       };
+
+enum CIMD_Cmd_Type {
+       CIMD_Cmd_Ack,
+       CIMD_Cmd_Nak,
+       CIMD_Cmd_Login,
+       CIMD_Cmd_Logout,
+       CIMD_Cmd_Submit,
+       CIMD_Cmd_Retrieve,
+       CIMD_Cmd_RetrieveReply,
+       CIMD_Cmd_Count,
+       CIMD_Cmd_CountReply,
+       CIMD_Cmd_SubmitBin,             /* protocol version 1.12+ */
+       CIMD_Cmd_DCSEnable,             /* protocol version 1.14+ */
+       CIMD_Cmd_NULL           /* stdarg termination, MUST be last! */
+       };
+static const struct CIMD_Cmd CIMD_Cmd_BIP[]={
+       { /* CIMD_Cmd_Ack           */ 0x00,CIMD_PARAM_ENTRY(CIMD_Param_BIP_Ack          ) },
+       { /* CIMD_Cmd_Nak           */ 0x99,CIMD_PARAM_ENTRY(CIMD_Param_BIP_Nak          ) },
+       { /* CIMD_Cmd_Login         */ 0x01,CIMD_PARAM_ENTRY(CIMD_Param_BIP_Login        ) },
+       { /* CIMD_Cmd_Logout        */ 0x02,CIMD_PARAM_ENTRY(CIMD_Param_BIP_Logout       ) },
+       { /* CIMD_Cmd_Submit        */ 0x03,CIMD_PARAM_ENTRY(CIMD_Param_BIP_Submit       ) },
+       { /* CIMD_Cmd_Retrieve      */ 0x05,CIMD_PARAM_ENTRY(CIMD_Param_BIP_Retrieve     ) },
+       { /* CIMD_Cmd_RetrieveReply */ 0x06,CIMD_PARAM_ENTRY(CIMD_Param_BIP_RetrieveReply) },
+       { /* CIMD_Cmd_Count         */ 0x51,CIMD_PARAM_ENTRY(CIMD_Param_BIP_Count        ) },
+       { /* CIMD_Cmd_CountReply    */ 0x61,CIMD_PARAM_ENTRY(CIMD_Param_BIP_CountReply   ) },
+       { /* CIMD_Cmd_SubmitBin     */ 0x31,CIMD_PARAM_ENTRY(CIMD_Param_BIP_SubmitBin    ) },
+       { /* CIMD_Cmd_DCSEnable     */ 0x53,CIMD_PARAM_ENTRY(CIMD_Param_BIP_DCSEnable    ) },
+       };
+
+static const struct CIMD_Cmd *CIMD_Cmd=CIMD_Cmd_BIP;   /* FIXME: When other protocols get supported... */
+static unsigned CIMD_Cmdcnt=ARRAY_LEN(CIMD_Cmd_BIP);   /* FIXME: When other protocols get supported... */
+
+struct CIMD_Param_Ack_Nak_Error {
+       u16 code;
+       const char *msg;
+       };
+
+static const struct CIMD_Param_Ack_Nak_Error CIMD_Param_Ack_Nak_Error[]={
+       /* CIMD_Cmd_Login */
+       { 0x1101,N_("User is already logged in") },
+       { 0x1001,N_("Logging in is currently disabled") },
+       { 0x9999,N_("Username not found") },
+               /* 0x000? can be bitmask combined from: */
+       { 0x0001,N_("Invalid password") },
+       { 0x0002,N_("User has forbidden access") },
+       { 0x0008,N_("User is already registered") },            /* ??? difference from 0x1101 ? */
+       /* CIMD_Cmd_Retrieve / CIMD_Cmd_Count */
+       { 0x5001,N_("No SMS found or not detectable") },        /* ==CIMD_NAK_NO_SMS */
+       { 0x5011,N_("Function not enabled") },
+       /* CIMD_Cmd_Retrieve */
+       { 0x5021,N_("Automatical retrieve is active") },
+       /* CIMD_Cmd_Submit / CIMD_Cmd_SubmitBin */
+       { 0x3001,N_("Error during processing SMS in BMG") },
+       { 0x3061,N_("Destination number has invalid format") },
+       /* CIMD_Cmd_SubmitBin */
+       { 0x3051,N_("Invalid DCS") },           /* protocol version 1.14+ */
+       { 0x3081,N_("Invalid Text") },          /* protocol version 1.14+ */
+       { 0x3091,N_("SMS is empty") },          /* protocol version 1.14+ */
+       { 0x3099,N_("SMS submit too fast") },   /* protocol version 1.16+ */
+       };
+
+
+static struct CIMD_paramslot CIMD_RX_Packet_slot[CIMD_PARAMSLOTS];
+#define CIMD_RX_PACKET_PARAMSLOT(paramsample) (CIMD_RX_Packet_slot+1+(unsigned)(paramsample))
+static enum CIMD_Cmd_Type CIMD_RX_Packet_Cmd;
+static unsigned CIMD_RX_Packet_slots;
+
+
+/* RETURNS: Processed */
+typedef bool (*CIMD_RX_PatrolFunc)(void);
+typedef void (*CIMD_RX_PatrolReset)(void);
+
+struct CIMD_RX_Patrol {
+       enum CIMD_Cmd_Type cmd;
+       CIMD_RX_PatrolFunc func;
+       CIMD_RX_PatrolReset reset;
+       };
+
+static const struct CIMD_RX_Patrol *CIMD_RX_Patrol_Current;
+
+static char *CIMD_RX_Patrol_Ack_Error;
+static u16 CIMD_RX_Patrol_Nak_Error;
+
+
+#ifndef WIN32
+
+static pthread_t Thread;
+# if __unices__
+static pthread_t selThread;
+# endif
+
+#endif
+
+/* Local variables used by get/set phonebook entry code. Buffer is used as a
+   source or destination for phonebook data and other functions... Error is
+   set to GE_NONE by calling function, set to GE_COMPLETE or an error code by
+   handler routines as appropriate. */
+
+static GSM_SMSMessage     *CurrentSMSMessage;
+static GSM_Error          CurrentSMSMessageError;
+
+static GSM_SMSStatus      *CurrentSMSStatus;
+static GSM_Error          CurrentSMSStatusError;
+
+static unsigned char      Revision[GSM_MAX_REVISION_LENGTH];
+static unsigned char      Model   [GSM_MAX_MODEL_LENGTH];
+
+
+static u8 CIMD_TX_SendCommand_buf[sizeof(CIMD_CatchBuffer)];   /* sizeof() is not required to be == but it is appropriate */
+static u8 *CIMD_TX_SendCommand_buf_d=CIMD_TX_SendCommand_buf;
+
+struct CIMD_paramslot {
+       u16 code;
+       enum CIMD_ELF elf;
+       union {
+               struct { u8    i;               } HexByte;
+               struct { u16   i;               } HexWord;
+               struct { int   i;               } Decimal;
+               struct { char *s;               } StringZ;
+               struct { u8   *buf; size_t len; } HexBlock;
+               struct { int _dummy;            } Empty;
+               } u;
+       };
+
+/* slots are sorted by moving the whole blocks but they are small so it is OK
+ */
+static struct CIMD_paramslot CIMD_TX_SendCommand_paramslots[CIMD_PARAMSLOTS];
+static int CIMD_TX_SendCommand_paramslots_full;
+static enum CIMD_Cmd_Type CIMD_TX_SendCommand_Cmd;
+
+/* RETURNS: Success
+ */
+static bool CIMD_TX_SendCommand_vpushparam(u16 code,enum CIMD_ELF elf,va_list *app)
+{
+struct CIMD_paramslot *slot=CIMD_TX_SendCommand_paramslots+(CIMD_TX_SendCommand_paramslots_full++);
+
+       if (slot>=CIMD_TX_SendCommand_paramslots+ARRAY_LEN(CIMD_TX_SendCommand_paramslots)) {
+               /* assertion */
+               fprintf(stderr,"Out of param slots!\n");
+               return(false);
+               }
+       slot->code=code;
+       slot->elf=elf;
+       switch (elf) {
+               case CIMD_ELF_HexByte:
+                       slot->u.HexByte.i=va_arg((*app),int);
+                       break;
+               case CIMD_ELF_HexWord:
+                       slot->u.HexWord.i=va_arg((*app),int);
+                       break;
+               case CIMD_ELF_Decimal:
+                       slot->u.Decimal.i=va_arg((*app),int);
+                       break;
+               case CIMD_ELF_StringZ:
+                       slot->u.StringZ.s=va_arg((*app),char *);
+                       break;
+               case CIMD_ELF_HexBlock:
+                       slot->u.HexBlock.buf=va_arg((*app),u8 *);
+                       slot->u.HexBlock.len=va_arg((*app),size_t);
+                       break;
+               case CIMD_ELF_Empty:
+                       break;
+               }
+       return(true);
+}
+
+/* RETURNS: Success
+ */
+static bool CIMD_TX_SendCommand_pushparam(u16 code,enum CIMD_ELF elf,...)
+{
+va_list ap;
+bool r;
+
+       va_start(ap,elf);
+       r=CIMD_TX_SendCommand_vpushparam(code,elf,&ap);
+       va_end(ap);
+       return(r);
+}
+
+static int CIMD_TX_SendCommand_paramslots_compare
+       (const struct CIMD_paramslot *a,const struct CIMD_paramslot *b)
+{
+       return((b->code<a->code)-(a->code<b->code));
+}
+
+/* RETURNS: Success
+ */
+static bool CIMD_TX_SendCommand_storeparam(struct CIMD_paramslot *slot)
+{
+       switch (slot->elf) {
+               case CIMD_ELF_HexByte:
+                       CIMD_TX_SendCommand_buf_d+=sprintf(CIMD_TX_SendCommand_buf_d,"%02X",slot->u.HexByte.i);
+                       break;
+               case CIMD_ELF_HexWord:
+                       CIMD_TX_SendCommand_buf_d+=sprintf(CIMD_TX_SendCommand_buf_d,"%04X",slot->u.HexWord.i);
+                       break;
+               case CIMD_ELF_Decimal:
+                       CIMD_TX_SendCommand_buf_d+=sprintf(CIMD_TX_SendCommand_buf_d,"%d",slot->u.Decimal.i);
+                       break;
+               case CIMD_ELF_StringZ: {
+size_t len;
+                       if (!slot->u.StringZ.s)         /* NULL is interpreted as "" */
+                               break;
+                       len=strlen(slot->u.StringZ.s);
+                       if (CIMD_TX_SendCommand_buf_d+len > CIMD_TX_SendCommand_buf_d+sizeof(CIMD_TX_SendCommand_buf))
+                               return(false);          /* error - overflow */
+                       memcpy(CIMD_TX_SendCommand_buf_d,slot->u.StringZ.s,len);
+                       CIMD_TX_SendCommand_buf_d+=len;
+                       } break;
+               case CIMD_ELF_HexBlock:
+                       if (CIMD_TX_SendCommand_buf_d+2*slot->u.HexBlock.len
+                                       > CIMD_TX_SendCommand_buf_d+sizeof(CIMD_TX_SendCommand_buf))
+                               return(false);          /* error - overflow */
+                       CIMD_TX_SendCommand_buf_d=SMS_BlockToHex(CIMD_TX_SendCommand_buf_d,
+                                       slot->u.HexBlock.buf,slot->u.HexBlock.len);     /* never fails */
+                       break;
+               case CIMD_ELF_Empty:
+                       break;
+               }
+       return(true);   /* success */
+}
+
+static unsigned CIMD_KeepAlives=0;
+static pthread_mutex_t CIMD_KeepAlivesLock;
+
+/* This function is NOT thread-safe!
+ */
+static void CIMD_KeepAlivesLockInit(void)
+{
+static bool done=false;
+
+       if (done)
+               return;
+       pthread_mutex_init(&CIMD_KeepAlivesLock,NULL);
+       done=true;
+}
+
+static void CIMD_TX_SendCommand(GSM_Error *errorcodep,const struct CIMD_RX_Patrol *patrol,enum CIMD_Cmd_Type cmd,...);
+
+static void CIMD_KeepAlivesCheck(void)
+{
+unsigned alives;
+
+       CIMD_KeepAlivesLockInit();
+       pthread_mutex_lock(&CIMD_KeepAlivesLock);
+       alives=CIMD_KeepAlives;
+       CIMD_KeepAlives=0;
+       pthread_mutex_unlock(&CIMD_KeepAlivesLock);
+
+       while (alives) {
+               CIMD_TX_SendCommand(NULL/*errorcodep*/,NULL/*patrol*/,
+                       CIMD_Cmd_Nak,
+                               CIMD_Param_Nak_Cmd,(CIMD_Cmd+(unsigned)CIMD_Cmd_Ack)->code,     /* ==0x00 */
+                               CIMD_Param_Nak_Error,CIMD_NAK_KEEPALIVE_REPLY,                  /* ==0x9998 */
+                               CIMD_Param_Nak_NULL);
+               alives--;
+               }
+}
+
+/* RETURNS: Success
+ */
+static bool CIMD_TX_SendCommandResend(boid)
+{
+int writephone_got;
+
+       writephone_got=WRITEPHONE(PortFD, CIMD_TX_SendCommand_buf, CIMD_TX_SendCommand_buf_d-CIMD_TX_SendCommand_buf);
+
+#ifdef CIMD_DEBUG
+       write(1,"CMD:<STX>",9);
+       write(1,CIMD_TX_SendCommand_buf+1,CIMD_TX_SendCommand_buf_d-1-(CIMD_TX_SendCommand_buf+1));
+       write(1,"<ETX>\n",6);
+#endif
+
+       return(writephone_got==(CIMD_TX_SendCommand_buf_d-CIMD_TX_SendCommand_buf));
+}
+
+static void CIMD_TX_SendCommand(GSM_Error *errorcodep,const struct CIMD_RX_Patrol *patrol,enum CIMD_Cmd_Type cmd,...)
+{
+va_list ap;
+const struct CIMD_Cmd *cmdstruct;
+enum CIMD_Param_SAMPLE param;
+const struct CIMD_Param *paramstruct;
+u8 *u8s,xsum;
+struct CIMD_paramslot *slot;
+int parami;
+
+       CIMD_KeepAlivesCheck();
+
+       va_start(ap,cmd);
+       if (errorcodep)
+               *errorcodep=GE_BUSY;
+       CIMD_RX_Patrol_Current=patrol;
+
+       CIMD_TX_SendCommand_Cmd=cmd;
+       CIMD_TX_SendCommand_paramslots_full=0;
+       if ((unsigned)cmd>=CIMD_Cmdcnt)
+               goto fail;              /* assert */
+       cmdstruct=CIMD_Cmd+(unsigned)cmd;
+
+       if (!CIMD_TX_SendCommand_pushparam(0/*code*/,CIMD_ELF_HexByte/*elf*/,cmdstruct->code))
+               goto fail;
+       for (;;) {
+               param=va_arg(ap,enum CIMD_Param_SAMPLE);
+               if ((unsigned)param==cmdstruct->paramcnt)
+                       break;          /* CIMD_Param_*_NULL reached */
+               if ((unsigned)param>cmdstruct->paramcnt)
+                       goto fail;      /* assert */
+               paramstruct=(cmdstruct->param+(unsigned)param);
+               if (!CIMD_TX_SendCommand_vpushparam(paramstruct->code,paramstruct->elf,&ap))
+                       goto fail;
+               }
+       qsort(CIMD_TX_SendCommand_paramslots,CIMD_TX_SendCommand_paramslots_full,
+                       sizeof(*CIMD_TX_SendCommand_paramslots),
+                       (int (*)(const void *,const void *))CIMD_TX_SendCommand_paramslots_compare);
+
+       CIMD_TX_SendCommand_buf_d=CIMD_TX_SendCommand_buf;
+       *CIMD_TX_SendCommand_buf_d++=CIMD_MARK_START;
+
+       slot=CIMD_TX_SendCommand_paramslots;
+       for (parami=0;slot<CIMD_TX_SendCommand_paramslots+CIMD_TX_SendCommand_paramslots_full;parami++) {
+               if (CIMD_TX_SendCommand_buf_d +32/*safety*/ > CIMD_TX_SendCommand_buf_d+sizeof(CIMD_TX_SendCommand_buf)) {
+                               /* error - overflow */
+fail:
+                       if (errorcodep)
+                               *errorcodep=GE_INTERNALERROR;
+                       return;
+                       }
+               if (slot->code==parami) {
+                       if (!CIMD_TX_SendCommand_storeparam(slot))      /* error */
+                               goto fail;
+                       }
+               if (slot->code>=parami)
+                       *CIMD_TX_SendCommand_buf_d++=CIMD_MARK_SEP;
+               if (slot->code<=parami)
+                       slot++;
+               }
+       xsum=0;
+       for (u8s=CIMD_TX_SendCommand_buf;u8s<CIMD_TX_SendCommand_buf_d;u8s++)
+               xsum+=*u8s;
+       CIMD_TX_SendCommand_buf_d+=sprintf(CIMD_TX_SendCommand_buf_d,"%02X",(unsigned)xsum);
+       *CIMD_TX_SendCommand_buf_d++=CIMD_MARK_STOP;
+
+       CIMD_CatchBufferStart(errorcodep);
+
+       if (!CIMD_TX_SendCommandResend())
+               goto fail;
+
+       /* success but we don't wait for the result code */
+}
+
+/* This function is used to get storage status from the phone. It currently
+   supports two different memory areas - internal and SIM. */
+
+static GSM_Error 
+wait_on(volatile GSM_Error *what, int timeout)
+{
+GSM_Error r=GE_TIMEOUT;        /* shut up GCC when (timeout==0) */
+
+       while (timeout && ((r=*what)==GE_BUSY)) {
+               if (!--timeout) {
+                       r=GE_TIMEOUT;
+                       break;
+                       }
+               CIMD_KeepAlivesCheck();
+               usleep(100000);
+       }
+       /* any specific patrollers are no longer valid */
+       CIMD_RX_Patrol_Current=NULL;
+
+#ifdef CIMD_DEBUG
+       printf("wait_on finish, timeout=%d\n",(r==GE_TIMEOUT));
+#endif
+
+       return(r);
+}
+
+#define WAIT_ON(what, timeout) \
+       do { \
+               GSM_Error res = wait_on(what, timeout); \
+               if (res != GE_NONE) \
+                       return res; \
+        } while (0)
+
+/* I hope GCC gets this bunch optimized on the normal the normal errorcodep!=NULL case
+ */
+#define CIMD_TX_SENDCOMMAND_WAIT_ON(errorcodep,timeout,patrol,args...) \
+       do { \
+GSM_Error _CIMD_TX_SENDCOMMAND_WAIT_ON_err,*_CIMD_TX_SENDCOMMAND_WAIT_ON_errp; \
+ \
+               if (!(_CIMD_TX_SENDCOMMAND_WAIT_ON_errp=(errorcodep))) \
+                       _CIMD_TX_SENDCOMMAND_WAIT_ON_errp=&_CIMD_TX_SENDCOMMAND_WAIT_ON_err; \
+               CIMD_TX_SendCommand(_CIMD_TX_SENDCOMMAND_WAIT_ON_errp,(patrol),args); \
+               WAIT_ON(_CIMD_TX_SENDCOMMAND_WAIT_ON_errp,(timeout)); \
+       } while (0)
+
+
+#define CIMD_ERR_WRAPPER(expr) \
+       do { \
+               GSM_Error err=(expr); \
+               if (err!=GE_NONE) \
+                       return(err); \
+       } while (0)
+
+/* Convert SPEC field of BIP protocol using CIMD protocol on BMG<->SMSC link
+ */
+static unsigned char CIMD_SMStoSPEC_BIP_CIMD(GSM_SMSMessage *SMS)
+{
+       return(0
+                       /* bit 0 (enable) & bits 4-7 (value) - priority - not supported by Gnokii
+                        */
+                       |((!!SMS->ReplyViaSameSMSC)<<1) /*ReplyPath*/
+                       |((!!SMS->UDHPresent)<<2) /*UDH set*/
+                       );
+}
+
+static unsigned char CIMD_SMStoDCS(GSM_SMSMessage *SMS)
+{
+int class=(SMS->Class==-1 ? DEFAULT_CLASS : SMS->Class);
+
+#if 0 /* NEVER send 0 for BIP CIMD as it would translate it to F6 !!!
+       */
+       if (!SMS->EightBit && class==DEFAULT_CLASS)
+               return(0x00);
+#endif
+       return(0xF0 | ((!!SMS->EightBit)<<2) | ((class&0x03)<<0));
+}
+
+static GSM_Error CIMD_PhoneSetup(void)
+{
+#if 1 /* HACK */
+       CIMD_TX_SENDCOMMAND_WAIT_ON(NULL/*errorcodep*/,100/*timeout*/,NULL/*patrol*/,
+               CIMD_Cmd_Login,
+                       /* NULLs will be interpreted as ""
+                       */
+                       CIMD_Param_Login_ID,CFG_Get(CFG_Info,"CIMD","name"),
+                       CIMD_Param_Login_PWD,CFG_Get(CFG_Info,"CIMD","password"),
+                       CIMD_Param_Login_NULL);
+       if (!CIMD_RX_Patrol_Ack_Error)
+               Revision[0]='\0';
+       else
+               SAFE_STRNCPY_SIZEOF(Revision,CIMD_RX_Patrol_Ack_Error);
+
+       CIMD_TX_SENDCOMMAND_WAIT_ON(NULL/*errorcodep*/,10/*timeout*/,NULL/*patrol*/,
+               CIMD_Cmd_DCSEnable,
+                       CIMD_Param_DCSEnable_Type,(u16)CIMD_Param_DCSEnable_Type_PID_DCS_SPEC,
+                       CIMD_Param_DCSEnable_NULL);
+#endif
+
+       return(GE_NONE);
+}
+
+
+static void CIMD_RX_Char(char rx_byte);
+static void CIMD_SigHandler(int status);
+static bool CIMD_OpenSerial(void);
+
+GSM_Phone phone_cimd;  /* forward declaration */
+
+/* Initialise variables and state machine. */
+
+static GSM_Error CIMD_Init(GSM_Data *data, GSM_Statemachine *state)
+{
+       RequestTerminate = false;
+
+       SAFE_STRNCPY_SIZEOF(Model,data->Model);
+
+       /* Create and start main thread. */
+
+#ifdef WIN32
+{
+int rtn;
+       rtn = ! OpenConnection(State->Link.PortDevice,CIMD_RX_Char,CIMD_KeepAliveProc);
+       if (rtn != 0) {
+               return(GE_INTERNALERROR);
+}
+#else
+       SAFE_STRNCPY_SIZEOF(PortDevice,state->Link.PortDevice);
+       if (!CIMD_OpenSerial())
+               return(GE_INTERNALERROR);
+#endif
+
+        CIMD_ERR_WRAPPER(CIMD_PhoneSetup());
+
+       return (GE_NONE);
+}
+
+#if __unices__
+/* thread for handling incoming data */
+void CIMD_SelectLoop()
+{
+       int err;
+       fd_set readfds;
+       struct timeval timeout;
+
+       FD_ZERO(&readfds);
+       FD_SET(device_portfd, &readfds);
+       /* set timeout to 15 seconds */
+       timeout.tv_sec=15;
+       timeout.tv_usec=0;
+       while (!RequestTerminate) {
+               err = select(device_portfd + 1, &readfds, NULL, NULL, &timeout);
+               /* call singal handler to process incoming data */
+               if ( err > 0 ) CIMD_SigHandler(0);
+               else if (err == -1) perror("Error in SelectLoop");
+       }
+}
+#endif
+
+/* Applications should call CIMD_Terminate to shut down the CIMD thread and
+   close the serial port. */
+
+static GSM_Error CIMD_Terminate(GSM_Data *data, GSM_Statemachine *state)
+{
+GSM_Error err;
+
+#if 1 /* HACK */
+       /* Don't wait too much as we can have already broken link
+        */
+       CIMD_TX_SendCommand(&err/*errorcodep*/,NULL/*patrol*/,
+               CIMD_Cmd_Logout,
+                       CIMD_Param_Logout_NULL);
+       wait_on(&err,20/*timeout*/);            /* errors ignored, of course */
+#endif
+
+       /* Request termination of thread */
+       RequestTerminate = true;
+
+#ifndef WIN32
+       /* Now wait for thread to terminate. */
+       pthread_join(Thread, NULL);
+
+       /* Close serial port. */
+       device_close();
+
+#else
+       CloseConnection();
+#endif
+
+       return(GE_NONE);
+}
+
+/* messagecenter->No" is NOT set as it is input argument */
+static void CIMD_MessageCenterClear(GSM_MessageCenter *messagecenter)
+{
+       messagecenter->Name[0]='\0';            /* not present up to Nokia 9110i */
+       messagecenter->Recipient[0]='\0';       /* not present up to Nokia 9110i */
+       messagecenter->Number[0]='\0';          /* default */
+       messagecenter->Format=GSMF_Text;        /* default */
+       messagecenter->Validity=GSMV_72_Hours;  /* default */
+}
+
+static bool CIMD_RX_Patrol_CountReply(void)
+{
+       CurrentSMSStatus->UnRead=/*FALLTHRU*/
+       CurrentSMSStatus->Used  =/*FALLTHRU*/
+       CurrentSMSStatus->Slots =/*FALLTHRU*/
+                       CIMD_RX_PACKET_PARAMSLOT(CIMD_Param_CountReply_Used)->u.Decimal.i;
+
+       CurrentSMSStatusError=GE_NONE;
+       return(true);
+}
+
+static const struct CIMD_RX_Patrol CIMD_RX_Patrol_CountReply_struct=
+       { CIMD_Cmd_CountReply,CIMD_RX_Patrol_CountReply };
+
+static GSM_Error CIMD_GetSMSStatus(GSM_Data *data, GSM_Statemachine *state)
+{
+       CurrentSMSStatus = data->SMSStatus;
+       CurrentSMSStatus->UnRead=0;     /* default */
+       CurrentSMSStatus->Used  =0;     /* default */
+       CurrentSMSStatus->Slots =0;     /* default */
+#if 1 /* HACK */
+       CIMD_TX_SENDCOMMAND_WAIT_ON(&CurrentSMSStatusError/*errorcodep*/,100/*timeout*/,&CIMD_RX_Patrol_CountReply_struct/*patrol*/,
+               CIMD_Cmd_Count,
+                       CIMD_Param_Count_NULL);
+#endif
+
+       return(GE_NONE);
+}
+
+static GSM_Error CIMD_GetImei(GSM_Data *data, GSM_Statemachine *state)
+{
+       /* not supported by the protocol */
+       data->Imei[0]='\0';
+       return (GE_NONE);
+}
+
+static GSM_Error CIMD_GetRevision(GSM_Data *data, GSM_Statemachine *state)
+{
+       if (*Revision) {
+               strcpy(data->Revision,Revision);
+               return (GE_NONE);
+       } else return (GE_TRYAGAIN);
+}
+
+static GSM_Error CIMD_GetModel(GSM_Data *data, GSM_Statemachine *state)
+{
+       /* not supported by the protocol */
+       strcpy(data->Model,Model);
+       return (GE_NONE);
+}
+
+static GSM_Error CIMD_GetManufacturer(GSM_Data *data, GSM_Statemachine *state)
+{
+       /* not supported by the protocol */
+       data->Imei[0]='\0';
+       return (GE_NONE);
+}
+
+static void CIMD_DateTimeSetCurrent(GSM_DateTime *datetime)
+{
+time_t current=time(NULL);
+struct tm *tm=localtime(&current);
+
+       datetime->AlarmEnabled=false;
+
+       datetime->Year  =tm->tm_year+1900;
+       datetime->Month =tm->tm_mon+1;
+       datetime->Day   =tm->tm_mday;
+       datetime->Hour  =tm->tm_hour;
+       datetime->Minute=tm->tm_min;
+       datetime->Second=tm->tm_sec;
+
+       datetime->Timezone=timezone;
+}
+
+/* scts=="18.06.1998 22:33:23"
+ */
+static bool CIMD_SCTStoSMS(GSM_SMSMessage *SMS,const char *scts)
+{
+GSM_DateTime *DateTime=&SMS->Time;
+const char *fmt="%2d.%2d.%4d %2d:%2d:%2d";     /* trailing '\0' IS used! */
+const char *fs,*ss;
+
+       fs=fmt;
+       ss=scts;
+       do {
+               while (*fs=='%') {
+int nums=(*++fs)-'0';
+                       while (nums--)
+                               if (!isdigit(*ss++))
+                                       return(false);
+                       }
+               if (*fs++!=*ss)
+                       return(false);
+               } while (fs[-1]);
+       /* string is completely valid now */
+
+       sscanf(scts,fmt,
+                       &DateTime->Day,
+                       &DateTime->Month,
+                       &DateTime->Year,
+                       &DateTime->Hour,
+                       &DateTime->Minute,
+                       &DateTime->Second);
+       DateTime->Timezone=0;
+
+       return(true);
+}
+
+static void CIMD_DCStoSMS(GSM_SMSMessage *SMS,unsigned char dcs)
+{
+       switch ((dcs&0xF0)>>4) {
+               case 0x0:
+                       switch (dcs&0x0F) {
+                               case 0x0:
+                                       CurrentSMSMessage->EightBit=false;
+                                       break;
+                               }
+                       break;
+               case 0xF:
+                       CurrentSMSMessage->EightBit=!!(dcs&0x04);       /* bit 2 */
+                       CurrentSMSMessage->Class=(dcs&0x03);            /* bits 0 & 1 */
+                       break;
+               }
+}
+
+/* Convert SPEC field of BIP protocol using CIMD protocol on BMG<->SMSC link
+ */
+static void CIMD_SPEC_BIP_CIMDtoSMS(GSM_SMSMessage *SMS,u8 spec)
+{
+       /* Such specification not supported by BIP:
+        */
+       CurrentSMSMessage->Type=GST_MT;
+       CurrentSMSMessage->Status=GSS_NOTSENTREAD;
+
+       /* bit 0 (enable) & bits 4-7 (value) - priority - not supported by Gnokii
+        */
+
+       SMS->UDHPresent=!!(spec&(1<<2));
+
+       SMS->ReplyViaSameSMSC=!!(spec&(1<<1));
+}
+
+static bool CIMD_RX_Patrol_RetrieveReply(void)
+{
+u16 pid_dcs;   /* upper=PID, lower=DCS */
+
+       {
+const char *destination=CIMD_RX_PACKET_PARAMSLOT(CIMD_Param_RetrieveReply_Destination)->u.StringZ.s;
+
+               if (strlen(destination)+1 > sizeof(CurrentSMSMessage->Sender)) {
+fail:
+                       CurrentSMSMessageError=GE_INTERNALERROR;
+                       return(true);           /* error */
+                       }
+               strcpy(CurrentSMSMessage->Sender,destination);
+               }
+
+       if ((CurrentSMSMessage->EightBit=CIMD_RX_PACKET_PARAMSLOT(CIMD_Param_RetrieveReply_Is8bit)->u.Decimal.i)) {
+const char *hextext=CIMD_RX_PACKET_PARAMSLOT(CIMD_Param_RetrieveReply_Text)->u.StringZ.s;
+size_t hextextlen=strlen(hextext);
+
+               if ((hextextlen&1) || (hextextlen/2 > sizeof(CurrentSMSMessage->MessageText)))
+                       goto fail;              /* error - message too long */
+               if (!SMS_BlockFromHex(CurrentSMSMessage->MessageText/*d*/,hextext,hextextlen))
+                       goto fail;              /* error - parse error */
+               CurrentSMSMessage->MessageTextLength=hextextlen/2;
+               }
+       else {  /* 7bit */
+const char *text=CIMD_RX_PACKET_PARAMSLOT(CIMD_Param_RetrieveReply_Text)->u.StringZ.s;
+size_t textlen=strlen(text);
+
+               if (textlen+1 > sizeof(CurrentSMSMessage->MessageText))
+                       goto fail;              /* error - message too long */
+               strcpy(CurrentSMSMessage->MessageText,text);
+               CurrentSMSMessage->MessageTextLength=textlen;
+               }
+
+       /* errors ignored as it is not fatal */
+       CIMD_SCTStoSMS(CurrentSMSMessage,CIMD_RX_PACKET_PARAMSLOT(CIMD_Param_RetrieveReply_Timestamp)->u.StringZ.s);
+
+       pid_dcs=CIMD_RX_PACKET_PARAMSLOT(CIMD_Param_RetrieveReply_PID_DCS)->u.HexWord.i;
+
+       /* FIXME: Is it correct to fill "MessageCenter" fields from SMS body? */
+       CurrentSMSMessage->MessageCenter.Format=(GSM_SMSMessageFormat)(pid_dcs>>8U);    /* <pid> */
+       CIMD_DCStoSMS(CurrentSMSMessage,(pid_dcs&0x00FFU));
+       CIMD_SPEC_BIP_CIMDtoSMS(CurrentSMSMessage,CIMD_RX_PACKET_PARAMSLOT(CIMD_Param_RetrieveReply_SPEC)->u.HexByte.i);
+
+       if (CurrentSMSMessage->UDHPresent) {
+u8 udhlen;
+               if (CurrentSMSMessage->MessageTextLength<=0)
+                       goto fail;
+               udhlen=1/*sizeof(udhlen)*/ +CurrentSMSMessage->MessageText[0];
+               if (udhlen > CurrentSMSMessage->MessageTextLength)
+                       goto fail;
+               memcpy(CurrentSMSMessage->UDH,CurrentSMSMessage->MessageText,udhlen);
+               memmove(CurrentSMSMessage->MessageText,CurrentSMSMessage->MessageText+udhlen,
+                               CurrentSMSMessage->MessageTextLength-udhlen +1/*Trailing '\0'*/);
+               CurrentSMSMessage->MessageTextLength-=udhlen;
+               }
+
+       CurrentSMSMessageError=GE_NONE;
+       return(true);
+}
+
+static const struct CIMD_RX_Patrol CIMD_RX_Patrol_RetrieveReply_struct=
+       { CIMD_Cmd_RetrieveReply,CIMD_RX_Patrol_RetrieveReply };
+
+static GSM_Error CIMD_GetSMS(GSM_Data *data, GSM_Statemachine *state)
+{
+       CurrentSMSMessage = data->SMSMessage;
+
+       if (CurrentSMSMessage->MemoryType!=GMT_SM)
+               return(GE_INVALIDMEMORYTYPE);
+
+       CIMD_DateTimeSetCurrent(&CurrentSMSMessage->Time);              /* default */
+       CIMD_DateTimeSetCurrent(&CurrentSMSMessage->SMSCTime);          /* not present in the protocol */
+       CurrentSMSMessage->MessageTextLength=0;                         /* default */
+       CurrentSMSMessage->Validity=72/*hours*/*60;                     /* default */
+       CurrentSMSMessage->UDHPresent=false;                            /* default */
+       CurrentSMSMessage->MessageText[0]='\0';                         /* default */
+
+       CIMD_MessageCenterClear(&CurrentSMSMessage->MessageCenter);     /* default */
+       CurrentSMSMessage->MessageCenter.No=0;                          /* default - input for GetSMSCenter */
+
+       CurrentSMSMessage->Sender[0]='\0';                              /* default */
+       CurrentSMSMessage->Destination[0]='\0';                         /* default */
+       CurrentSMSMessage->MessageNumber=CurrentSMSMessage->Location;   /* default */
+       /* CurrentSMSMessage->MemoryType is input argument */
+       CurrentSMSMessage->Type=GST_UN;                                 /* default, detection of EMPTY SMSes! */
+       CurrentSMSMessage->Status=GSS_SENTREAD;                         /* default */
+       CurrentSMSMessage->Class=DEFAULT_CLASS;                         /* default */
+       CurrentSMSMessage->EightBit=false;                              /* default */
+       CurrentSMSMessage->Compression=false;                           /* default */
+       /* CurrentSMSMessage->Location is input argument */
+       CurrentSMSMessage->ReplyViaSameSMSC=false;                      /* default */
+
+       CIMD_TX_SendCommand(&CurrentSMSMessageError/*errorcodep*/,&CIMD_RX_Patrol_RetrieveReply_struct/*patrol*/,
+               CIMD_Cmd_Retrieve,
+                       CIMD_Param_Retrieve_NULL);
+       if (GE_NONE!=wait_on(&CurrentSMSMessageError,90/*timeout*/)) {
+               if (CIMD_RX_Patrol_Nak_Error!=CIMD_NAK_NO_SMS)
+                       return(CurrentSMSMessageError);
+               /* We don't return GE_EMPTYSMSLOCATION as when we have already eaten
+                * all the wating SMSes there cannot be any other one
+                */
+               return(GE_INVALIDSMSLOCATION);
+               }
+
+       return(GE_NONE);
+}
+
+static GSM_Error CIMD_SendSMS(GSM_Data *data, GSM_Statemachine *state)
+{
+u8 bintext[sizeof(CurrentSMSMessage->UDH)+sizeof(CurrentSMSMessage->MessageText)],*d;
+char hexbintext[sizeof(bintext) +1/*Trailing '\0'*/];
+
+       CurrentSMSMessage = data->SMSMessage;
+
+       CurrentSMSMessage->MessageNumber=0;     /* default */
+
+       d=bintext;
+       if (CurrentSMSMessage->UDHPresent) {
+size_t UDHlen=1+CurrentSMSMessage->UDH[0];
+
+               memcpy(d,CurrentSMSMessage->UDH,UDHlen);
+               d+=UDHlen;
+#ifdef CIMD_SUBMIT_7BIT
+               if (!CurrentSMSMessage->EightBit)       /* We are not able to send UDH by 7bit channel */
+                       return(GE_NOTSUPPORTED);
+#endif
+               }
+       if (
+#ifdef CIMD_SUBMIT_7BIT
+                       1       /* Never do 7bit->8bit encoding when CIMD_SUBMIT_7BIT */
+#else
+                       CurrentSMSMessage->EightBit
+#endif
+                       ) {
+               memcpy(d,CurrentSMSMessage->MessageText,CurrentSMSMessage->MessageTextLength);
+               d+=CurrentSMSMessage->MessageTextLength;
+       }
+#ifndef CIMD_SUBMIT_7BIT
+       else {
+size_t byteslen=PackSevenBitsToEight(
+                       /* check it out yourself, really the number of used bits for UDH header on the start
+                        * as we will need to allocate initial bit to align SMS->MessageText on the 7-bit boundary
+                        */
+                       (7-(d-bintext))%7,
+                       CurrentSMSMessage->MessageText,d);
+               d+=byteslen;
+       }
+#endif
+
+       if (
+#ifdef CIMD_SUBMIT_7BIT
+                       CurrentSMSMessage->EightBit
+#else
+                       1       /* Never use plain Submit for 8bit messages */
+#endif
+                       ) {
+               *(SMS_BlockToHex(hexbintext,bintext,(d-bintext)/*len*/))='\0';
+
+               /* DANGER: Keep in sync with CIMD_Cmd_Submit !!!
+                */
+               CIMD_TX_SENDCOMMAND_WAIT_ON(NULL/*errorcodep*/,150/*timeout*/,NULL/*patrol*/,
+                       CIMD_Cmd_SubmitBin,
+                               CIMD_Param_SubmitBin_Destination,CurrentSMSMessage->Destination,
+                               CIMD_Param_SubmitBin_Text,hexbintext,
+                               CIMD_Param_SubmitBin_ValidityPeriod,SMS_Validity_to_VP(CurrentSMSMessage->Validity),
+                               CIMD_Param_SubmitBin_AUX,               /* Empty */
+                               CIMD_Param_SubmitBin_DCS,(unsigned)CIMD_SMStoDCS(CurrentSMSMessage),
+                               CIMD_Param_SubmitBin_PID,(u8)CurrentSMSMessage->MessageCenter.Format,
+                               CIMD_Param_SubmitBin_SPEC,CIMD_SMStoSPEC_BIP_CIMD(CurrentSMSMessage),
+                               CIMD_Param_SubmitBin_NULL);
+               }
+#ifdef CIMD_SUBMIT_7BIT
+       else {
+               *d='\0';
+
+               /* DANGER: Keep in sync with CIMD_Cmd_SubmitBin !!!
+                */
+               CIMD_TX_SENDCOMMAND_WAIT_ON(NULL/*errorcodep*/,150/*timeout*/,NULL/*patrol*/,
+                       CIMD_Cmd_Submit,
+                               CIMD_Param_Submit_Destination,CurrentSMSMessage->Destination,
+                               CIMD_Param_Submit_Text,bintext,
+                               CIMD_Param_Submit_ValidityPeriod,SMS_Validity_to_VP(CurrentSMSMessage->Validity),
+                               CIMD_Param_Submit_AUX,          /* Empty */
+                               CIMD_Param_Submit_DCS,(unsigned)CIMD_SMStoDCS(CurrentSMSMessage),
+                               CIMD_Param_Submit_PID,(u8)CurrentSMSMessage->MessageCenter.Format,
+                               CIMD_Param_Submit_SPEC,CIMD_SMStoSPEC_BIP_CIMD(CurrentSMSMessage),
+                               CIMD_Param_Submit_NULL);
+               }
+#endif
+
+       return(GE_SMSSENDOK);
+}
+
+static GSM_Error CIMD_Reset(GSM_Data *data, GSM_Statemachine *state)
+{
+       CIMD_ERR_WRAPPER(CIMD_PhoneSetup());
+
+       return(GE_NONE);
+}
+
+#ifndef WIN32
+
+/* Called by initialisation code to open comm port in asynchronous mode. */
+
+static bool CIMD_OpenSerial(void)
+{
+       int result;
+  
+#if __unices__
+       int rtn;
+#else
+       struct sigaction sig_io;
+
+       /* Set up and install handler before enabling async IO on port. */
+
+       sig_io.sa_handler = CIMD_SigHandler;
+       sig_io.sa_flags = 0;
+       sigaction (SIGIO, &sig_io, NULL);
+#endif
+
+       /* Open device. */
+
+       result = device_open(PortDevice, false/*with_odd_parity*/, true/*with_async*/, -1/*with_hw_handshake*/, GCT_Serial);
+
+       if (!result) {
+               perror(_("Couldn't open CIMD device"));
+               return false;
+       }
+
+#if __unices__
+       /* create a thread to handle incoming data from mobile phone */
+       rtn = pthread_create(&selThread, NULL, (void*)CIMD_SelectLoop, (void*)NULL);
+       if (rtn != 0) return false;
+#endif
+
+       /* device_changespeed() not needed as device_open() now automatically
+        * sets the user-specified (or default) speed.
+        */
+       return (true);
+}
+
+static void CIMD_SigHandler(int status)
+{
+       unsigned char buffer[255];
+       int count, res;
+       res = device_read(buffer, 255);
+       for (count = 0; count < res ; count ++)
+               CIMD_RX_Char(buffer[count]);
+}
+#endif /* WIN32 */
+
+static bool CIMD_RX_Patrol_Ack(void)
+{
+       if ((CIMD_Cmd+(unsigned)CIMD_Cmd_Nak)->code == CIMD_RX_PACKET_PARAMSLOT(CIMD_Param_Ack_Cmd)->u.HexByte.i) { /* ==0x99 */
+               /* We cannot send the keepalive here as some dangerous operation may be in progress
+                */
+               CIMD_KeepAlivesLockInit();
+               pthread_mutex_lock(&CIMD_KeepAlivesLock);
+               CIMD_KeepAlives++;
+               pthread_mutex_unlock(&CIMD_KeepAlivesLock);
+               return(false);
+               }
+
+       if (CIMD_RX_PACKET_PARAMSLOT(CIMD_Param_Ack_Cmd)->u.HexByte.i!=(CIMD_Cmd+(unsigned)CIMD_TX_SendCommand_Cmd)->code)
+               return(false);          /* Ack for some unknown command */
+
+       free(CIMD_RX_Patrol_Ack_Error);
+       CIMD_RX_Patrol_Ack_Error=strdup(CIMD_RX_PACKET_PARAMSLOT(CIMD_Param_Ack_Error)->u.StringZ.s);
+
+       /* Some patrol may have already indicated some error!
+        */
+        if (CIMD_CatchBufferErrorP && *CIMD_CatchBufferErrorP==GE_BUSY)
+                *CIMD_CatchBufferErrorP=GE_NONE;
+
+       return(true);
+}
+
+static void CIMD_RX_Patrol_Ack_reset(void)
+{
+       free(CIMD_RX_Patrol_Ack_Error);
+       CIMD_RX_Patrol_Ack_Error=NULL;
+}
+
+static const char *CIMD_RX_Patrol_Nak_resolve(u16 errorcode)
+{
+const struct CIMD_Param_Ack_Nak_Error *errorp;
+
+       for (errorp=CIMD_Param_Ack_Nak_Error;errorp<CIMD_Param_Ack_Nak_Error+ARRAY_LEN(CIMD_Param_Ack_Nak_Error);errorp++)
+               if (errorcode==errorp->code)
+                       return(_(errorp->msg));
+       return(_("Unknown error code"));
+}
+
+static void CIMD_RX_Patrol_Nak_dump(u16 errorcode)
+{
+       fprintf(stderr,_("Got CIMD error code 0x%04X: "),errorcode);
+       if (!(errorcode & ~0x000F)) {   /* combined error code */
+u16 errormask;
+               fprintf(stderr,_("combined:"));
+               for (errormask=0x0001;errormask<=errorcode;errormask<<=1)
+                       if (errorcode&errormask)
+                               fprintf(stderr," +%s",CIMD_RX_Patrol_Nak_resolve(errormask));
+               fputc('\n',stderr);
+               return;
+               }
+       fprintf(stderr,"%s\n",CIMD_RX_Patrol_Nak_resolve(errorcode));
+}
+
+static bool CIMD_RX_Patrol_Nak(void)
+{
+       if ((CIMD_Cmd+(unsigned)CIMD_Cmd_Nak)->code == CIMD_RX_PACKET_PARAMSLOT(CIMD_Param_Nak_Cmd)->u.HexByte.i        /* ==0x99 */
+               && CIMD_NAK_RESEND == CIMD_RX_PACKET_PARAMSLOT(CIMD_Param_Nak_Error)->u.HexWord.i) {    /* 0x9999 */
+               fprintf(stderr,_("WARNING: Requested to resend last packet!!\n"));
+               CIMD_TX_SendCommandResend();
+               return(true);
+               }
+
+       if (CIMD_RX_PACKET_PARAMSLOT(CIMD_Param_Nak_Cmd)->u.HexByte.i!=(CIMD_Cmd+(unsigned)CIMD_TX_SendCommand_Cmd)->code)
+               return(false);          /* Nak for some unknown command */
+
+       CIMD_RX_Patrol_Nak_Error=CIMD_RX_PACKET_PARAMSLOT(CIMD_Param_Nak_Error)->u.HexWord.i;
+       if (CIMD_CatchBufferErrorP)
+               *CIMD_CatchBufferErrorP=GE_INTERNALERROR;
+
+       CIMD_RX_Patrol_Nak_dump(CIMD_RX_Patrol_Nak_Error);
+
+       return(true);
+}
+
+static void CIMD_RX_Patrol_Nak_reset(void)
+{
+       CIMD_RX_Patrol_Nak_Error=0;
+}
+
+static const struct CIMD_RX_Patrol CIMD_RX_Patrols[]={
+       { CIMD_Cmd_Ack,CIMD_RX_Patrol_Ack,CIMD_RX_Patrol_Ack_reset },
+       { CIMD_Cmd_Nak,CIMD_RX_Patrol_Nak,CIMD_RX_Patrol_Nak_reset },
+       };
+
+
+static void CIMD_CatchBufferReset(void)
+{
+       CIMD_CatchBufferPtr=CIMD_CatchBuffer;
+}
+
+static void CIMD_CatchBufferStart(GSM_Error *errorcodep)
+{
+static GSM_Error err_trashcan;
+const struct CIMD_RX_Patrol *patrol;
+
+       if (!errorcodep)
+               errorcodep=&err_trashcan;
+       CIMD_CatchBufferErrorP=errorcodep;
+
+       CIMD_CatchBufferReset();
+
+       for (patrol=CIMD_RX_Patrols;patrol<CIMD_RX_Patrols+ARRAY_LEN(CIMD_RX_Patrols);patrol++)
+               if (patrol->reset)
+                       (*patrol->reset)();
+}
+
+static u8 *CIMD_RX_ProcessRawPacket_parseparam_buf_s;
+
+/* RETURNS: Success
+ */
+static bool CIMD_RX_ProcessRawPacket_parseparam(struct CIMD_paramslot *slot)
+{
+       switch (slot->elf) {
+
+               case CIMD_ELF_HexByte: {
+unsigned parsedbyte;
+                       if (0   || !isxdigit(CIMD_RX_ProcessRawPacket_parseparam_buf_s[0])
+                               || !isxdigit(CIMD_RX_ProcessRawPacket_parseparam_buf_s[1])
+                               || CIMD_MARK_SEP!=CIMD_RX_ProcessRawPacket_parseparam_buf_s[2]
+                               )
+                               return(false);
+                       if (1!=sscanf(CIMD_RX_ProcessRawPacket_parseparam_buf_s,"%X" CIMD_MARK_SEPS,&parsedbyte))
+                               return(false);
+                       slot->u.HexByte.i=parsedbyte;
+                       CIMD_RX_ProcessRawPacket_parseparam_buf_s+=2+1;
+                       } break;
+
+               case CIMD_ELF_HexWord: {
+unsigned parsedword;
+                       if (0   || !isxdigit(CIMD_RX_ProcessRawPacket_parseparam_buf_s[0])
+                               || !isxdigit(CIMD_RX_ProcessRawPacket_parseparam_buf_s[1])
+                               || !isxdigit(CIMD_RX_ProcessRawPacket_parseparam_buf_s[2])
+                               || !isxdigit(CIMD_RX_ProcessRawPacket_parseparam_buf_s[3])
+                               || CIMD_MARK_SEP!=CIMD_RX_ProcessRawPacket_parseparam_buf_s[4]
+                               )
+                               return(false);
+                       if (1!=sscanf(CIMD_RX_ProcessRawPacket_parseparam_buf_s,"%X" CIMD_MARK_SEPS,&parsedword))
+                               return(false);
+                       slot->u.HexWord.i=parsedword;
+                       CIMD_RX_ProcessRawPacket_parseparam_buf_s+=4+1;
+                       } break;
+
+               case CIMD_ELF_Decimal: {
+int parsedint;
+u8 *start=CIMD_RX_ProcessRawPacket_parseparam_buf_s;
+                       if (CIMD_MARK_SEP==*start)
+                               return(false);          /* empty Decimal not permitted */
+                       while (isdigit(*CIMD_RX_ProcessRawPacket_parseparam_buf_s))
+                               CIMD_RX_ProcessRawPacket_parseparam_buf_s++;
+                       if (CIMD_MARK_SEP!=*CIMD_RX_ProcessRawPacket_parseparam_buf_s++)        /* skip CIMD_MARK_SEP */
+                               return(false);
+                       if (1!=sscanf(start,"%d" CIMD_MARK_SEPS,&parsedint))
+                               return(false);
+                       slot->u.Decimal.i=parsedint;
+                       } break;
+
+               case CIMD_ELF_StringZ:
+                       slot->u.StringZ.s=CIMD_RX_ProcessRawPacket_parseparam_buf_s;
+                       while (CIMD_MARK_SEP!=*CIMD_RX_ProcessRawPacket_parseparam_buf_s)
+                               CIMD_RX_ProcessRawPacket_parseparam_buf_s++;
+                       *CIMD_RX_ProcessRawPacket_parseparam_buf_s++='\0';      /* skip CIMD_MARK_SEP */
+                       break;
+
+               case CIMD_ELF_HexBlock:
+                       slot->u.HexBlock.buf=CIMD_RX_ProcessRawPacket_parseparam_buf_s;
+
+                       while (CIMD_MARK_SEP!=*CIMD_RX_ProcessRawPacket_parseparam_buf_s)
+                               CIMD_RX_ProcessRawPacket_parseparam_buf_s++;
+                       if ((CIMD_RX_ProcessRawPacket_parseparam_buf_s-slot->u.HexBlock.buf)&1)
+                               return(false);          /* odd number of xdigits */
+
+                       slot->u.HexBlock.len=(CIMD_RX_ProcessRawPacket_parseparam_buf_s-slot->u.HexBlock.buf);
+                       if (!SMS_BlockFromHex(slot->u.HexBlock.buf/*d*/,slot->u.HexBlock.buf/*s*/,slot->u.HexBlock.len*2))
+                               return(false);          /* parse error */
+
+                       CIMD_RX_ProcessRawPacket_parseparam_buf_s++;    /* skip CIMD_MARK_SEP */
+                       break;
+
+               case CIMD_ELF_Empty:
+                       if (CIMD_MARK_SEP!=*CIMD_RX_ProcessRawPacket_parseparam_buf_s)
+                               return(false);          /* some content found */
+                       break;
+               }
+       return(true);   /* success */
+}
+
+
+static bool CIMD_RX_EvalPatrol(const struct CIMD_RX_Patrol *patrol)
+{
+       if (!patrol)
+               return(false);  /* not recognized */
+       if (CIMD_RX_Packet_Cmd!=patrol->cmd)
+               return(false);  /* not recognized */
+       return((*patrol->func)());
+}
+
+static inline void CIMD_RX_ProcessPacket(void)
+{
+const struct CIMD_RX_Patrol *patrol;
+
+       for (patrol=CIMD_RX_Patrols;patrol<CIMD_RX_Patrols+ARRAY_LEN(CIMD_RX_Patrols);patrol++)
+               if (CIMD_RX_EvalPatrol(patrol))
+                       return;         /* recognized and processed - whether successfuly is not interesting */
+       CIMD_RX_EvalPatrol(CIMD_RX_Patrol_Current);
+}
+
+static inline void CIMD_RX_ProcessRawPacket(void)
+{
+unsigned xsumgot;
+u8 *u8s,xsum;
+u16 position;
+const struct CIMD_Param *param;
+const struct CIMD_Cmd *cmdp;
+
+#ifdef CIMD_DEBUG
+       write(1,"GOT:<STX>",9);
+       write(1,CIMD_CatchBuffer+1,CIMD_CatchBufferPtr-1-(CIMD_CatchBuffer+1));
+       write(1,"<ETX>\n",6);
+#endif
+
+       if (CIMD_CatchBufferPtr<CIMD_CatchBuffer +1/*START*/ +1/*SEP*/ +2/*xsum*/ +1/*STOP*/)
+               return;         /* error - buffer too short */
+       if (0   || CIMD_CatchBufferPtr[-4]!=CIMD_MARK_SEP
+               || !isxdigit(CIMD_CatchBufferPtr[-3])
+               || !isxdigit(CIMD_CatchBufferPtr[-2])
+               )
+               return;         /* error - invalid buffer tail */
+
+       /* (*Ptr) will now point to the end of parameter part */
+       CIMD_CatchBufferPtr-=3;
+       if (1!=sscanf(CIMD_CatchBufferPtr,"%X" CIMD_MARK_SEPS,&xsumgot))
+               return;         /* INTERNAL - we have already checked the validity! */
+       xsum=0;
+       for (u8s=CIMD_CatchBuffer;u8s<CIMD_CatchBufferPtr;u8s++)
+               xsum+=*u8s;
+       if (xsum!=xsumgot)
+               return;         /* error - invalid checksum */
+
+       CIMD_RX_ProcessRawPacket_parseparam_buf_s=CIMD_CatchBuffer +1/*START*/;
+       CIMD_RX_Packet_slots=1;         /* just [0] now */
+       CIMD_RX_Packet_slot[0].code=0;  /* ignored now - position */
+       CIMD_RX_Packet_slot[0].elf=CIMD_ELF_HexByte;
+       if (!CIMD_RX_ProcessRawPacket_parseparam(CIMD_RX_Packet_slot+0))
+               return;         /* error - cmd code not parsed */
+
+       for (cmdp=CIMD_Cmd;cmdp<CIMD_Cmd+CIMD_Cmdcnt;cmdp++)
+               if (cmdp->code==CIMD_RX_Packet_slot[0].u.HexByte.i) break;
+       if (cmdp>=CIMD_Cmd+CIMD_Cmdcnt)
+               return;         /* error - unknown command */
+       CIMD_RX_Packet_Cmd=(enum CIMD_Cmd_Type)(cmdp-CIMD_Cmd);
+
+       /* "position" handling is BIP dependency! */
+       for (position=1;CIMD_RX_ProcessRawPacket_parseparam_buf_s<CIMD_CatchBufferPtr;position++) {
+struct CIMD_paramslot *slot;
+
+               /* This code search is a crude overhead just for BIP now :-)
+                */
+               for (param=cmdp->param;param<cmdp->param+cmdp->paramcnt;param++)
+                       if (position==param->code)
+                               break;
+               if (param>=cmdp->param+cmdp->paramcnt) {        /* not found - ignore */
+                       while (CIMD_MARK_SEP!=*CIMD_RX_ProcessRawPacket_parseparam_buf_s++);    /* skip CIMD_MARK_SEP */
+                       continue;
+                       }
+               slot=CIMD_RX_Packet_slot+1+(param-cmdp->param);
+               slot->code=param->code;
+               slot->elf=param->elf;
+               if (!CIMD_RX_ProcessRawPacket_parseparam(slot))
+                       return;         /* error - unable to parse required param */
+               /* CIMD_MARK_SEP is skipped by CIMD_RX_ProcessRawPacket_parseparam() automatically
+                */
+               CIMD_RX_Packet_slots++;
+               }
+       /* The packet is now parsed */
+
+       CIMD_RX_ProcessPacket();
+}
+
+/* RX_State machine for receive handling.  Called once for each character
+   received from the phone/phone. */
+
+static void CIMD_RX_Char(char rx_byte)
+{
+#ifdef CIMD_DEBUG
+#if 0
+       dprintf(_("Received character '%c' (0x%02X)\n"),
+               (!isgraph(rx_byte) ? '?' : rx_byte),(unsigned char)rx_byte);
+#endif
+       putchar(rx_byte);
+#endif
+
+       /* NEVER store '\0' as it would knock-out completely our patrolling system!
+        * It is invalid anyway
+        */
+       if (rx_byte=='\0') {
+               CIMD_CatchBufferReset();
+               return;
+               }
+
+       if (CIMD_CatchBufferPtr==CIMD_CatchBuffer && rx_byte!=CIMD_MARK_START) {
+               /* No haven't yet catched any START yet */
+               return;
+               }
+       if (CIMD_CatchBufferPtr>=CIMD_CatchBuffer+sizeof(CIMD_CatchBuffer)) {
+               fprintf(stderr,_("WARNING: Incoming CIMD packet too long to fit (>%d bytes)!\n"),sizeof(CIMD_CatchBuffer));
+               CIMD_CatchBufferReset();
+               }
+
+       *CIMD_CatchBufferPtr++=rx_byte;
+       if (rx_byte==CIMD_MARK_STOP) {
+               CIMD_RX_ProcessRawPacket();
+               CIMD_CatchBufferReset();
+               }
+}
+
+/* Here we initialise model specific functions. */
+
+#define CIMD_FUNCTIONS_ENTRY(name) \
+       case GOP_##name:        return(CIMD_##name(data,state));
+
+static GSM_Error CIMD_Functions(GSM_Operation op, GSM_Data *data, GSM_Statemachine *state)
+{
+static GSM_Statemachine *catcher=NULL;
+
+       if (!catcher || catcher==state)
+               catcher=state;
+       else
+               *((char *)NULL)=1;
+
+       switch (op) {
+               CIMD_FUNCTIONS_ENTRY(Init)
+               CIMD_FUNCTIONS_ENTRY(Terminate)
+               CIMD_FUNCTIONS_ENTRY(GetSMSStatus)
+               CIMD_FUNCTIONS_ENTRY(GetSMS)
+               CIMD_FUNCTIONS_ENTRY(SendSMS)
+               CIMD_FUNCTIONS_ENTRY(GetImei)
+               CIMD_FUNCTIONS_ENTRY(GetRevision)
+               CIMD_FUNCTIONS_ENTRY(GetModel)
+               CIMD_FUNCTIONS_ENTRY(GetManufacturer)
+               CIMD_FUNCTIONS_ENTRY(Reset)
+
+               case GOP_Identify: {
+GSM_Error err,r=GE_NONE;
+                       if (GE_NONE!=(err=CIMD_GetImei        (data,state)))
+                               r=err;
+                       if (GE_NONE!=(err=CIMD_GetRevision    (data,state)))
+                               r=err;
+                       if (GE_NONE!=(err=CIMD_GetModel       (data,state)))
+                               r=err;
+                       if (GE_NONE!=(err=CIMD_GetManufacturer(data,state)))
+                               r=err;
+                       return(r);
+                       }
+
+               default:
+                       return(GE_NOTIMPLEMENTED);
+       }
+}
+
+GSM_Phone phone_cimd = {
+       UNIMPLEMENTED,  /* IncomingFunctions - we don't use default StateMachine */
+       UNIMPLEMENTED,  /* DefaultFunction   - we don't use default StateMachine */
+        /* Mobile phone information */
+       {
+               "BIP" /* |CIMD - not yet */,    /* Supported models */
+               100,                    /* Max RF Level (AT+CSQ) */
+               0,                      /* Min RF Level (AT+CSQ) */
+               GRF_Percentage,         /* RF level units */
+               100,                    /* Max Battery Level (AT+CBC) */
+               0,                      /* Min Battery Level (AT+CBC) */
+               GBU_Percentage,         /* Battery level units */
+               GDT_None,               /* Have date/time support */
+               GDT_None,               /* Alarm supports time only */
+               0,                      /* No alarm available */
+               48, 84,                 /* Startup logo size */
+               14, 72,                 /* Op logo size */
+               14, 72,                 /* Caller logo size */
+       },
+       CIMD_Functions,
+};