ofono/src/smsutil.c

4799 lines
100 KiB
C
Raw Permalink Normal View History

/*
*
* oFono - Open Source Telephony
*
2011-10-10 20:39:42 +00:00
* Copyright (C) 2008-2011 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <glib.h>
#include <ell/ell.h>
#include "util.h"
#include "storage.h"
#include "smsutil.h"
#define uninitialized_var(x) x = x
#define SMS_BACKUP_MODE 0600
#define SMS_BACKUP_PATH STORAGEDIR "/%s/sms_assembly"
#define SMS_BACKUP_PATH_DIR SMS_BACKUP_PATH "/%s-%i-%i"
#define SMS_BACKUP_PATH_FILE SMS_BACKUP_PATH_DIR "/%03i"
#define SMS_SR_BACKUP_PATH STORAGEDIR "/%s/sms_sr"
#define SMS_SR_BACKUP_PATH_FILE SMS_SR_BACKUP_PATH "/%s-%s"
#define SMS_TX_BACKUP_PATH STORAGEDIR "/%s/tx_queue"
#define SMS_TX_BACKUP_PATH_DIR SMS_TX_BACKUP_PATH "/%lu-%lu-%s"
#define SMS_TX_BACKUP_PATH_FILE SMS_TX_BACKUP_PATH_DIR "/%03i"
#define SMS_ADDR_FMT "%24[0-9A-F]"
#define SMS_MSGID_FMT "%40[0-9A-F]"
2010-11-30 10:44:48 +00:00
/*
* Time zone accounts for daylight saving time, and the two extreme time
* zones on earth are UTC-12 and UTC+14.
*/
#define MAX_TIMEZONE 56
#define MIN_TIMEZONE -48
static GSList *sms_assembly_add_fragment_backup(struct sms_assembly *assembly,
const struct sms *sms, time_t ts,
const struct sms_address *addr,
guint16 ref, guint8 max, guint8 seq,
gboolean backup);
/*
* This function uses the meanings of digits 10..15 according to the rules
* defined in 23.040 Section 9.1.2.3 and 24.008 Table 10.5.118
*/
2009-06-18 08:45:55 +00:00
void extract_bcd_number(const unsigned char *buf, int len, char *out)
{
2009-06-18 08:45:55 +00:00
static const char digit_lut[] = "0123456789*#abc\0";
unsigned char oct;
int i;
2009-06-18 08:45:55 +00:00
for (i = 0; i < len; i++) {
oct = buf[i];
2012-08-31 15:03:56 +00:00
out[i * 2] = digit_lut[oct & 0x0f];
out[i * 2 + 1] = digit_lut[(oct & 0xf0) >> 4];
2009-06-18 08:45:55 +00:00
}
2012-08-31 15:03:56 +00:00
out[i * 2] = '\0';
}
static inline int to_semi_oct(char in)
{
int digit;
switch (in) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
2010-05-26 14:58:47 +00:00
digit = in - '0';
break;
case '*':
digit = 10;
break;
case '#':
digit = 11;
break;
case 'A':
case 'a':
digit = 12;
break;
case 'B':
case 'b':
digit = 13;
break;
case 'C':
case 'c':
digit = 14;
break;
default:
digit = -1;
break;
}
return digit;
}
2009-08-05 07:08:40 +00:00
void encode_bcd_number(const char *number, unsigned char *out)
{
while (number[0] != '\0' && number[1] != '\0') {
*out = to_semi_oct(*number++);
*out++ |= to_semi_oct(*number++) << 4;
}
if (*number)
*out = to_semi_oct(*number) | 0xf0;
}
2010-06-21 20:33:13 +00:00
/*
* Returns whether the DCS could be parsed successfully, e.g. no reserved
2009-06-05 18:27:16 +00:00
* values were used
*/
gboolean sms_dcs_decode(guint8 dcs, enum sms_class *cls,
enum sms_charset *charset,
gboolean *compressed, gboolean *autodelete)
{
guint8 upper = (dcs & 0xf0) >> 4;
enum sms_charset ch;
enum sms_class cl;
gboolean comp;
gboolean autodel;
/* MWI DCS types are handled in sms_mwi_dcs_decode */
if (upper >= 0x8 && upper <= 0xE)
return FALSE;
upper = (dcs & 0xc0) >> 6;
switch (upper) {
case 0:
case 1:
autodel = upper;
comp = (dcs & 0x20) ? TRUE : FALSE;
if (dcs & 0x10)
2010-11-19 13:57:22 +00:00
cl = (enum sms_class) (dcs & 0x03);
2009-06-05 18:27:16 +00:00
else
cl = SMS_CLASS_UNSPECIFIED;
if (((dcs & 0x0c) >> 2) < 3)
ch = (enum sms_charset) ((dcs & 0x0c) >> 2);
else
return FALSE;
break;
case 3:
comp = FALSE;
autodel = FALSE;
if (dcs & 0x4)
ch = SMS_CHARSET_8BIT;
else
ch = SMS_CHARSET_7BIT;
2010-11-19 13:57:22 +00:00
cl = (enum sms_class) (dcs & 0x03);
2009-06-05 18:27:16 +00:00
break;
default:
return FALSE;
};
if (compressed)
*compressed = comp;
if (autodelete)
*autodelete = autodel;
if (cls)
*cls = cl;
if (charset)
*charset = ch;
return TRUE;
}
gboolean sms_mwi_dcs_decode(guint8 dcs, enum sms_mwi_type *type,
enum sms_charset *charset,
gboolean *active, gboolean *discard)
{
guint8 upper = (dcs & 0xf0) >> 4;
enum sms_mwi_type t;
enum sms_charset ch;
gboolean dis;
gboolean act;
if (upper < 0xC || upper > 0xE)
return FALSE;
upper = (dcs & 0x30) >> 4;
if (upper == 0)
dis = TRUE;
else
dis = FALSE;
2010-12-02 09:43:27 +00:00
/*
* As per 3GPP TS 23.038 specification, if bits 7..4 set to 1110,
* text included in the user data is coded in the uncompresssed
* UCS2 character set.
*/
if (upper == 2)
ch = SMS_CHARSET_UCS2;
else
ch = SMS_CHARSET_7BIT;
act = (dcs & 0x8) ? TRUE : FALSE;
t = (enum sms_mwi_type) (dcs & 0x3);
if (type)
*type = t;
if (charset)
*charset = ch;
if (active)
*active = act;
if (discard)
*discard = dis;
return TRUE;
}
int sms_udl_in_bytes(guint8 ud_len, guint8 dcs)
{
int len_7bit = (ud_len + 1) * 7 / 8;
int len_8bit = ud_len;
guint8 upper;
if (dcs == 0)
return len_7bit;
upper = (dcs & 0xc0) >> 6;
switch (upper) {
case 0:
case 1:
if (dcs & 0x20) /* compressed */
return len_8bit;
switch ((dcs & 0x0c) >> 2) {
case 0:
return len_7bit;
case 1:
return len_8bit;
case 2:
2009-06-01 23:11:12 +00:00
return len_8bit;
}
return 0;
case 2:
return 0;
case 3:
switch ((dcs & 0x30) >> 4) {
case 0:
case 1:
return len_7bit;
case 2:
2009-06-01 23:11:12 +00:00
return len_8bit;
case 3:
if (dcs & 0x4)
return len_8bit;
else
return len_7bit;
}
break;
default:
break;
};
return 0;
}
static inline gboolean next_octet(const unsigned char *pdu, int len,
int *offset, unsigned char *oct)
{
if (len == *offset)
return FALSE;
*oct = pdu[*offset];
*offset = *offset + 1;
return TRUE;
}
static inline gboolean set_octet(unsigned char *pdu, int *offset,
unsigned char oct)
{
pdu[*offset] = oct;
*offset = *offset + 1;
return TRUE;
}
gboolean sms_encode_scts(const struct sms_scts *in, unsigned char *pdu,
int *offset)
{
guint timezone;
if (in->year > 99)
return FALSE;
if (in->month > 12)
return FALSE;
if (in->day > 31)
return FALSE;
if (in->hour > 23)
return FALSE;
if (in->minute > 59)
return FALSE;
if (in->second > 59)
return FALSE;
if ((in->timezone > MAX_TIMEZONE || in->timezone < MIN_TIMEZONE) &&
in->has_timezone == TRUE)
return FALSE;
pdu = pdu + *offset;
pdu[0] = ((in->year / 10) & 0x0f) | (((in->year % 10) & 0x0f) << 4);
pdu[1] = ((in->month / 10) & 0x0f) | (((in->month % 10) & 0x0f) << 4);
pdu[2] = ((in->day / 10) & 0x0f) | (((in->day % 10) & 0x0f) << 4);
pdu[3] = ((in->hour / 10) & 0x0f) | (((in->hour % 10) & 0x0f) << 4);
pdu[4] = ((in->minute / 10) & 0x0f) | (((in->minute % 10) & 0x0f) << 4);
pdu[5] = ((in->second / 10) & 0x0f) | (((in->second % 10) & 0x0f) << 4);
if (in->has_timezone == FALSE) {
pdu[6] = 0xff;
goto out;
}
timezone = abs(in->timezone);
pdu[6] = ((timezone / 10) & 0x07) | (((timezone % 10) & 0x0f) << 4);
if (in->timezone < 0)
pdu[6] |= 0x8;
out:
*offset += 7;
return TRUE;
}
guint8 sms_decode_semi_octet(guint8 in)
{
return (in & 0x0f) * 10 + (in >> 4);
}
2010-04-02 06:20:33 +00:00
gboolean sms_decode_scts(const unsigned char *pdu, int len,
int *offset, struct sms_scts *out)
{
unsigned char oct = 0;
if ((len - *offset) < 7)
return FALSE;
next_octet(pdu, len, offset, &oct);
out->year = sms_decode_semi_octet(oct);
if (out->year > 99)
return FALSE;
next_octet(pdu, len, offset, &oct);
out->month = sms_decode_semi_octet(oct);
if (out->month > 12)
return FALSE;
next_octet(pdu, len, offset, &oct);
out->day = sms_decode_semi_octet(oct);
if (out->day > 31)
return FALSE;
next_octet(pdu, len, offset, &oct);
out->hour = sms_decode_semi_octet(oct);
if (out->hour > 23)
return FALSE;
next_octet(pdu, len, offset, &oct);
out->minute = sms_decode_semi_octet(oct);
if (out->minute > 59)
return FALSE;
next_octet(pdu, len, offset, &oct);
out->second = sms_decode_semi_octet(oct);
if (out->second > 59)
return FALSE;
next_octet(pdu, len, offset, &oct);
2010-06-21 20:33:13 +00:00
/*
* Time Zone indicates the difference, expressed in quarters
* of an hour, between the local time and GMT. In the first of the two
* semioctets, the first bit (bit 3 of the seventh octet of the
* TPServiceCentreTimeStamp field) represents the algebraic
* sign of this difference (0: positive, 1: negative).
*/
out->timezone = (oct & 0x07) * 10 + ((oct & 0xf0) >> 4);
if (oct & 0x08)
out->timezone = out->timezone * -1;
2010-11-30 10:44:48 +00:00
if ((out->timezone > MAX_TIMEZONE) || (out->timezone < MIN_TIMEZONE))
return FALSE;
out->has_timezone = TRUE;
return TRUE;
}
static gboolean decode_validity_period(const unsigned char *pdu, int len,
int *offset,
enum sms_validity_period_format vpf,
struct sms_validity_period *vp)
{
switch (vpf) {
case SMS_VALIDITY_PERIOD_FORMAT_ABSENT:
return TRUE;
case SMS_VALIDITY_PERIOD_FORMAT_RELATIVE:
if (!next_octet(pdu, len, offset, &vp->relative))
return FALSE;
return TRUE;
case SMS_VALIDITY_PERIOD_FORMAT_ABSOLUTE:
2010-04-02 06:20:33 +00:00
if (!sms_decode_scts(pdu, len, offset, &vp->absolute))
return FALSE;
return TRUE;
case SMS_VALIDITY_PERIOD_FORMAT_ENHANCED:
2010-06-21 20:33:13 +00:00
/*
* TODO: Parse out enhanced structure properly
* 23.040 Section 9.2.3.12.3
*/
if ((len - *offset) < 7)
return FALSE;
memcpy(vp->enhanced, pdu + *offset, 7);
*offset = *offset + 7;
return TRUE;
default:
break;
}
return FALSE;
}
static gboolean encode_validity_period(const struct sms_validity_period *vp,
enum sms_validity_period_format vpf,
unsigned char *pdu, int *offset)
{
switch (vpf) {
case SMS_VALIDITY_PERIOD_FORMAT_ABSENT:
return TRUE;
case SMS_VALIDITY_PERIOD_FORMAT_RELATIVE:
set_octet(pdu, offset, vp->relative);
return TRUE;
case SMS_VALIDITY_PERIOD_FORMAT_ABSOLUTE:
return sms_encode_scts(&vp->absolute, pdu, offset);
case SMS_VALIDITY_PERIOD_FORMAT_ENHANCED:
/* TODO: Write out proper enhanced VP structure */
memcpy(pdu + *offset, vp->enhanced, 7);
*offset = *offset + 7;
return TRUE;
default:
break;
}
return FALSE;
}
gboolean sms_encode_address_field(const struct sms_address *in, gboolean sc,
unsigned char *pdu, int *offset)
{
const char *addr = (const char *)&in->address;
size_t len = strlen(addr);
unsigned char addr_len = 0;
unsigned char p[10];
pdu = pdu + *offset;
if (len == 0 && sc) {
pdu[0] = 0;
*offset = *offset + 1;
return TRUE;
}
if (len == 0)
goto out;
if (in->number_type == SMS_NUMBER_TYPE_ALPHANUMERIC) {
long written;
long packed;
unsigned char *gsm;
unsigned char *r;
/* TP-OA's 10 octets transport 11 8-bit chars */
if (g_utf8_strlen(addr, strlen(addr)) > 11)
return FALSE;
gsm = convert_utf8_to_gsm(in->address, len, NULL, &written, 0);
if (gsm == NULL)
return FALSE;
if (written > 11) {
2018-12-21 18:35:06 +00:00
l_free(gsm);
return FALSE;
}
2018-12-19 21:51:47 +00:00
r = pack_7bit_own_buf(gsm, written, 0, false, &packed, 0, p);
2018-12-21 18:35:06 +00:00
l_free(gsm);
if (r == NULL)
return FALSE;
if (sc)
addr_len = packed + 1;
else
addr_len = (written * 7 + 3) / 4;
} else {
int j = 0;
int i;
int c;
if (len > 20)
return FALSE;
for (i = 0; in->address[i]; i++) {
c = to_semi_oct(in->address[i]);
if (c < 0)
return FALSE;
if ((i % 2) == 0) {
p[j] = c;
} else {
p[j] |= c << 4;
j++;
}
}
if ((i % 2) == 1) {
p[j] |= 0xf0;
j++;
}
if (sc)
addr_len = j + 1;
else
addr_len = i;
}
out:
pdu[0] = addr_len;
pdu[1] = (in->number_type << 4) | in->numbering_plan | 0x80;
2012-08-31 15:03:56 +00:00
memcpy(pdu + 2, p, (sc ? addr_len - 1 : (addr_len + 1) / 2));
*offset = *offset + 2 + (sc ? addr_len - 1 : (addr_len + 1) / 2);
return TRUE;
}
gboolean sms_decode_address_field(const unsigned char *pdu, int len,
int *offset, gboolean sc,
struct sms_address *out)
{
unsigned char addr_len;
unsigned char addr_type;
int byte_len;
if (!next_octet(pdu, len, offset, &addr_len))
return FALSE;
if (sc && addr_len == 0) {
out->address[0] = '\0';
return TRUE;
}
if (!next_octet(pdu, len, offset, &addr_type))
return FALSE;
if (sc)
byte_len = addr_len - 1;
else
byte_len = (addr_len + 1) / 2;
if ((len - *offset) < byte_len)
return FALSE;
out->number_type = bit_field(addr_type, 4, 3);
out->numbering_plan = bit_field(addr_type, 0, 4);
if (out->number_type != SMS_NUMBER_TYPE_ALPHANUMERIC) {
2009-08-06 22:29:55 +00:00
extract_bcd_number(pdu + *offset, byte_len, out->address);
2009-06-18 08:45:55 +00:00
*offset += byte_len;
} else {
int chars;
long written;
unsigned char *res;
char *utf8;
if (sc)
chars = byte_len * 8 / 7;
else
chars = addr_len * 4 / 7;
2010-06-21 20:33:13 +00:00
/*
* This cannot happen according to 24.011, however
* nothing is said in 23.040
*/
if (chars == 0) {
out->address[0] = '\0';
return TRUE;
}
2018-12-19 21:51:47 +00:00
res = unpack_7bit(pdu + *offset, byte_len, 0, false, chars,
&written, 0);
*offset = *offset + (addr_len + 1) / 2;
if (res == NULL)
return FALSE;
utf8 = convert_gsm_to_utf8(res, written, NULL, NULL, 0);
l_free(res);
if (!utf8)
return FALSE;
/*
* TP-OA's 10 octets transport 11 8-bit chars,
* 22 bytes+terminator in UTF-8.
*/
if (strlen(utf8) > 22) {
l_free(utf8);
return FALSE;
}
strcpy(out->address, utf8);
l_free(utf8);
}
return TRUE;
}
static gboolean encode_deliver(const struct sms_deliver *in, unsigned char *pdu,
int *offset)
{
int ud_oct_len;
unsigned char oct;
oct = 0;
if (!in->mms)
oct |= 1 << 2;
if (in->sri)
oct |= 1 << 5;
if (in->rp)
oct |= 1 << 7;
if (in->udhi)
oct |= 1 << 6;
set_octet(pdu, offset, oct);
if (sms_encode_address_field(&in->oaddr, FALSE, pdu, offset) == FALSE)
return FALSE;
set_octet(pdu, offset, in->pid);
set_octet(pdu, offset, in->dcs);
if (sms_encode_scts(&in->scts, pdu, offset) == FALSE)
return FALSE;
set_octet(pdu, offset, in->udl);
ud_oct_len = sms_udl_in_bytes(in->udl, in->dcs);
memcpy(pdu + *offset, in->ud, ud_oct_len);
*offset = *offset + ud_oct_len;
return TRUE;
}
static gboolean decode_deliver(const unsigned char *pdu, int len,
struct sms *out)
{
int offset = 0;
int expected;
unsigned char octet;
out->type = SMS_TYPE_DELIVER;
if (!next_octet(pdu, len, &offset, &octet))
return FALSE;
out->deliver.mms = !is_bit_set(octet, 2);
out->deliver.sri = is_bit_set(octet, 5);
out->deliver.udhi = is_bit_set(octet, 6);
out->deliver.rp = is_bit_set(octet, 7);
if (!sms_decode_address_field(pdu, len, &offset,
FALSE, &out->deliver.oaddr))
return FALSE;
if (!next_octet(pdu, len, &offset, &out->deliver.pid))
return FALSE;
if (!next_octet(pdu, len, &offset, &out->deliver.dcs))
return FALSE;
2010-04-02 06:20:33 +00:00
if (!sms_decode_scts(pdu, len, &offset, &out->deliver.scts))
return FALSE;
if (!next_octet(pdu, len, &offset, &out->deliver.udl))
return FALSE;
expected = sms_udl_in_bytes(out->deliver.udl, out->deliver.dcs);
if ((len - offset) < expected)
return FALSE;
2012-08-31 15:03:56 +00:00
memcpy(out->deliver.ud, pdu + offset, expected);
return TRUE;
}
static gboolean encode_submit_ack_report(const struct sms_submit_ack_report *in,
unsigned char *pdu, int *offset)
{
unsigned char oct;
oct = 1;
if (in->udhi)
oct |= 1 << 6;
set_octet(pdu, offset, oct);
set_octet(pdu, offset, in->pi);
if (!sms_encode_scts(&in->scts, pdu, offset))
return FALSE;
if (in->pi & 0x1)
set_octet(pdu, offset, in->pid);
if (in->pi & 0x2)
set_octet(pdu, offset, in->dcs);
if (in->pi & 0x4) {
int ud_oct_len = sms_udl_in_bytes(in->udl, in->dcs);
set_octet(pdu, offset, in->udl);
memcpy(pdu + *offset, in->ud, ud_oct_len);
*offset = *offset + ud_oct_len;
}
return TRUE;
}
static gboolean encode_submit_err_report(const struct sms_submit_err_report *in,
unsigned char *pdu, int *offset)
{
unsigned char oct;
oct = 0x1;
if (in->udhi)
oct |= 1 << 6;
set_octet(pdu, offset, oct);
set_octet(pdu, offset, in->fcs);
set_octet(pdu, offset, in->pi);
if (!sms_encode_scts(&in->scts, pdu, offset))
return FALSE;
if (in->pi & 0x1)
set_octet(pdu, offset, in->pid);
if (in->pi & 0x2)
set_octet(pdu, offset, in->dcs);
if (in->pi & 0x4) {
int ud_oct_len = sms_udl_in_bytes(in->udl, in->dcs);
set_octet(pdu, offset, in->udl);
memcpy(pdu + *offset, in->ud, ud_oct_len);
*offset = *offset + ud_oct_len;
}
return TRUE;
}
static gboolean decode_submit_report(const unsigned char *pdu, int len,
struct sms *out)
{
int offset = 0;
unsigned char octet;
gboolean udhi;
2009-10-15 20:56:48 +00:00
guint8 uninitialized_var(fcs);
guint8 pi;
struct sms_scts *scts;
guint8 pid = 0;
guint8 dcs = 0;
guint8 udl = 0;
if (!next_octet(pdu, len, &offset, &octet))
return FALSE;
udhi = is_bit_set(octet, 6);
if (!next_octet(pdu, len, &offset, &octet))
return FALSE;
2010-06-21 20:33:13 +00:00
/*
* At this point we don't know whether this is an ACK or an ERROR.
* FCS can only have values 0x80 and above, as 0x00 - 0x7F are reserved
* according to 3GPP 23.040. For PI, the values can be only in
* bit 0, 1, 2 with the 7th bit reserved as an extension. Since
* bits 3-6 are not used, assume no extension is feasible, so if the
* value of this octet is >= 0x80, this is an FCS and thus an error
* report tpdu.
*/
if (octet >= 0x80) {
out->type = SMS_TYPE_SUBMIT_REPORT_ERROR;
fcs = octet;
if (!next_octet(pdu, len, &offset, &octet))
return FALSE;
scts = &out->submit_err_report.scts;
} else {
scts = &out->submit_ack_report.scts;
out->type = SMS_TYPE_SUBMIT_REPORT_ACK;
}
pi = octet & 0x07;
2010-04-02 06:20:33 +00:00
if (!sms_decode_scts(pdu, len, &offset, scts))
return FALSE;
if (pi & 0x01) {
if (!next_octet(pdu, len, &offset, &pid))
return FALSE;
}
if (pi & 0x02) {
if (!next_octet(pdu, len, &offset, &dcs))
return FALSE;
}
if (out->type == SMS_TYPE_SUBMIT_REPORT_ERROR) {
out->submit_err_report.udhi = udhi;
out->submit_err_report.fcs = fcs;
out->submit_err_report.pi = pi;
out->submit_err_report.pid = pid;
out->submit_err_report.dcs = dcs;
} else {
out->submit_ack_report.udhi = udhi;
out->submit_ack_report.pi = pi;
out->submit_ack_report.pid = pid;
out->submit_ack_report.dcs = dcs;
}
if (pi & 0x04) {
int expected;
if (!next_octet(pdu, len, &offset, &udl))
return FALSE;
expected = sms_udl_in_bytes(udl, dcs);
if ((len - offset) < expected)
return FALSE;
if (out->type == SMS_TYPE_SUBMIT_REPORT_ERROR) {
out->submit_err_report.udl = udl;
memcpy(out->submit_err_report.ud,
2012-08-31 15:03:56 +00:00
pdu + offset, expected);
} else {
out->submit_ack_report.udl = udl;
memcpy(out->submit_ack_report.ud,
2012-08-31 15:03:56 +00:00
pdu + offset, expected);
}
}
return TRUE;
}
static gboolean encode_status_report(const struct sms_status_report *in,
unsigned char *pdu, int *offset)
{
unsigned char octet;
octet = 0x2;
if (!in->mms)
octet |= 1 << 2;
if (!in->srq)
octet |= 1 << 5;
if (!in->udhi)
octet |= 1 << 6;
set_octet(pdu, offset, octet);
set_octet(pdu, offset, in->mr);
if (!sms_encode_address_field(&in->raddr, FALSE, pdu, offset))
return FALSE;
if (!sms_encode_scts(&in->scts, pdu, offset))
return FALSE;
if (!sms_encode_scts(&in->dt, pdu, offset))
return FALSE;
octet = in->st;
set_octet(pdu, offset, octet);
if (in->pi == 0)
return TRUE;
set_octet(pdu, offset, in->pi);
if (in->pi & 0x01)
set_octet(pdu, offset, in->pid);
if (in->pi & 0x02)
set_octet(pdu, offset, in->dcs);
if (in->pi & 0x4) {
int ud_oct_len = sms_udl_in_bytes(in->udl, in->dcs);
set_octet(pdu, offset, in->udl);
memcpy(pdu + *offset, in->ud, ud_oct_len);
*offset = *offset + ud_oct_len;
}
return TRUE;
}
static gboolean decode_status_report(const unsigned char *pdu, int len,
struct sms *out)
{
int offset = 0;
unsigned char octet;
out->type = SMS_TYPE_STATUS_REPORT;
if (!next_octet(pdu, len, &offset, &octet))
return FALSE;
out->status_report.mms = !is_bit_set(octet, 2);
out->status_report.srq = is_bit_set(octet, 5);
out->status_report.udhi = is_bit_set(octet, 6);
if (!next_octet(pdu, len, &offset, &out->status_report.mr))
return FALSE;
if (!sms_decode_address_field(pdu, len, &offset, FALSE,
&out->status_report.raddr))
return FALSE;
2010-04-02 06:20:33 +00:00
if (!sms_decode_scts(pdu, len, &offset, &out->status_report.scts))
return FALSE;
2010-04-02 06:20:33 +00:00
if (!sms_decode_scts(pdu, len, &offset, &out->status_report.dt))
return FALSE;
if (!next_octet(pdu, len, &offset, &octet))
return FALSE;
out->status_report.st = octet;
2010-06-21 20:33:13 +00:00
/*
* We have to be careful here, PI is labeled as Optional in 23.040
* which is different from RP-ERR & RP-ACK for both Deliver & Submit
* reports
*/
if ((len - offset) == 0)
return TRUE;
if (!next_octet(pdu, len, &offset, &octet))
return FALSE;
out->status_report.pi = octet & 0x07;
if (out->status_report.pi & 0x01) {
if (!next_octet(pdu, len, &offset, &out->status_report.pid))
return FALSE;
}
if (out->status_report.pi & 0x02) {
if (!next_octet(pdu, len, &offset, &out->status_report.dcs))
return FALSE;
}
if (out->status_report.pi & 0x04) {
int expected;
if (!next_octet(pdu, len, &offset, &out->status_report.udl))
return FALSE;
expected = sms_udl_in_bytes(out->status_report.udl,
out->status_report.dcs);
if ((len - offset) < expected)
return FALSE;
2012-08-31 15:03:56 +00:00
memcpy(out->status_report.ud, pdu + offset, expected);
}
return TRUE;
}
static gboolean encode_deliver_ack_report(const struct sms_deliver_ack_report *in,
unsigned char *pdu,
int *offset)
{
unsigned char oct;
oct = 0;
if (in->udhi)
oct |= 1 << 6;
set_octet(pdu, offset, oct);
set_octet(pdu, offset, in->pi);
if (in->pi & 0x1)
set_octet(pdu, offset, in->pid);
if (in->pi & 0x2)
set_octet(pdu, offset, in->dcs);
if (in->pi & 0x4) {
int ud_oct_len = sms_udl_in_bytes(in->udl, in->dcs);
set_octet(pdu, offset, in->udl);
memcpy(pdu + *offset, in->ud, ud_oct_len);
*offset = *offset + ud_oct_len;
}
return TRUE;
}
static gboolean encode_deliver_err_report(const struct sms_deliver_err_report *in,
unsigned char *pdu,
int *offset)
{
unsigned char oct;
oct = 0;
if (in->udhi)
oct |= 1 << 6;
set_octet(pdu, offset, oct);
set_octet(pdu, offset, in->fcs);
set_octet(pdu, offset, in->pi);
if (in->pi & 0x1)
set_octet(pdu, offset, in->pid);
if (in->pi & 0x2)
set_octet(pdu, offset, in->dcs);
if (in->pi & 0x4) {
int ud_oct_len = sms_udl_in_bytes(in->udl, in->dcs);
set_octet(pdu, offset, in->udl);
memcpy(pdu + *offset, in->ud, ud_oct_len);
*offset = *offset + ud_oct_len;
}
return TRUE;
}
static gboolean decode_deliver_report(const unsigned char *pdu, int len,
struct sms *out)
{
int offset = 0;
unsigned char octet;
gboolean udhi;
2009-10-15 20:56:48 +00:00
guint8 uninitialized_var(fcs);
guint8 pi;
guint8 pid = 0;
guint8 dcs = 0;
guint8 udl = 0;
if (!next_octet(pdu, len, &offset, &octet))
return FALSE;
udhi = is_bit_set(octet, 6);
if (!next_octet(pdu, len, &offset, &octet))
return FALSE;
2010-06-21 20:33:13 +00:00
/*
* At this point we don't know whether this is an ACK or an ERROR.
* FCS can only have values 0x80 and above, as 0x00 - 0x7F are reserved
* according to 3GPP 23.040. For PI, the values can be only in
* bit 0, 1, 2 with the 7th bit reserved as an extension. Since
* bits 3-6 are not used, assume no extension is feasible, so if the
* value of this octet is >= 0x80, this is an FCS and thus an error
* report tpdu.
*/
if (octet >= 0x80) {
out->type = SMS_TYPE_DELIVER_REPORT_ERROR;
fcs = octet;
if (!next_octet(pdu, len, &offset, &octet))
return FALSE;
} else {
out->type = SMS_TYPE_DELIVER_REPORT_ACK;
}
pi = octet & 0x07;
if (pi & 0x01) {
if (!next_octet(pdu, len, &offset, &pid))
return FALSE;
}
if (pi & 0x02) {
if (!next_octet(pdu, len, &offset, &dcs))
return FALSE;
}
if (out->type == SMS_TYPE_DELIVER_REPORT_ERROR) {
out->deliver_err_report.udhi = udhi;
out->deliver_err_report.fcs = fcs;
out->deliver_err_report.pi = pi;
out->deliver_err_report.pid = pid;
out->deliver_err_report.dcs = dcs;
} else {
out->deliver_ack_report.udhi = udhi;
out->deliver_ack_report.pi = pi;
out->deliver_ack_report.pid = pid;
out->deliver_ack_report.dcs = dcs;
}
if (pi & 0x04) {
int expected;
if (!next_octet(pdu, len, &offset, &udl))
return FALSE;
expected = sms_udl_in_bytes(udl, dcs);
if ((len - offset) < expected)
return FALSE;
if (out->type == SMS_TYPE_DELIVER_REPORT_ERROR) {
out->deliver_err_report.udl = udl;
memcpy(out->deliver_err_report.ud,
2012-08-31 15:03:56 +00:00
pdu + offset, expected);
} else {
out->deliver_ack_report.udl = udl;
memcpy(out->deliver_ack_report.ud,
2012-08-31 15:03:56 +00:00
pdu + offset, expected);
}
}
return TRUE;
}
static gboolean encode_submit(const struct sms_submit *in,
unsigned char *pdu, int *offset)
{
unsigned char octet;
int ud_oct_len;
/* SMS Submit */
octet = 0x1;
if (in->rd)
octet |= 1 << 2;
if (in->rp)
octet |= 1 << 7;
octet |= in->vpf << 3;
if (in->udhi)
octet |= 1 << 6;
if (in->srr)
octet |= 1 << 5;
set_octet(pdu, offset, octet);
set_octet(pdu, offset, in->mr);
if (sms_encode_address_field(&in->daddr, FALSE, pdu, offset) == FALSE)
return FALSE;
set_octet(pdu, offset, in->pid);
set_octet(pdu, offset, in->dcs);
if (!encode_validity_period(&in->vp, in->vpf, pdu, offset))
return FALSE;
set_octet(pdu, offset, in->udl);
ud_oct_len = sms_udl_in_bytes(in->udl, in->dcs);
memcpy(pdu + *offset, in->ud, ud_oct_len);
*offset = *offset + ud_oct_len;
return TRUE;
}
gboolean sms_decode_unpacked_stk_pdu(const unsigned char *pdu, int len,
struct sms *out)
{
unsigned char octet;
int offset = 0;
if (!next_octet(pdu, len, &offset, &octet))
return FALSE;
if ((octet & 0x3) != 1)
return FALSE;
out->type = SMS_TYPE_SUBMIT;
out->submit.rd = is_bit_set(octet, 2);
out->submit.vpf = bit_field(octet, 3, 2);
out->submit.rp = is_bit_set(octet, 7);
out->submit.udhi = is_bit_set(octet, 6);
out->submit.srr = is_bit_set(octet, 5);
if (!next_octet(pdu, len, &offset, &out->submit.mr))
return FALSE;
if (!sms_decode_address_field(pdu, len, &offset,
FALSE, &out->submit.daddr))
return FALSE;
if (!next_octet(pdu, len, &offset, &out->submit.pid))
return FALSE;
if (!next_octet(pdu, len, &offset, &out->submit.dcs))
return FALSE;
/* Now we override the DCS */
out->submit.dcs = 0xF0;
if (!decode_validity_period(pdu, len, &offset, out->submit.vpf,
&out->submit.vp))
return FALSE;
if (!next_octet(pdu, len, &offset, &out->submit.udl))
return FALSE;
if ((len - offset) < out->submit.udl)
return FALSE;
2018-12-19 21:51:47 +00:00
pack_7bit_own_buf(pdu + offset, out->submit.udl, 0, false,
NULL, 0, out->submit.ud);
return TRUE;
}
static gboolean decode_submit(const unsigned char *pdu, int len,
struct sms *out)
{
unsigned char octet;
int offset = 0;
int expected;
out->type = SMS_TYPE_SUBMIT;
if (!next_octet(pdu, len, &offset, &octet))
return FALSE;
out->submit.rd = is_bit_set(octet, 2);
out->submit.vpf = bit_field(octet, 3, 2);
out->submit.rp = is_bit_set(octet, 7);
out->submit.udhi = is_bit_set(octet, 6);
out->submit.srr = is_bit_set(octet, 5);
if (!next_octet(pdu, len, &offset, &out->submit.mr))
return FALSE;
if (!sms_decode_address_field(pdu, len, &offset,
FALSE, &out->submit.daddr))
return FALSE;
if (!next_octet(pdu, len, &offset, &out->submit.pid))
return FALSE;
if (!next_octet(pdu, len, &offset, &out->submit.dcs))
return FALSE;
if (!decode_validity_period(pdu, len, &offset, out->submit.vpf,
&out->submit.vp))
return FALSE;
if (!next_octet(pdu, len, &offset, &out->submit.udl))
return FALSE;
expected = sms_udl_in_bytes(out->submit.udl, out->submit.dcs);
if ((len - offset) < expected)
return FALSE;
if (expected > (int) sizeof(out->submit.ud))
return FALSE;
2012-08-31 15:03:56 +00:00
memcpy(out->submit.ud, pdu + offset, expected);
return TRUE;
}
static gboolean encode_command(const struct sms_command *in,
unsigned char *pdu, int *offset)
{
unsigned char octet;
octet = 0x2;
if (in->udhi)
octet |= 1 << 6;
if (in->srr)
octet |= 1 << 5;
set_octet(pdu, offset, octet);
set_octet(pdu, offset, in->mr);
set_octet(pdu, offset, in->pid);
octet = in->ct;
set_octet(pdu, offset, octet);
set_octet(pdu, offset, in->mn);
if (!sms_encode_address_field(&in->daddr, FALSE, pdu, offset))
return FALSE;
set_octet(pdu, offset, in->cdl);
memcpy(pdu + *offset, in->cd, in->cdl);
*offset = *offset + in->cdl;
return TRUE;
}
static gboolean decode_command(const unsigned char *pdu, int len,
struct sms *out)
{
unsigned char octet;
int offset = 0;
out->type = SMS_TYPE_COMMAND;
if (!next_octet(pdu, len, &offset, &octet))
return FALSE;
out->command.udhi = is_bit_set(octet, 6);
out->command.srr = is_bit_set(octet, 5);
if (!next_octet(pdu, len, &offset, &out->command.mr))
return FALSE;
if (!next_octet(pdu, len, &offset, &out->command.pid))
return FALSE;
if (!next_octet(pdu, len, &offset, &octet))
return FALSE;
out->command.ct = octet;
if (!next_octet(pdu, len, &offset, &out->command.mn))
return FALSE;
if (!sms_decode_address_field(pdu, len, &offset,
FALSE, &out->command.daddr))
return FALSE;
if (!next_octet(pdu, len, &offset, &out->command.cdl))
return FALSE;
if ((len - offset) < out->command.cdl)
return FALSE;
2012-08-31 15:03:56 +00:00
memcpy(out->command.cd, pdu + offset, out->command.cdl);
return TRUE;
}
/* Buffer must be at least 164 (tpud) + 12 (SC address) bytes long */
gboolean sms_encode(const struct sms *in, int *len, int *tpdu_len,
unsigned char *pdu)
{
int offset = 0;
int tpdu_start;
if (in->type == SMS_TYPE_DELIVER || in->type == SMS_TYPE_SUBMIT ||
in->type == SMS_TYPE_COMMAND ||
in->type == SMS_TYPE_STATUS_REPORT)
if (!sms_encode_address_field(&in->sc_addr, TRUE, pdu, &offset))
return FALSE;
tpdu_start = offset;
switch (in->type) {
case SMS_TYPE_DELIVER:
if (encode_deliver(&in->deliver, pdu, &offset) == FALSE)
return FALSE;
break;
case SMS_TYPE_DELIVER_REPORT_ACK:
if (!encode_deliver_ack_report(&in->deliver_ack_report, pdu,
&offset))
return FALSE;
break;
case SMS_TYPE_DELIVER_REPORT_ERROR:
if (!encode_deliver_err_report(&in->deliver_err_report, pdu,
&offset))
return FALSE;
break;
case SMS_TYPE_STATUS_REPORT:
if (!encode_status_report(&in->status_report, pdu, &offset))
return FALSE;
break;
case SMS_TYPE_SUBMIT:
if (!encode_submit(&in->submit, pdu, &offset))
return FALSE;
break;
case SMS_TYPE_SUBMIT_REPORT_ACK:
if (!encode_submit_ack_report(&in->submit_ack_report, pdu,
&offset))
return FALSE;
break;
case SMS_TYPE_SUBMIT_REPORT_ERROR:
if (!encode_submit_err_report(&in->submit_err_report, pdu,
&offset))
return FALSE;
break;
case SMS_TYPE_COMMAND:
if (!encode_command(&in->command, pdu, &offset))
return FALSE;
break;
default:
return FALSE;
};
if (tpdu_len)
*tpdu_len = offset - tpdu_start;
if (len)
*len = offset;
return TRUE;
}
gboolean sms_decode(const unsigned char *pdu, int len, gboolean outgoing,
int tpdu_len, struct sms *out)
{
unsigned char type;
int offset = 0;
if (out == NULL)
return FALSE;
if (len == 0)
return FALSE;
memset(out, 0, sizeof(*out));
if (tpdu_len < len) {
if (!sms_decode_address_field(pdu, len, &offset,
TRUE, &out->sc_addr))
return FALSE;
}
if ((len - offset) < tpdu_len)
return FALSE;
/* 23.040 9.2.3.1 */
type = pdu[offset] & 0x3;
if (outgoing)
type |= 0x4;
pdu = pdu + offset;
switch (type) {
case 0:
return decode_deliver(pdu, tpdu_len, out);
case 1:
return decode_submit_report(pdu, tpdu_len, out);
case 2:
return decode_status_report(pdu, tpdu_len, out);
case 3:
/* According to 9.2.3.1, Reserved treated as deliver */
return decode_deliver(pdu, tpdu_len, out);
case 4:
return decode_deliver_report(pdu, tpdu_len, out);
case 5:
return decode_submit(pdu, tpdu_len, out);
case 6:
return decode_command(pdu, tpdu_len, out);
}
return FALSE;
}
const guint8 *sms_extract_common(const struct sms *sms, gboolean *out_udhi,
guint8 *out_dcs, guint8 *out_udl,
guint8 *out_max)
{
const guint8 *ud = NULL;
guint8 uninitialized_var(udl);
guint8 uninitialized_var(max);
gboolean uninitialized_var(udhi);
guint8 uninitialized_var(dcs);
switch (sms->type) {
case SMS_TYPE_DELIVER:
udhi = sms->deliver.udhi;
ud = sms->deliver.ud;
udl = sms->deliver.udl;
dcs = sms->deliver.dcs;
max = sizeof(sms->deliver.ud);
break;
case SMS_TYPE_DELIVER_REPORT_ACK:
udhi = sms->deliver_ack_report.udhi;
ud = sms->deliver_ack_report.ud;
udl = sms->deliver_ack_report.udl;
dcs = sms->deliver_ack_report.dcs;
max = sizeof(sms->deliver_ack_report.ud);
break;
case SMS_TYPE_DELIVER_REPORT_ERROR:
udhi = sms->deliver_err_report.udhi;
ud = sms->deliver_err_report.ud;
udl = sms->deliver_err_report.udl;
dcs = sms->deliver_err_report.dcs;
max = sizeof(sms->deliver_err_report.ud);
break;
case SMS_TYPE_STATUS_REPORT:
udhi = sms->status_report.udhi;
ud = sms->status_report.ud;
udl = sms->status_report.udl;
dcs = sms->status_report.dcs;
max = sizeof(sms->status_report.ud);
break;
case SMS_TYPE_SUBMIT:
udhi = sms->submit.udhi;
ud = sms->submit.ud;
udl = sms->submit.udl;
dcs = sms->submit.dcs;
max = sizeof(sms->submit.ud);
break;
case SMS_TYPE_SUBMIT_REPORT_ACK:
udhi = sms->submit_ack_report.udhi;
ud = sms->submit_ack_report.ud;
udl = sms->submit_ack_report.udl;
dcs = sms->submit_ack_report.dcs;
max = sizeof(sms->submit_ack_report.ud);
break;
case SMS_TYPE_SUBMIT_REPORT_ERROR:
udhi = sms->submit_err_report.udhi;
ud = sms->submit_err_report.ud;
udl = sms->submit_err_report.udl;
dcs = sms->submit_err_report.dcs;
max = sizeof(sms->submit_err_report.ud);
break;
case SMS_TYPE_COMMAND:
udhi = sms->command.udhi;
ud = sms->command.cd;
udl = sms->command.cdl;
dcs = 0;
max = sizeof(sms->command.cd);
break;
};
if (ud == NULL)
return NULL;
if (out_udhi)
*out_udhi = udhi;
if (out_dcs)
*out_dcs = dcs;
if (out_udl)
*out_udl = udl;
if (out_max)
*out_max = max;
return ud;
}
static gboolean verify_udh(const guint8 *hdr, guint8 max_len)
{
guint8 max_offset;
guint8 offset;
/* Must have at least one information-element if udhi is true */
if (hdr[0] < 2)
return FALSE;
if (hdr[0] >= max_len)
return FALSE;
2010-06-21 20:33:13 +00:00
/*
* According to 23.040: If the length of the User Data Header is
* such that there are too few or too many octets in the final
* Information Element then the whole User Data Header shall be
* ignored.
*/
max_offset = hdr[0] + 1;
offset = 1;
do {
if ((offset + 2) > max_offset)
return FALSE;
if ((offset + 2 + hdr[offset + 1]) > max_offset)
return FALSE;
offset = offset + 2 + hdr[offset + 1];
} while (offset < max_offset);
if (offset != max_offset)
return FALSE;
return TRUE;
}
gboolean sms_udh_iter_init(const struct sms *sms, struct sms_udh_iter *iter)
{
gboolean udhi = FALSE;
const guint8 *hdr;
guint8 udl;
guint8 dcs;
guint8 max_len;
guint8 max_ud_len;
hdr = sms_extract_common(sms, &udhi, &dcs, &udl, &max_ud_len);
if (hdr == NULL)
return FALSE;
if (!udhi)
return FALSE;
if (sms->type == SMS_TYPE_COMMAND)
max_len = udl;
else
max_len = sms_udl_in_bytes(udl, dcs);
/* Can't actually store the HDL + IEI / IEL */
if (max_len < 3)
return FALSE;
if (max_len > max_ud_len)
2009-06-11 22:00:14 +00:00
return FALSE;
if (!verify_udh(hdr, max_len))
return FALSE;
iter->data = hdr;
iter->offset = 1;
return TRUE;
}
gboolean sms_udh_iter_init_from_cbs(const struct cbs *cbs,
struct sms_udh_iter *iter)
{
gboolean udhi = FALSE;
const guint8 *hdr;
guint8 max_ud_len;
cbs_dcs_decode(cbs->dcs, &udhi, NULL, NULL, NULL, NULL, NULL);
if (!udhi)
return FALSE;
hdr = cbs->ud;
max_ud_len = cbs->udlen;
/* Must have at least one information-element if udhi is true */
if (hdr[0] < 2)
return FALSE;
if (hdr[0] >= max_ud_len)
return FALSE;
if (!verify_udh(hdr, max_ud_len))
return FALSE;
iter->data = hdr;
iter->offset = 1;
return TRUE;
}
guint8 sms_udh_iter_get_udh_length(struct sms_udh_iter *iter)
{
return iter->data[0];
}
const guint8 *sms_udh_iter_get_ud_after_header(struct sms_udh_iter *iter)
{
return iter->data + iter->data[0] + 1;
}
enum sms_iei sms_udh_iter_get_ie_type(struct sms_udh_iter *iter)
{
if (iter->offset > iter->data[0])
return SMS_IEI_INVALID;
return (enum sms_iei) iter->data[iter->offset];
}
guint8 sms_udh_iter_get_ie_length(struct sms_udh_iter *iter)
{
guint8 ie_len;
ie_len = iter->data[iter->offset + 1];
return ie_len;
}
void sms_udh_iter_get_ie_data(struct sms_udh_iter *iter, guint8 *data)
{
guint8 ie_len;
ie_len = iter->data[iter->offset + 1];
memcpy(data, &iter->data[iter->offset + 2], ie_len);
}
gboolean sms_udh_iter_has_next(struct sms_udh_iter *iter)
{
guint8 total_len = iter->data[0];
guint8 cur_ie_len = iter->data[iter->offset + 1];
if ((iter->offset + 2 + cur_ie_len) > total_len)
return FALSE;
return TRUE;
}
gboolean sms_udh_iter_next(struct sms_udh_iter *iter)
{
if (iter->offset > iter->data[0])
return FALSE;
iter->offset = iter->offset + 2 + iter->data[iter->offset + 1];
if (iter->offset > iter->data[0])
return FALSE;
return TRUE;
}
2010-06-21 20:33:13 +00:00
/*
* Returns both forms of time. The time_t value returns the time in local
* timezone. The struct tm is filled out with the remote time information
*/
time_t sms_scts_to_time(const struct sms_scts *scts, struct tm *remote)
{
struct tm t;
time_t ret;
t.tm_sec = scts->second;
t.tm_min = scts->minute;
t.tm_hour = scts->hour;
t.tm_mday = scts->day;
t.tm_mon = scts->month - 1;
t.tm_isdst = -1;
if (scts->year > 80)
t.tm_year = scts->year;
else
t.tm_year = scts->year + 100;
ret = mktime(&t);
/* Adjust local time by the local timezone information */
ret += t.tm_gmtoff;
/* Set the proper timezone on the remote side */
t.tm_gmtoff = scts->timezone * 15 * 60;
/* Now adjust by the remote timezone information */
ret -= t.tm_gmtoff;
if (remote)
memcpy(remote, &t, sizeof(struct tm));
return ret;
}
void sms_address_from_string(struct sms_address *addr, const char *str)
{
addr->numbering_plan = SMS_NUMBERING_PLAN_ISDN;
if (str[0] == '+') {
addr->number_type = SMS_NUMBER_TYPE_INTERNATIONAL;
2012-08-31 15:03:56 +00:00
strcpy(addr->address, str + 1);
} else {
addr->number_type = SMS_NUMBER_TYPE_UNKNOWN;
strcpy(addr->address, str);
}
}
const char *sms_address_to_string(const struct sms_address *addr)
{
static char buffer[64];
if (addr->number_type == SMS_NUMBER_TYPE_INTERNATIONAL &&
(strlen(addr->address) > 0) &&
addr->address[0] != '+') {
buffer[0] = '+';
strcpy(buffer + 1, addr->address);
} else {
strcpy(buffer, addr->address);
}
return buffer;
}
static gboolean extract_app_port_common(struct sms_udh_iter *iter, int *dst,
int *src, gboolean *is_8bit)
{
enum sms_iei iei;
guint8 addr_hdr[4];
int srcport = -1;
int dstport = -1;
gboolean uninitialized_var(is_addr_8bit);
2010-06-21 20:33:13 +00:00
/*
* According to the specification, we have to use the last
* useable header. Also, we have to ignore ports that are reserved:
* A receiving entity shall ignore (i.e. skip over and commence
* processing at the next information element) any information element
* where the value of the Information-Element-Data is Reserved or not
* supported.
2010-09-21 19:18:15 +00:00
*/
while ((iei = sms_udh_iter_get_ie_type(iter)) !=
SMS_IEI_INVALID) {
switch (iei) {
case SMS_IEI_APPLICATION_ADDRESS_8BIT:
if (sms_udh_iter_get_ie_length(iter) != 2)
break;
sms_udh_iter_get_ie_data(iter, addr_hdr);
if (addr_hdr[0] < 240)
break;
if (addr_hdr[1] < 240)
break;
dstport = addr_hdr[0];
srcport = addr_hdr[1];
is_addr_8bit = TRUE;
break;
case SMS_IEI_APPLICATION_ADDRESS_16BIT:
if (sms_udh_iter_get_ie_length(iter) != 4)
break;
sms_udh_iter_get_ie_data(iter, addr_hdr);
if (((addr_hdr[0] << 8) | addr_hdr[1]) > 49151)
break;
dstport = (addr_hdr[0] << 8) | addr_hdr[1];
srcport = (addr_hdr[2] << 8) | addr_hdr[3];
is_addr_8bit = FALSE;
break;
default:
break;
}
sms_udh_iter_next(iter);
}
if (dstport == -1 || srcport == -1)
return FALSE;
if (dst)
*dst = dstport;
if (src)
*src = srcport;
if (is_8bit)
*is_8bit = is_addr_8bit;
return TRUE;
}
gboolean sms_extract_app_port(const struct sms *sms, int *dst, int *src,
gboolean *is_8bit)
{
struct sms_udh_iter iter;
if (!sms_udh_iter_init(sms, &iter))
return FALSE;
return extract_app_port_common(&iter, dst, src, is_8bit);
}
2009-06-14 05:19:35 +00:00
gboolean sms_extract_concatenation(const struct sms *sms, guint16 *ref_num,
guint8 *max_msgs, guint8 *seq_num)
{
struct sms_udh_iter iter;
enum sms_iei iei;
guint8 concat_hdr[4];
guint16 uninitialized_var(rn);
guint8 uninitialized_var(max), uninitialized_var(seq);
gboolean concatenated = FALSE;
2010-06-21 20:33:13 +00:00
/*
* We must ignore the entire user_data header here:
* If the length of the User Data Header is such that there
* are too few or too many octets in the final Information
* Element then the whole User Data Header shall be ignored.
*/
if (!sms_udh_iter_init(sms, &iter))
return FALSE;
2010-06-21 20:33:13 +00:00
/*
* According to the specification, we have to use the last
* useable header:
* In the event that IEs determined as not repeatable are
* duplicated, the last occurrence of the IE shall be used.
* In the event that two or more IEs occur which have mutually
* exclusive meanings (e.g. an 8bit port address and a 16bit
* port address), then the last occurring IE shall be used.
*/
while ((iei = sms_udh_iter_get_ie_type(&iter)) !=
SMS_IEI_INVALID) {
switch (iei) {
case SMS_IEI_CONCATENATED_8BIT:
if (sms_udh_iter_get_ie_length(&iter) != 3)
break;
sms_udh_iter_get_ie_data(&iter, concat_hdr);
if (concat_hdr[1] == 0)
break;
2009-06-14 05:20:06 +00:00
if (concat_hdr[2] == 0 || concat_hdr[2] > concat_hdr[1])
break;
rn = concat_hdr[0];
max = concat_hdr[1];
seq = concat_hdr[2];
concatenated = TRUE;
break;
case SMS_IEI_CONCATENATED_16BIT:
if (sms_udh_iter_get_ie_length(&iter) != 4)
break;
sms_udh_iter_get_ie_data(&iter, concat_hdr);
if (concat_hdr[2] == 0)
break;
if (concat_hdr[3] == 0 ||
2009-06-14 05:20:06 +00:00
concat_hdr[3] > concat_hdr[2])
break;
rn = (concat_hdr[0] << 8) | concat_hdr[1];
max = concat_hdr[2];
seq = concat_hdr[3];
concatenated = TRUE;
break;
default:
break;
}
sms_udh_iter_next(&iter);
}
if (!concatenated)
return FALSE;
if (ref_num)
*ref_num = rn;
if (max_msgs)
*max_msgs = max;
if (seq_num)
*seq_num = seq;
return TRUE;
}
2009-06-13 02:19:30 +00:00
gboolean sms_extract_language_variant(const struct sms *sms, guint8 *locking,
guint8 *single)
{
struct sms_udh_iter iter;
enum sms_iei iei;
guint8 variant;
2010-06-21 20:33:13 +00:00
/*
* We must ignore the entire user_data header here:
* If the length of the User Data Header is such that there
* are too few or too many octets in the final Information
* Element then the whole User Data Header shall be ignored.
*/
if (!sms_udh_iter_init(sms, &iter))
return FALSE;
2010-06-21 20:33:13 +00:00
/*
* According to the specification, we have to use the last
* useable header:
* In the event that IEs determined as not repeatable are
* duplicated, the last occurrence of the IE shall be used.
* In the event that two or more IEs occur which have mutually
* exclusive meanings (e.g. an 8bit port address and a 16bit
* port address), then the last occurring IE shall be used.
*/
while ((iei = sms_udh_iter_get_ie_type(&iter)) !=
SMS_IEI_INVALID) {
switch (iei) {
case SMS_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT:
if (sms_udh_iter_get_ie_length(&iter) != 1)
break;
sms_udh_iter_get_ie_data(&iter, &variant);
if (single)
*single = variant;
break;
case SMS_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT:
if (sms_udh_iter_get_ie_length(&iter) != 1)
break;
sms_udh_iter_get_ie_data(&iter, &variant);
if (locking)
*locking = variant;
break;
default:
break;
}
sms_udh_iter_next(&iter);
}
return TRUE;
}
2009-06-13 02:19:30 +00:00
/*!
* Decodes a list of SMSes that contain a datagram. The list must be
* sorted in order of the sequence number. This function assumes that
* all fragments are coded using 8-bit character set.
*
* Returns a pointer to a newly allocated array or NULL if the
* conversion could not be performed
*/
unsigned char *sms_decode_datagram(GSList *sms_list, long *out_len)
{
GSList *l;
const struct sms *sms;
unsigned char *buf;
long len = 0;
for (l = sms_list; l; l = l->next) {
guint8 taken = 0;
guint8 udl;
const guint8 *ud;
struct sms_udh_iter iter;
sms = l->data;
ud = sms_extract_common(sms, NULL, NULL, &udl, NULL);
if (ud == NULL)
2009-06-13 02:19:30 +00:00
return NULL;
2010-06-21 20:33:13 +00:00
/*
* Note we do this because we must check whether the UDH
2009-06-13 02:19:30 +00:00
* is properly formatted. If not, the entire UDH is ignored
*/
if (sms_udh_iter_init(sms, &iter))
taken = sms_udh_iter_get_udh_length(&iter) + 1;
len += udl - taken;
}
/* Data is probably in headers we can't understand */
if (len == 0)
return NULL;
buf = g_try_new(unsigned char, len);
if (buf == NULL)
2009-06-13 02:19:30 +00:00
return NULL;
len = 0;
for (l = sms_list; l; l = l->next) {
guint8 taken = 0;
guint8 udl;
const guint8 *ud;
struct sms_udh_iter iter;
sms = l->data;
ud = sms_extract_common(sms, NULL, NULL, &udl, NULL);
if (sms_udh_iter_init(sms, &iter))
taken = sms_udh_iter_get_udh_length(&iter) + 1;
memcpy(buf + len, ud + taken, udl - taken);
len += udl - taken;
}
if (out_len)
*out_len = len;
return buf;
}
2009-06-13 02:19:55 +00:00
2009-06-19 13:38:24 +00:00
static inline int sms_text_capacity_gsm(int max, int offset)
{
return max - (offset * 8 + 6) / 7;
}
2009-06-13 02:19:55 +00:00
/*!
* Decodes a list of SMSes that contain a text in either 7bit or UCS2 encoding.
* The list must be sorted in order of the sequence number. This function
* assumes that all fragments have a proper DCS.
*
* Returns a pointer to a newly allocated string or NULL if the conversion
* failed.
*/
char *sms_decode_text(GSList *sms_list)
{
GSList *l;
GString *str;
2010-02-04 06:24:14 +00:00
const struct sms *sms;
2009-06-13 02:19:55 +00:00
int guess_size = g_slist_length(sms_list);
char *utf8;
void *utf16 = NULL;
size_t utf16_size = 0;
2009-06-13 02:19:55 +00:00
if (guess_size == 1)
guess_size = 160;
else
guess_size = (guess_size - 1) * 160;
str = g_string_sized_new(guess_size);
for (l = sms_list; l; l = l->next) {
guint8 taken = 0;
guint8 dcs;
guint8 udl;
enum sms_charset charset;
int udl_in_bytes;
const guint8 *ud;
struct sms_udh_iter iter;
char *converted;
sms = l->data;
ud = sms_extract_common(sms, NULL, &dcs, &udl, NULL);
if (!sms_mwi_dcs_decode(dcs, NULL, &charset, NULL, NULL) &&
!sms_dcs_decode(dcs, NULL, &charset, NULL, NULL))
continue;
if (charset == SMS_CHARSET_8BIT)
continue;
if (sms_udh_iter_init(sms, &iter))
taken = sms_udh_iter_get_udh_length(&iter) + 1;
udl_in_bytes = sms_udl_in_bytes(udl, dcs);
2009-10-12 20:43:04 +00:00
if (udl_in_bytes == taken)
continue;
2009-06-13 02:19:55 +00:00
if (charset == SMS_CHARSET_7BIT) {
unsigned char buf[160];
long written;
guint8 locking_shift = 0;
guint8 single_shift = 0;
2009-06-19 13:38:24 +00:00
int max_chars = sms_text_capacity_gsm(udl, taken);
2009-06-13 02:19:55 +00:00
if (unpack_7bit_own_buf(ud + taken,
udl_in_bytes - taken,
2018-12-19 21:51:47 +00:00
taken, false, max_chars,
&written, 0, buf) == NULL)
continue;
2009-06-13 02:19:55 +00:00
/* Take care of improperly split fragments */
if (buf[written-1] == 0x1b)
written = written - 1;
2010-12-07 02:11:55 +00:00
sms_extract_language_variant(sms, &locking_shift,
&single_shift);
2010-06-21 20:33:13 +00:00
/*
* If language is not defined in 3GPP TS 23.038,
* implementations are instructed to ignore it
*/
if (locking_shift > SMS_ALPHABET_URDU)
locking_shift = GSM_DIALECT_DEFAULT;
if (single_shift > SMS_ALPHABET_URDU)
single_shift = GSM_DIALECT_DEFAULT;
converted = convert_gsm_to_utf8_with_lang(buf, written,
NULL, NULL, 0,
locking_shift,
single_shift);
if (converted) {
g_string_append(str, converted);
l_free(converted);
}
2009-06-13 02:19:55 +00:00
} else {
const guint8 *from = ud + taken;
2010-06-21 20:33:13 +00:00
/*
* According to the spec: A UCS2 character shall not be
2009-06-13 02:19:55 +00:00
* split in the middle; if the length of the User Data
* Header is odd, the maximum length of the whole TP-UD
* field is 139 octets
*/
size_t num_ucs2_chars = (udl_in_bytes - taken) >> 1;
2009-06-13 02:19:55 +00:00
num_ucs2_chars = num_ucs2_chars << 1;
/*
* In theory SMS supports encoding using UCS2 which
* is 16-bit, however in the real world messages
* are encoded in UTF-16 which can be 4 bytes and
* a multiple fragment message can split a 4-byte
* character in the middle. So accumulate the
* entire message before converting to UTF-8.
*/
utf16 = l_realloc(utf16, utf16_size + num_ucs2_chars);
memcpy(utf16 + utf16_size, from, num_ucs2_chars);
utf16_size += num_ucs2_chars;
2009-06-13 02:19:55 +00:00
}
}
if (utf16) {
char *converted;
/* Strings are in UTF16-BE, so convert if needed */
if (L_CPU_TO_BE16(0x8000) != 0x8000) {
size_t i;
uint16_t *p = utf16;
for (i = 0; i < utf16_size / 2; i++)
p[i] = __builtin_bswap16(p[i]);
}
converted = l_utf8_from_utf16(utf16, utf16_size);
2009-06-13 02:19:55 +00:00
if (converted) {
g_string_append(str, converted);
l_free(converted);
2009-06-13 02:19:55 +00:00
}
l_free(utf16);
2009-06-13 02:19:55 +00:00
}
utf8 = g_string_free(str, FALSE);
return utf8;
}
2009-06-14 07:00:06 +00:00
static int sms_serialize(unsigned char *buf, const struct sms *sms)
{
int len, tpdu_len;
sms_encode(sms, &len, &tpdu_len, buf + 1);
buf[0] = tpdu_len;
return len + 1;
}
static gboolean sms_deserialize(const unsigned char *buf,
struct sms *sms, int len)
{
if (len < 1)
return FALSE;
return sms_decode(buf + 1, len - 1, FALSE, buf[0], sms);
}
static gboolean sms_deserialize_outgoing(const unsigned char *buf,
struct sms *sms, int len)
{
if (len < 1)
return FALSE;
return sms_decode(buf + 1, len - 1, TRUE, buf[0], sms);
}
static gboolean sms_assembly_extract_address(const char *straddr,
struct sms_address *out)
{
unsigned char pdu[12];
long len;
int offset = 0;
if (decode_hex_own_buf(straddr, -1, &len, 0, pdu) == NULL)
return FALSE;
return sms_decode_address_field(pdu, len, &offset, FALSE, out);
}
gboolean sms_address_to_hex_string(const struct sms_address *in, char *straddr)
{
unsigned char pdu[12];
int offset = 0;
if (sms_encode_address_field(in, FALSE, pdu, &offset) == FALSE)
return FALSE;
if (encode_hex_own_buf(pdu, offset, 0, straddr) == NULL)
return FALSE;
straddr[offset * 2 + 1] = '\0';
return TRUE;
}
static void sms_assembly_load(struct sms_assembly *assembly,
const struct dirent *dir)
{
struct sms_address addr;
DECLARE_SMS_ADDR_STR(straddr);
guint16 ref;
guint8 max;
guint8 seq;
char *path;
int len;
struct stat segment_stat;
struct dirent **segments;
char *endp;
int r;
int i;
unsigned char buf[177];
struct sms segment;
if (dir->d_type != DT_DIR)
return;
/* Max of SMS address size is 12 bytes, hex encoded */
if (sscanf(dir->d_name, SMS_ADDR_FMT "-%hi-%hhi",
straddr, &ref, &max) < 3)
return;
if (sms_assembly_extract_address(straddr, &addr) == FALSE)
return;
path = g_strdup_printf(SMS_BACKUP_PATH "/%s",
assembly->imsi, dir->d_name);
len = scandir(path, &segments, NULL, versionsort);
g_free(path);
if (len < 0)
return;
for (i = 0; i < len; i++) {
if (segments[i]->d_type != DT_REG)
continue;
seq = strtol(segments[i]->d_name, &endp, 10);
if (*endp != '\0')
continue;
r = read_file(buf, sizeof(buf), SMS_BACKUP_PATH "/%s/%s",
assembly->imsi,
dir->d_name, segments[i]->d_name);
if (r < 0)
continue;
if (!sms_deserialize(buf, &segment, r))
continue;
path = g_strdup_printf(SMS_BACKUP_PATH "/%s/%s",
assembly->imsi,
dir->d_name, segments[i]->d_name);
r = stat(path, &segment_stat);
g_free(path);
if (r != 0)
continue;
2009-09-08 21:40:38 +00:00
/* Errors cannot occur here */
sms_assembly_add_fragment_backup(assembly, &segment,
segment_stat.st_mtime,
2009-09-08 21:40:38 +00:00
&addr, ref, max, seq, FALSE);
}
for (i = 0; i < len; i++)
free(segments[i]);
free(segments);
}
static gboolean sms_assembly_store(struct sms_assembly *assembly,
struct sms_assembly_node *node,
const struct sms *sms, guint8 seq)
{
unsigned char buf[177];
int len;
DECLARE_SMS_ADDR_STR(straddr);
if (assembly->imsi == NULL)
return FALSE;
if (sms_address_to_hex_string(&node->addr, straddr) == FALSE)
return FALSE;
len = sms_serialize(buf, sms);
if (write_file(buf, len, SMS_BACKUP_MODE,
SMS_BACKUP_PATH_FILE, assembly->imsi, straddr,
node->ref, node->max_fragments, seq) != len)
return FALSE;
return TRUE;
}
static void sms_assembly_backup_free(struct sms_assembly *assembly,
struct sms_assembly_node *node)
{
char *path;
int seq;
DECLARE_SMS_ADDR_STR(straddr);
if (assembly->imsi == NULL)
return;
if (sms_address_to_hex_string(&node->addr, straddr) == FALSE)
return;
for (seq = 0; seq < node->max_fragments; seq++) {
int offset = seq / 32;
int bit = 1 << (seq % 32);
if (node->bitmap[offset] & bit) {
path = g_strdup_printf(SMS_BACKUP_PATH_FILE,
assembly->imsi, straddr,
node->ref, node->max_fragments, seq);
unlink(path);
g_free(path);
}
}
path = g_strdup_printf(SMS_BACKUP_PATH_DIR, assembly->imsi, straddr,
node->ref, node->max_fragments);
rmdir(path);
g_free(path);
}
struct sms_assembly *sms_assembly_new(const char *imsi)
2009-06-14 07:00:06 +00:00
{
struct sms_assembly *ret = g_new0(struct sms_assembly, 1);
char *path;
struct dirent **entries;
int len;
if (imsi) {
ret->imsi = imsi;
/* Restore state from backup */
path = g_strdup_printf(SMS_BACKUP_PATH, imsi);
len = scandir(path, &entries, NULL, alphasort);
g_free(path);
if (len < 0)
return ret;
while (len--) {
sms_assembly_load(ret, entries[len]);
free(entries[len]);
}
free(entries);
}
return ret;
2009-06-14 07:00:06 +00:00
}
void sms_assembly_free(struct sms_assembly *assembly)
{
GSList *l;
for (l = assembly->assembly_list; l; l = l->next) {
struct sms_assembly_node *node = l->data;
2016-04-22 13:10:07 +00:00
g_slist_free_full(node->fragment_list, g_free);
2009-06-14 07:00:06 +00:00
g_free(node);
}
g_slist_free(assembly->assembly_list);
g_free(assembly);
}
GSList *sms_assembly_add_fragment(struct sms_assembly *assembly,
const struct sms *sms, time_t ts,
const struct sms_address *addr,
guint16 ref, guint8 max, guint8 seq)
{
return sms_assembly_add_fragment_backup(assembly, sms,
ts, addr, ref, max, seq, TRUE);
}
static GSList *sms_assembly_add_fragment_backup(struct sms_assembly *assembly,
const struct sms *sms, time_t ts,
const struct sms_address *addr,
guint16 ref, guint8 max, guint8 seq,
gboolean backup)
2009-06-14 07:00:06 +00:00
{
unsigned int offset = seq / 32;
unsigned int bit = 1 << (seq % 32);
2009-06-14 07:00:06 +00:00
GSList *l;
GSList *prev;
struct sms *newsms;
struct sms_assembly_node *node;
GSList *completed;
unsigned int position;
unsigned int i;
unsigned int j;
2009-06-14 07:00:06 +00:00
prev = NULL;
for (l = assembly->assembly_list; l; prev = l, l = l->next) {
node = l->data;
if (node->addr.number_type != addr->number_type)
continue;
if (node->addr.numbering_plan != addr->numbering_plan)
continue;
if (strcmp(node->addr.address, addr->address))
continue;
if (ref != node->ref)
continue;
2010-09-01 15:59:32 +00:00
/*
* Message Reference and address the same, but max is not
2009-06-14 07:00:06 +00:00
* ignore the SMS completely
*/
if (max != node->max_fragments)
return NULL;
/* Now check if we already have this seq number */
if (node->bitmap[offset] & bit)
return NULL;
/*
* Iterate over the bitmap to find in which position
* should the fragment be inserted -- basically we
* walk each bit in the bitmap until the bit we care
* about (offset:bit) and count which are stored --
* that gives us in which position we have to insert.
*/
2009-06-14 07:00:06 +00:00
position = 0;
for (i = 0; i < offset; i++)
for (j = 0; j < 32; j++)
if (node->bitmap[i] & (1 << j))
position += 1;
for (j = 1; j < bit; j = j << 1)
if (node->bitmap[offset] & j)
position += 1;
goto out;
}
node = g_new0(struct sms_assembly_node, 1);
memcpy(&node->addr, addr, sizeof(struct sms_address));
node->ts = ts;
node->ref = ref;
node->max_fragments = max;
assembly->assembly_list = g_slist_prepend(assembly->assembly_list,
node);
prev = NULL;
l = assembly->assembly_list;
position = 0;
out:
newsms = g_new(struct sms, 1);
memcpy(newsms, sms, sizeof(struct sms));
node->fragment_list = g_slist_insert(node->fragment_list,
newsms, position);
node->bitmap[offset] |= bit;
node->num_fragments += 1;
if (node->num_fragments < node->max_fragments) {
if (backup)
sms_assembly_store(assembly, node, sms, seq);
2009-06-14 07:00:06 +00:00
return NULL;
}
2009-06-14 07:00:06 +00:00
completed = node->fragment_list;
sms_assembly_backup_free(assembly, node);
2009-06-14 07:00:06 +00:00
if (prev)
prev->next = l->next;
else
assembly->assembly_list = l->next;
g_free(node);
g_slist_free_1(l);
2009-06-14 07:00:06 +00:00
return completed;
}
/*!
* Expires all incomplete messages that have been received at time prior
* to one given by before argument. The fragment list is freed and the
* SMSes are vaporized.
*/
void sms_assembly_expire(struct sms_assembly *assembly, time_t before)
{
GSList *cur;
GSList *prev;
GSList *tmp;
2009-06-14 07:00:06 +00:00
prev = NULL;
cur = assembly->assembly_list;
while (cur) {
struct sms_assembly_node *node = cur->data;
if (node->ts > before) {
prev = cur;
cur = cur->next;
continue;
}
sms_assembly_backup_free(assembly, node);
2016-04-22 13:10:07 +00:00
g_slist_free_full(node->fragment_list, g_free);
2009-06-14 07:00:06 +00:00
g_free(node);
if (prev)
prev->next = cur->next;
else
assembly->assembly_list = cur->next;
tmp = cur;
2009-06-14 07:00:06 +00:00
cur = cur->next;
g_slist_free_1(tmp);
2009-06-14 07:00:06 +00:00
}
}
static gboolean sha1_equal(gconstpointer v1, gconstpointer v2)
{
return memcmp(v1, v2, SMS_MSGID_LEN) == 0;
}
static guint sha1_hash(gconstpointer v)
{
guint h;
memcpy(&h, v, sizeof(h));
return h;
}
static void sr_assembly_load_backup(GHashTable *assembly_table,
const char *imsi,
const struct dirent *addr_dir)
{
struct sms_address addr;
DECLARE_SMS_ADDR_STR(straddr);
struct id_table_node *node;
GHashTable *id_table;
int r;
char *assembly_table_key;
unsigned int *id_table_key;
char msgid_str[SMS_MSGID_LEN * 2 + 1];
unsigned char msgid[SMS_MSGID_LEN];
char endc;
if (addr_dir->d_type != DT_REG)
return;
/*
* All SMS-messages under the same IMSI-code are
* included in the same directory.
* So, SMS-address and message ID are included in the same file name
* Max of SMS address size is 12 bytes, hex encoded
* Max of SMS SHA1 hash is 20 bytes, hex encoded
*/
if (sscanf(addr_dir->d_name, SMS_ADDR_FMT "-" SMS_MSGID_FMT "%c",
straddr, msgid_str, &endc) != 2)
return;
if (sms_assembly_extract_address(straddr, &addr) == FALSE)
return;
if (strlen(msgid_str) != 2 * SMS_MSGID_LEN)
return;
if (decode_hex_own_buf(msgid_str, 2 * SMS_MSGID_LEN,
NULL, 0, msgid) == NULL)
return;
node = g_new0(struct id_table_node, 1);
r = read_file((unsigned char *) node,
sizeof(struct id_table_node),
SMS_SR_BACKUP_PATH "/%s",
imsi, addr_dir->d_name);
if (r < 0) {
g_free(node);
return;
}
id_table = g_hash_table_lookup(assembly_table,
sms_address_to_string(&addr));
/* Create hashtable keyed by the to address if required */
if (id_table == NULL) {
id_table = g_hash_table_new_full(sha1_hash, sha1_equal,
g_free, g_free);
assembly_table_key = g_strdup(sms_address_to_string(&addr));
g_hash_table_insert(assembly_table, assembly_table_key,
id_table);
}
/* Node ready, create key and add them to the table */
id_table_key = g_memdup(msgid, SMS_MSGID_LEN);
g_hash_table_insert(id_table, id_table_key, node);
}
2010-06-17 13:14:54 +00:00
struct status_report_assembly *status_report_assembly_new(const char *imsi)
{
char *path;
int len;
struct dirent **addresses;
2010-06-17 13:14:54 +00:00
struct status_report_assembly *ret =
g_new0(struct status_report_assembly, 1);
ret->assembly_table = g_hash_table_new_full(g_str_hash, g_str_equal,
2010-11-19 13:57:22 +00:00
g_free, (GDestroyNotify) g_hash_table_destroy);
2010-06-17 13:14:54 +00:00
if (imsi) {
2010-06-17 13:14:54 +00:00
ret->imsi = imsi;
/* Restore state from backup */
path = g_strdup_printf(SMS_SR_BACKUP_PATH, imsi);
len = scandir(path, &addresses, NULL, alphasort);
g_free(path);
if (len < 0)
return ret;
/*
* Go through different addresses. Each address can relate to
* 1-n msg_ids.
*/
while (len--) {
sr_assembly_load_backup(ret->assembly_table, imsi,
addresses[len]);
g_free(addresses[len]);
}
g_free(addresses);
}
2010-06-17 13:14:54 +00:00
return ret;
}
static gboolean sr_assembly_add_fragment_backup(const char *imsi,
const struct id_table_node *node,
const struct sms_address *addr,
const unsigned char *msgid)
{
int len = sizeof(struct id_table_node);
DECLARE_SMS_ADDR_STR(straddr);
char msgid_str[SMS_MSGID_LEN * 2 + 1];
if (imsi == NULL)
return FALSE;
if (sms_address_to_hex_string(addr, straddr) == FALSE)
return FALSE;
if (encode_hex_own_buf(msgid, SMS_MSGID_LEN, 0, msgid_str) == NULL)
return FALSE;
/* storagedir/%s/sms_sr/%s-%s */
if (write_file((unsigned char *) node, len, SMS_BACKUP_MODE,
SMS_SR_BACKUP_PATH_FILE, imsi,
straddr, msgid_str) != len)
return FALSE;
return TRUE;
}
static gboolean sr_assembly_remove_fragment_backup(const char *imsi,
const struct sms_address *addr,
const unsigned char *sha1)
{
char *path;
DECLARE_SMS_ADDR_STR(straddr);
char msgid_str[SMS_MSGID_LEN * 2 + 1];
if (imsi == NULL)
return FALSE;
if (sms_address_to_hex_string(addr, straddr) == FALSE)
return FALSE;
if (encode_hex_own_buf(sha1, SMS_MSGID_LEN, 0, msgid_str) == FALSE)
return FALSE;
path = g_strdup_printf(SMS_SR_BACKUP_PATH_FILE,
imsi, straddr, msgid_str);
unlink(path);
g_free(path);
return TRUE;
}
2010-06-17 13:14:54 +00:00
void status_report_assembly_free(struct status_report_assembly *assembly)
{
g_hash_table_destroy(assembly->assembly_table);
g_free(assembly);
}
static gboolean sr_st_to_delivered(enum sms_st st, gboolean *delivered)
{
if (st >= SMS_ST_TEMPFINAL_CONGESTION && st <= SMS_ST_TEMPFINAL_LAST)
return FALSE;
if (st >= SMS_ST_TEMPORARY_CONGESTION && st <= SMS_ST_TEMPORARY_LAST)
return FALSE;
if (st <= SMS_ST_COMPLETED_LAST) {
*delivered = TRUE;
return TRUE;
}
if (st >= SMS_ST_PERMANENT_RP_ERROR && st <= SMS_ST_PERMANENT_LAST) {
*delivered = FALSE;
return TRUE;
}
return FALSE;
}
static struct id_table_node *find_by_mr_and_mark(GHashTable *id_table,
unsigned char mr,
GHashTableIter *out_iter,
unsigned char **out_id)
2010-06-17 13:14:54 +00:00
{
unsigned int offset = mr / 32;
unsigned int bit = 1 << (mr % 32);
gpointer key, value;
struct id_table_node *node;
2010-06-17 13:14:54 +00:00
g_hash_table_iter_init(out_iter, id_table);
while (g_hash_table_iter_next(out_iter, &key, &value)) {
node = value;
/* Address and MR matched */
if (node->mrs[offset] & bit) {
node->mrs[offset] ^= bit;
*out_id = key;
return node;
}
}
return NULL;
}
/*
* Key (receiver address) does not exist in assembly. Some networks can change
* address to international format, although address is sent in the national
* format. Handle also change from national to international format.
* Notify these special cases by comparing only last six digits of the assembly
* addresses and received address. If address contains less than six digits,
* compare only existing digits.
*/
static struct id_table_node *fuzzy_lookup(struct status_report_assembly *assy,
const struct sms *sr,
const char **out_addr,
GHashTableIter *out_iter,
unsigned char **out_msgid)
{
GHashTableIter iter_addr;
gpointer key, value;
const char *r_addr;
2010-06-17 13:14:54 +00:00
r_addr = sms_address_to_string(&sr->status_report.raddr);
g_hash_table_iter_init(&iter_addr, assy->assembly_table);
while (g_hash_table_iter_next(&iter_addr, &key, &value)) {
const char *s_addr = key;
GHashTable *id_table = value;
unsigned int len, r_len, s_len;
unsigned int i;
struct id_table_node *node;
if (r_addr[0] == '+' && s_addr[0] == '+')
continue;
if (r_addr[0] != '+' && s_addr[0] != '+')
continue;
r_len = strlen(r_addr);
s_len = strlen(s_addr);
len = MIN(6, MIN(r_len, s_len));
for (i = 0; i < len; i++)
if (s_addr[s_len - i - 1] != r_addr[r_len - i - 1])
break;
/* Not all digits matched. */
if (i < len)
continue;
/* Address matched. Check message reference. */
node = find_by_mr_and_mark(id_table, sr->status_report.mr,
out_iter, out_msgid);
if (node != NULL) {
*out_addr = s_addr;
return node;
}
}
2010-06-17 13:14:54 +00:00
return NULL;
}
gboolean status_report_assembly_report(struct status_report_assembly *assembly,
const struct sms *sr,
unsigned char *out_msgid,
gboolean *out_delivered)
{
const char *straddr;
GHashTable *id_table;
GHashTableIter iter;
struct sms_address addr;
struct id_table_node *node;
gboolean delivered;
gboolean pending;
unsigned char *msgid;
int i;
/* We ignore temporary or tempfinal status reports */
if (sr_st_to_delivered(sr->status_report.st, &delivered) == FALSE)
return FALSE;
straddr = sms_address_to_string(&sr->status_report.raddr);
id_table = g_hash_table_lookup(assembly->assembly_table, straddr);
if (id_table != NULL)
node = find_by_mr_and_mark(id_table, sr->status_report.mr,
&iter, &msgid);
else
node = fuzzy_lookup(assembly, sr, &straddr, &iter, &msgid);
/* Unable to find a message reference belonging to this address */
if (node == NULL)
return FALSE;
node->deliverable = node->deliverable && delivered;
/* If we haven't sent the entire message yet, wait until sent */
if (node->sent_mrs < node->total_mrs)
return FALSE;
/* Figure out if we are expecting more status reports */
for (i = 0, pending = FALSE; i < 8; i++) {
/* There are still pending mr(s). */
if (node->mrs[i] != 0) {
pending = TRUE;
break;
2010-06-17 13:14:54 +00:00
}
}
2010-06-17 13:14:54 +00:00
sms_address_from_string(&addr, straddr);
if (pending == TRUE && node->deliverable == TRUE) {
/*
* More status reports expected, and already received
* reports completed. Update backup file.
*/
sr_assembly_add_fragment_backup(assembly->imsi, node,
&addr, msgid);
return FALSE;
}
if (out_delivered)
*out_delivered = node->deliverable;
if (out_msgid)
memcpy(out_msgid, msgid, SMS_MSGID_LEN);
sr_assembly_remove_fragment_backup(assembly->imsi, &addr, msgid);
id_table = g_hash_table_iter_get_hash_table(&iter);
g_hash_table_iter_remove(&iter);
if (g_hash_table_size(id_table) == 0)
g_hash_table_remove(assembly->assembly_table, straddr);
return TRUE;
2010-06-17 13:14:54 +00:00
}
void status_report_assembly_add_fragment(
struct status_report_assembly *assembly,
const unsigned char *msgid,
2010-06-17 13:14:54 +00:00
const struct sms_address *to,
unsigned char mr, time_t expiration,
unsigned char total_mrs)
{
unsigned int offset = mr / 32;
unsigned int bit = 1 << (mr % 32);
GHashTable *id_table;
struct id_table_node *node;
unsigned char *id_table_key;
2010-06-17 13:14:54 +00:00
id_table = g_hash_table_lookup(assembly->assembly_table,
sms_address_to_string(to));
/* Create hashtable keyed by the to address if required */
2010-06-17 13:14:54 +00:00
if (id_table == NULL) {
id_table = g_hash_table_new_full(sha1_hash, sha1_equal,
2010-06-17 13:14:54 +00:00
g_free, g_free);
g_hash_table_insert(assembly->assembly_table,
g_strdup(sms_address_to_string(to)),
id_table);
2010-06-17 13:14:54 +00:00
}
node = g_hash_table_lookup(id_table, msgid);
/* Create node in the message id hashtable if required */
2010-06-17 13:14:54 +00:00
if (node == NULL) {
id_table_key = g_memdup(msgid, SMS_MSGID_LEN);
2010-06-17 13:14:54 +00:00
node = g_new0(struct id_table_node, 1);
node->total_mrs = total_mrs;
node->deliverable = TRUE;
g_hash_table_insert(id_table, id_table_key, node);
}
2010-06-17 13:14:54 +00:00
/* id_table and node both exists */
node->mrs[offset] |= bit;
node->expiration = expiration;
node->sent_mrs++;
sr_assembly_add_fragment_backup(assembly->imsi, node, to, msgid);
2010-06-17 13:14:54 +00:00
}
void status_report_assembly_expire(struct status_report_assembly *assembly,
2010-09-10 14:28:19 +00:00
time_t before)
2010-06-17 13:14:54 +00:00
{
2010-09-10 14:28:19 +00:00
GHashTable *id_table;
GHashTableIter iter_addr, iter_node;
struct sms_address addr;
char *straddr;
gpointer key;
struct id_table_node *node;
g_hash_table_iter_init(&iter_addr, assembly->assembly_table);
/*
* Go through different addresses. Each address can relate to
* 1-n msg_ids.
*/
while (g_hash_table_iter_next(&iter_addr, (gpointer) &straddr,
(gpointer) &id_table)) {
sms_address_from_string(&addr, straddr);
g_hash_table_iter_init(&iter_node, id_table);
/* Go through different messages. */
while (g_hash_table_iter_next(&iter_node, &key,
(gpointer) &node)) {
/*
* If message is expired, removed it from the
* hash-table and remove the backup-file
*/
if (node->expiration <= before) {
g_hash_table_iter_remove(&iter_node);
sr_assembly_remove_fragment_backup(
assembly->imsi,
&addr,
key);
2010-09-10 14:28:19 +00:00
}
}
/*
* If all messages are removed, remove address
* from the hash-table.
*/
if (g_hash_table_size(id_table) == 0)
g_hash_table_iter_remove(&iter_addr);
}
2010-06-17 13:14:54 +00:00
}
static int sms_tx_load_filter(const struct dirent *dent)
{
char *endp;
guint8 seq __attribute__ ((unused));
if (dent->d_type != DT_REG)
return 0;
seq = strtol(dent->d_name, &endp, 10);
if (*endp != '\0')
return 0;
return 1;
}
/*
* Each directory contains a file per pdu.
*/
static GSList *sms_tx_load(const char *imsi, const struct dirent *dir)
{
GSList *list = NULL;
struct dirent **pdus;
char *path;
int len, r;
unsigned char buf[177];
struct sms s;
if (dir->d_type != DT_DIR)
return NULL;
path = g_strdup_printf(SMS_TX_BACKUP_PATH "/%s", imsi, dir->d_name);
len = scandir(path, &pdus, sms_tx_load_filter, versionsort);
g_free(path);
if (len < 0)
return NULL;
while (len--) {
r = read_file(buf, sizeof(buf), SMS_TX_BACKUP_PATH "/%s/%s",
imsi, dir->d_name, pdus[len]->d_name);
if (r < 0)
goto free_pdu;
if (sms_deserialize_outgoing(buf, &s, r) == FALSE)
goto free_pdu;
list = g_slist_prepend(list, g_memdup(&s, sizeof(s)));
free_pdu:
g_free(pdus[len]);
}
g_free(pdus);
return list;
}
static int sms_tx_queue_filter(const struct dirent *dirent)
{
if (dirent->d_type != DT_DIR)
return 0;
if (!strcmp(dirent->d_name, ".") || !strcmp(dirent->d_name, ".."))
return 0;
return 1;
}
/*
* populate the queue with tx_backup_entry from stored backup
* data.
*/
GQueue *sms_tx_queue_load(const char *imsi)
{
GQueue *retq = 0;
char *path;
struct dirent **entries;
int len;
int i;
unsigned long id;
if (imsi == NULL)
return NULL;
path = g_strdup_printf(SMS_TX_BACKUP_PATH, imsi);
len = scandir(path, &entries, sms_tx_queue_filter, versionsort);
if (len < 0)
goto nodir_exit;
retq = g_queue_new();
for (i = 0, id = 0; i < len; i++) {
char uuid[SMS_MSGID_LEN * 2 + 1];
GSList *msg_list;
unsigned long oldid;
unsigned long flags;
char *oldpath, *newpath;
struct txq_backup_entry *entry;
struct dirent *dir = entries[i];
char endc;
if (sscanf(dir->d_name, "%lu-%lu-" SMS_MSGID_FMT "%c",
&oldid, &flags, uuid, &endc) != 3)
continue;
if (strlen(uuid) != 2 * SMS_MSGID_LEN)
continue;
msg_list = sms_tx_load(imsi, dir);
if (msg_list == NULL)
continue;
entry = g_new0(struct txq_backup_entry, 1);
entry->msg_list = msg_list;
entry->flags = flags;
decode_hex_own_buf(uuid, -1, NULL, 0, entry->uuid);
g_queue_push_tail(retq, entry);
/* Don't bother re-shuffling the ids if they are the same */
if (oldid == id) {
id++;
continue;
}
oldpath = g_strdup_printf("%s/%s", path, dir->d_name);
newpath = g_strdup_printf(SMS_TX_BACKUP_PATH_DIR,
imsi, id++, flags, uuid);
/* rename directory to reflect new position in queue */
rename(oldpath, newpath);
g_free(newpath);
g_free(oldpath);
}
for (i = 0; i < len; i++)
g_free(entries[i]);
g_free(entries);
nodir_exit:
g_free(path);
return retq;
}
gboolean sms_tx_backup_store(const char *imsi, unsigned long id,
unsigned long flags, const char *uuid,
guint8 seq, const unsigned char *pdu,
int pdu_len, int tpdu_len)
{
unsigned char buf[177];
int len;
if (!imsi)
return FALSE;
memcpy(buf + 1, pdu, pdu_len);
buf[0] = tpdu_len;
len = pdu_len + 1;
/*
* file name is: imsi/tx_queue/order-flags-uuid/pdu
*/
if (write_file(buf, len, SMS_BACKUP_MODE, SMS_TX_BACKUP_PATH_FILE,
imsi, id, flags, uuid, seq) != len)
return FALSE;
return TRUE;
}
void sms_tx_backup_free(const char *imsi, unsigned long id,
unsigned long flags, const char *uuid)
{
char *path;
struct dirent **entries;
int len;
path = g_strdup_printf(SMS_TX_BACKUP_PATH_DIR,
imsi, id, flags, uuid);
len = scandir(path, &entries, NULL, versionsort);
if (len < 0)
goto nodir_exit;
/* skip '..' and '.' entries */
while (len-- > 2) {
struct dirent *dir = entries[len];
char *file = g_strdup_printf("%s/%s", path, dir->d_name);
unlink(file);
g_free(file);
g_free(entries[len]);
}
g_free(entries[1]);
g_free(entries[0]);
g_free(entries);
rmdir(path);
nodir_exit:
g_free(path);
}
void sms_tx_backup_remove(const char *imsi, unsigned long id,
unsigned long flags, const char *uuid,
guint8 seq)
{
char *path;
path = g_strdup_printf(SMS_TX_BACKUP_PATH_FILE,
imsi, id, flags, uuid, seq);
unlink(path);
g_free(path);
}
static inline GSList *sms_list_append(GSList *l, const struct sms *in)
{
struct sms *sms;
sms = g_new(struct sms, 1);
memcpy(sms, in, sizeof(struct sms));
l = g_slist_prepend(l, sms);
return l;
}
2010-11-02 18:40:03 +00:00
/*
* Prepares a datagram for transmission. Breaks up into fragments if
* necessary using ref as the concatenated message reference number.
* Returns a list of sms messages in order.
2010-11-02 18:40:03 +00:00
*
* @use_delivery_reports: value for the Status-Report-Request field
* (23.040 3.2.9, 9.2.2.2)
*/
GSList *sms_datagram_prepare(const char *to,
const unsigned char *data, unsigned int len,
guint16 ref, gboolean use_16bit_ref,
unsigned short src, unsigned short dst,
gboolean use_16bit_port,
gboolean use_delivery_reports)
{
struct sms template;
unsigned int offset;
unsigned int written;
unsigned int left;
guint8 seq;
GSList *r = NULL;
memset(&template, 0, sizeof(struct sms));
template.type = SMS_TYPE_SUBMIT;
template.submit.rd = FALSE;
template.submit.vpf = SMS_VALIDITY_PERIOD_FORMAT_RELATIVE;
template.submit.rp = FALSE;
template.submit.srr = use_delivery_reports;
template.submit.mr = 0;
template.submit.vp.relative = 0xA7; /* 24 Hours */
template.submit.dcs = 0x04; /* Class Unspecified, 8 Bit */
template.submit.udhi = TRUE;
sms_address_from_string(&template.submit.daddr, to);
offset = 1;
if (use_16bit_port) {
template.submit.ud[0] += 6;
template.submit.ud[offset] = SMS_IEI_APPLICATION_ADDRESS_16BIT;
template.submit.ud[offset + 1] = 4;
template.submit.ud[offset + 2] = (dst & 0xff00) >> 8;
template.submit.ud[offset + 3] = dst & 0xff;
template.submit.ud[offset + 4] = (src & 0xff00) >> 8;
template.submit.ud[offset + 5] = src & 0xff;
2010-11-02 18:40:03 +00:00
offset += 6;
} else {
template.submit.ud[0] += 4;
template.submit.ud[offset] = SMS_IEI_APPLICATION_ADDRESS_8BIT;
template.submit.ud[offset + 1] = 2;
template.submit.ud[offset + 2] = dst & 0xff;
template.submit.ud[offset + 3] = src & 0xff;
2010-11-02 18:40:03 +00:00
offset += 4;
}
if (len <= (140 - offset)) {
template.submit.udl = len + offset;
memcpy(template.submit.ud + offset, data, len);
return sms_list_append(NULL, &template);
}
if (use_16bit_ref) {
template.submit.ud[0] += 6;
template.submit.ud[offset] = SMS_IEI_CONCATENATED_16BIT;
template.submit.ud[offset + 1] = 4;
template.submit.ud[offset + 2] = (ref & 0xff00) >> 8;
template.submit.ud[offset + 3] = ref & 0xff;
2010-11-02 18:40:03 +00:00
offset += 6;
} else {
template.submit.ud[0] += 5;
template.submit.ud[offset] = SMS_IEI_CONCATENATED_8BIT;
template.submit.ud[offset + 1] = 3;
template.submit.ud[offset + 2] = ref & 0xff;
2010-11-02 18:40:03 +00:00
offset += 5;
}
seq = 0;
left = len;
written = 0;
while (left > 0) {
unsigned int chunk;
seq += 1;
chunk = 140 - offset;
if (left < chunk)
chunk = left;
template.submit.udl = chunk + offset;
memcpy(template.submit.ud + offset, data + written, chunk);
written += chunk;
left -= chunk;
template.submit.ud[offset - 1] = seq;
r = sms_list_append(r, &template);
if (seq == 255)
break;
}
if (left > 0) {
2016-04-22 13:10:07 +00:00
g_slist_free_full(r, g_free);
2010-11-02 18:40:03 +00:00
return NULL;
} else {
GSList *l;
for (l = r; l; l = l->next) {
struct sms *sms = l->data;
sms->submit.ud[offset - 2] = seq;
}
}
r = g_slist_reverse(r);
return r;
}
2010-09-21 19:18:15 +00:00
/*
* Prepares the text for transmission. Breaks up into fragments if
2009-06-19 15:16:11 +00:00
* necessary using ref as the concatenated message reference number.
* Returns a list of sms messages in order.
*
* @use_delivery_reports: value for the Status-Report-Request field
* (23.040 3.2.9, 9.2.2.2)
2009-06-19 15:16:11 +00:00
*/
GSList *sms_text_prepare_with_alphabet(const char *to, const char *utf8,
guint16 ref, gboolean use_16bit,
gboolean use_delivery_reports,
enum sms_alphabet alphabet)
{
struct sms template;
int offset = 0;
unsigned char *gsm_encoded = NULL;
void *ucs2_encoded = NULL;
long written;
long left;
guint8 seq;
GSList *r = NULL;
enum gsm_dialect used_locking;
enum gsm_dialect used_single;
enum gsm_dialect dialect;
memset(&template, 0, sizeof(struct sms));
template.type = SMS_TYPE_SUBMIT;
template.submit.rd = FALSE;
template.submit.vpf = SMS_VALIDITY_PERIOD_FORMAT_RELATIVE;
template.submit.rp = FALSE;
template.submit.srr = use_delivery_reports;
template.submit.mr = 0;
template.submit.vp.relative = 0xA7; /* 24 Hours */
sms_address_from_string(&template.submit.daddr, to);
/* There are two enums for the same thing */
dialect = (enum gsm_dialect)alphabet;
/*
* UDHI, UDL, UD and DCS actually depend on the contents of
* the text, and also on the GSM dialect we use to encode it.
*/
gsm_encoded = convert_utf8_to_gsm_best_lang(utf8, -1, NULL, &written, 0,
dialect, &used_locking,
&used_single);
if (!gsm_encoded) {
size_t converted;
2009-06-22 17:45:18 +00:00
ucs2_encoded = l_utf8_to_ucs2be(utf8, &converted);
if (!ucs2_encoded)
return NULL;
written = converted - 2;
}
if (gsm_encoded != NULL)
template.submit.dcs = 0x00; /* Class Unspecified, 7 Bit */
else
template.submit.dcs = 0x08; /* Class Unspecified, UCS2 */
if (gsm_encoded != NULL && used_single != GSM_DIALECT_DEFAULT) {
if (!offset)
offset = 1;
template.submit.ud[0] += 3;
template.submit.ud[offset] = SMS_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT;
template.submit.ud[offset + 1] = 1;
template.submit.ud[offset + 2] = used_single;
offset += 3;
}
if (gsm_encoded != NULL && used_locking != GSM_DIALECT_DEFAULT) {
if (!offset)
offset = 1;
template.submit.ud[0] += 3;
template.submit.ud[offset] = SMS_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT;
template.submit.ud[offset + 1] = 1;
template.submit.ud[offset + 2] = used_locking;
offset += 3;
}
if (offset != 0)
template.submit.udhi = TRUE;
if (gsm_encoded && (written <= sms_text_capacity_gsm(160, offset))) {
template.submit.udl = written + (offset * 8 + 6) / 7;
2018-12-19 21:51:47 +00:00
pack_7bit_own_buf(gsm_encoded, written, offset, false, NULL,
0, template.submit.ud + offset);
2018-12-21 18:35:06 +00:00
l_free(gsm_encoded);
return sms_list_append(NULL, &template);
}
if (ucs2_encoded && (written <= (140 - offset))) {
template.submit.udl = written + offset;
memcpy(template.submit.ud + offset, ucs2_encoded, written);
l_free(ucs2_encoded);
return sms_list_append(NULL, &template);
}
template.submit.udhi = TRUE;
if (!offset)
offset = 1;
if (use_16bit) {
template.submit.ud[0] += 6;
template.submit.ud[offset] = SMS_IEI_CONCATENATED_16BIT;
template.submit.ud[offset + 1] = 4;
template.submit.ud[offset + 2] = (ref & 0xff00) >> 8;
template.submit.ud[offset + 3] = ref & 0xff;
offset += 6;
} else {
template.submit.ud[0] += 5;
template.submit.ud[offset] = SMS_IEI_CONCATENATED_8BIT;
template.submit.ud[offset + 1] = 3;
template.submit.ud[offset + 2] = ref & 0xff;
offset += 5;
}
2009-06-19 17:49:25 +00:00
seq = 0;
left = written;
written = 0;
while (left > 0) {
long chunk;
2009-06-19 17:49:25 +00:00
seq += 1;
if (gsm_encoded) {
chunk = sms_text_capacity_gsm(160, offset);
if (left < chunk)
chunk = left;
if (gsm_encoded[written + chunk - 1] == 0x1b)
chunk -= 1;
template.submit.udl = chunk + (offset * 8 + 6) / 7;
pack_7bit_own_buf(gsm_encoded + written, chunk,
2018-12-19 21:51:47 +00:00
offset, false, NULL, 0,
template.submit.ud + offset);
} else {
chunk = 140 - offset;
chunk &= ~0x1;
if (left < chunk)
chunk = left;
template.submit.udl = chunk + offset;
memcpy(template.submit.ud + offset,
ucs2_encoded + written, chunk);
}
written += chunk;
left -= chunk;
template.submit.ud[offset - 1] = seq;
r = sms_list_append(r, &template);
if (seq == 255)
break;
}
if (gsm_encoded)
g_free(gsm_encoded);
if (ucs2_encoded)
l_free(ucs2_encoded);
if (left > 0) {
2016-04-22 13:10:07 +00:00
g_slist_free_full(r, g_free);
return NULL;
} else {
GSList *l;
for (l = r; l; l = l->next) {
struct sms *sms = l->data;
sms->submit.ud[offset - 2] = seq;
}
}
r = g_slist_reverse(r);
return r;
}
2009-06-26 00:15:50 +00:00
GSList *sms_text_prepare(const char *to, const char *utf8, guint16 ref,
gboolean use_16bit,
gboolean use_delivery_reports)
{
return sms_text_prepare_with_alphabet(to, utf8, ref, use_16bit,
use_delivery_reports,
SMS_ALPHABET_DEFAULT);
}
2009-06-26 00:15:50 +00:00
gboolean cbs_dcs_decode(guint8 dcs, gboolean *udhi, enum sms_class *cls,
enum sms_charset *charset, gboolean *compressed,
enum cbs_language *language, gboolean *iso639)
{
guint8 upper = (dcs & 0xf0) >> 4;
guint8 lower = dcs & 0xf;
enum sms_charset ch;
enum sms_class cl;
enum cbs_language lang = CBS_LANGUAGE_UNSPECIFIED;
gboolean iso = FALSE;
gboolean comp = FALSE;
gboolean udh = FALSE;
if (upper == 0x3 || upper == 0x8 || (upper >= 0xA && upper <= 0xE))
return FALSE;
switch (upper) {
case 0:
ch = SMS_CHARSET_7BIT;
cl = SMS_CLASS_UNSPECIFIED;
2010-11-19 13:57:22 +00:00
lang = (enum cbs_language) lower;
2009-06-26 00:15:50 +00:00
break;
case 1:
if (lower > 1)
return FALSE;
if (lower == 0)
ch = SMS_CHARSET_7BIT;
else
ch = SMS_CHARSET_UCS2;
cl = SMS_CLASS_UNSPECIFIED;
iso = TRUE;
break;
case 2:
if (lower > 4)
return FALSE;
ch = SMS_CHARSET_7BIT;
cl = SMS_CLASS_UNSPECIFIED;
2010-11-19 13:57:22 +00:00
lang = (enum cbs_language) dcs;
2009-06-26 00:15:50 +00:00
break;
case 4:
case 5:
case 6:
case 7:
comp = (dcs & 0x20) ? TRUE : FALSE;
if (dcs & 0x10)
2010-11-19 13:57:22 +00:00
cl = (enum sms_class) (dcs & 0x03);
2009-06-26 00:15:50 +00:00
else
cl = SMS_CLASS_UNSPECIFIED;
if (((dcs & 0x0c) >> 2) < 3)
2010-11-19 13:57:22 +00:00
ch = (enum sms_charset) ((dcs & 0x0c) >> 2);
2009-06-26 00:15:50 +00:00
else
return FALSE;
2010-02-04 06:05:57 +00:00
break;
2009-06-26 00:15:50 +00:00
case 9:
udh = TRUE;
2010-11-19 13:57:22 +00:00
cl = (enum sms_class) (dcs & 0x03);
2009-06-26 00:15:50 +00:00
if (((dcs & 0x0c) >> 2) < 3)
2010-11-19 13:57:22 +00:00
ch = (enum sms_charset) ((dcs & 0x0c) >> 2);
2009-06-26 00:15:50 +00:00
else
return FALSE;
break;
case 15:
if (lower & 0x8)
return FALSE;
if (lower & 0x4)
ch = SMS_CHARSET_8BIT;
else
ch = SMS_CHARSET_7BIT;
if (lower & 0x3)
2010-11-19 13:57:22 +00:00
cl = (enum sms_class) (lower & 0x3);
2009-06-26 00:15:50 +00:00
else
cl = SMS_CLASS_UNSPECIFIED;
break;
default:
return FALSE;
};
if (udhi)
*udhi = udh;
if (cls)
*cls = cl;
if (charset)
*charset = ch;
if (compressed)
*compressed = comp;
if (language)
*language = lang;
if (iso639)
*iso639 = iso;
return TRUE;
}
gboolean cbs_decode(const unsigned char *pdu, int len, struct cbs *out)
{
/* CBS is (almost) always a fixed length of 88 bytes */
if (len < 6 || len > 88)
return FALSE;
out->gs = (enum cbs_geo_scope) ((pdu[0] >> 6) & 0x03);
out->message_code = ((pdu[0] & 0x3f) << 4) | ((pdu[1] >> 4) & 0xf);
out->update_number = (pdu[1] & 0xf);
out->message_identifier = (pdu[2] << 8) | pdu[3];
out->dcs = pdu[4];
out->max_pages = pdu[5] & 0xf;
out->page = (pdu[5] >> 4) & 0xf;
/* Allow the last fragment to be truncated */
if (len != 88 && out->max_pages != out->page)
return FALSE;
2010-09-21 19:18:15 +00:00
/*
* If a mobile receives the code 0000 in either the first field or
* the second field then it shall treat the CBS message exactly the
* same as a CBS message with page parameter 0001 0001 (i.e. a single
* page message).
*/
if (out->max_pages == 0 || out->page == 0) {
out->max_pages = 1;
out->page = 1;
}
out->udlen = (guint8)(len - 6);
memcpy(out->ud, pdu + 6, out->udlen);
if (out->udlen < 82)
memset(out->ud + out->udlen, 0, 82 - out->udlen);
return TRUE;
}
gboolean cbs_encode(const struct cbs *cbs, int *len, unsigned char *pdu)
{
pdu[0] = (cbs->gs << 6) | ((cbs->message_code >> 4) & 0x3f);
pdu[1] = ((cbs->message_code & 0xf) << 4) | cbs->update_number;
pdu[2] = cbs->message_identifier >> 8;
pdu[3] = cbs->message_identifier & 0xff;
pdu[4] = cbs->dcs;
pdu[5] = cbs->max_pages | (cbs->page << 4);
memcpy(pdu + 6, cbs->ud, 82);
if (len)
*len = 88;
return TRUE;
}
gboolean cbs_extract_app_port(const struct cbs *cbs, int *dst, int *src,
gboolean *is_8bit)
{
struct sms_udh_iter iter;
if (!sms_udh_iter_init_from_cbs(cbs, &iter))
return FALSE;
return extract_app_port_common(&iter, dst, src, is_8bit);
}
gboolean iso639_2_from_language(enum cbs_language lang, char *iso639)
{
switch (lang) {
case CBS_LANGUAGE_GERMAN:
iso639[0] = 'd';
iso639[1] = 'e';
iso639[2] = '\0';
return TRUE;
case CBS_LANGUAGE_ENGLISH:
iso639[0] = 'e';
iso639[1] = 'n';
iso639[2] = '\0';
return TRUE;
case CBS_LANGUAGE_ITALIAN:
iso639[0] = 'i';
iso639[1] = 't';
iso639[2] = '\0';
return TRUE;
case CBS_LANGUAGE_FRENCH:
iso639[0] = 'f';
iso639[1] = 'r';
iso639[2] = '\0';
return TRUE;
case CBS_LANGUAGE_SPANISH:
iso639[0] = 'e';
iso639[1] = 's';
iso639[2] = '\0';
return TRUE;
case CBS_LANGUAGE_DUTCH:
iso639[0] = 'n';
iso639[1] = 'l';
iso639[2] = '\0';
return TRUE;
case CBS_LANGUAGE_SWEDISH:
iso639[0] = 's';
iso639[1] = 'v';
iso639[2] = '\0';
return TRUE;
case CBS_LANGUAGE_DANISH:
iso639[0] = 'd';
iso639[1] = 'a';
iso639[2] = '\0';
return TRUE;
case CBS_LANGUAGE_PORTUGESE:
iso639[0] = 'p';
iso639[1] = 't';
iso639[2] = '\0';
return TRUE;
case CBS_LANGUAGE_FINNISH:
iso639[0] = 'f';
iso639[1] = 'i';
iso639[2] = '\0';
return TRUE;
case CBS_LANGUAGE_NORWEGIAN:
iso639[0] = 'n';
iso639[1] = 'o';
iso639[2] = '\0';
return TRUE;
case CBS_LANGUAGE_GREEK:
iso639[0] = 'e';
iso639[1] = 'l';
iso639[2] = '\0';
return TRUE;
case CBS_LANGUAGE_TURKISH:
iso639[0] = 't';
iso639[1] = 'r';
iso639[2] = '\0';
return TRUE;
case CBS_LANGUAGE_HUNGARIAN:
iso639[0] = 'h';
iso639[1] = 'u';
iso639[2] = '\0';
return TRUE;
case CBS_LANGUAGE_POLISH:
iso639[0] = 'p';
iso639[1] = 'l';
iso639[2] = '\0';
return TRUE;
case CBS_LANGUAGE_CZECH:
iso639[0] = 'c';
iso639[1] = 's';
iso639[2] = '\0';
return TRUE;
case CBS_LANGUAGE_HEBREW:
iso639[0] = 'h';
iso639[1] = 'e';
iso639[2] = '\0';
return TRUE;
case CBS_LANGUAGE_ARABIC:
iso639[0] = 'a';
iso639[1] = 'r';
iso639[2] = '\0';
return TRUE;
case CBS_LANGUAGE_RUSSIAN:
iso639[0] = 'r';
iso639[1] = 'u';
iso639[2] = '\0';
return TRUE;
case CBS_LANGUAGE_ICELANDIC:
iso639[0] = 'i';
iso639[1] = 's';
iso639[2] = '\0';
return TRUE;
default:
iso639[0] = '\0';
break;
}
return FALSE;
}
2009-07-02 17:46:26 +00:00
char *cbs_decode_text(GSList *cbs_list, char *iso639_lang)
{
GSList *l;
2010-02-04 06:24:14 +00:00
const struct cbs *cbs;
enum sms_charset uninitialized_var(charset);
2009-07-02 17:46:26 +00:00
enum cbs_language lang;
gboolean uninitialized_var(iso639);
2009-07-02 17:46:26 +00:00
int bufsize = 0;
unsigned char *buf;
char *utf8;
if (cbs_list == NULL)
return NULL;
2010-09-21 19:18:15 +00:00
/*
* CBS can only come from the network, so we're much less lenient
2009-07-02 17:46:26 +00:00
* on what we support. Namely we require the same charset to be
* used across all pages.
*/
for (l = cbs_list; l; l = l->next) {
enum sms_charset curch;
gboolean curiso;
cbs = l->data;
if (!cbs_dcs_decode(cbs->dcs, NULL, NULL,
&curch, NULL, &lang, &curiso))
return NULL;
if (l == cbs_list) {
iso639 = curiso;
charset = curch;
}
if (curch != charset)
return NULL;
if (curiso != iso639)
return NULL;
if (curch == SMS_CHARSET_8BIT)
return NULL;
if (curch == SMS_CHARSET_7BIT) {
2009-07-02 17:55:01 +00:00
bufsize += CBS_MAX_GSM_CHARS;
2009-07-02 17:46:26 +00:00
if (iso639)
bufsize -= 3;
} else {
bufsize += cbs->udlen;
2009-07-02 17:46:26 +00:00
if (iso639)
bufsize -= 2;
}
}
if (lang) {
cbs = cbs_list->data;
if (iso639) {
struct sms_udh_iter iter;
int taken = 0;
if (sms_udh_iter_init_from_cbs(cbs, &iter))
taken = sms_udh_iter_get_udh_length(&iter) + 1;
unpack_7bit_own_buf(cbs->ud + taken, cbs->udlen - taken,
2018-12-19 21:51:47 +00:00
taken, false, 2,
2009-07-14 03:27:33 +00:00
NULL, 0,
(unsigned char *)iso639_lang);
2009-07-02 17:46:26 +00:00
iso639_lang[2] = '\0';
} else {
2009-07-02 17:46:26 +00:00
iso639_2_from_language(lang, iso639_lang);
}
2009-07-02 17:46:26 +00:00
}
buf = l_new(unsigned char, bufsize);
2009-07-02 17:46:26 +00:00
bufsize = 0;
for (l = cbs_list; l; l = l->next) {
const guint8 *ud;
struct sms_udh_iter iter;
int taken = 0;
cbs = l->data;
ud = cbs->ud;
if (sms_udh_iter_init_from_cbs(cbs, &iter))
taken = sms_udh_iter_get_udh_length(&iter) + 1;
if (charset == SMS_CHARSET_7BIT) {
2009-07-02 17:55:01 +00:00
unsigned char unpacked[CBS_MAX_GSM_CHARS];
2009-07-02 17:46:26 +00:00
long written;
2009-07-02 17:55:01 +00:00
int max_chars;
2009-07-02 17:46:26 +00:00
int i;
2009-07-02 17:55:01 +00:00
max_chars =
sms_text_capacity_gsm(CBS_MAX_GSM_CHARS, taken);
unpack_7bit_own_buf(ud + taken, cbs->udlen - taken,
2018-12-19 21:51:47 +00:00
taken, false, max_chars,
2009-07-02 17:46:26 +00:00
&written, 0, unpacked);
i = iso639 ? 3 : 0;
2010-09-21 19:18:15 +00:00
/*
* CR is a padding character, which means we can
* safely discard everything afterwards if there are
* only trailing CR characters.
2009-07-02 17:46:26 +00:00
*/
for (; i < written; i++, bufsize++) {
if (unpacked[i] == '\r') {
int j;
for (j = i + 1; j < written; j++)
if (unpacked[j] != '\r')
break;
if (j == written)
break;
}
2009-07-02 17:46:26 +00:00
buf[bufsize] = unpacked[i];
}
2010-09-21 19:18:15 +00:00
/*
* It isn't clear whether extension sequences
2009-07-02 17:46:26 +00:00
* (2 septets) must be wholly present in the page
* and not broken over multiple pages. The behavior
* is probably the same as SMS, but we don't make
* the check here since the specification isn't clear
*/
} else {
int num_ucs2_chars = (cbs->udlen - taken) >> 1;
2009-07-02 17:46:26 +00:00
int i = taken;
int max_offset = taken + num_ucs2_chars * 2;
2010-09-21 19:18:15 +00:00
/*
* It is completely unclear how UCS2 chars are handled
2009-07-02 17:46:26 +00:00
* especially across pages or when the UDH is present.
2010-09-21 19:18:15 +00:00
* For now do the best we can.
2009-07-02 17:46:26 +00:00
*/
if (iso639) {
i += 2;
num_ucs2_chars -= 1;
}
while (i < max_offset) {
if (ud[i] == 0x00 && ud[i + 1] == '\r') {
int j = i + 2;
for (; j < max_offset; j = j + 2)
if (ud[j + 1] != '\r' ||
ud[j] != 0x00)
break;
if (j == max_offset)
break;
}
2009-07-02 17:46:26 +00:00
buf[bufsize] = ud[i];
2012-08-31 15:03:56 +00:00
buf[bufsize + 1] = ud[i + 1];
2009-07-02 17:46:26 +00:00
bufsize += 2;
i += 2;
}
}
}
if (charset == SMS_CHARSET_7BIT)
utf8 = convert_gsm_to_utf8(buf, bufsize, NULL, NULL, 0);
else
utf8 = l_utf8_from_ucs2be(buf, bufsize);
2009-07-02 17:46:26 +00:00
l_free(buf);
2009-07-02 17:46:26 +00:00
return utf8;
}
2009-07-03 00:00:27 +00:00
static inline gboolean cbs_is_update_newer(unsigned int n, unsigned int o)
{
unsigned int old_update = o & 0xf;
unsigned int new_update = n & 0xf;
if (new_update == old_update)
return FALSE;
2010-09-21 19:18:15 +00:00
/*
* Any Update Number eight or less higher (modulo 16) than the last
2009-07-03 00:00:27 +00:00
* received Update Number will be considered more recent, and shall be
* treated as a new CBS message, provided the mobile has not been
* switched off.
*/
if (new_update <= ((old_update + 8) % 16))
return TRUE;
return FALSE;
}
2011-01-12 10:52:03 +00:00
struct cbs_assembly *cbs_assembly_new(void)
2009-07-03 00:00:27 +00:00
{
return g_new0(struct cbs_assembly, 1);
}
void cbs_assembly_free(struct cbs_assembly *assembly)
{
GSList *l;
for (l = assembly->assembly_list; l; l = l->next) {
struct cbs_assembly_node *node = l->data;
2016-04-22 13:10:07 +00:00
g_slist_free_full(node->pages, g_free);
2009-07-03 00:00:27 +00:00
g_free(node);
}
g_slist_free(assembly->assembly_list);
g_slist_free(assembly->recv_plmn);
g_slist_free(assembly->recv_loc);
g_slist_free(assembly->recv_cell);
g_free(assembly);
}
static gint cbs_compare_node_by_gs(gconstpointer a, gconstpointer b)
{
const struct cbs_assembly_node *node = a;
unsigned int gs = GPOINTER_TO_UINT(b);
if (((node->serial >> 14) & 0x3) == gs)
return 0;
return 1;
}
static gint cbs_compare_node_by_update(gconstpointer a, gconstpointer b)
{
const struct cbs_assembly_node *node = a;
unsigned int serial = GPOINTER_TO_UINT(b);
if ((serial & (~0xf)) != (node->serial & (~0xf)))
return 1;
if (cbs_is_update_newer(node->serial, serial))
return 1;
return 0;
}
static gint cbs_compare_recv_by_serial(gconstpointer a, gconstpointer b)
{
unsigned int old_serial = GPOINTER_TO_UINT(a);
unsigned int new_serial = GPOINTER_TO_UINT(b);
if ((old_serial & (~0xf)) == (new_serial & (~0xf)))
return 0;
return 1;
}
static void cbs_assembly_expire(struct cbs_assembly *assembly,
GCompareFunc func, gconstpointer *userdata)
{
GSList *l;
GSList *prev;
GSList *tmp;
2010-09-21 19:18:15 +00:00
/*
* Take care of the case where several updates are being
* reassembled at the same time. If the newer one is assembled
2009-07-03 00:00:27 +00:00
* first, then the subsequent old update is discarded, make
* sure that we're also discarding the assembly node for the
* partially assembled ones
*/
prev = NULL;
l = assembly->assembly_list;
while (l) {
struct cbs_assembly_node *node = l->data;
if (func(node, userdata) != 0) {
prev = l;
l = l->next;
continue;
}
if (prev)
prev->next = l->next;
else
assembly->assembly_list = l->next;
2016-04-22 13:10:07 +00:00
g_slist_free_full(node->pages, g_free);
2009-07-03 00:00:27 +00:00
g_free(node->pages);
tmp = l;
l = l->next;
g_slist_free_1(tmp);
}
}
void cbs_assembly_location_changed(struct cbs_assembly *assembly, gboolean plmn,
2009-07-03 00:00:27 +00:00
gboolean lac, gboolean ci)
{
2010-09-21 19:18:15 +00:00
/*
* Location Area wide (in GSM) (which means that a CBS message with the
2009-07-03 00:00:27 +00:00
* same Message Code and Update Number may or may not be "new" in the
* next cell according to whether the next cell is in the same Location
* Area as the current cell), or
*
* Service Area Wide (in UMTS) (which means that a CBS message with the
* same Message Code and Update Number may or may not be "new" in the
* next cell according to whether the next cell is in the same Service
* Area as the current cell)
*
* NOTE 4: According to 3GPP TS 23.003 [2] a Service Area consists of
* one cell only.
*/
if (plmn) {
lac = TRUE;
g_slist_free(assembly->recv_plmn);
assembly->recv_plmn = NULL;
cbs_assembly_expire(assembly, cbs_compare_node_by_gs,
GUINT_TO_POINTER(CBS_GEO_SCOPE_PLMN));
}
2009-07-03 00:00:27 +00:00
if (lac) {
/* If LAC changed, then cell id has changed */
ci = TRUE;
g_slist_free(assembly->recv_loc);
assembly->recv_loc = NULL;
cbs_assembly_expire(assembly, cbs_compare_node_by_gs,
GUINT_TO_POINTER(CBS_GEO_SCOPE_SERVICE_AREA));
}
if (ci) {
g_slist_free(assembly->recv_cell);
assembly->recv_cell = NULL;
cbs_assembly_expire(assembly, cbs_compare_node_by_gs,
GUINT_TO_POINTER(CBS_GEO_SCOPE_CELL_IMMEDIATE));
cbs_assembly_expire(assembly, cbs_compare_node_by_gs,
GUINT_TO_POINTER(CBS_GEO_SCOPE_CELL_NORMAL));
}
}
GSList *cbs_assembly_add_page(struct cbs_assembly *assembly,
const struct cbs *cbs)
{
struct cbs *newcbs;
struct cbs_assembly_node *node;
GSList *completed;
unsigned int new_serial;
GSList **recv;
GSList *l;
GSList *prev;
int position;
new_serial = cbs->gs << 14;
new_serial |= cbs->message_code << 4;
new_serial |= cbs->update_number;
new_serial |= cbs->message_identifier << 16;
if (cbs->gs == CBS_GEO_SCOPE_PLMN)
recv = &assembly->recv_plmn;
else if (cbs->gs == CBS_GEO_SCOPE_SERVICE_AREA)
recv = &assembly->recv_loc;
else
recv = &assembly->recv_cell;
/* Have we seen this message before? */
l = g_slist_find_custom(*recv, GUINT_TO_POINTER(new_serial),
cbs_compare_recv_by_serial);
/* If we have, is the message newer? */
if (l && !cbs_is_update_newer(new_serial, GPOINTER_TO_UINT(l->data)))
return NULL;
/* Easy case first, page 1 of 1 */
if (cbs->max_pages == 1 && cbs->page == 1) {
if (l)
l->data = GUINT_TO_POINTER(new_serial);
else
*recv = g_slist_prepend(*recv,
GUINT_TO_POINTER(new_serial));
newcbs = g_new(struct cbs, 1);
memcpy(newcbs, cbs, sizeof(struct cbs));
completed = g_slist_append(NULL, newcbs);
return completed;
}
prev = NULL;
position = 0;
for (l = assembly->assembly_list; l; prev = l, l = l->next) {
int j;
node = l->data;
if (new_serial != node->serial)
continue;
if (node->bitmap & (1 << cbs->page))
return NULL;
for (j = 1; j < cbs->page; j++)
if (node->bitmap & (1 << j))
position += 1;
goto out;
}
node = g_new0(struct cbs_assembly_node, 1);
node->serial = new_serial;
assembly->assembly_list = g_slist_prepend(assembly->assembly_list,
node);
prev = NULL;
l = assembly->assembly_list;
position = 0;
out:
newcbs = g_new(struct cbs, 1);
memcpy(newcbs, cbs, sizeof(struct cbs));
node->pages = g_slist_insert(node->pages, newcbs, position);
node->bitmap |= 1 << cbs->page;
if (g_slist_length(node->pages) < cbs->max_pages)
return NULL;
completed = node->pages;
if (prev)
prev->next = l->next;
else
assembly->assembly_list = l->next;
g_free(node);
g_slist_free_1(l);
cbs_assembly_expire(assembly, cbs_compare_node_by_update,
GUINT_TO_POINTER(new_serial));
*recv = g_slist_prepend(*recv, GUINT_TO_POINTER(new_serial));
return completed;
}
2009-09-09 19:28:20 +00:00
static inline int skip_to_next_field(const char *str, int pos, int len)
{
if (pos < len && str[pos] == ',')
pos += 1;
while (pos < len && str[pos] == ' ')
pos += 1;
return pos;
}
static gboolean next_range(const char *str, int *offset, gint *min, gint *max)
{
int pos;
int end;
int len;
int low = 0;
int high = 0;
len = strlen(str);
pos = *offset;
while (pos < len && str[pos] == ' ')
pos += 1;
end = pos;
while (str[end] >= '0' && str[end] <= '9') {
low = low * 10 + (int)(str[end] - '0');
end += 1;
}
if (pos == end)
return FALSE;
if (str[end] != '-') {
high = low;
goto out;
}
pos = end = end + 1;
while (str[end] >= '0' && str[end] <= '9') {
high = high * 10 + (int)(str[end] - '0');
end += 1;
}
if (pos == end)
return FALSE;
out:
*offset = skip_to_next_field(str, end, len);
if (min)
*min = low;
if (max)
*max = high;
return TRUE;
}
GSList *cbs_optimize_ranges(GSList *ranges)
2009-09-09 19:28:20 +00:00
{
struct cbs_topic_range *range;
unsigned char bitmap[125];
GSList *l;
unsigned short i;
GSList *ret = NULL;
memset(bitmap, 0, sizeof(bitmap));
for (l = ranges; l; l = l->next) {
range = l->data;
for (i = range->min; i <= range->max; i++) {
int byte_offset = i / 8;
int bit = i % 8;
bitmap[byte_offset] |= 1 << bit;
}
}
range = NULL;
for (i = 0; i <= 999; i++) {
int byte_offset = i / 8;
int bit = i % 8;
if (is_bit_set(bitmap[byte_offset], bit) == FALSE) {
if (range) {
ret = g_slist_prepend(ret, range);
range = NULL;
}
continue;
}
if (range) {
range->max = i;
continue;
}
range = g_new0(struct cbs_topic_range, 1);
range->min = i;
range->max = i;
}
if (range != NULL)
ret = g_slist_prepend(ret, range);
ret = g_slist_reverse(ret);
return ret;
}
GSList *cbs_extract_topic_ranges(const char *ranges)
{
int min;
int max;
int offset = 0;
GSList *ret = NULL;
GSList *tmp;
while (next_range(ranges, &offset, &min, &max) == TRUE) {
if (min < 0 || min > 999)
return NULL;
if (max < 0 || max > 999)
return NULL;
if (max < min)
return NULL;
}
if (ranges[offset] != '\0')
return NULL;
offset = 0;
while (next_range(ranges, &offset, &min, &max) == TRUE) {
struct cbs_topic_range *range = g_new0(struct cbs_topic_range, 1);
range->min = min;
range->max = max;
ret = g_slist_prepend(ret, range);
}
tmp = cbs_optimize_ranges(ret);
2016-04-22 13:10:07 +00:00
g_slist_free_full(ret, g_free);
2009-09-09 19:28:20 +00:00
return tmp;
}
static inline int element_length(unsigned short element)
{
if (element <= 9)
return 1;
if (element <= 99)
return 2;
if (element <= 999)
return 3;
if (element <= 9999)
return 4;
return 5;
2009-09-09 19:28:20 +00:00
}
static inline int range_length(struct cbs_topic_range *range)
{
if (range->min == range->max)
return element_length(range->min);
return element_length(range->min) + element_length(range->max) + 1;
}
char *cbs_topic_ranges_to_string(GSList *ranges)
{
int len = 0;
int nelem = 0;
struct cbs_topic_range *range;
GSList *l;
char *ret;
2009-09-10 02:25:03 +00:00
if (ranges == NULL)
return g_new0(char, 1);
2009-09-09 19:28:20 +00:00
for (l = ranges; l; l = l->next) {
range = l->data;
len += range_length(range);
nelem += 1;
}
/* Space for ranges, commas and terminator null */
ret = g_new(char, len + nelem);
len = 0;
for (l = ranges; l; l = l->next) {
range = l->data;
if (range->min != range->max)
len += sprintf(ret + len, "%hu-%hu",
range->min, range->max);
else
len += sprintf(ret + len, "%hu", range->min);
if (l->next != NULL)
ret[len++] = ',';
}
return ret;
}
static gint cbs_topic_compare(gconstpointer a, gconstpointer b)
{
const struct cbs_topic_range *range = a;
unsigned short topic = GPOINTER_TO_UINT(b);
if (topic >= range->min && topic <= range->max)
return 0;
return 1;
}
gboolean cbs_topic_in_range(unsigned int topic, GSList *ranges)
{
if (ranges == NULL)
return FALSE;
return g_slist_find_custom(ranges, GUINT_TO_POINTER(topic),
cbs_topic_compare) != NULL;
}
2010-06-17 10:35:07 +00:00
char *ussd_decode(int dcs, int len, const unsigned char *data)
{
gboolean udhi;
enum sms_charset charset;
gboolean compressed;
gboolean iso639;
char *utf8;
if (!cbs_dcs_decode(dcs, &udhi, NULL, &charset,
&compressed, NULL, &iso639))
return NULL;
if (udhi || compressed || iso639)
return NULL;
switch (charset) {
case SMS_CHARSET_7BIT:
{
long written;
2018-12-19 21:51:47 +00:00
unsigned char *unpacked = unpack_7bit(data, len, 0, true, 0,
2010-06-17 10:35:07 +00:00
&written, 0);
if (unpacked == NULL)
return NULL;
utf8 = convert_gsm_to_utf8(unpacked, written, NULL, NULL, 0);
l_free(unpacked);
2010-06-17 10:35:07 +00:00
break;
}
case SMS_CHARSET_8BIT:
utf8 = convert_gsm_to_utf8(data, len, NULL, NULL, 0);
break;
case SMS_CHARSET_UCS2:
utf8 = g_convert((const gchar *) data, len,
"UTF-8//TRANSLIT", "UCS-2BE",
NULL, NULL, NULL);
break;
default:
utf8 = NULL;
}
return utf8;
}
2010-09-13 23:00:10 +00:00
gboolean ussd_encode(const char *str, long *items_written, unsigned char *pdu)
{
unsigned char *converted = NULL;
long written;
long num_packed;
if (pdu == NULL)
2010-09-13 23:00:10 +00:00
return FALSE;
converted = convert_utf8_to_gsm(str, -1, NULL, &written, 0);
if (converted == NULL || written > 182) {
2018-12-21 18:35:06 +00:00
l_free(converted);
2010-09-14 16:54:57 +00:00
return FALSE;
}
2010-09-13 23:00:10 +00:00
2018-12-19 21:51:47 +00:00
pack_7bit_own_buf(converted, written, 0, true, &num_packed, 0, pdu);
2018-12-21 18:35:06 +00:00
l_free(converted);
2010-09-13 23:00:10 +00:00
if (num_packed < 1)
return FALSE;
if (items_written)
*items_written = num_packed;
return TRUE;
}