asterisk-chan-dongle/pdu.c

946 lines
27 KiB
C

/*
Copyright (C) 2010 bg <bg_one@mail.ru>
Copyright (C) 2020 Max von Buelow <max@m9x.de>
*/
#include "ast_config.h"
#include <errno.h> /* EINVAL ENOMEM E2BIG */
#include "pdu.h"
#include "helpers.h" /* dial_digit_code() */
#include "char_conv.h" /* utf8_to_hexstr_ucs2() */
#include "gsm7_luts.h"
#include "error.h"
/* SMS-SUBMIT format
SCA 1..12 octet(s) Service Center Address information element
octets
1 Length of Address (minimal 0)
2 Type of Address
3 12 Address
PDU-type 1 octet Protocol Data Unit Type
bits
1 0 MTI Message Type Indicator Parameter describing the message type 00 means SMS-DELIVER 01 means SMS-SUBMIT
0 0 SMS-DELIVER (SMSC ==> MS)
or
SMS-DELIVER REPORT (MS ==> SMSC, is generated automatically by the MOBILE, after receiving a SMS-DELIVER)
0 1 SMS-SUBMIT (MS ==> SMSC)
or
SMS-SUBMIT REPORT (SMSC ==> MS)
1 0 SMS-STATUS REPORT (SMSC ==> MS)
or
SMS-COMMAND (MS ==> SMSC)
1 1 Reserved
2 RD Reject Duplicate
0 Instruct the SMSC to accept an SMS-SUBMIT for an short message still
held in the SMSC which has the same MR and
1 Instruct the SMSC to reject an SMS-SUBMIT for an short message still
held in the SMSC which has the same MR and DA as a previosly
submitted short message from the same OA.
4 3 VPF Validity Period Format Parameter indicating whether or not the VP field is present
0 0 VP field is not present
0 1 Reserved
1 0 VP field present an integer represented (relative)
1 1 VP field present an semi-octet represented (absolute)
5 SRR Status Report Request Parameter indicating if the MS has requested a status report
0 A status report is not requested
1 A status report is requested
6 UDHI User Data Header Indicator Parameter indicating that the UD field contains a header
0 The UD field contains only the short message
1 The beginning of the UD field contains a header in addition of the short message
7 RP Reply Path Parameter indicating that Reply Path exists
0 Reply Path parameter is not set in this PDU
1 Reply Path parameter is set in this PDU
MR 1 octet Message Reference
The MR field gives an integer (0..255) representation of a reference number of the SMSSUBMIT submitted to the SMSC by the MS.
! notice: at the MOBILE the MR is generated automatically, -anyway you have to generate it a possible entry is for example ”00H” !
DA 2-12 octets Destination Address
octets
1 Length of Address (of BCD digits!)
2 Type of Address
3 12 Address
PID 1 octet Protocol Identifier
The PID is the information element by which the Transport Layer either refers to the higher
layer protocol being used, or indicates interworking with a certain type of telematic device.
here are some examples of PID codings:
00H: The PDU has to be treat as a short message
41H: Replace Short Message Type1
....
47H: Replace Short Message Type7
Another description:
Bit7 bit6 (bit 7 = 0, bit 6 = 0)
l 0 0 Assign bits 0..5, the values are defined as follows.
l 1 0 Assign bits 0..5, the values are defined as follows.
l 0 1 Retain
l 1 1 Assign bits 0..5 for special use of SC
Bit5 values:
l 0: No interworking, but SME-to-SME protocol
l 1: Telematic interworking (in this situation , value of bits4...0 is
valid)
Interface Description for HUAWEI EV-DO Data Card AT Commands
All rights reserved Page 73 , Total 140
Bit4...Bit0: telematic devices type identifier. If the value is 1 0 0 1 0, it
indicates email. Other values are not supported currently.
DCS 1 octet Data Coding Scheme
VP 0,1,7 octet(s) Validity Period
UDL 1 octet User Data Length
UD 0-140 octets User Data
*/
/* SMS-DELIVER format
SCA 1..12 octet(s) Service Center Address information element
octets
1 Length of Address (minimal 0)
2 Type of Address
3 12 Address
PDU-type 1 octet Protocol Data Unit Type
bits
1 0 MTI Message Type Indicator Parameter describing the message type 00 means SMS-DELIVER 01 means SMS-SUBMIT
0 0 SMS-DELIVER (SMSC ==> MS)
or
SMS-DELIVER REPORT (MS ==> SMSC, is generated automatically by the MOBILE, after receiving a SMS-DELIVER)
0 1 SMS-SUBMIT (MS ==> SMSC)
or
SMS-SUBMIT REPORT (SMSC ==> MS)
1 0 SMS-STATUS REPORT (SMSC ==> MS)
or
SMS-COMMAND (MS ==> SMSC)
1 1 Reserved
2 MMS More Messages to Send Parameter indicating whether or not there are more messages to send
0 More messages are waiting for the MS in the SMSC
1 No more messages are waiting for the MS in the SMSC
4 3 Reserved
5 SRI Status Report Indication Parameter indicating if the SME has requested a status report
0 A status report will not be returned to the SME
1 A status report will be returned to the SME
6 UDHI User Data Header Indicator Parameter indicating that the UD field contains a header
0 The UD field contains only the short message
1 The beginning of the UD field contains a header in addition of the short message
7 RP Reply Path Parameter indicating that Reply Path exists
0 Reply Path parameter is not set in this PDU
1 Reply Path parameter is set in this PDU
OA 2-12 octets Originator Address
octets
1 Length of Address (of BCD digits!)
2 Type of Address
3 12 Address
PID 1 octet Protocol Identifier
The PID is the information element by which the Transport Layer either refers to the higher
layer protocol being used, or indicates interworking with a certain type of telematic device.
here are some examples of PID codings:
00H: The PDU has to be treat as a short message
41H: Replace Short Message Type1
....
47H: Replace Short Message Type7
Another description:
Bit7 bit6 (bit 7 = 0, bit 6 = 0)
l 0 0 Assign bits 0..5, the values are defined as follows.
l 1 0 Assign bits 0..5, the values are defined as follows.
l 0 1 Retain
l 1 1 Assign bits 0..5 for special use of SC
Bit5 values:
l 0: No interworking, but SME-to-SME protocol
l 1: Telematic interworking (in this situation , value of bits4...0 is
valid)
Interface Description for HUAWEI EV-DO Data Card AT Commands
All rights reserved Page 73 , Total 140
Bit4...Bit0: telematic devices type identifier. If the value is 1 0 0 1 0, it
indicates email. Other values are not supported currently.
DCS 1 octet Data Coding Scheme
SCTS 7 octets Service Center Time Stamp
UDL 1 octet User Data Length
UD 0-140 octets User Data, may be prepended by User Data Header see UDHI flag
octets
1 opt UDHL Total number of Octets in UDH
? IEIa
? IEIDLa
? IEIDa
? IEIb
...
*/
/* Address octets: 0=length_in_nibbles, 1=EXT/TON/NPI, 2..11=address
* (destination address (TP-DA), originator address (TP-OA) and recipient address (TP-RA))
* EXT: bit7: 1 "no extension"
* TON: bit6..4: see below
* NPI: bit3..0: see below
* Source: https://en.wikipedia.org/wiki/GSM_03.40 */
#define TP_A_EXT (1 << 7)
#define TP_A_EXT_NOEXT (1 << 7)
#define TP_A_TON (7 << 4)
#define TP_A_TON_UNKNOWN (0 << 4)
#define TP_A_TON_INTERNATIONAL (1 << 4)
#define TP_A_TON_NATIONAL (2 << 4)
#define TP_A_TON_NETSPECIFIC (3 << 4)
#define TP_A_TON_SUBSCRIBERNUM (4 << 4)
#define TP_A_TON_ALPHANUMERIC (5 << 4)
#define TP_A_TON_ABBREVIATEDNUM (6 << 4)
#define TP_A_TON_RESERVED (7 << 4)
#define TP_A_NPI (15 << 0)
#define TP_A_NPI_UNKNOWN (0 << 0)
#define TP_A_NPI_TEL_E164_E163 (1 << 0)
#define TP_A_NPI_TELEX (3 << 0)
#define TP_A_NPI_SVCCENTR_SPEC1 (4 << 0)
#define TP_A_NPI_SVCCENTR_SPEC2 (5 << 0)
#define TP_A_NPI_NATIONALNUM (8 << 0)
#define TP_A_NPI_PRIVATENUM (9 << 0)
#define TP_A_NPI_ERMESNUM (10 << 0)
#define TP_A_NPI_RESERVED (15 << 0)
#define NUMBER_TYPE_INTERNATIONAL (TP_A_EXT_NOEXT | TP_A_TON_INTERNATIONAL | TP_A_NPI_TEL_E164_E163) /* 0x91 */
#define NUMBER_TYPE_NATIONAL (TP_A_EXT_NOEXT | TP_A_TON_SUBSCRIBERNUM | TP_A_NPI_NATIONALNUM) /* 0xC8 */
#define NUMBER_TYPE_ALPHANUMERIC (TP_A_EXT_NOEXT | TP_A_TON_ALPHANUMERIC | TP_A_NPI_UNKNOWN) /* 0xD0 */
/* maybe NUMBER_TYPE_NETWORKSHORT should be 0xB1 ??? */
#define NUMBER_TYPE_NETWORKSHORT (TP_A_EXT_NOEXT | TP_A_TON_NETSPECIFIC | TP_A_NPI_PRIVATENUM) /* 0xB9 */
#define NUMBER_TYPE_UNKNOWN (TP_A_EXT_NOEXT | TP_A_TON_UNKNOWN | TP_A_NPI_TEL_E164_E163) /* 0x81 */
/* Reject Duplicate */
#define PDUTYPE_RD_SHIFT 2
#define PDUTYPE_RD_ACCEPT (0x00 << PDUTYPE_RD_SHIFT)
#define PDUTYPE_RD_REJECT (0x01 << PDUTYPE_RD_SHIFT)
/* Validity Period Format */
#define PDUTYPE_VPF_SHIFT 3
#define PDUTYPE_VPF_NOT_PRESENT (0x00 << PDUTYPE_VPF_SHIFT)
#define PDUTYPE_VPF_RESERVED (0x01 << PDUTYPE_VPF_SHIFT)
#define PDUTYPE_VPF_RELATIVE (0x02 << PDUTYPE_VPF_SHIFT)
#define PDUTYPE_VPF_ABSOLUTE (0x03 << PDUTYPE_VPF_SHIFT)
/* Status Report Request */
#define PDUTYPE_SRR_SHIFT 5
#define PDUTYPE_SRR_NOT_REQUESTED (0x00 << PDUTYPE_SRR_SHIFT)
#define PDUTYPE_SRR_REQUESTED (0x01 << PDUTYPE_SRR_SHIFT)
/* User Data Header Indicator */
#define PDUTYPE_UDHI_SHIFT 6
#define PDUTYPE_UDHI_NO_HEADER (0x00 << PDUTYPE_UDHI_SHIFT)
#define PDUTYPE_UDHI_HAS_HEADER (0x01 << PDUTYPE_UDHI_SHIFT)
#define PDUTYPE_UDHI_MASK (0x01 << PDUTYPE_UDHI_SHIFT)
#define PDUTYPE_UDHI(pdutype) ((pdutype) & PDUTYPE_UDHI_MASK)
/* eply Path Parameter */
#define PDUTYPE_RP_SHIFT 7
#define PDUTYPE_RP_IS_NOT_SET (0x00 << PDUTYPE_RP_SHIFT)
#define PDUTYPE_RP_IS_SET (0x01 << PDUTYPE_RP_SHIFT)
#define PDU_MESSAGE_REFERENCE 0x00 /* assigned by MS */
#define PDU_PID_SMS 0x00 /* bit5 No interworking, but SME-to-SME protocol = SMS */
#define PDU_PID_EMAIL 0x32 /* bit5 Telematic interworking, bits 4..0 0x 12 = email */
#define PDU_PID_SMS_REPLACE_MASK 0x40 /* bit7 Replace Short Message function activated (TP-PID = 0x41 to 0x47) */
/* bits 3..2 */
#define PDU_DCS_ALPHABET_SHIFT 2
#define PDU_DCS_ALPHABET_7BIT (0x00 << PDU_DCS_ALPHABET_SHIFT)
#define PDU_DCS_ALPHABET_8BIT (0x01 << PDU_DCS_ALPHABET_SHIFT)
#define PDU_DCS_ALPHABET_UCS2 (0x02 << PDU_DCS_ALPHABET_SHIFT)
#define PDU_DCS_ALPHABET_MASK (0x03 << PDU_DCS_ALPHABET_SHIFT)
#define PDU_DCS_ALPHABET(dcs) ((dcs) & PDU_DCS_ALPHABET_MASK)
#define ROUND_UP2(x) (((x) + 1) & (0xFFFFFFFF << 1))
#define DIV2UP(x) (((x) + 1)/2)
#define CSMS_GSM7_MAX_LEN 153
#define SMS_GSM7_MAX_LEN 160
#define CSMS_UCS2_MAX_LEN 67
#define SMS_UCS2_MAX_LEN 70
EXPORT_DEF void pdu_udh_init(pdu_udh_t *udh)
{
udh->ref = 0;
udh->parts = 1;
udh->order = 0;
udh->ss = 0;
udh->ls = 0;
}
#/* get digit code, 0 if invalid */
static uint8_t pdu_digit2code(char digit)
{
switch (digit) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
return digit - '0';
case '*':
return 0xa;
case '#':
return 0xb;
case 'a':
case 'A':
return 0xc;
break;
case 'b':
case 'B':
return 0xd;
case 'c':
case 'C':
return 0xe;
default:
return 255;
}
}
#/* */
static char pdu_code2digit(uint8_t code)
{
switch (code) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
return code + '0';
case 0xa:
return '*';
case 0xb:
return '#';
case 0xc:
return 'A';
case 0xd:
return 'B';
case 0xe:
return 'C';
default:
return 0;
}
}
#/* convert minutes to relative VP value */
static int pdu_relative_validity(unsigned minutes)
{
#define DIV_UP(x,y) (((x)+(y)-1)/(y))
/*
0 ... 143 (vp + 1) * 5 minutes 5 ... 720 m = (vp + 1) * 5 m / 5 - 1 = vp
144...167 12 hours + (vp - 143) * 30 minutes 750 ... 1440 m = 720 + (vp - 143) * 30 (m - 720) / 30 + 143 = m / 30 + 119
168...196 (vp - 166) * 1 day 2880 ... 43200 m = (vp - 166) * 1440 (m / 1440) + 166
197...255 (vp - 192) * 1 week 50400 ...635040 m = (vp - 192) * 10080 (m / 10080) + 192
*/
int validity;
if(minutes <= 720)
validity = DIV_UP(minutes, 5) - 1;
else if(minutes <= 1440)
validity = DIV_UP(minutes, 30) + 119;
else if(minutes <= 43200)
validity = DIV_UP(minutes, 1440) + 166;
else if(minutes <= 635040)
validity = DIV_UP(minutes, 10080) + 192;
else
validity = 0xFF;
return validity;
#undef DIV_UP
}
/*!
* \brief Store number in PDU
* \param buffer -- pointer to place where number will be stored, CALLER MUST be provide length + 2 bytes of buffer
* \param number -- phone number w/o leading '+'
* \param length -- length of number
* \return number of bytes written to buffer
*/
static int pdu_store_number(uint8_t* buffer, int toa, const char *number, unsigned length)
{
int i = 0;
buffer[i++] = toa;
unsigned j;
for (j = 0; j + 1 < length; j += 2) {
uint8_t a = pdu_digit2code(number[j]);
uint8_t b = pdu_digit2code(number[j + 1]);
if (a == 255 || b == 255) {
return -1;
}
buffer[i++] = a | b << 4;
}
if (j != length) {
uint8_t a = pdu_digit2code(number[j]);
if (a == 255) {
return -1;
}
buffer[i++] = a | 0xf0;
}
return i;
}
#/* reverse of pdu_store_number() */
static int pdu_parse_number(const uint8_t *pdu, size_t pdu_length,
unsigned digits, char *number, size_t num_len)
{
if (num_len < digits + 2) {
return -ENOMEM;
}
int toa;
int i = 0, res;
toa = pdu[i++];
unsigned syms = ROUND_UP2(digits);
if (syms > pdu_length - i) {
return -EINVAL;
}
if ((toa & TP_A_TON) == TP_A_TON_ALPHANUMERIC) {
uint16_t number16tmp[num_len];
res = gsm7_unpack_decode((const char*)pdu + i, syms, number16tmp, num_len, 0, 0, 0);
if (res < 0) return -EINVAL;
res = ucs2_to_utf8(number16tmp, res, number, num_len);
i += syms / 2;
number += res;
} else {
if ((toa & TP_A_TON) == TP_A_TON_INTERNATIONAL) {
*number++ = '+';
}
for (unsigned j = 0; j < syms / 2; ++j) {
int c = pdu[i];
*number++ = pdu_code2digit(c & 0xf);
char o = c >> 4;
if (o != 0xf) *number++ = pdu_code2digit(o);
++i;
}
}
*number = '\0';
return i;
}
#/* */
static int pdu_parse_timestamp(const uint8_t *pdu, size_t length, char *out)
{
int d, m, y, h, i, s, o, os;
if (length >= 7) {
y = (10 * (pdu[0] & 15) + (pdu[0] >> 4)) + 2000;
m = 10 * (pdu[1] & 15) + (pdu[1] >> 4);
d = 10 * (pdu[2] & 15) + (pdu[2] >> 4);
h = 10 * (pdu[3] & 15) + (pdu[3] >> 4);
i = 10 * (pdu[4] & 15) + (pdu[4] >> 4);
s = 10 * (pdu[5] & 15) + (pdu[5] >> 4);
o = (pdu[6] >> 4) + 10 * (pdu[6] & 7);
os = pdu[6] & 0x8;
sprintf(out, "%02d-%02d-%02d %02d:%02d:%02d %c%02d:%02d", y, m, d, h, i, s, os ? '-' : '+', o / 4, (o % 4) * 15);
return 7;
}
return -1;
}
EXPORT_DEF int pdu_build_mult(pdu_part_t *pdus, const char *sca, const char *dst, const uint16_t* msg, size_t msg_len, unsigned valid_minutes, int srr, uint8_t csmsref)
{
uint16_t msg_gsm7[msg_len];
int gsm7_len = gsm7_encode(msg, msg_len, msg_gsm7);
unsigned split = 0;
unsigned cnt = 0, i = 0, off = 0;
if (gsm7_len >= 0) {
if (gsm7_len > SMS_GSM7_MAX_LEN) {
split = CSMS_GSM7_MAX_LEN;
} else {
split = SMS_GSM7_MAX_LEN;
}
while (off < msg_len) {
unsigned septets = 0, n;
for (n = 0; off + n < msg_len; ++n) {
unsigned req = msg_gsm7[off + n] > 255 ? 2 : 1;
if (septets + req >= split) {
break;
}
septets += req;
}
++cnt;
off += n;
}
if (cnt > 255) {
chan_dongle_err = E_2BIG;
return -1;
}
off = 0;
while (off < msg_len) {
unsigned septets = 0, n;
for (n = 0; off + n < msg_len; ++n) {
unsigned req = msg_gsm7[off + n] > 255 ? 2 : 1;
if (septets + req >= split) {
break;
}
septets += req;
}
pdu_udh_t udh;
udh.ref = csmsref;
udh.order = i + 1;
udh.parts = cnt;
ssize_t curlen = pdu_build(pdus[i].buffer, PDU_LENGTH, &pdus[i].tpdu_length, sca, dst, PDU_DCS_ALPHABET_7BIT, msg_gsm7 + off, n, septets, valid_minutes, srr, &udh);
if (curlen < 0) {
/* pdu_build sets chan_dongle_err */
return -1;
}
pdus[i].length = curlen;
off += n;
++i;
}
} else {
if (msg_len > SMS_UCS2_MAX_LEN) {
split = CSMS_UCS2_MAX_LEN;
} else {
split = SMS_UCS2_MAX_LEN;
}
cnt = (msg_len + split - 1) / split;
if (cnt > 255) {
chan_dongle_err = E_2BIG;
return -1;
}
while (off < msg_len) {
unsigned r = msg_len - off;
unsigned n = r < split ? r : split;
pdu_udh_t udh;
udh.ref = csmsref;
udh.order = i + 1;
udh.parts = cnt;
ssize_t curlen = pdu_build(pdus[i].buffer, PDU_LENGTH, &pdus[i].tpdu_length, sca, dst, PDU_DCS_ALPHABET_UCS2, msg + off, n, n * 2, valid_minutes, srr, &udh);
if (curlen < 0) {
/* pdu_build sets chan_dongle_err */
return -1;
}
pdus[i].length = curlen;
off += n;
++i;
}
}
return i;
}
EXPORT_DEF ssize_t pdu_build(uint8_t *buffer, size_t length, size_t *tpdulen, const char *sca, const char *dst, int dcs, const uint16_t *msg, unsigned msg_reallen, unsigned msg_len, unsigned valid_minutes, int srr, const pdu_udh_t *udh)
{
int len = 0;
int sca_toa = NUMBER_TYPE_INTERNATIONAL;
int dst_toa;
int pdutype = PDUTYPE_MTI_SMS_SUBMIT | PDUTYPE_RD_ACCEPT | PDUTYPE_VPF_RELATIVE | PDUTYPE_SRR_NOT_REQUESTED | PDUTYPE_UDHI_NO_HEADER | PDUTYPE_RP_IS_NOT_SET;
int use_udh = udh->parts > 1;
int res;
if (use_udh) pdutype |= PDUTYPE_UDHI_HAS_HEADER;
unsigned dst_len;
unsigned sca_len;
if (sca[0] == '+') {
++sca;
}
if (dst[0] == '+') {
dst_toa = NUMBER_TYPE_INTERNATIONAL;
++dst;
} else {
if (strlen(dst) < 6) {
dst_toa = NUMBER_TYPE_NETWORKSHORT;
} else {
dst_toa = NUMBER_TYPE_UNKNOWN;
}
}
/* count length of strings */
sca_len = strlen(sca);
dst_len = strlen(dst);
/* SCA Length */
/* Type-of-address of the SMSC */
/* Address of SMSC */
if (sca_len) {
buffer[len++] = 1 + DIV2UP(sca_len);
res = pdu_store_number(buffer + len, sca_toa, sca, sca_len);
if (res < 0) {
chan_dongle_err = E_BUILD_SCA;
return -1;
}
len += res;
} else {
buffer[len++] = 0;
}
sca_len = len;
if(srr)
pdutype |= PDUTYPE_SRR_REQUESTED;
/* PDU-type */
/* TP-Message-Reference. Value will be ignored. The phone will set the number itself. */
/* Address-Length */
/* Type-of-address of the sender number */
buffer[len++] = pdutype;
buffer[len++] = PDU_MESSAGE_REFERENCE;
buffer[len++] = dst_len;
/* Destination address */
res = pdu_store_number(buffer + len, dst_toa, dst, dst_len);
if (res < 0) {
chan_dongle_err = E_BUILD_PHONE_NUMBER;
return -1;
}
len += res;
/* TP-PID. Protocol identifier */
/* TP-DCS. Data coding scheme */
/* TP-Validity-Period */
/* TP-User-Data-Length */
buffer[len++] = PDU_PID_SMS;
buffer[len++] = dcs;
buffer[len++] = pdu_relative_validity(valid_minutes);
buffer[len++] = msg_len + (!use_udh ? 0 : dcs == PDU_DCS_ALPHABET_UCS2 ? 6 : 7);
/* encode UDH */
int msg_padding = 0;
if (use_udh) {
buffer[len++] = 5;
buffer[len++] = 0;
buffer[len++] = 3;
buffer[len++] = udh->ref;
buffer[len++] = udh->parts;
buffer[len++] = udh->order;
msg_padding = 1;
}
/* TP-User-Data */
if (dcs == PDU_DCS_ALPHABET_UCS2) {
memcpy(buffer + len, (const char*)msg, msg_len);
len += msg_len;
} else {
len += (
gsm7_pack(msg, msg_reallen, (char*)buffer + len, length - len - 1,
msg_padding) + 1) / 2;
}
/* also check message limit in 176 octets of TPDU */
*tpdulen = len - sca_len;
if (*tpdulen > TPDU_LENGTH) {
chan_dongle_err = E_2BIG;
return -1;
}
if (len > PDU_LENGTH) {
chan_dongle_err = E_2BIG;
return -1;
}
return len;
}
/*!
* \brief Parse PDU
* \param pdu -- SCA + TPDU
* \param tpdu_length -- length of TPDU in octets
* \param timestamp -- 25 bytes for timestamp string
* \return 0 on success
*/
EXPORT_DEF int pdu_parse_sca(const uint8_t *pdu, size_t pdu_length, char *sca, size_t sca_len)
{
int i = 0;
int sca_digits = (pdu[i++] - 1) * 2;
int field_len = pdu_parse_number(pdu + i, pdu_length - i, sca_digits, sca, sca_len);
if (field_len <= 0) {
chan_dongle_err = E_INVALID_SCA;
return -1;
}
i += field_len;
return i;
}
EXPORT_DEF int tpdu_parse_type(const uint8_t *pdu, size_t pdu_length, int *type)
{
if (pdu_length < 1) {
chan_dongle_err = E_INVALID_TPDU_TYPE;
return -1;
}
*type = *pdu;
return 1;
}
EXPORT_DEF int tpdu_parse_status_report(const uint8_t *pdu, size_t pdu_length, int *mr, char *ra,
size_t ra_len, char *scts, char *dt, int *st)
{
unsigned i = 0;
int field_len;
if (i + 2 > pdu_length) {
chan_dongle_err = E_UNKNOWN;
return -1;
}
*mr = pdu[i++];
int ra_digits = pdu[i++];
field_len = pdu_parse_number(pdu + i, pdu_length - i, ra_digits, ra, ra_len);
if (field_len < 0) {
chan_dongle_err = E_INVALID_PHONE_NUMBER;
return -1;
}
i += field_len;
if (i + 14 + 1 > pdu_length) {
chan_dongle_err = E_INVALID_TIMESTAMP;
return -1;
}
i += pdu_parse_timestamp(pdu + i, pdu_length - i, scts);
i += pdu_parse_timestamp(pdu + i, pdu_length - i, dt);
*st = pdu[i++];
return 0;
}
EXPORT_DEF int tpdu_parse_deliver(const uint8_t *pdu, size_t pdu_length, int tpdu_type,
char *oa, size_t oa_len, char *scts, uint16_t *msg, pdu_udh_t *udh)
{
unsigned i = 0;
int field_len, oa_digits, pid, dcs, alphabet, udl, udhl;
int msg_padding = 0;
if (i + 1 > pdu_length) {
chan_dongle_err = E_UNKNOWN;
return -1;
}
oa_digits = pdu[i++];
field_len = pdu_parse_number(pdu + i, pdu_length - i, oa_digits, oa, oa_len);
if (field_len < 0) {
chan_dongle_err = E_INVALID_PHONE_NUMBER;
return -1;
}
i += field_len;
if (i + 2 + 7 + 1 > pdu_length) {
chan_dongle_err = E_UNKNOWN;
return -1;
}
pid = pdu[i++];
dcs = pdu[i++];
i += pdu_parse_timestamp(pdu + i, pdu_length - i, scts);
udl = pdu[i++];
if (pid != PDU_PID_SMS && !(0x41 <= pid && pid <= 0x47) /* PDU_PID_SMS_REPLACE_MASK */) {
/* 3GPP TSS 23.040 v14.0.0 (2017-013) */
/* """The MS (Mobile Station, _we_) shall interpret
* reserved, obsolete, or unsupported values as the
* value 00000000 but shall store them exactly as
* received.""" */
/* Lots of small variations in interpretations, but none
* really useful to us. We'll just go with accepting
* everything. */
/* A handfull from the list: */
/* 0x20..0x3E: different "telematic" devices */
/* 0x32: e-mail */
/* 0x38..0x3E: various Service Center specific codes */
/* 0x3F: gsm/umts station */
/* 0x40: silent sms; ME should ack but not tell the user */
/* 0x41..0x47: sms updates (replacing the previous sms #N) */
ast_log(LOG_NOTICE, "Treating TP-PID value 0x%hhx as regular SMS\n",
(unsigned char)pid);
}
/* http://www.etsi.org/deliver/etsi_gts/03/0338/05.00.00_60/gsmts_0338v050000p.pdf */
/* The TP-Data-Coding-Scheme field, defined in GSM 03.40,
* indicates the data coding scheme of the TP-UD field, and may
* indicate a message class. The octet is used according to a
* coding group which is indicated in bits 7..4. The octet is
* then coded as follows: */
{
int dcs_hi = dcs >> 4;
int dcs_lo = dcs & 0xF;
int reserved = 0;
alphabet = -1; /* 7bit, 8bit, ucs2 */
switch (dcs_hi) {
case 0x0: /* HIGH 0000: Regular message */
case 0x1: /* HIGH 0001: Regular message with class */
case 0x4: /* HIGH 0100: Marked for self-destruct */
case 0x5: /* HIGH 0101: Marked for self-destruct with class */
case 0xF: /* HIGH 1111: Data coding/message class */
/* Apparently bits 0..3 are not reserved anymore:
* bits 3..2: {7bit, 8bit, ucs2, undef} */
alphabet = PDU_DCS_ALPHABET(dcs);
/* Bits 3..2 set to 11 is reserved, but
* according to 3GPP TS 23.038 v14.0.0 (2017-03)
* for HIGH 1111 bit 3 (regardless of bit 2) is
* reserved. */
if (alphabet == PDU_DCS_ALPHABET_MASK) {
reserved = 1;
}
/* if 0x1 || 0xF then (dsc_lo & 3): {
* class0, class1-ME-specific,
* class2-SIM-specific,
* class3-TE-specific (3GPP TS 27.005)} */
break;
case 0x2: /* HIGH 0010: Compressed regular message */
case 0x3: /* HIGH 0011: Compressed regular with class */
case 0x6: /* HIGH 0110: Compressed, marked for self-destruct */
case 0x7: /* HIGH 0111: Compressed, marked for self-destruct with class */
chan_dongle_err = E_UNKNOWN;
return -1;
case 0xC: /* HIGH 1100: "Discard" MWI */
case 0xD: /* HIGH 1101: "Store" MWI */
/* if 0xC then the recipient may discard message
* contents, and only show notification */
/*inactive_active = (dcs_lo & 8);*/
reserved = (dcs_lo & 4); /* bit 2 reserved */
/* (dsc_lo & 3): {VM, Fax, E-mail, Other} */
break;
default:
chan_dongle_err = E_UNKNOWN;
reserved = 1;
break;
}
if (reserved) {
chan_dongle_err = E_UNKNOWN;
return -1;
}
if (alphabet == -1) {
chan_dongle_err = E_UNKNOWN;
return -1;
}
}
if (alphabet == PDU_DCS_ALPHABET_8BIT) {
// TODO: What to do with binary messages? Are there any?
// Return an error as it is dangerous to forward the raw binary data as text
chan_dongle_err = E_INVALID_CHARSET;
return -1;
}
/* calculate number of octets in UD */
int udl_nibbles = -1; /* only used for 7bit alphabet */
unsigned udl_bytes = udl;
if (alphabet == PDU_DCS_ALPHABET_7BIT) {
udl_nibbles = (udl * 7 + 3) / 4;
udl_bytes = (udl_nibbles + 1) / 2;
}
if (udl_bytes != pdu_length - i) {
chan_dongle_err = E_UNKNOWN;
return -1;
}
if (PDUTYPE_UDHI(tpdu_type) == PDUTYPE_UDHI_HAS_HEADER) {
if (i + 1 > pdu_length) {
chan_dongle_err = E_UNKNOWN;
return -1;
}
udhl = pdu[i++];
/* adjust 7-bit padding */
if (alphabet == PDU_DCS_ALPHABET_7BIT) {
msg_padding = 6 - (udhl % 7);
udl_nibbles -= (udhl + 1) * 2;
}
/* NOTE: UDHL count octets no need calculation */
if (pdu_length - i < (size_t)udhl) {
chan_dongle_err = E_UNKNOWN;
return -1;
}
while (udhl >= 2) {
int iei_type, iei_len;
/* get type byte */
iei_type = pdu[i++];
/* get length byte */
iei_len = pdu[i++];
/* subtract bytes */
udhl -= 2;
if (iei_len >= 0 && iei_len <= udhl) {
switch (iei_type) {
case 0x00: /* Concatenated */
if (iei_len != 3) {
chan_dongle_err = E_UNKNOWN;
return -1;
}
udh->ref = pdu[i++];
udh->parts = pdu[i++];
udh->order = pdu[i++];
udhl -= 3;
break;
case 0x08: /* Concatenated, 16 bit ref */
if (iei_len != 4) {
chan_dongle_err = E_UNKNOWN;
return -1;
}
udh->ref = (pdu[i++] << 8);
udh->ref |= pdu[i++];
udh->parts = pdu[i++];
udh->order = pdu[i++];
udhl -= 4;
break;
case 0x24: /* National Language Single Shift */
if (iei_len != 1) {
chan_dongle_err = E_UNKNOWN;
return -1;
}
udh->ss = pdu[i++];
break;
case 0x25: /* National Language Single Shift */
if (iei_len != 1) {
chan_dongle_err = E_UNKNOWN;
return -1;
}
udh->ls = pdu[i++];
break;
default:
/* skip rest of IEI */
i += iei_len;
udhl -= iei_len;
}
} else {
chan_dongle_err = E_UNKNOWN;
return -1;
}
}
/* skip rest of UDH, if any */
i += udhl;
}
int msg_len = pdu_length - i, out_len;
if (alphabet == PDU_DCS_ALPHABET_7BIT) {
out_len = gsm7_unpack_decode(
(const char*)pdu + i, udl_nibbles, msg,
1024 /* assume enough memory, as SMS messages are limited in size */,
msg_padding, udh->ls, udh->ss);
if (out_len < 0) {
chan_dongle_err = E_DECODE_GSM7;
return -1;
}
} else {
out_len = msg_len / 2;
memcpy((char*)msg, pdu + i, msg_len);
msg[out_len] = '\0';
}
msg[out_len] = '\0';
return out_len;
}