/***************************************************************************
 *   copyright           : (C) 2002 by Hendrik Sattler                     *
 *   mail                : post@hendrik-sattler.de                         *
 *                                                                         *
 *   This program is free software; 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.                                   *
 *                                                                         *
 ***************************************************************************/

#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#ifndef _REENTRANT
# define _REENTRANT
#endif

#include <smspdu.h>
#include "smscoding.h"
#include "smsudh.h"

#include <charsets.h>
#include <helper.h>
#include <timeincl.h>
#include <gtincl.h>

#include <stdlib.h>
#include <string.h>
#include <ctype.h>

static uint8_t sms_octet (uint8_t in, uint8_t base) {
  return ((in>>4)&0xF)%base + (in&0xF)*base;
}

void sms_statusreport_match (struct sms** list) {
  struct sms** outgoing;
  struct sms** reports;
  unsigned int l = 0;
  unsigned int o = 0;
  unsigned int r = 0;
  unsigned int rmax = 0;
  struct sms_pdu* oc;
  struct sms_pdu* rc;

  //sort from list into outgoing and reports
  while (list[l] != NULL) ++l;
  outgoing = mem_alloc((l+1)*sizeof(*outgoing),0);
  reports = mem_alloc((l+1)*sizeof(*reports),0);
  l = 0;
  while (list[l] != NULL) {
    switch (list[l]->decoded->pdu->options.type) {
    case SMS_TYPE_SUBMIT:
      outgoing[o++] = list[l++];
      break;
    case SMS_TYPE_STATUS_REPORT:
      if (list[l]->decoded->pdu->options.u.status.sr == 0) {
	//SMS-STATUS-REPORT is answer to a SMS-SUBMIT
	// == 1 would be answer to a SMS-COMMAND
	reports[rmax++] = list[l++];
      }
      break;

    default: ++l; break;
    }
  }
  outgoing[o] = NULL;
  reports[rmax] = NULL;

  //now trying to match all reports to an outgoing message
  for (o = 0; outgoing[o] != NULL; ++o) {
    oc = outgoing[o]->decoded->pdu;
    for (r = 0; r < rmax; ++r) {
      if (reports[r] != NULL) {
	rc = reports[r]->decoded->pdu;
	if (oc->extra.statusreport.mref == rc->extra.statusreport.mref &&
	    gsm_number_compare(&oc->address,&rc->address)) {
	  //there may be more than one status report per message...
	  if (oc->ud[oc->partnum].ack != NULL) {
	    //...so match the latest one to link with the final status
	    if (oc->ud[oc->partnum].ack->time.value < rc->extra.statusreport.status->time.value) {
	      oc->ud[oc->partnum].ack->message_parent = NULL;
	      oc->ud[oc->partnum].ack = rc->extra.statusreport.status;
	      oc->ud[oc->partnum].ack->message_parent = &(oc->ud[oc->partnum]);
	    }
	  } else {
	    oc->ud[oc->partnum].ack = rc->extra.statusreport.status;
	    oc->ud[oc->partnum].ack->message_parent = &(oc->ud[oc->partnum]);
	  }
	  //disable all previously matched reports in the list
	  reports[r] = NULL;
	}
      }
    }
  }

  mem_realloc(outgoing,0);
  mem_realloc(reports,0);
}

int sms_multipart_merge (struct sms** list) {
  struct sms** multipart; //for multipart matches
  struct sms** results; //for the final list, copied back to list
  struct sms** nondel; //matched messages that need special deletion
  unsigned int l = 0;
  unsigned int m = 0;
  unsigned int r = 0;
  unsigned int n = 0;
  struct sms_pdu* c1;
  struct sms_pdu* c2;

  if (list == NULL || list[0] == NULL) {
    return 0;
  }

  while (list[l] != NULL) ++l;
  multipart = mem_alloc((l+1)*sizeof(*multipart),0);
  multipart[0] = NULL;
  results = mem_alloc((l+1)*sizeof(*results),0); 
  results[0] = NULL;
  nondel = mem_alloc((l+1)*sizeof(*nondel),0);  
  nondel[0] = NULL;

  for (l = 0; list[l] != NULL; ++l) {
    if (list[l]->decoded != NULL &&
	list[l]->decoded->pdu->parts > 1) {
      c1 = list[l]->decoded->pdu;
      for (m = 0; multipart[m] != NULL; ++m) {
	c2 = multipart[m]->decoded->pdu;
	//criteria for matching the parts
	if (c1->options.type == c2->options.type &&
	    c1->multipart_id == c2->multipart_id &&
	    c1->parts == c2->parts &&
	    gsm_number_compare(&c1->address,&c2->address)) {
	  if (c1->partnum == c2->partnum ||
	      (multipart[m]->encoded[c1->partnum] != NULL)) {
	    /* We now have two concatenated short messages
	     * from the same sender with the same id.
	     * In this case, a clean match is not possible,
	     * thus abort the whole process.
	     */
	    //reverting all previous matches
	    for (m = 0; multipart[m] != NULL; ++m) {
	      c2 = multipart[m]->decoded->pdu;
	      for (n = 0; n < c2->parts; ++n) {
		if (c2->partnum != n) {
		  sms_pdu_ud_delete(&c2->ud[n]);
		  sms_pdu_ud_init(&c2->ud[n]);
		  multipart[m]->encoded[n] = NULL;		  
		}
	      }
	    }
	    mem_realloc(multipart,0);
	    mem_realloc(results,0);
	    mem_realloc(nondel,0);
	    fprintf(stderr,"Warning: Duplicated multipart messages id from the same sender detected.\n");
	    return -1;
	  } else {
	    c2->ud[c1->partnum] = c1->ud[c1->partnum];
	    multipart[m]->encoded[c1->partnum] = list[l]->encoded[c1->partnum];
	    nondel[n++] = list[l];
	    nondel[n] = NULL;
	    break;
	  }
	}
      }
      if (multipart[m] == NULL) {
	multipart[m++] = list[l];
	multipart[m] = NULL;
	results[r++] = list[l];
	results[r] = NULL;
      }
    } else {
      results[r++] = list[l];
      results[r] = NULL;
    }
  }

  mem_realloc(multipart,0);
  /* Copy back the results
   */
  for (l = 0; list[l] != NULL; ++l) {
    list[l] = NULL;
  }
  for (r = 0; results[r] != NULL; ++r) {
    list[r] = results[r];
  }
  mem_realloc(results,0);
  /* for nondel list, make sure that encoded messages,
   * message header and text do not get deleted
   */
  for (n = 0; nondel[n] != NULL; ++n) {
    nondel[n]->encoded = NULL;
    nondel[n]->decoded->pdu->ud = NULL;
    sms_delete(nondel[n]);
    mem_realloc(nondel[n],0);
  }
  mem_realloc(nondel,0);
  return 1;
}


enum sms_pdu_type sms_pdu_type_get (enum sms_direction d, uint8_t type) {
  switch (d) {
  case SMS_INCOMING:
    switch (type&3) {
    case 0: return SMS_TYPE_DELIVER;
    case 1: return SMS_TYPE_SUBMIT_REPORT;
    case 2: return SMS_TYPE_STATUS_REPORT;
    default: return SMS_TYPE_DELIVER;
    }
  case SMS_OUTGOING:
    switch (type&3) {
    case 0: return SMS_TYPE_DELIVER_REPORT;
    case 1: return SMS_TYPE_SUBMIT;
    case 2: return SMS_TYPE_COMMAND;
    default: return SMS_TYPE_SUBMIT;
    }
  }
  //never reaches this point
  return SMS_TYPE_DELIVER;
}

static
void sms_pdu_decode_options (enum sms_direction d, uint8_t type,
			     struct sms_pdu_options* p)
{
  if (p == NULL) return;

  p->type = sms_pdu_type_get(d,type);
  if (type&0x04) {
    if (p->type == SMS_TYPE_DELIVER) {
      p->u.deliver.mms = 1;
    } else if (p->type == SMS_TYPE_STATUS_REPORT) {
      p->u.status.mms = 1;
    } else if (p->type == SMS_TYPE_SUBMIT) {
      p->u.submit.rd = 1;
    }
  }
  if (type&0x20) {
    switch (p->type) {
    case SMS_TYPE_DELIVER: p->u.deliver.sr = 1; break;
    case SMS_TYPE_SUBMIT: p->u.submit.sr = 1; break;
    case SMS_TYPE_COMMAND: p->u.command.sr = 1; break;
    case SMS_TYPE_STATUS_REPORT: p->u.status.sr = 1; break;
    default: break;
    }
  }
  if (type&0x40) {
    p->udh_present = 1;
  }
  if (type&0x80) {
    if (p->type == SMS_TYPE_DELIVER) p->u.deliver.rp = 1;
    else if (p->type == SMS_TYPE_SUBMIT) p->u.submit.rp = 1;
  }
}

static
void sms_pdu_dcs_decode (uint8_t dcs, struct sms_pdu_dcs* p) {
  if (p == NULL) return;
  if ((dcs&0x8c) == 0x00 || //0xxx 00xx
      (dcs&0xe0) == 0xb0 || //110x xxxx
      (dcs&0xf4) == 0xf0  //1111 x0xx
      /*((dcs&0xf0) >= 0x80 && (dcs&0xf0) <= 0xb0)*/ //reserved
      ) {
    p->encoding = SMS_CHARSET_GSM;
  } else if ((dcs&0x8c) == 0x08 || //0xxx 10xx
	     (dcs&0xf0) == 0xe0    //1110 xxxx
	     ) {
    p->encoding = SMS_CHARSET_UCS2;
  } else {
    p->encoding = SMS_CHARSET_8BIT;
  }

  if ((dcs&0x80) == 0) {
    p->type = SMS_DCS_TYPE_NORMAL;
    p->value.normal.autodel = (dcs>>6)&1; //0x40;
    p->value.normal.compressed = (dcs>>5)&1; //0x20
    if (dcs&0x10)
      switch (dcs&3) {
      case 0: p->value.normal.class = SMS_CLASS_FLASH; break;
      case 1: p->value.normal.class = SMS_CLASS_ME; break;
      case 2: p->value.normal.class = SMS_CLASS_SIM; break;
      case 3: p->value.normal.class = SMS_CLASS_TE; break;
      }
    else
      p->value.normal.class = SMS_CLASS_NONE;
  } else {
    if (dcs >= 0xF0) {
      p->type = SMS_DCS_TYPE_NORMAL;
      p->value.normal.autodel = 0;
      p->value.normal.compressed = 0;
      if (dcs&0x10)
	switch (dcs&3) {
	case 0: p->value.normal.class = SMS_CLASS_FLASH; break;
	case 1: p->value.normal.class = SMS_CLASS_ME; break;
	case 2: p->value.normal.class = SMS_CLASS_SIM; break;
	case 3: p->value.normal.class = SMS_CLASS_TE; break;
	}
      else
	p->value.normal.class = SMS_CLASS_NONE;
    } else if (dcs >= 0xC0) {
      p->type = SMS_DCS_TYPE_IND;
      if ((dcs&0x30) == 0) p->value.ind.group = SMS_DCS_IND_DISCARD;
      else p->value.ind.group = SMS_DCS_IND_STORE;
      p->value.ind.sense = (dcs>>3)&1;
      switch(dcs&0x03) {
      case 0: p->value.ind.type = SMS_DCS_IND_VOICE; break;
      case 1: p->value.ind.type = SMS_DCS_IND_FAX; break;
      case 2: p->value.ind.type = SMS_DCS_IND_EMAIL; break;
      case 3: p->value.ind.type = SMS_DCS_IND_OTHER; break;
      }
    }
  }
}

/* returns the number of character to advance in pdu
 */
static
size_t sms_pdu_address_decode (uint8_t* pdu,
			       struct gsm_number* n)
{
  size_t len;
  
  len = pdu[0]; //number of semioctets
  len = (len+(len%2))/2;
  gsm_number_init(n);
  gsm_number_set_toa(n,pdu[1]);
  if (len) {
    switch (n->toa.type) {
    case GSM_NUMBER_TYPE_TEXT:
      //those are octets, not semi-octets
      n->text = sms_data_decode(SMS_CHARSET_GSM,
				pdu+2,(len*8)/7,0);
      break;
    default:
      gsm_number_set_semioctets(n,pdu[1],pdu+2,len);
    }
  }
  return len+2;
}

static
unsigned int sms_pdu_scts_decode (uint8_t* pdu, struct sms_pdu_time* t)
{
  char* temp;
  int tz;

  //read real timezone offset
  tz = sms_octet(pdu[6],10);
  /* parse negation bit */
  if (tz >= 80) tz = (-1)*(tz-80);

  temp = mem_alloc(20,1);
  sprintf(temp,"\"%02x/%02x/%02x,%02x:%02x:%02x\"",
	  sms_octet(pdu[0],16),sms_octet(pdu[1],16),sms_octet(pdu[2],16),
	  sms_octet(pdu[3],16),sms_octet(pdu[4],16),sms_octet(pdu[5],16));
  sms_pdu_time_fill(t,temp,tz);
  mem_realloc(temp,0);
  return 7;
}

static
void sms_pdu_userdata_decode (uint8_t* pdu, size_t len,
			      struct sms_pdu* sms)
{
  uint8_t udlen = pdu[0];
  uint8_t udhsize = 0;
  
  ++pdu;
  if ((len-1)*8/7 < udlen) udlen = (len-1)*8/7;
  if (udlen) {
    //assume that only one part is present until we can look at the header
    sms->parts = 1;
    sms->partnum = 0;
    //fill all headers (also calls sms_udh_multipart_mark())
    if (sms->options.udh_present) {
      udhsize = pdu[0];
    } else {
      udhsize = 0;
    }
    sms_udh_fill(sms,pdu);
    //decode text
    sms->ud[sms->partnum].text = sms_data_decode(sms->scheme.encoding,
						 pdu,udlen,
						 sms->options.udh_present);
  }
}

static
struct sms_pdu* sms_pdu_decode_statusreport (struct sms_pdu_raw* tpdu,
					     size_t off)
{
  struct sms_pdu* sms;
  uint8_t* pdu = tpdu->data+off;
  uint8_t temp = 0;

  sms = mem_alloc(sizeof(*sms),0);
  sms_pdu_init(sms);

  sms_pdu_decode_options(SMS_INCOMING,pdu[0],&sms->options);

  /* From ETSI TS 123.040 v5.3.0 R5 9.2.2.3:
   * Where the SMS-STATUS-REPORT is the result of an
   * SMS-COMMAND and the TP-Command-Type was an Enquiry,
   * the TP-MR returned in the SMS-STATUS-REPORT shall be the
   * TP-MN which was sent in the SMS-COMMAND (i.e. the TP-MR
   * of the previously submitted SM to which the Enquiry refers).
   */
  sms->extra.statusreport.mref = pdu[1];
  pdu += 2;

  pdu += sms_pdu_address_decode(pdu,&sms->address);  

  pdu += sms_pdu_scts_decode(pdu,&sms->timedata);

  sms->extra.statusreport.status = mem_alloc(sizeof(*sms->extra.statusreport.status),0);
  sms_pdu_message_status_init(sms->extra.statusreport.status);
  sms->extra.statusreport.status->status_parent = sms;
  pdu += sms_pdu_scts_decode(pdu,&sms->extra.statusreport.status->time);
  //fixme: decode status->s with more detail
  sms->extra.statusreport.status->status = pdu[0];
  pdu += 1;

  /* From ETSI TS 123.040 v5.3.0 R5 9.2.2.3:
   * parameter indicator:
   * Mandatory if any of the optional parameters following
   * TP-PI is present, otherwise optional.
   */
  if (tpdu->size-off > (size_t)(pdu-tpdu->data)) {
    temp = pdu[0];
    if (temp != 0xff) { //Siemens S55 now fills the rest with 0xff
      pdu += 1;
      if (temp&0x01) { //PID present
	sms->pid = pdu[0];
	pdu += 1;
      }
      if (temp&0x02) { //DCS present
	sms_pdu_dcs_decode(pdu[0],&sms->scheme);
	pdu += 1;
      }
      if (temp&0x04) { //user data present
	sms_pdu_userdata_decode(pdu,tpdu->size-(size_t)(pdu-tpdu->data),sms);
      }
    }
  }
  return sms;
}

static
struct sms_pdu* sms_pdu_decode_deliver (struct sms_pdu_raw* tpdu,
					size_t off)
{
  struct sms_pdu* sms;
  uint8_t* pdu = tpdu->data+off;

  sms = mem_alloc(sizeof(*sms),0);
  sms_pdu_init(sms);

  sms_pdu_decode_options(SMS_INCOMING,pdu[0],&sms->options);
  pdu += 1;
  pdu += sms_pdu_address_decode(pdu,&sms->address);
  sms->pid = pdu[0];
  pdu += 1;

  sms_pdu_dcs_decode(pdu[0],&sms->scheme);
  pdu += 1;

  pdu += sms_pdu_scts_decode(pdu,&sms->timedata);

  sms_pdu_userdata_decode(pdu,tpdu->size-(size_t)(pdu-tpdu->data),sms);

  return sms;
}

static
struct sms_pdu* sms_pdu_decode_submit (struct sms_pdu_raw* tpdu,
				       size_t off)
{
  struct sms_pdu* sms;
  uint8_t* pdu = tpdu->data+off;
  uint8_t type = pdu[0];
  uint8_t i;
  char* temp;

  temp = NULL;

  sms = mem_alloc(sizeof(*sms),0);
  sms_pdu_init(sms);
  sms_pdu_decode_options(SMS_OUTGOING,type,&sms->options);
  sms->extra.submit.mref = pdu[1];
  pdu += 2;

  pdu += sms_pdu_address_decode(pdu,&sms->address);

  sms->pid = pdu[0];
  sms_pdu_dcs_decode(pdu[1],&sms->scheme);
  pdu += 2;

  switch (type&0x18) {
  case 0x00: //VP not present
    sms->timedata.format = SMS_PDU_TIME_NONE;
    break;
  case 0x08: //VP enhanced
    i = pdu[0]; //reading octet 1
    if (i&0x80) { //extension bit
      //not supported
      sms->timedata.format = SMS_PDU_TIME_NONE;
    }
    //ignore bit 6 (single shot SM)
    //bits 5,4 and 3 are reserved
    switch (i&0x07) { //bits 2, 1 and 0
    default: //reserved values
    case 0x00:
      sms->timedata.format = SMS_PDU_TIME_NONE;
      break;
    case 0x01: //value in seconds (1 octet)
      sms->timedata.format = SMS_PDU_TIME_RELATIVE;
      sms->timedata.value = pdu[1];
      if (sms->timedata.value == 0) {
	sms->timedata.format = SMS_PDU_TIME_NONE;
      }
      break;
    case 0x02: //real relative value
      sms->timedata.format = SMS_PDU_TIME_RELATIVE;
      i = pdu[1];
      if ( i <= 143) { //5 minutes
	sms->timedata.value = (i+1)*5*60;
      } else if (144 <= i && i <= 167) { //30 minutes
	sms->timedata.value = (i-143+24)*30*60;
      } else if (168 <= i && i <= 196) {//1 day
	sms->timedata.value = (i-166)*(3600*24);
      } else if (197 <= i) {//1 week
	sms->timedata.value = (i-192)*(3600*24*7);
      }
      break;
    case 0x03: // value as HH:MM:SS
      sms->timedata.format = SMS_PDU_TIME_RELATIVE;
      temp = mem_alloc(9,1);
      sms->timedata.value = ((pdu[1]&0xF)*10 + ((pdu[1]>>4)&0xF))*3600 +
	                    ((pdu[2]&0xF)*10 + ((pdu[2]>>4)&0xF))*60 +
	                    (pdu[3]&0xF) + ((pdu[3]>>4)&0xF);
      break;
    }
    pdu += 7;
    break;
  case 0x10: //VP relative
    sms->timedata.format = SMS_PDU_TIME_RELATIVE;
    i = pdu[0];
    if (i <= 143) { //5 minutes
      sms->timedata.value = (i+1)*5*60;
    } else if (144 <= i && i <= 167) { //30 minutes
      sms->timedata.value = (i-143+24)*30*60;
    } else if (168 <= i && i <= 196) {//1 day
      sms->timedata.value = (i-166)*(3600*24);
    } else if (197 <= i) {//1 week
      sms->timedata.value = (i-192)*(3600*24*7);
    }
    pdu += 1;
    break;
  case 0x18: //VP absolute
    pdu += sms_pdu_scts_decode(pdu,&sms->timedata);
    break;
  }

  sms_pdu_userdata_decode(pdu,tpdu->size-(size_t)(pdu-tpdu->data),sms);

  return sms;
}

struct sms_tpdu* sms_pdu_decode (struct sms_slot_data* sms)
{
  struct sms_tpdu* decoded;
  uint8_t* pdu = NULL;
  uint8_t sca_len = 0;

  decoded = mem_alloc(sizeof(*decoded),0);
  sms_tpdu_init(decoded);

  //extracting SMSC
  sca_len = sms->tpdu.data[0];
  if (sca_len > 0) {
    gsm_number_set_semioctets(&(decoded->sca),sms->tpdu.data[1],
			      sms->tpdu.data+2,sca_len-1);
  }
  pdu = sms->tpdu.data+sca_len+1;

  //checking if PDUtype is supported and can thus be decoded
  switch (sms_pdu_type_get(sms->dir,pdu[0])) {
  case SMS_TYPE_DELIVER:
    decoded->pdu = sms_pdu_decode_deliver(&sms->tpdu,sca_len+1);
    break;
  case SMS_TYPE_SUBMIT:
    decoded->pdu = sms_pdu_decode_submit(&sms->tpdu,sca_len+1);;
    break;
  case SMS_TYPE_STATUS_REPORT:
    decoded->pdu = sms_pdu_decode_statusreport(&sms->tpdu,sca_len+1);;
    break;
  case SMS_TYPE_SUBMIT_REPORT:
  case SMS_TYPE_DELIVER_REPORT:
  case SMS_TYPE_COMMAND:
    fprintf(stderr,"%s: %s\n",_("Unsupported pdu type"),
            sms_pdu_type_string(sms_pdu_type_get(sms->dir,pdu[0])));
    sms_tpdu_delete(decoded);
    decoded = mem_realloc(decoded,0);
    break;
  }  

  return decoded;
}

static
unsigned int sms_pdu_submitdeliver_len (enum sms_pdu_type type,
					uint8_t vpsize,
					struct sms_pdu_raw* raw,
					size_t offset)
{
  struct sms_pdu_dcs d;
  size_t retval = offset+1;
  uint8_t temp;

  if (type == SMS_TYPE_SUBMIT) {
    /* minimal PDU length 7 octets */
    if (raw->size < 7) return 0; //invalid
    retval += 1; //MR
  } else {
    /* minimal PDU length 13 octets */
    if (raw->size < 13) return 0; //invalid
  }

  retval += 2 + (raw->data[retval] + raw->data[retval]%2)/2; //DA
  retval += 1; //PID

  if (raw->size < retval+1) return 0;
  sms_pdu_dcs_decode(raw->data[retval],&d);
  retval += 1+vpsize; //DCS,VP

  if (raw->size < retval+1) return 0;
  temp = raw->data[retval];
  if (d.encoding == SMS_CHARSET_GSM) {
    if (temp > 160) return 0;
    retval += (temp*7)/8;
    /* If the length is no multiple of 8,
     * an extra octet is needed
     */
    if (temp%8) ++retval;
  } else {
    if (temp > 140) return 0;
    retval += temp;
  }
  ++retval; //UDL

  return retval-offset;
}

static
unsigned int sms_pdu_statusreport_len (struct sms_pdu_raw* raw,
				       size_t offset)
{
  struct sms_pdu_dcs d;
  size_t retval = offset+1; //MR
  uint8_t temp;

  /* minimal PDU length 20 octets */
  if (raw->size < 20) return 0; //invalid

  retval += 2 + (raw->data[retval] + raw->data[retval]%2)/2; //RA

  retval += 7 + 7 + 1; //SCTS,DT,ST

  if (raw->size < retval+1) return 0;
  temp = raw->data[retval];
  if (temp&0x01) ++retval; //PID
  if (temp&0x02) { //DCS
    sms_pdu_dcs_decode(raw->data[++retval],&d);
  }
  if (temp&0x04) { //user data
    if (raw->size < retval+1) return 0;
    temp = raw->data[retval];
    if (d.encoding == SMS_CHARSET_GSM) {
      retval += (temp*7)/8;
      /* If the length is no multiple of 8,
       * an extra octet is needed
       */
      if (temp%8) ++retval;
    } else {
      retval += temp;
    }
  }
  ++retval; //UDL
  if (retval > 164) return 0;

  return retval-offset;
}

static
unsigned int sms_pdu_len_off (enum sms_direction d,
			      struct sms_pdu_raw* raw,
			      size_t offset)
{
  uint8_t type;
  struct sms_pdu_options p;
  unsigned int temp = 0;

  if (raw->size < 1) return 0;
  type = raw->data[offset];
  sms_pdu_decode_options(d,type,&p);
 
  switch (p.type) {
  case SMS_TYPE_SUBMIT:
    switch (type&0x18) { //VP
    default: temp += 6;
    case 0x10: temp += 1;
    case 0x00: break;
    }
    return sms_pdu_submitdeliver_len(p.type,temp,raw,offset);
  case SMS_TYPE_DELIVER:
    return sms_pdu_submitdeliver_len(p.type,7,raw,offset);
  case SMS_TYPE_STATUS_REPORT:
    return sms_pdu_statusreport_len(raw,offset);
  default:
    return 0;
  }
}

unsigned int sms_pdu_len (enum sms_direction d,
			  struct sms_pdu_raw* raw)
{
  return sms_pdu_len_off(d,raw,0);
}

unsigned int sms_tpdu_len (enum sms_direction d,
			   struct sms_pdu_raw* raw)
{
  if (raw->size < 1 || raw->data[0] > 11) return 0;
  return sms_pdu_len_off(d,raw,raw->data[0]+1);
}
