Logo Search packages:      
Sourcecode: smstools version File versions  Download package

pdu.c

/*
SMS Server Tools 3
Copyright (C) Keijo Kasvi
http://smstools3.kekekasvi.com/

Based on SMS Server Tools 2 from Stefan Frings
http://www.meinemullemaus.de/

This program is free software unless you got it under another license directly
from the author. You can redistribute it and/or modify it under the terms of
the GNU General Public License as published by the Free Software Foundation.
Either version 2 of the License, or (at your option) any later version.
*/

#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <ctype.h>
#include <syslog.h>
#include "pdu.h"
#include "smsd_cfg.h"
#include "logging.h"

#define MAX_ADDRESS_LENGTH 50
#define MAX_SMSC_ADDRESS_LENGTH 30

char *err_too_short = "string is too short";
char *err_pdu_content = "invalid character(s) in string";

int add_warning(char *buffer, char *format, ...)
{
  int result = 1;
  va_list argp;
  char text[2048];
  char *title = "Warning: ";

  va_start(argp, format);
  vsnprintf(text, sizeof(text), format, argp);
  va_end(argp);

  if (buffer)
  {
    if (strlen(buffer) + strlen(text) +strlen(title) +1/* for \n */ < SIZE_WARNING_HEADERS)
      sprintf(strchr(buffer, 0), "%s%s\n", title, text);
    else
    {
      result = 0;
      writelogfile(LOG_ERR, process_title, "PDU %s%s", title, text);
    }
  }

  return result;
}

void pdu_error(char **err_str, int position, int length, char *format, ...)
{
  va_list argp;
  char text[2048];
  char *title = "PDU ERROR: ";
  char tmp[51];

  va_start(argp, format);
  vsnprintf(text, sizeof(text), format, argp);
  va_end(argp);

  if (position >= 0)
  {
    if (length > 0)
      sprintf(tmp, "Position %i,%i: ", position +1, length);
    else
      sprintf(tmp, "Position %i: ", position +1);
  }
  else
    *tmp = 0;

  if (!(*err_str))
  {
    if ((*err_str = (char *)malloc(strlen(tmp) +strlen(text) +strlen(title) +2)))
      *err_str[0] = 0;
  }
  else
    *err_str = (char *)realloc((void *)*err_str, strlen(*err_str) +strlen(title) +strlen(tmp) +strlen(text) +2);

  if (*err_str)
    sprintf(strchr(*err_str, 0), "%s%s%s\n", title, tmp, text);
}

int isXdigit(char ch)
{
  if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F'))
    return 1;
  return 0;
}

/* Swap every second character */
void swapchars(char* string) 
{
  int Length;
  int position;
  char c;

  Length=strlen(string);
  for (position=0; position<Length-1; position+=2)
  {
    c=string[position];
    string[position]=string[position+1];
    string[position+1]=c;
  }
}

// Converts an ascii text to a pdu string 
// text might contain zero values because this is a valid character code in sms
// character set. Therefore we need the length parameter.
// If udh is not 0, then insert it to the beginning of the message.
// The string must be in hex-dump format: "05 00 03 AF 02 01". 
// The first byte is the size of the UDH.
int text2pdu(char* text, int length, char* pdu, char* udh)
{
  char tmp[500];
  char octett[10];
  int pdubitposition;
  int pdubyteposition=0;
  int character;
  int bit;
  int pdubitnr;
  int counted_characters=0;
  int udh_size_octets;   // size of the udh in octets, should equal to the first byte + 1
  int udh_size_septets;  // size of udh in septets (7bit text characters)
  int fillbits;          // number of filler bits between udh and ud.
  int counter;

#ifdef DEBUGMSG
  printf("!! text2pdu(text=..., length=%i, pdu=..., udh=%s\n",length,udh);
#endif
  pdu[0]=0;
  // Check the udh
  if (udh)
  {
    udh_size_octets=(strlen(udh)+2)/3;
    udh_size_septets=((udh_size_octets)*8+6)/7;
    fillbits=7-(udh_size_octets % 7);
    if (fillbits==7)
      fillbits=0;

    // copy udh to pdu, skipping the space characters
    for (counter=0; counter<udh_size_octets; counter++)
    {
      pdu[counter*2]=udh[counter*3];
      pdu[counter*2+1]=udh[counter*3+1];
    }
    pdu[counter*2]=0;
#ifdef DEBUGMSG
  printf("!! pdu=%s, fillbits=%i\n",pdu,fillbits);
#endif
  } 
  else
  {
    udh_size_octets=0;
    udh_size_septets=0; 
    fillbits=0;
  }
  // limit size of the message to maximum allowed size
  if (length>maxsms_pdu-udh_size_septets)
    length=maxsms_pdu-udh_size_septets;
  //clear the tmp buffer
  for (character=0;character<sizeof(tmp);character++)
    tmp[character]=0;
  // Convert 8bit text stream to 7bit stream
  for (character=0;character<length;character++)
  {
    counted_characters++;
    for (bit=0;bit<7;bit++)
    {
      pdubitnr=7*character+bit+fillbits;
      pdubyteposition=pdubitnr/8;
      pdubitposition=pdubitnr%8;
      if (text[character] & (1<<bit))
        tmp[pdubyteposition]=tmp[pdubyteposition] | (1<<pdubitposition);
      else
        tmp[pdubyteposition]=tmp[pdubyteposition] & ~(1<<pdubitposition);
    }
  }
  tmp[pdubyteposition+1]=0;
  // convert 7bit stream to hex-dump
  for (character=0;character<=pdubyteposition; character++)
  {
    sprintf(octett,"%02X",(unsigned char) tmp[character]);
    strcat(pdu,octett);
  }
#ifdef DEBUGMSG
  printf("!! pdu=%s\n",pdu);
#endif
  return counted_characters+udh_size_septets;
}

/* Converts binary to PDU string, this is basically a hex dump. */
void binary2pdu(char* binary, int length, char* pdu)
{
  int character;
  char octett[10];

  if (length>maxsms_binary)
    length=maxsms_binary;
  pdu[0]=0;
  for (character=0;character<length; character++)
  {
    sprintf(octett,"%02X",(unsigned char) binary[character]);
    strcat(pdu,octett);
  }
}

// Make the PDU string from a mesage text and destination phone number.
// The destination variable pdu has to be big enough. 
// alphabet indicates the character set of the message.
// flash_sms enables the flash flag.
// mode select the pdu version (old or new).
// if udh is true, then udh_data contains the optional user data header in hex dump, example: "05 00 03 AF 02 01"

void make_pdu(char* number, char* message, int messagelen, int alphabet, int flash_sms, int report, int with_udh,
              char* udh_data, char* mode, char* pdu, int validity, int replace_msg)
{
  int coding;
  int flags;
  char tmp[50];
  char tmp2[500];
  int numberformat;
  int numberlength;
  char *p;
  int l;

  if (number[0]=='s')  // Is number starts with s, then send it without number format indicator
  {
    numberformat=129;
    strcpy(tmp,number+1);
  }
  else
  {
    numberformat=145;
    strcpy(tmp,number);
  }
  numberlength=strlen(tmp);
  // terminate the number with F if the length is odd
  if (numberlength%2)
    strcat(tmp,"F");
  // Swap every second character
  swapchars(tmp);

  flags=1; // SMS-Sumbit MS to SMSC
  if (with_udh)
    flags+=64; // User Data Header
  if (strcmp(mode,"old")!=0)
    flags+=16; // Validity field
  if (report>0)
    flags+=32; // Request Status Report

  if (alphabet == 1)
    coding = 4; // 8bit binary
  else if (alphabet == 2)
    coding = 8; // 16bit
  else
    coding = 0; // 7bit
  if (flash_sms > 0)
    coding += 0x10; // Bits 1 and 0 have a message class meaning (class 0, alert)

  /* Create the PDU string of the message */
  if (alphabet==1 || alphabet==2)
  {
    // Unicode message can be concatenated:
    //if (alphabet == 2 && with_udh)
    // Binary message can be concatenated too:
    if (with_udh)
    {
      strcpy(tmp2, udh_data);
      while ((p = strchr(tmp2, ' ')))
        strcpy(p, p +1);
      l = strlen(tmp2) /2;
      binary2pdu(message, messagelen, strchr(tmp2, 0));
      messagelen += l;
    }
    else
      binary2pdu(message,messagelen,tmp2);
  }
  else
    messagelen=text2pdu(message,messagelen,tmp2,udh_data);
  /* concatenate the first part of the PDU string */
  if (strcmp(mode,"old")==0)
    sprintf(pdu,"%02X00%02X%02X%s00%02X%02X",flags,numberlength,numberformat,tmp,coding,messagelen);
  else
  {
    if (validity < 0 || validity > 255)
      validity = validity_period;
    sprintf(pdu, "00%02X00%02X%02X%s%02X%02X%02X%02X",
            flags, numberlength, numberformat, tmp,
            (replace_msg >= 1 && replace_msg <= 7)? 0x40 +replace_msg : 0,
            coding, validity, messagelen);
  }
  /* concatenate the text to the PDU string */
  strcat(pdu,tmp2);
}

int octet2bin(char* octet) /* converts an octet to a 8-Bit value */
{
  int result=0;

  if (octet[0]>57)
    result+=octet[0]-55;
  else
    result+=octet[0]-48;
  result=result<<4;
  if (octet[1]>57)
    result+=octet[1]-55;
  else
    result+=octet[1]-48;
  return result;
}

// Converts an octet to a 8bit value,
// returns < in case of error.
int octet2bin_check(char *octet)
{
  if (octet[0] == 0)
    return -1;
  if (octet[1] == 0)
    return -2;
  if (!isXdigit(octet[0]))
    return -3;
  if (!isXdigit(octet[1]))
    return -4;
  return octet2bin(octet);
}

/* converts a PDU-String to text, text might contain zero values! */
/* the first octet is the length */
/* return the length of text, -1 if there is a PDU error, -2 if PDU is too short */
/* with_udh must be set already if the message has an UDH */
/* this function does not detect the existance of UDH automatically. */
int pdu2text(char* pdu, char* text, int with_udh, char* udh, int *errorpos) 
{
  int bitposition=0;
  int byteposition;
  int byteoffset;
  int charcounter;
  int bitcounter;
  int septets;
  int octets;
  int udhsize;
  int octetcounter;
  int skip_characters;
  char c;
  char binary[500];
  int i;
  int result;

#ifdef DEBUGMSG
  printf("!! pdu2text(pdu=%s,...)\n",pdu);
#endif

  if ((septets = octet2bin_check(pdu)) < 0)
  {
    if (errorpos)
      *errorpos = -1 * septets -3;
    return (septets >= -2)? -2: -1;
  }

  if (with_udh)
  {
    // copy the data header to udh and convert to hex dump
    // There was at least one octet and next will give an error if there is no more data:
    if ((udhsize = octet2bin_check(pdu +2)) < 0)
    {
      if (errorpos)
        *errorpos = -1 * udhsize -3 +2;
      return (udhsize >= -2)? -2: -1;
    }

    i = 0;
    result = -1;
    for (octetcounter=0; octetcounter<udhsize+1; octetcounter++)
    {
      if (strlen(udh) +3 >= SIZE_UDH_DATA)
      {
        i = octetcounter *2 +2;
        result = -2;
        break;
      }
      udh[octetcounter*3]=pdu[(octetcounter<<1)+2];
      if (!isXdigit(udh[octetcounter *3]))
      {
        i = octetcounter *2 +2;
        if (!udh[octetcounter *3])
          result = -2;
        break;
      }
      udh[octetcounter*3+1]=pdu[(octetcounter<<1)+3];
      if (!isXdigit(udh[octetcounter *3 +1]))
      {
        i = octetcounter *2 +3;
        if (!udh[octetcounter *3 +1])
          result = -2;
        break;
      }
      udh[octetcounter*3+2]=' '; 
    }
    // append null termination to udh string.
    udh[octetcounter*3]=0;
    if (i)
    {
      if (errorpos)
        *errorpos = i;
      return result;
    }

    // Calculate how many text charcters include the UDH.
    // After the UDH there may follow filling bits to reach a 7bit boundary.
    skip_characters=(((udhsize+1)*8)+6)/7;
#ifdef DEBUGMSG
  printf("!! septets=%i\n",septets);
  printf("!! udhsize=%i\n",udhsize);
  printf("!! skip_characters=%i\n",skip_characters);
#endif
  }
  else
  {
    if (udh) 
      udh[0]=0;
    skip_characters=0;
  }

  // Copy user data of pdu to a buffer
  octets=(septets*7+7)/8;     
  for (octetcounter=0; octetcounter<octets; octetcounter++)
  {
    if ((i = octet2bin_check(pdu +(octetcounter << 1) +2)) < 0)
    {
      if (errorpos)
      {
        *errorpos = octetcounter *2 +2;
        if (i == -2 || i == -4)
          (*errorpos)++;
      }
      return (i >= -2)? -2: -1;
    }
    binary[octetcounter] = i;
  }

  // Then convert from 8-Bit to 7-Bit encapsulated in 8 bit 
  // skipping storing of some characters used by UDH.
  for (charcounter=0; charcounter<septets; charcounter++)
  {
    c=0;
    for (bitcounter=0; bitcounter<7; bitcounter++)
    {
      byteposition=bitposition/8;
      byteoffset=bitposition%8;
      if (binary[byteposition]&(1<<byteoffset))
        c=c|128;
      bitposition++;
      c=(c>>1)&127; /* The shift fills with 1, but I want 0 */
    }
    if (charcounter>=skip_characters)
      text[charcounter-skip_characters]=c; 
  }
  text[charcounter-skip_characters]=0;
  return charcounter-skip_characters;
}

/* Converts a PDU string to binary. Return -1 if there is a PDU error, -2 if PDU is too short */
int pdu2binary(char* pdu, char* binary, int *errorpos)
{
  int octets;
  int octetcounter;
  int i;

  if ((octets = octet2bin_check(pdu)) < 0)
  {
    if (errorpos)
      *errorpos = -1 * octets -3;
    return (octets >= -2)? -2: -1;
  }

  for (octetcounter=0; octetcounter<octets; octetcounter++)
  {
    if ((i = octet2bin_check(pdu +(octetcounter << 1) +2)) < 0)
    {
      if (errorpos)
      {
        *errorpos = octetcounter *2 +2;
        if (i == -2 || i == -4)
          (*errorpos)++;
      }
      return (i >= -2)? -2: -1;
    }
    else
      binary[octetcounter] = i;
  }
  binary[octets]=0;
  return octets;
}

int explain_toa(char *dest, char *octet)
{
  int result;
  char *p;

  if ((result = octet2bin_check(octet)) != -1)
  {
    switch ((result & 0x70) >> 4)
    {
      case 0: p = "unknown"; break;
      case 1: p = "international"; break;
      case 2: p = "national"; break;
      case 3: p = "network specific"; break;
      case 4: p = "subsciber"; break;
      case 5: p = "alphanumeric"; break;
      case 6: p = "abbreviated"; break;
      case 7: p = "reserved"; break;
    }
    sprintf(dest, "%.2s %s", octet, p);

    switch (result & 0x0F)
    {
      case 0: p = "unknown"; break;
      case 1: p = "ISDN/telephone"; break;
      case 3: p = "data"; break;
      case 4: p = "telex"; break;
      case 8: p = "national"; break;
      case 9: p = "private"; break;
      case 10: p = "ERMES"; break;
      default: p = "reserved"; break;
    }
    sprintf(strchr(dest, 0), ", %s", p);
  }

  return result;
}

// Subroutine for messages type 0 (SMS-Deliver)
// Input:
// Src_Pointer points to the PDU string
// Output:
// sendr Sender
// date and time Date/Time-stamp
// message the message text or binary data
// returns length of message
int split_type_0(char *full_pdu, char* Src_Pointer, int* alphabet, char* sendr, char* date, char* time, char* message,
                 int with_udh, char* udh, char *from_toa, int *replace, char **err_str, char *warning_headers)
{
  int Length;
  int padding;
  char tmpsender[100];
  int result = 0;
  int i;
  int errorpos;

#ifdef DEBUGMSG
  printf("!! split_type_0(Src_Pointer=%s, ...\n",Src_Pointer);
#endif

  // There should be at least address-length and address-type:
  if (strlen(Src_Pointer) < 4)
    pdu_error(err_str, Src_Pointer -full_pdu, 4, "While trying to read address length and address type: %s", err_too_short);
  else
  {
    Length = octet2bin_check(Src_Pointer);
    if (Length < 1 || Length > MAX_ADDRESS_LENGTH)
      pdu_error(err_str, Src_Pointer -full_pdu, 2, "Invalid sender address length: \"%.2s\"", Src_Pointer);
    else
    {
      padding=Length%2;
      Src_Pointer+=2;
      i = explain_toa(from_toa, Src_Pointer);
      if (i < 0)
        pdu_error(err_str, Src_Pointer -full_pdu, 2, "Invalid sender address type: \"%.2s\"", Src_Pointer);
      else if (i < 0x80)
        pdu_error(err_str, Src_Pointer -full_pdu, 2, "Missing bit 7 in sender address type: \"%.2s\"", Src_Pointer);
      else
      {
        Src_Pointer += 2;
        if ((i & 112) == 80)
        {  // Sender is alphanumeric
          if (strlen(Src_Pointer) < Length +padding)
            pdu_error(err_str, Src_Pointer -full_pdu, Length +padding,
                      "While trying to read sender address (alphanumeric, length %i): %s",
                      Length +padding, err_too_short);
          else
          {
            snprintf(tmpsender,Length+3+padding,"%02X%s",Length*4/7,Src_Pointer);
            if (pdu2text(tmpsender,sendr,0,0, NULL) < 0)
              pdu_error(err_str, Src_Pointer -full_pdu, Length +padding,
                        "While reading alphanumeric sender address: %s", err_pdu_content);
          }
        }
        else
        {  // sender is numeric
          if (strlen(Src_Pointer) < Length +padding)
            pdu_error(err_str, Src_Pointer -full_pdu, Length +padding,
                      "While trying to read sender address (numeric, length %i): %s",
                      Length +padding, err_too_short);
          else
          {
            strncpy(sendr, Src_Pointer, Length +padding);
            sendr[Length +padding] = 0;
            swapchars(sendr);
            i = Length +padding -1;
            if (padding)
            {
              if (sendr[i] != 'F')
                add_warning(warning_headers, "Length of numeric sender address is odd, but not terminated with 'F'.");
              else
                sendr[i] = 0;
            }
            else
            {
              if (sendr[i] == 'F')
              {
                add_warning(warning_headers, "Length of numeric sender address is even, but still was terminated with 'F'.");
                sendr[i] = 0;
              }
            }

            for (i = 0; sendr[i]; i++)
              if (!isdigit(sendr[i]))
              {
                // Not a fatal error (?)
                //pdu_error(err_str, Src_Pointer -full_pdu, Length +padding, "Invalid character(s) in sender address: \"%s\"", sendr);
                //*sendr = 0;
                add_warning(warning_headers, "Invalid character(s) in sender address.");
                break;
              }
          }
        }
      }
    }

    if (!(*err_str))
    {
      Src_Pointer += Length +padding;
      // Next there should be:
      // XX protocol identifier
      // XX data encoding scheme
      // XXXXXXXXXXXXXX time stamp, 7 octets
      // XX length of user data
      // ( XX... user data  )
      if (strlen(Src_Pointer) < 20)
        pdu_error(err_str, Src_Pointer -full_pdu, 20, "While trying to read TP-PID, TP-DSC, TP-SCTS and TP-UDL: %s", err_too_short);
      else
      {
        if ((i = octet2bin_check(Src_Pointer)) < 0)
          pdu_error(err_str, Src_Pointer -full_pdu, 2, "Invalid protocol identifier: \"%.2s\"", Src_Pointer);
        else
        {
          if ((i & 0xF8) == 0x40)
            *replace = (i & 0x07);
          Src_Pointer += 2;
          if ((i = octet2bin_check(Src_Pointer)) < 0)
            pdu_error(err_str, Src_Pointer -full_pdu, 2, "Invalid data encoding scheme: \"%.2s\"", Src_Pointer);
          else
          {
            *alphabet = (i & 0x0C) >>2;
            if (*alphabet == 3)
              pdu_error(err_str, Src_Pointer -full_pdu, 2, "Invalid alphabet in data encoding scheme: value 3 is not supported.");
              // ...or should this be a warning? If so, GSM alphabet is then used as a default.

            if (*alphabet == 0)
              *alphabet = -1;

            if (!(*err_str))
            {
              Src_Pointer += 2;
              sprintf(date,"%c%c-%c%c-%c%c",Src_Pointer[1],Src_Pointer[0],Src_Pointer[3],Src_Pointer[2],Src_Pointer[5],Src_Pointer[4]);
              if (!isdigit(date[0]) || !isdigit(date[1]) || !isdigit(date[3]) || !isdigit(date[4]) || !isdigit(date[6]) || !isdigit(date[7]))
              {
                pdu_error(err_str, Src_Pointer -full_pdu, 6, "Invalid character(s) in date of Service Centre Time Stamp: \"%s\"", date);
                *date = 0;
              }
              else if (atoi(date +3) > 12 || atoi(date +6) > 31)
              {
                // Not a fatal error (?)
                //pdu_error(err_str, Src_Pointer -full_pdu, 6, "Invalid value(s) in date of Service Centre Time Stamp: \"%s\"", date);
                //*date = 0;
                add_warning(warning_headers, "Invalid values(s) in date of Service Centre Time Stamp.");
              }

              Src_Pointer += 6;
              sprintf(time,"%c%c:%c%c:%c%c",Src_Pointer[1],Src_Pointer[0],Src_Pointer[3],Src_Pointer[2],Src_Pointer[5],Src_Pointer[4]);
              if (!isdigit(time[0]) || !isdigit(time[1]) || !isdigit(time[3]) || !isdigit(time[4]) || !isdigit(time[6]) || !isdigit(time[7]))
              {
                pdu_error(err_str, Src_Pointer -full_pdu, 6, "Invalid character(s) in time of Service Centre Time Stamp: \"%s\"", time);
                *time = 0;
              }
              else if (atoi(time) > 23 || atoi(time +3) > 59 || atoi(time +6) > 59)
              {
                // Not a fatal error (?)
                //pdu_error(err_str, Src_Pointer -full_pdu, 6, "Invalid value(s) in time of Service Centre Time Stamp: \"%s\"", time);
                //*time = 0;
                add_warning(warning_headers, "Invalid values(s) in time of Service Centre Time Stamp.");
              }

              if (!(*err_str))
              {
                Src_Pointer += 6;
                // Time zone is not used but bytes are checked:
                if (octet2bin_check(Src_Pointer) < 0)
                  pdu_error(err_str, Src_Pointer -full_pdu, 2,
                            "Invalid character(s) in Time Zone of Service Centre Time Stamp: \"%.2s\"", Src_Pointer);
                else
                  Src_Pointer += 2;
              }
            }
          }
        }
      }
    }

    if (!(*err_str))
    {
      // Src_Pointer now points to the User data length, which octet exists.
      // TODO: Can udh-len be zero?

      if (*alphabet <= 0)
      {
        if ((result = pdu2text(Src_Pointer, message, with_udh, udh, &errorpos)) < 0)
          pdu_error(err_str, Src_Pointer -full_pdu +errorpos, 0, "While reading TP-UD (GSM text): %s",
                    (result == -1)? err_pdu_content : err_too_short);
      }
      else if (*alphabet == 1)
      {
        if ((result = pdu2binary(Src_Pointer, message, &errorpos)) < 0)
          pdu_error(err_str, Src_Pointer -full_pdu +errorpos, 0, "While reading TP-UD (binary): %s",
                    (result == -1)? err_pdu_content : err_too_short);
      }
      else
      {
        // Alphabet:UCS2(16)bit
        // If it seems that the message is a part of a concatenated message, header bytes are removed from
        // the begin of the message and values of these are printed to the udh buffer.
        // UDH-DATA: "05 00 03 02 03 02 "
        // NOTE: If the whole message consist of single part only, there still can be (or always is?) a
        // concatenated header with values partcount=1 and partnr=1.
        int octets;

        octets = pdu2binary(Src_Pointer, message, &errorpos);
        if (octets == -1)
          pdu_error(err_str, Src_Pointer -full_pdu +errorpos, 0, "While reading TP-UD (UCS2 text): %s", err_pdu_content);
        else if (octets > 6 && with_udh)
        {
          i = 0;
          if (message[0] >= 0x05 && message[1] == 0x00 && message[2] == 0x03)
          {
            // 8-bit reference number
            if (message[4] > 0x00 && message[5] > 0x00 && message[5] <= message[4])
              i = 1;
          }
          else if (message[0] >= 0x06 && message[1] == 0x08 && message[2] == 0x04)
          {
            // 16-bit reference number
            if (message[5] > 0x00 && message[6] > 0x00 && message[6] <= message[5])
              i = 1;
          }

          if (i)
          {
            *udh = 0;
            for (i = 0; i <= (unsigned char)message[0]; i++)
              sprintf(strchr(udh, 0), "%02X ", (unsigned char)message[i]);
            octets -= (unsigned char)message[0] +1;
            memcpy(message, message + (unsigned char)message[0] +1, octets);
          }
        }
        result = octets;
      }
    }
  }

  return result;
}

// Subroutine for messages type 2 (Status Report)
// Input: 
// Src_Pointer points to the PDU string
// Output:
// sendr Sender
// date and time Date/Time-stamp
// result is the status value and text translation
int split_type_2(char *full_pdu, char* Src_Pointer,char* sendr, char* date,char* time,char* result,
                 char *from_toa, char **err_str, char *warning_headers)
{
  int Length;
  int padding;
  int status;
  char temp[32];
  char tmpsender[100];
  int messageid;
  int i;
  char *p;

  strcat(result,"SMS STATUS REPORT\n");

  // There should be at least message-id, address-length and address-type:
  if (strlen(Src_Pointer) < 6)
    pdu_error(err_str, Src_Pointer -full_pdu, 6,
              "While trying to read message id, address length and address type: %s", err_too_short);
  else
  {
    // get message id
    if ((messageid = octet2bin_check(Src_Pointer)) < 0)
      pdu_error(err_str, Src_Pointer -full_pdu, 2, "Invalid message id: \"%.2s\"", Src_Pointer);
    else
    {
      sprintf(strchr(result, 0), "Message_id: %i\n", messageid);
      // get recipient address
      Src_Pointer+=2;
      Length = octet2bin_check(Src_Pointer);
      if (Length < 1 || Length > MAX_ADDRESS_LENGTH)
        pdu_error(err_str, Src_Pointer -full_pdu, 2, "Invalid recipient address length: \"%.2s\"", Src_Pointer);
      else
      {
        padding=Length%2;
        Src_Pointer+=2;
        i = explain_toa(from_toa, Src_Pointer);
        if (i < 0)
          pdu_error(err_str, Src_Pointer -full_pdu, 2, "Invalid recipient address type: \"%.2s\"", Src_Pointer);
        else if (i < 0x80)
          pdu_error(err_str, Src_Pointer -full_pdu, 2, "Missing bit 7 in recipient address type: \"%.2s\"", Src_Pointer);
        else
        {
          Src_Pointer += 2;
          if ((i & 112) == 80)
          {  // Sender is alphanumeric
            if (strlen(Src_Pointer) < Length +padding)
              pdu_error(err_str, Src_Pointer -full_pdu, Length +padding,
                        "While trying to read recipient address (alphanumeric, length %i): %s",
                        Length +padding, err_too_short);
            else
            {
              snprintf(tmpsender,Length+3+padding,"%02X%s",Length*4/7,Src_Pointer);
              if (pdu2text(tmpsender,sendr,0,0, NULL) < 0)
                pdu_error(err_str, Src_Pointer -full_pdu, Length +padding,
                          "While reading alphanumeric recipient address: %s", err_pdu_content);
            }
          }
          else
          {  // sender is numeric
            if (strlen(Src_Pointer) < Length +padding)
              pdu_error(err_str, Src_Pointer -full_pdu, Length +padding,
                        "While trying to read recipient address (numeric, length %i): %s",
                        Length +padding, err_too_short);
            else
            {
              strncpy(sendr,Src_Pointer,Length+padding);
              sendr[Length +padding] = 0;
              swapchars(sendr);
              i = Length +padding -1;
              if (padding)
              {
                if (sendr[i] != 'F')
                  add_warning(warning_headers, "Length of numeric recipient address is odd, but not terminated with 'F'.");
                else
                  sendr[i] = 0;
              }
              else
              {
                if (sendr[i] == 'F')
                {
                  add_warning(warning_headers, "Length of numeric recipient address is even, but still was terminated with 'F'.");
                  sendr[i] = 0;
                }
              }

              for (i = 0; sendr[i]; i++)
                if (!isdigit(sendr[i]))
                {
                  // Not a fatal error (?)
                  //pdu_error(err_str, Src_Pointer -full_pdu, Length +padding, "Invalid character(s) in recipient address: \"%s\"", sendr);
                  //*sendr = 0;
                  add_warning(warning_headers, "Invalid character(s) in recipient address.");
                  break;
                }
            }
          }

          if (!(*err_str))
          {
            Src_Pointer+=Length+padding;
            if (strlen(Src_Pointer) < 14)
              pdu_error(err_str, Src_Pointer -full_pdu, 14, "While trying to read SMSC Timestamp: %s", err_too_short);
            else
            {
              // get SMSC timestamp
              sprintf(date,"%c%c-%c%c-%c%c",Src_Pointer[1],Src_Pointer[0],Src_Pointer[3],Src_Pointer[2],Src_Pointer[5],Src_Pointer[4]);
              if (!isdigit(date[0]) || !isdigit(date[1]) || !isdigit(date[3]) || !isdigit(date[4]) || !isdigit(date[6]) || !isdigit(date[7]))
              {
                pdu_error(err_str, Src_Pointer -full_pdu, 6, "Invalid character(s) in date of SMSC Timestamp: \"%s\"", date);
                *date = 0;
              }
              else if (atoi(date +3) > 12 || atoi(date +6) > 31)
              {
                // Not a fatal error (?)
                //pdu_error(err_str, Src_Pointer -full_pdu, 6, "Invalid value(s) in date of SMSC Timestamp: \"%s\"", date);
                //*date = 0;
                add_warning(warning_headers, "Invalid value(s) in date of SMSC Timestamp.");
              }

              Src_Pointer += 6;
              sprintf(time,"%c%c:%c%c:%c%c",Src_Pointer[1],Src_Pointer[0],Src_Pointer[3],Src_Pointer[2],Src_Pointer[5],Src_Pointer[4]);
              if (!isdigit(time[0]) || !isdigit(time[1]) || !isdigit(time[3]) || !isdigit(time[4]) || !isdigit(time[6]) || !isdigit(time[7]))
              {
                pdu_error(err_str, Src_Pointer -full_pdu, 6, "Invalid character(s) in time of SMSC Timestamp: \"%s\"", time);
                *time = 0;
              }
              else if (atoi(time) > 23 || atoi(time +3) > 59 || atoi(time +6) > 59)
              {
                // Not a fatal error (?)
                //pdu_error(err_str, Src_Pointer -full_pdu, 6, "Invalid value(s) in time of SMSC Timestamp: \"%s\"", time);
                //*time = 0;
                add_warning(warning_headers, "Invalid value(s) in time of SMSC Timestamp.");
              }

              if (!(*err_str))
              {
                Src_Pointer += 6;
                // Time zone is not used but bytes are checked:
                if (octet2bin_check(Src_Pointer) < 0)
                  pdu_error(err_str, Src_Pointer -full_pdu, 2,
                            "Invalid character(s) in Time Zone of SMSC Time Stamp: \"%.2s\"", Src_Pointer);
                else
                  Src_Pointer += 2;
              }
            }
          }

          if (!(*err_str))
          {
            if (strlen(Src_Pointer) < 14)
              pdu_error(err_str, Src_Pointer -full_pdu, 14, "While trying to read Discharge Timestamp: %s", err_too_short);
            else
            {
              // get Discharge timestamp
              sprintf(temp,"%c%c-%c%c-%c%c %c%c:%c%c:%c%c",Src_Pointer[1],Src_Pointer[0],Src_Pointer[3],Src_Pointer[2],Src_Pointer[5],Src_Pointer[4],Src_Pointer[7],Src_Pointer[6],Src_Pointer[9],Src_Pointer[8],Src_Pointer[11],Src_Pointer[10]);
              if (!isdigit(temp[0]) || !isdigit(temp[1]) || !isdigit(temp[3]) || !isdigit(temp[4]) || !isdigit(temp[6]) || !isdigit(temp[7]) || 
                  !isdigit(temp[9]) || !isdigit(temp[10]) || !isdigit(temp[12]) || !isdigit(temp[13]) || !isdigit(temp[15]) || !isdigit(temp[16]))
                pdu_error(err_str, Src_Pointer -full_pdu, 12, "Invalid character(s) in Discharge Timestamp: \"%s\"", temp);
              else if (atoi(temp +3) > 12 || atoi(temp +6) > 31 || atoi(temp +9) > 24 || atoi(temp +12) > 59 || atoi(temp +16) > 59)
              {
                // Not a fatal error (?)
                //pdu_error(err_str, Src_Pointer -full_pdu, 12, "Invalid value(s) in Discharge Timestamp: \"%s\"", temp);
                add_warning(warning_headers, "Invalid values(s) in Discharge Timestamp.");
              }

              if (!(*err_str))
              {
                Src_Pointer += 12;
                // Time zone is not used but bytes are checked:
                if (octet2bin_check(Src_Pointer) < 0)
                  pdu_error(err_str, Src_Pointer -full_pdu, 2,
                            "Invalid character(s) in Time Zone of Discharge Time Stamp: \"%.2s\"", Src_Pointer);
                else
                  Src_Pointer += 2;
              }
            }

            if (!(*err_str))
            {
              sprintf(strchr(result, 0), "Discharge_timestamp: %s", temp);
              if (strlen(Src_Pointer) < 2)
                pdu_error(err_str, Src_Pointer -full_pdu, 2, "While trying to read Status: %s", err_too_short);
              else
              {
                // get Status
                if ((status = octet2bin_check(Src_Pointer)) < 0)
                  pdu_error(err_str, Src_Pointer -full_pdu, 2, "Invalid Status: \"%.2s\"", Src_Pointer);
                else
                {
                  switch (status)
                  {
                    case 0: p = "Ok,short message received by the SME"; break;
                    case 1: p = "Ok,short message forwarded by the SC to the SME but the SC is unable to confirm delivery"; break;
                    case 2: p = "Ok,short message replaced by the SC"; break;

                    case 32: p = "Still trying,congestion"; break;
                    case 33: p = "Still trying,SME busy"; break;
                    case 34: p = "Still trying,no response sendr SME"; break;
                    case 35: p = "Still trying,service rejected"; break;
                    case 36: p = "Still trying,quality of service not available"; break;
                    case 37: p = "Still trying,error in SME"; break;

                    case 64: p = "Error,remote procedure error"; break;
                    case 65: p = "Error,incompatible destination"; break;
                    case 66: p = "Error,connection rejected by SME"; break;
                    case 67: p = "Error,not obtainable"; break;
                    case 68: p = "Error,quality of service not available"; break;
                    case 69: p = "Error,no interworking available"; break;
                    case 70: p = "Error,SM validity period expired"; break;
                    case 71: p = "Error,SM deleted by originating SME"; break;
                    case 72: p = "Error,SM deleted by SC administration"; break;
                    case 73: p = "Error,SM does not exist"; break;

                    case 96: p = "Error,congestion"; break;
                    case 97: p = "Error,SME busy"; break;
                    case 98: p = "Error,no response sendr SME"; break;
                    case 99: p = "Error,service rejected"; break;
                    case 100: p = "Error,quality of service not available"; break;
                    case 101: p = "Error,error in SME"; break;

                    default: p = "unknown";
                  }
                  sprintf(strchr(result, 0), "\nStatus: %i,%s", status, p);
                }
              }
            }
          }
        }
      }
    }
  }

  return strlen(result);
}

// Splits a PDU string into the parts 
// Input: 
// pdu is the pdu string
// mode can be old or new and selects the pdu version
// Output:
// alphabet indicates the character set of the message.
// sendr Sender
// date and time Date/Time-stamp
// message is the message text or binary message
// smsc that sent this message
// with_udh return the udh flag of the message
// is_statusreport is 1 if this was a status report
// is_unsupported_pdu is 1 if this pdu was not supported
// Returns the length of the message 
int splitpdu(char* pdu, char* mode, int* alphabet, char* sendr, char* date, char* time, char* message,
             char* smsc, int* with_udh, char* udh_data, int* is_statusreport, int* is_unsupported_pdu,
             char *from_toa, int *report, int *replace, char *warning_headers)
{
  int Length;
  int Type;
  char* Pointer;
  int result = 0;
  char *err_str = NULL;
  char *save_err_str = NULL;
  char *try_mode = mode;
  int try_count;
  int i;

  for (try_count = 0; try_count < 2; try_count++)
  {
    if (try_count)
    {
      if (strcmp(mode, "new") == 0)
        try_mode = "old";
      else
        try_mode = "new";
    }

    sendr[0]=0;
    date[0]=0;
    time[0]=0;
    message[0]=0;
    smsc[0]=0; 
    *alphabet=0;
    *with_udh=0;

    // TODO: possible udh_data could be always initialized here?

    *is_statusreport = 0;
    *is_unsupported_pdu = 0;
    from_toa[0] = 0;
    *report = 0;
    *replace = -1;

    if (warning_headers)
      *warning_headers = 0;
#ifdef DEBUGMSG
  printf("!! splitpdu(pdu=%s, mode=%s, ...)\n",pdu,mode);
#endif

    Pointer=pdu;

    if (strlen(Pointer) < 2)
      pdu_error(&err_str, Pointer -pdu, 2, "While trying to read first octet: %s", err_too_short);
    else
    {
      if (strcmp(try_mode, "new") == 0)
      {
        if ((Length = octet2bin_check(Pointer)) < 0)
          pdu_error(&err_str, Pointer -pdu, 2, "While reading first octet: %s", err_pdu_content);
        else
        {
          // smsc number is not mandatory
          if (Length == 0)
            Pointer += 2;
          else
          {
            // Address type and at least one octet is expected:
            if (Length < 2 || Length > MAX_SMSC_ADDRESS_LENGTH)
              pdu_error(&err_str, Pointer -pdu, 2, "Invalid sender SMSC address length: \"%.2s\"", Pointer);
            else
            {
              Length = Length *2 -2;
              // No padding because the given value is number of octets.
              if (strlen(Pointer) < Length +4)
                pdu_error(&err_str, Pointer -pdu, Length +4, "While trying to read sender SMSC address (length %i): %s",
                          Length, err_too_short);
              else
              {
                Pointer += 2;
                i = octet2bin_check(Pointer);
                if (i < 0)
                  pdu_error(&err_str, Pointer -pdu, 2, "Invalid sender SMSC address type: \"%.2s\"", Pointer);
                else if (i < 0x80)
                  pdu_error(&err_str, Pointer -pdu, 2, "Missing bit 7 in sender SMSC address type: \"%.2s\"", Pointer);
                else
                {
                  Pointer += 2;
                  strncpy(smsc, Pointer, Length);
                  smsc[Length] = 0;
                  swapchars(smsc);
                  // Does any SMSC use alphanumeric number?
                  if ((i & 112) == 80)
                  {
                    // There can be only hex digits from the original PDU.
                    // The number itself is wrong in this case.
                    for (i = 0; smsc[i]; i++)
                      if (!isXdigit(smsc[i]))
                      {
                        pdu_error(&err_str, Pointer -pdu, Length, "Invalid character(s) in alphanumeric SMSC address: \"%s\"", smsc);
                        *smsc = 0;
                        break;
                      }
                  }
                  else
                  {
                    // Last character is allowed as F (and dropped) but all other non-numeric will produce an error:
                    if (smsc[Length -1] == 'F')
                      smsc[Length -1] = 0;
                    for (i = 0; smsc[i]; i++)
                      if (!isdigit(smsc[i]))
                      {
                        // Not a fatal error (?)
                        //pdu_error(&err_str, Pointer -pdu, Length, "Invalid character(s) in numeric SMSC address: \"%s\"", smsc);
                        //*smsc = 0;
                        add_warning(warning_headers, "Invalid character(s) in numeric SMSC address");
                        break;
                      }
                  }

                  if (!err_str)
                    Pointer += Length;
                }
              }
            }
          }
        }
      }

      if (!err_str)
      {
        if (strlen(Pointer) < 2)
          pdu_error(&err_str, Pointer -pdu, 2, "While trying to read First octet of the SMS-DELIVER PDU: %s", err_too_short);
        else
        {
          if ((i = octet2bin_check(Pointer)) < 0)
            pdu_error(&err_str, Pointer -pdu, 2, "While reading First octet of the SMS-DELIVER PDU: %s", err_pdu_content);
          else
          {
            // Unused bits 3 and 4 should be zero, failure with this produces a warning:
            if (i & 0x18)
              add_warning(warning_headers, "Unused bits 3 and 4 are used in the first octet of the SMS-DELIVER PDU.");

            if (i & 0x40) // Is UDH bit set?
              *with_udh = 1;
            if (i & 0x20) // Is status report going to be returned to the SME?
              *report = 1;

            Type = i & 3;
            if (Type==0) // SMS Deliver
            {
              Pointer += 2;
              result = split_type_0(pdu, Pointer, alphabet, sendr, date, time, message, *with_udh, udh_data,
                                    from_toa, replace, &err_str, warning_headers);
              // TODO: If a decoding fails, the reason is invalid or missing characters in the PDU.
              // Can also be too high TP-UDL value. When a text is partially decoded, this could be shown
              // in the error report. This might help a troubleshooting. Cannot be done for binary message
              // and Unicode text needs decoding before written.
            }
            else if (Type==2)  // Status Report
            {
              Pointer += 2;
              result = split_type_2(pdu, Pointer, sendr, date, time, message, from_toa, &err_str, warning_headers);
              *is_statusreport=1;
            }
            else if (Type==1) // Sent message
            {
              pdu_error(&err_str, -1, 0, "%s%.2s%s", "The PDU data (", Pointer,
                        ") says that this is a sent message. Can only decode received messages.");
              *is_unsupported_pdu = 1;
            }
            else
            {
              pdu_error(&err_str, -1, 0, "%s%.2s%s%i%s", "The PDU data (", Pointer, ") says that the message format is ",
                        Type, " which is not supported. Cannot decode.");
              *is_unsupported_pdu = 1;
            }
          }
        }
      }
    }

    if (!err_str)
      try_count++; // All ok, no more tries required.
    else
    {
      *alphabet = 0;
      *with_udh = 0;
      // Possible udh_data is now incorrect:
      if (udh_data)
        *udh_data = 0;
      *is_statusreport = 0;

      if (try_count == 0)
      {
        // First try. Save the result and try again with another PDU mode.
        if ((save_err_str = (char *)malloc(strlen(err_str) +1)))
          strcpy(save_err_str, err_str);
      }
      else
      {
        // Second try. Nothing more to do. Return report and some information.
        char *n_mode = "new (with CSA)";
        char *o_mode = "old (without CSA)";

        *message = 0;
        if (save_err_str)
          sprintf(message, "First tried with PDU mode %s:\n%s\nNext ", (*mode == 'n')? n_mode : o_mode, save_err_str);

        sprintf(strchr(message, 0), "tried with PDU mode %s:\n%s\n", (*mode == 'n')? o_mode : n_mode, err_str);
        sprintf(strchr(message, 0), "No success. This PDU cannot be decoded. There is something wrong.\n");

        if (!(*is_unsupported_pdu))
          strcat(message, "\nIf you are unsure, confused or angry, please view the GSM 03.40\n"
                          "(ETSI TS 100 901) and related documents for details of correct\n"
                          "PDU format. You can also get some help via the Support Website.\n");
        *is_unsupported_pdu = 1;
      }

      result = strlen(message);
      free(err_str);
      err_str = NULL;
    }
  }

  if (save_err_str)
    free(save_err_str);

  return result;
}

Generated by  Doxygen 1.6.0   Back to index