ofono/src/smsutil.c

3404 lines
70 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-2009 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 "types.h"
#include "common.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"
#define SMS_BACKUP_PATH_DIR SMS_BACKUP_PATH "/%s-%i-%i"
#define SMS_BACKUP_PATH_FILE SMS_BACKUP_PATH_DIR "/%03i"
#define SMS_ADDR_FMT "%21[0-9+*#]"
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);
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':
digit = 0;
break;
case '1':
digit = 1;
break;
case '2':
digit = 2;
break;
case '3':
digit = 3;
break;
case '4':
digit = 4;
break;
case '5':
digit = 5;
break;
case '6':
digit = 6;
break;
case '7':
digit = 7;
break;
case '8':
digit = 8;
break;
case '9':
digit = 9;
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;
if (upper == 3)
ch = SMS_CHARSET_UCS2;
else
ch = SMS_CHARSET_7BIT;
act = dcs & 0x8;
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;
}
static gboolean 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 > 12*4-1) || (in->timezone < -(12*4-1)))
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);
timezone = abs(in->timezone);
pdu[6] = ((timezone / 10) & 0x07) | (((timezone % 10) & 0x0f) << 4);
if (in->timezone < 0)
pdu[6] |= 0x8;
*offset += 7;
return TRUE;
}
static gboolean 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 = (oct & 0x0f) * 10 + ((oct & 0xf0) >> 4);
next_octet(pdu, len, offset, &oct);
out->month = (oct & 0x0f) * 10 + ((oct & 0xf0) >> 4);
next_octet(pdu, len, offset, &oct);
out->day = (oct & 0x0f) * 10 + ((oct & 0xf0) >> 4);
next_octet(pdu, len, offset, &oct);
out->hour = (oct & 0x0f) * 10 + ((oct & 0xf0) >> 4);
next_octet(pdu, len, offset, &oct);
out->minute = (oct & 0x0f) * 10 + ((oct & 0xf0) >> 4);
next_octet(pdu, len, offset, &oct);
out->second = (oct & 0x0f) * 10 + ((oct & 0xf0) >> 4);
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;
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 (!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 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)
{
size_t len = strlen(in->address);
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;
if (len > 11)
return FALSE;
gsm = convert_utf8_to_gsm(in->address, len, NULL, &written, 0);
if (!gsm)
return FALSE;
r = pack_7bit_own_buf(gsm, written, 0, FALSE, &packed, 0, p);
g_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)
return FALSE;
utf8 = convert_gsm_to_utf8(res, written, NULL, NULL, 0);
g_free(res);
if (!utf8)
return FALSE;
if (strlen(utf8) > 20) {
g_free(utf8);
return FALSE;
}
strcpy(out->address, utf8);
g_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 (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 (!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 (!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 (!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 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 (!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 (!encode_scts(&in->scts, pdu, offset))
return FALSE;
if (!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 (!decode_scts(pdu, len, &offset, &out->status_report.scts))
return FALSE;
if (!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;
} else
out->status_report.dcs = 0;
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 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;
}
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;
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)
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)
return FALSE;
if (len == 0)
return FALSE;
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)
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)
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 = 82;
/* 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;
if (((addr_hdr[2] << 8) | addr_hdr[3]) > 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)
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)
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 = sms_list->data;
int guess_size = g_slist_length(sms_list);
char *utf8;
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 (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);
unpack_7bit_own_buf(ud + taken, udl_in_bytes - taken,
taken, FALSE, max_chars,
&written, 0, buf);
/* 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 >= GSM_DIALECT_INVALID)
locking_shift = GSM_DIALECT_DEFAULT;
if (single_shift >= GSM_DIALECT_INVALID)
single_shift = GSM_DIALECT_DEFAULT;
converted = convert_gsm_to_utf8_with_lang(buf, written,
NULL, NULL, 0,
locking_shift,
single_shift);
} else {
const gchar *from = (const gchar *)(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
*/
gssize num_ucs2_chars = (udl_in_bytes - taken) >> 1;
num_ucs2_chars = num_ucs2_chars << 1;
converted = g_convert(from, num_ucs2_chars,
"UTF-8//TRANSLIT", "UCS-2BE",
NULL, NULL, NULL);
}
if (converted) {
g_string_append(str, converted);
g_free(converted);
}
}
utf8 = g_string_free(str, FALSE);
if (strlen(utf8) == 0) {
g_free(utf8);
return NULL;
}
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;
}
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 void sms_assembly_load(struct sms_assembly *assembly,
const struct dirent *dir)
{
struct sms_address addr;
char straddr[sizeof(addr.address) + 1];
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;
if (sscanf(dir->d_name, SMS_ADDR_FMT "-%hi-%hhi",
straddr, &ref, &max) < 3)
return;
sms_address_from_string(&addr, straddr);
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;
if (sms_assembly_add_fragment_backup(assembly, &segment,
segment_stat.st_mtime,
&addr, ref, max, seq, FALSE)) {
/* This should not happen */
}
}
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;
if (!assembly->imsi)
return FALSE;
len = sms_serialize(buf, sms);
if (write_file(buf, len, SMS_BACKUP_MODE,
SMS_BACKUP_PATH_FILE, assembly->imsi,
sms_address_to_string(&node->addr),
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;
if (!assembly->imsi)
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,
sms_address_to_string(&node->addr),
node->ref, node->max_fragments, seq);
unlink(path);
g_free(path);
}
}
path = g_strdup_printf(SMS_BACKUP_PATH_DIR, assembly->imsi,
sms_address_to_string(&node->addr),
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_foreach(node->fragment_list, (GFunc)g_free, 0);
g_slist_free(node->fragment_list);
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)
{
int offset = seq / 32;
int bit = 1 << (seq % 32);
GSList *l;
GSList *prev;
struct sms *newsms;
struct sms_assembly_node *node;
GSList *completed;
int position;
int i;
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;
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_foreach(node->fragment_list, (GFunc)g_free, 0);
g_slist_free(node->fragment_list);
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 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 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. If ref_offset is given,
* then the ref_offset contains the reference number offset or 0
* if no concatenation took place.
*/
GSList *sms_text_prepare(const char *utf8, guint16 ref,
gboolean use_16bit, int *ref_offset)
{
struct sms template;
int offset = 0;
unsigned char *gsm_encoded = NULL;
char *ucs2_encoded = NULL;
long written;
long 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 = FALSE;
template.submit.mr = 0;
template.submit.vp.relative = 0xA7; /* 24 Hours */
/* UDHI, UDL, UD and DCS actually depend on what we have in the text */
gsm_encoded = convert_utf8_to_gsm(utf8, -1, NULL, &written, 0);
if (!gsm_encoded) {
gsize converted;
ucs2_encoded = g_convert(utf8, -1, "UCS-2BE//TRANSLIT", "UTF-8",
NULL, &converted, NULL);
written = converted;
}
if (!gsm_encoded && !ucs2_encoded)
return NULL;
if (gsm_encoded)
template.submit.dcs = 0x00; /* Class Unspecified, 7 Bit */
else
template.submit.dcs = 0x08; /* Class Unspecified, UCS2 */
if (offset != 0)
template.submit.udhi = FALSE;
if (gsm_encoded && (written <= sms_text_capacity_gsm(160, offset))) {
if (ref_offset)
*ref_offset = 0;
template.submit.udl = written + (offset * 8 + 6) / 7;
pack_7bit_own_buf(gsm_encoded, written, offset, FALSE, NULL,
0, template.submit.ud + offset);
g_free(gsm_encoded);
return sms_list_append(NULL, &template);
}
if (ucs2_encoded && (written <= (140 - offset))) {
if (ref_offset)
*ref_offset = 0;
template.submit.udl = written + offset;
memcpy(template.submit.ud + offset, ucs2_encoded, written);
g_free(ucs2_encoded);
return sms_list_append(NULL, &template);
}
template.submit.udhi = TRUE;
if (!offset)
offset = 1;
if (ref_offset)
*ref_offset = offset + 2;
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 & 0xf0) >> 8;
template.submit.ud[offset + 3] = ref & 0xf;
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 & 0xf;
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 (gsm_encoded[written + chunk - 1] == 0x1b)
chunk -= 1;
if (left < chunk)
chunk = left;
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)
g_free(ucs2_encoded);
if (left > 0) {
g_slist_foreach(r, (GFunc)g_free, NULL);
g_slist_free(r);
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;
}
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;
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 always a fixed length of 88 bytes */
if (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;
/* 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;
}
memcpy(out->ud, pdu + 6, 82);
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);
}
static 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 = cbs_list->data;
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 += 82;
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, 82 - taken,
taken, FALSE, 2,
NULL, 0,
(unsigned char *)iso639_lang);
iso639_lang[2] = '\0';
} else
iso639_2_from_language(lang, iso639_lang);
}
buf = g_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, 82 - 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
*/
for (; i < written; i++, bufsize++) {
if (unpacked[i] == '\r')
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 = (82 - 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] == '\r')
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 = g_convert((char *)buf, bufsize, "UTF-8//TRANSLIT",
"UCS-2BE", NULL, NULL, NULL);
g_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()
{
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_foreach(node->pages, (GFunc)g_free, 0);
g_slist_free(node->pages);
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_foreach(node->pages, (GFunc)g_free, NULL);
g_slist_free(node->pages);
g_free(node->pages);
tmp = l;
l = l->next;
g_slist_free_1(tmp);
}
}
void cbs_assembly_location_changed(struct cbs_assembly *assembly,
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 (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;
}