/* -------------------------------------------------------------------- */
/* SMS Client, send messages to mobile phones and pagers                */
/*                                                                      */
/* ucp.c                                                                */
/*                                                                      */
/*  Copyright (C) 1997,1998,1999 Angelo Masci and Chuck Hurd            */
/*                                                                      */
/*  This library is free software; you can redistribute it and/or       */
/*  modify it under the terms of the GNU Library General Public         */
/*  License as published by the Free Software Foundation; either        */
/*  version 2 of the License, or (at your option) any later version.    */
/*                                                                      */
/*  This library is distributed in the hope that it will be useful,     */
/*  but WITHOUT ANY WARRANTY; without even the implied warranty of      */
/*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU   */
/*  Library General Public License for more details.                    */
/*                                                                      */
/*  You should have received a copy of the GNU Library General Public   */
/*  License along with this library; if not, write to the Free          */
/*  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
/*                                                                      */
/*  You can contact the author at this e-mail address:                  */
/*                                                                      */
/*  angelo@styx.demon.co.uk                                             */
/*  hurd@hurd.is.ge.com							*/
/*                                                                      */
/* -------------------------------------------------------------------- */
/* $Id$
   -------------------------------------------------------------------- */

/* -------------------------------------------------------------------- */
/* UCP Protocol: European Telecommunications Standards Institute (ETSI)	*/
/*                      http://www.etsi.org				*/
/*               European Telecommunication Standard ETS 300 133-3	*/
/*  "Enhanced Radio MEssage System (ERMES) Part 3: Network aspects", 	*/
/*  277 pages. very partial summary of Section 8: I5 Interface, 	*/
/*  Universal Computer Protocol						*/
/* 									*/
/* also: Short Message Service Centre, External Machine Interface, 	*/
/*       Specification Version 3.1.2   					*/
/*       CMG Telecommunications & Utilities BV, Netherlands		*/
/*       obtained from the Dutch telco KPN				*/
/*       describes an "EMI extended subset" of UCP which defines the 	*/
/*       51-58 oper. codes.						*/
/* 									*/
/* 									*/
/* Message format:                             All in ascii characters	*/
/*      <STX> HEADER DATA CHECKSUM <ETX>				*/
/* 									*/
/*       Fields within HEADER and DATA are terminated by the char "/".	*/
/*       Empty optional fields are indicated by two successive "/" 	*/
/*       separators.							*/
/*       Bytes in alphanumeric message fields are encoded as		*/
/*          two ascii characters (0-9,A-F) representing the hexidecimal	*/
/*          value of the encoded byte ("hex encoding").			*/
/*          For example, ascii "A" is encoded as "41"			*/
/*       Transparent data messages are also hex encoded, but 4 bits at 	*/
/*       a time, with each nyble being encoded as 1 ascii char 0-9,A-F.	*/
/* 									*/
/*    CHECKSUM:  add up all of the bytes of HEADER and DATA, including 	*/
/*               the field separators.  The checksum is the least 	*/
/*               significant byte (8 bits) of the sum.  This byte is 	*/
/*               sent hex encoded (i.e., two ascii characters 		*/
/*               representing the hex value of the checksum.)		*/
/* 									*/
/* HEADER FORMAT:							*/
/* 									*/
/*       TRN / LEN / ORC / OT /       ("/" is literal field separator)	*/
/* 									*/
/*       TRN  2 numeric char      Transaction reference number		*/
/*       LEN  5 numeric char      Total number of chars between 	*/
/*                                STX & ETX, right justified, 		*/
/*                                zero filled.				*/
/*       ORC  "O", "R", or "C"    "Operation", "Result", or repeated 	*/
/*                                operation				*/
/*       OT   2 numeric char      Operation Type			*/
/*              OT=01:  Call Input (message submission)			*/
/* 									*/
/*       An "Operation" message is sent (to the service center), and a 	*/
/*       "Result" is returned.  If the "Result" is syntatically 	*/
/*       incorrect or does not arrive within a timeout period, the 	*/
/*       operation may be retried, with the same TRN and data, but with */
/*       ORC set to "C" - to notify the service center that this 	*/
/*       operation is a possible duplicate.  (Note: "C" is not 		*/
/*       available in the EMI extended subset.)				*/
/* 									*/
/* 									*/
/*  examples:								*/
/*     01/00068/O/01/...  Call input operation, reference 1, total 	*/
/*                        length 68					*/
/*     01/00042/R/01/...  Call input result (response), reference 1, 	*/
/*                        total length 42				*/
/* 									*/
/* 									*/
/* DATA FORMAT  for ORC="O", OT=01   "Call input operation"  		*/
/*                                   (submit message)			*/
/* 									*/
/*      AdC / OAdC / OAC / MT / ... additional parameters depending 	*/
/*                                  on MT				*/
/* 									*/
/*   additional parameters:						*/
/*      MT = 1:   (none)                    ("Tone-only")		*/
/*      MT = 2:   NM /                      (Numeric message)		*/
/*      MT=  3:   AM /                      (Alphanumeric ascii 	*/
/*                                           message)			*/
/*      MT=  4:   NB / TD /                 (Transparent data)		*/
/*      MT=  6:   AM / CS /                 (Alphanumeric, special 	*/
/*                                           character set)		*/
/* 									*/
/*      AdC   string of numeric char    Address code, recipient		*/
/*      OAdC  string of numeric char    Address code, originator 	*/
/*                                      (optional)			*/
/*      OAC   string of char            Authentication code, originator	*/
/*					(optional)			*/
/*      MT    1 numeric char (1-4,6)    Message type			*/
/*      NM    string of numeric char    Numeric Message (optional)	*/
/*      AM    hex encoded string        Alphanumeric message (optional)	*/
/*      TD    hex encoded string        Transparent data (optional)	*/
/*      NB    string of numeric char    number of bits in TD message	*/
/*      CS    2 numeric char (00-31)    Character set number (optional)	*/
/* 									*/
/*      Note: the EMI extended subset is limited to MT = 2, 3		*/
/* 									*/
/*  example:								*/
/*   0653328374///3/414243203132/  Alphanumeric message "ABC 12" 	*/
/*                                 for 0653328374			*/
/* 									*/
/* DATA FORMAT  for ORC="R", OT=01   "Call input result"		*/
/* 									*/
/*      ACK / SM /                      (Positive result)		*/
/*      NAC / EC / SM /                 (Negative result)		*/
/* 									*/
/*      ACK  1 character "A"    Postive acknowledgement			*/
/*      NAC  1 character "N"    Negative acknowledgement		*/
/*      SM   string of char     System Message (optional)		*/
/*      EC   2 numeric char     Error code - partial list:		*/
/*             01: checksum error					*/
/*             02: syntax error						*/
/*             03: operation not supported				*/
/*             04: operation not allowed				*/
/*             05: call barring active					*/
/*             06: recipient address code invalid			*/
/*             07: authentication failure				*/
/*             08: legitimisation code failure				*/
/*             23: message type not supported by system			*/
/*             24: message too long					*/
/*             26: message type invalid for pager type			*/
/*             28: invalid character set				*/
/* 									*/
/* actual examples:							*/
/*      A/0653328374:191098174717/					*/
/*         Positive result.  In this case the "system message" is	*/
/*         the AdC followed by :ddmmyyhhmmss date-time-stamp		*/
/*      N/01/ Checksum error /						*/
/*         Negative result						*/
/* 									*/
/* EXAMPLE EXCHANGE:							*/
/*									*/
/* -> <stx>00/00060/O/01/0653328374///3/54657374206D6573736167652031/B2<etx> */
/* <- <stx>00/00042/R/01/A/0653328374:191098174717/1E<etx>		*/
/* -> (hangs up)							*/
/* 									*/
/* -------------------------------------------------------------------- */

#include <stdio.h>
#include <string.h>
#include <stdarg.h>

#include "common/common.h"
#include "logfile/logfile.h"
#include "driver.h"
#include "error.h"
#include "ascii.h"
#include "ia5table.h"
#include "comms/comms.h"
#include "resource/resource.h"

/* -------------------------------------------------------------------- */

static struct ucp_env
{
        DRIVER_DEFAULT_ENV def;

        /* Place any extended driver    */
        /* variables here               */

} driver_env;

/* -------------------------------------------------------------------- */

static  RESOURCE resource_list[] =
  {
    { RESOURCE_STRING,  "SMS_comms_params",         0, 1, NULL, 0,  "8N1",      0,    &(driver_env.def.comms_params)        },
    { RESOURCE_STRING,  "SMS_centre_number",        0, 1, NULL, 0,  NULL,       0,    &(driver_env.def.centre_number)       },
    { RESOURCE_NUMERIC, "SMS_baud",                 0, 1, NULL, 0,  NULL,       9600, &(driver_env.def.baud)                },
    { RESOURCE_NUMERIC, "SMS_deliver_timeout",      0, 0, NULL, 0,  NULL,       30,   &(driver_env.def.deliver_timeout)     },
    { RESOURCE_NUMERIC, "SMS_timeout",              0, 0, NULL, 0,  NULL,       10,   &(driver_env.def.timeout)             },
    { RESOURCE_NUMERIC, "SMS_write_timeout",        0, 0, NULL, 0,  NULL,       10,   &(driver_env.def.write_timeout)       },
    { RESOURCE_NUMERIC, "SMS_max_deliver",          0, 0, NULL, 0,  NULL,       1,    &(driver_env.def.max_deliver)         },
    { RESOURCE_NULL,     NULL,                      0, 1, NULL, 0,  NULL,       0,    NULL                                  }
  };

/* -------------------------------------------------------------------- */

#define DELIVERTIMEOUT          (driver_env.def.deliver_timeout)
#define TIMEOUT                 (driver_env.def.timeout)
#define WRITETIMEOUT            (driver_env.def.write_timeout)

/* -------------------------------------------------------------------- */

#define FD                      (driver_env.def.fd)

/* -------------------------------------------------------------------- */

static int UCP_sendmessage(char *msisdn, char *message);
static int UCP_parse_response(char *string);
static void UCP_hangup(void);

/* -------------------------------------------------------------------- */

static char *UCP_header(int tranRefNo, char ORC, int opType, int dataLen )
{
          int     msgLen;
   static char    msgHead[20];
                                                /*  HEADER section       */

   msgLen = 14 + dataLen + 2;          /* len(header+data+checksum) */

                            /* header:  TRN/LEN/ORC/OT/         */

   sprintf(msgHead, "%2.2d/%5.5d/%c/%2.2d/", tranRefNo, msgLen, ORC, opType);

   return msgHead;
}

/* -------------------------------------------------------------------- */

static char *UCP01_data(char *msisdn, char *message, int msgType)
{
   static char    msgData[800];
          char   *src;
          char   *dest;
          int     nc;

                                                /*  DATA section         */

   msgType = 3;        /* alphanumeric - others aren't implemented here*/


                            /* data:  AdC/OAdc/OAC/MT/..   */
                            /*    ->  recipient///3/...    */

   nc = sprintf(msgData, "%s///%1.1d/", msisdn, msgType);

                            /* ... additional parameters depending on MT */
                            /* For MT=3 (alphanumeric) this is hex coded */
                            /* message data                              */

   dest = &msgData[nc];
   for (src = message;  *src;  src++)
   {
        sprintf(dest, "%02X", *src);
        dest += 2;
   }
   sprintf( dest, "/" );

   return msgData;
}
/* -------------------------------------------------------------------- */

static char *UCP_checksum(char *header, char *data )
{
   char   *p;
   int     sum;
   static char    buf[3];
                                                /*  CHECKSUM section       */
   sum = 0;
   for (p = header; *p != '\0'; p++)
   {
        sum += (unsigned char) *p;
        sum &= 0xFF;
   }
   for (p = data;   *p != '\0'; p++)
   {
        sum += (unsigned char) *p;
        sum &= 0xFF;
   }

  sprintf(buf, "%02X", sum);

  return buf;
}

/* -------------------------------------------------------------------- */

static char *UCP_CallInputOp(char *msisdn, char *message, int msgType)
{
   char   *msgHead,
          *msgData,
          *msgCksm,
          ORC;

   int    tranRefNo, 
          opType;

   static char    buf[1024];


   msgType = 3;        /* alphanumeric - others aren't implemented here */

   msgData = UCP01_data( msisdn, message, msgType );

   msgHead = UCP_header( tranRefNo=1, ORC='O', opType=1, strlen(msgData) );

   msgCksm = UCP_checksum( msgHead, msgData );

              /* operational message:  <stx> header data checksum <etx> */

   sprintf(buf, "%c%s%s%s%c", S_STX, msgHead, msgData, msgCksm, S_ETX );

   return buf;
}

/* -------------------------------------------------------------------- */
/* Return Values:                                                       */
/*       0 Positive ACK                                                 */
/*       1 Negative ACK                                                 */
/*      -1 Error                                                        */
/* -------------------------------------------------------------------- */
static int UCP_parse_response(char *string)
{
        int     result;

        int     tranRefNo,
                length,
                opType;

        char    ORC, 
                ack;

        /* ------------------------------------------------------------ */
        /* Example:                                                     */
        /* <STX>01/00045/R/01/A/0041544180972:161298112313/A6<ETX>      */
        /*     trn len orc ot a        recip:time         chksum        */
        /* ------------------------------------------------------------ */

	/*
	 * Filter any leading crud.
	 */
	while (*string && (*string != '\002')) string++;

        result = sscanf(string, "\002%02d/%05d/%c/%02d/%c",
                        &tranRefNo,
                        &length,
                        &ORC,
                        &opType,
                        &ack        );


        if (result != 5 || ORC != 'R' || opType != 1 ) return -1;

        /* ---------------------------- */

        if     ( ack == 'A' ) result =  0;
        else if( ack == 'N' ) result =  1;
        else                  result = -1;

        return result;
}

/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
static int UCP_sendmessage(char *msisdn, char *message)
{
    char   buf[MAX_RESPONSE_BUFSIZE],
           *ucpMessage,
           *msg;


    int    result;
    int    msgType = 3;      /*alphanumeric*/


    /* ------------------------------------ */
    /* Convert message to ia5 format        */
    /* This should be done prior to         */
    /* entering this function.              */
    /* ------------------------------------ */

    msg = message;
    while (*msg != '\0')
    {
        *msg = toia5(*msg);
        msg++;
    }

    /* ------------------------------------ */


    ucpMessage = UCP_CallInputOp(msisdn, message, msgType);

    twrite(FD, ucpMessage, strlen(ucpMessage), WRITETIMEOUT);

    if( expstr(FD, buf, "\03", MAX_RESPONSE_BUFSIZE, DELIVERTIMEOUT) == 0)
    {
        lprintf(LOG_STANDARD, "SMSC Respsonse: %s\n", buf);

        result = UCP_parse_response(buf);
        if     (result == 0)
        {
            lprintf(LOG_STANDARD, "Message accepted\n");
        }
        else if(result == 1)
        {
            lprintf(LOG_ERROR, "Message rejected\n");
            UCP_hangup();
            return EUCP_ACKFAILED;
        }
        else
        {
            lprintf(LOG_ERROR, "Bad message acknowledgement\n");
            UCP_hangup();
            return EUCP_BADACK;
        }
     }
     else
     {
        lprintf(LOG_ERROR, "No Message Response\n");
        UCP_hangup();
        return EUCP_NORESPONSE;
     }

     return 0;
}

/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
static void UCP_hangup(void)
{       default_hangup((DRIVER_DEFAULT_ENV *)(&driver_env));
}

/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
DEVICE_ENTRY ucp_device = {

        "UCP",
	"2.1",
        resource_list,
        (DRIVER_DEFAULT_ENV *)(&driver_env),

        default_init,
        default_main,
        default_validate_numeric_id,
        default_dial,
        default_hangup,
        default_send_disconnect,
        default_single_deliver,
        UCP_sendmessage,
        default_login
};
