/* Copyright (C) 2010 bg Copyright (C) 2020 Max von Buelow */ #include "ast_config.h" #include /* 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; }