ofono/src/smsutil.c

4799 lines
100 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
*
* oFono - Open Source Telephony
*
* 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]"
/*
* 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
*/
void extract_bcd_number(const unsigned char *buf, int len, char *out)
{
static const char digit_lut[] = "0123456789*#abc\0";
unsigned char oct;
int i;
for (i = 0; i < len; i++) {
oct = buf[i];
out[i * 2] = digit_lut[oct & 0x0f];
out[i * 2 + 1] = digit_lut[(oct & 0xf0) >> 4];
}
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':
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;
}
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;
}
/*
* Returns whether the DCS could be parsed successfully, e.g. no reserved
* 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)
cl = (enum sms_class) (dcs & 0x03);
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;
cl = (enum sms_class) (dcs & 0x03);
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;
/*
* 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:
return len_8bit;
}
return 0;
case 2:
return 0;
case 3:
switch ((dcs & 0x30) >> 4) {
case 0:
case 1:
return len_7bit;
case 2:
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);
}
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);
/*
* 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;
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:
if (!sms_decode_scts(pdu, len, offset, &vp->absolute))
return FALSE;
return TRUE;
case SMS_VALIDITY_PERIOD_FORMAT_ENHANCED:
/*
* 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) {
l_free(gsm);
return FALSE;
}
r = pack_7bit_own_buf(gsm, written, 0, false, &packed, 0, p);
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;
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) {
extract_bcd_number(pdu + *offset, byte_len, out->address);
*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;
/*
* This cannot happen according to 24.011, however
* nothing is said in 23.040
*/
if (chars == 0) {
out->address[0] = '\0';
return TRUE;
}
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;
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;
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;
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;
/*
* 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;
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,
pdu + offset, expected);
} else {
out->submit_ack_report.udl = udl;
memcpy(out->submit_ack_report.ud,
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;
if (!sms_decode_scts(pdu, len, &offset, &out->status_report.scts))
return FALSE;
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;
/*
* 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;
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;
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;
/*
* 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,
pdu + offset, expected);
} else {
out->deliver_ack_report.udl = udl;
memcpy(out->deliver_ack_report.ud,
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;
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;
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;
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;
/*
* 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)
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;
}
/*
* 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;
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);
/*
* 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.
*/
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);
}
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;
/*
* 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;
/*
* 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;
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 ||
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;
}
gboolean sms_extract_language_variant(const struct sms *sms, guint8 *locking,
guint8 *single)
{
struct sms_udh_iter iter;
enum sms_iei iei;
guint8 variant;
/*
* 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;
/*
* 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;
}
/*!
* 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)
return NULL;
/*
* Note we do this because we must check whether the UDH
* 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)
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;
}
static inline int sms_text_capacity_gsm(int max, int offset)
{
return max - (offset * 8 + 6) / 7;
}
/*!
* 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;
const struct sms *sms;
int guess_size = g_slist_length(sms_list);
char *utf8;
void *utf16 = NULL;
size_t utf16_size = 0;
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);
if (udl_in_bytes == taken)
continue;
if (charset == SMS_CHARSET_7BIT) {
unsigned char buf[160];
long written;
guint8 locking_shift = 0;
guint8 single_shift = 0;
int max_chars = sms_text_capacity_gsm(udl, taken);
if (unpack_7bit_own_buf(ud + taken,
udl_in_bytes - taken,
taken, false, max_chars,
&written, 0, buf) == NULL)
continue;
/* Take care of improperly split fragments */
if (buf[written-1] == 0x1b)
written = written - 1;
sms_extract_language_variant(sms, &locking_shift,
&single_shift);
/*
* 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);
}
} else {
const guint8 *from = ud + taken;
/*
* According to the spec: A UCS2 character shall not be
* 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;
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;
}
}
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);
if (converted) {
g_string_append(str, converted);
l_free(converted);
}
l_free(utf16);
}
utf8 = g_string_free(str, FALSE);
return utf8;
}
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;
/* Errors cannot occur here */
sms_assembly_add_fragment_backup(assembly, &segment,
segment_stat.st_mtime,
&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)
{
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;
}
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;
g_slist_free_full(node->fragment_list, g_free);
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)
{
unsigned int offset = seq / 32;
unsigned int bit = 1 << (seq % 32);
GSList *l;
GSList *prev;
struct sms *newsms;
struct sms_assembly_node *node;
GSList *completed;
unsigned int position;
unsigned int i;
unsigned int j;
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;
/*
* Message Reference and address the same, but max is not
* 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.
*/
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);
return NULL;
}
completed = node->fragment_list;
sms_assembly_backup_free(assembly, node);
if (prev)
prev->next = l->next;
else
assembly->assembly_list = l->next;
g_free(node);
g_slist_free_1(l);
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;
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);
g_slist_free_full(node->fragment_list, g_free);
g_free(node);
if (prev)
prev->next = cur->next;
else
assembly->assembly_list = cur->next;
tmp = cur;
cur = cur->next;
g_slist_free_1(tmp);
}
}
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);
}
struct status_report_assembly *status_report_assembly_new(const char *imsi)
{
char *path;
int len;
struct dirent **addresses;
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,
g_free, (GDestroyNotify) g_hash_table_destroy);
if (imsi) {
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);
}
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;
}
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)
{
unsigned int offset = mr / 32;
unsigned int bit = 1 << (mr % 32);
gpointer key, value;
struct id_table_node *node;
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;
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;
}
}
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;
}
}
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;
}
void status_report_assembly_add_fragment(
struct status_report_assembly *assembly,
const unsigned char *msgid,
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;
id_table = g_hash_table_lookup(assembly->assembly_table,
sms_address_to_string(to));
/* 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);
g_hash_table_insert(assembly->assembly_table,
g_strdup(sms_address_to_string(to)),
id_table);
}
node = g_hash_table_lookup(id_table, msgid);
/* Create node in the message id hashtable if required */
if (node == NULL) {
id_table_key = g_memdup(msgid, SMS_MSGID_LEN);
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);
}
/* 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);
}
void status_report_assembly_expire(struct status_report_assembly *assembly,
time_t before)
{
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);
}
}
/*
* 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);
}
}
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;
}
/*
* 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.
*
* @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;
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;
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;
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;
}
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) {
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;
}
/*
* Prepares the text for transmission. Breaks up into fragments if
* 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)
*/
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;
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;
pack_7bit_own_buf(gsm_encoded, written, offset, false, NULL,
0, template.submit.ud + offset);
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;
}
seq = 0;
left = written;
written = 0;
while (left > 0) {
long chunk;
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,
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) {
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;
}
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);
}
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;
lang = (enum cbs_language) lower;
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;
lang = (enum cbs_language) dcs;
break;
case 4:
case 5:
case 6:
case 7:
comp = (dcs & 0x20) ? TRUE : FALSE;
if (dcs & 0x10)
cl = (enum sms_class) (dcs & 0x03);
else
cl = SMS_CLASS_UNSPECIFIED;
if (((dcs & 0x0c) >> 2) < 3)
ch = (enum sms_charset) ((dcs & 0x0c) >> 2);
else
return FALSE;
break;
case 9:
udh = TRUE;
cl = (enum sms_class) (dcs & 0x03);
if (((dcs & 0x0c) >> 2) < 3)
ch = (enum sms_charset) ((dcs & 0x0c) >> 2);
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)
cl = (enum sms_class) (lower & 0x3);
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;
/*
* 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;
}
char *cbs_decode_text(GSList *cbs_list, char *iso639_lang)
{
GSList *l;
const struct cbs *cbs;
enum sms_charset uninitialized_var(charset);
enum cbs_language lang;
gboolean uninitialized_var(iso639);
int bufsize = 0;
unsigned char *buf;
char *utf8;
if (cbs_list == NULL)
return NULL;
/*
* CBS can only come from the network, so we're much less lenient
* 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) {
bufsize += CBS_MAX_GSM_CHARS;
if (iso639)
bufsize -= 3;
} else {
bufsize += cbs->udlen;
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,
taken, false, 2,
NULL, 0,
(unsigned char *)iso639_lang);
iso639_lang[2] = '\0';
} else {
iso639_2_from_language(lang, iso639_lang);
}
}
buf = l_new(unsigned char, bufsize);
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) {
unsigned char unpacked[CBS_MAX_GSM_CHARS];
long written;
int max_chars;
int i;
max_chars =
sms_text_capacity_gsm(CBS_MAX_GSM_CHARS, taken);
unpack_7bit_own_buf(ud + taken, cbs->udlen - taken,
taken, false, max_chars,
&written, 0, unpacked);
i = iso639 ? 3 : 0;
/*
* CR is a padding character, which means we can
* safely discard everything afterwards if there are
* only trailing CR characters.
*/
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;
}
buf[bufsize] = unpacked[i];
}
/*
* It isn't clear whether extension sequences
* (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;
int i = taken;
int max_offset = taken + num_ucs2_chars * 2;
/*
* It is completely unclear how UCS2 chars are handled
* especially across pages or when the UDH is present.
* For now do the best we can.
*/
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;
}
buf[bufsize] = ud[i];
buf[bufsize + 1] = ud[i + 1];
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);
l_free(buf);
return utf8;
}
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;
/*
* Any Update Number eight or less higher (modulo 16) than the last
* 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;
}
struct cbs_assembly *cbs_assembly_new(void)
{
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;
g_slist_free_full(node->pages, g_free);
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;
/*
* Take care of the case where several updates are being
* reassembled at the same time. If the newer one is assembled
* 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;
g_slist_free_full(node->pages, g_free);
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,
gboolean lac, gboolean ci)
{
/*
* Location Area wide (in GSM) (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 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));
}
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;
}
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)
{
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);
g_slist_free_full(ret, g_free);
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;
}
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;
if (ranges == NULL)
return g_new0(char, 1);
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;
}
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;
unsigned char *unpacked = unpack_7bit(data, len, 0, true, 0,
&written, 0);
if (unpacked == NULL)
return NULL;
utf8 = convert_gsm_to_utf8(unpacked, written, NULL, NULL, 0);
l_free(unpacked);
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;
}
gboolean ussd_encode(const char *str, long *items_written, unsigned char *pdu)
{
unsigned char *converted = NULL;
long written;
long num_packed;
if (pdu == NULL)
return FALSE;
converted = convert_utf8_to_gsm(str, -1, NULL, &written, 0);
if (converted == NULL || written > 182) {
l_free(converted);
return FALSE;
}
pack_7bit_own_buf(converted, written, 0, true, &num_packed, 0, pdu);
l_free(converted);
if (num_packed < 1)
return FALSE;
if (items_written)
*items_written = num_packed;
return TRUE;
}