[RFC] SMS support
Petri Aarnio
petri.aarnio at saunalahti.fi
Thu Jul 5 19:38:58 CEST 2007
Hi
Firstly fast comment on coding of the messages. It is defined in GSM
spec 03.38. The coding can be 7-bit, 8-bit or 16-bit.
7-bit is used for GSM default character set which includes all most
significant European languages characters. Almost all ASCII control
characters are dropped out.
8-bit format is originally meant for data messages.
16-bit is for UCS-2 coding which is a UNICODE format and thus can be
used with most languages in the world.
One SMS message lenght can be 140 bytes, which allows 160 7-bit
charactes and only 70 UCS-2 characters. Normally the phones deal with
this so that 7-bit character set is the default format and is used when
the string does not include characters outside of the default GSM
character set. Otherwise 16-bit UCS-2 format is used.
I'll get back to the other issues later. But I must say I wonder how is
it possible that SMS is under construction if the phone is going to be
on market in 4 days.
Best Regards,
Petri Aarnio
andrzej zaborowski kirjoitti:
> Hi,
> I made a first attempt at SMS sending support, I wanted to also have
> a go at listing messages and other functions but I started with
> sending and have a number of questions already. The diff of my
> changes is attached, it contains some unrelated fixes and formatting
> changes - I will clean this up before submitting anywhere.
>
> - Do we want to support both PDU and TEXT modes? Comments in
> different files suggest either. Asking around on IRC I was told that
> we want both, which makes sense because PDU allows more control over
> what is sent out, but it is optional and not all vendors have to
> support it.
>
> In the attached version I support both, with a way for the machine or
> vendor plugin to decide what mode to use on initialisation. Both modes
> were tested to work on the GTA01Bv4, however in PDU mode we have to
> report the PDU length 1 byte shorter than it is defined in the specs
> for the modem to accept the command (which took some guess-work,
> especially that the modem is picky about the message format and the
> error doesn't give much information about the failure).
>
> For that reason the message sent from libgsmd through usock uses a
> format that doesn't determine either PDU or TEXT, and the formatting
> is done on gsmd side.
>
> - How should we choose the coding for the messages? I tested the 7-
> and 8-bit coding and both worked but my other phone on which I was
> receiving, couldn't read the 8-bit messages. I don't know how GSM
> deals with national characters, but if it does at all, it probably
> needs 8- or 16-bit coding for that (but then we shouldn't make this
> default because 7-bit allows longest messages).
>
> - Did anyone notice that we're writing a \0 byte after every command
> before the final \r? I confirmed that everything is still fine without
> it, on the Neo. Should we get rid of it? Also how about passing the
> correct cmd length to atcmd_fill() and gsmd_ucmd_fill() and adding 1
> inside these functions instead of in the caller?
>
> - I use atcmd_submit() to submit the message contents but this is a
> bit of a hack because technically it's not an AT command, and the "> "
> prompt returned from modem is not an AT response, so we should
> probably have a separate parser for it. Also the "> " has no line
> termination after it so it gets buffered until the next response comes
> (e.g. +CSQ).
>
> And it turns out we always have to wait for the "> " prompt before
> sending message contents because otherwise the beginning of the
> text/PDU gets eaten.
>
> BTW on the occasion of implementing the modem in QEMU, I saw that in
> case the read() returns two or more responses in a single call, they
> will be all treated as a single line and the responses after the first
> one, are lost.
>
> Unrelated to that I noticed that when we're suspending/resuming the
> kernel prints the whole lot of messages to UART0 even if the modem is
> on. This can be seen if echo is enabled before suspend (ATE1), after
> resuming the modem prints a whole lot of kernel messages back at us.
>
> Cheers,
> Andrew
> ------------------------------------------------------------------------
>
> From 9420718b9b2be18df88da430c4e8d858522c1751 Mon Sep 17 00:00:00 2001
> From: Andrzej Zaborowski <balrog at zabor.org>
> Date: Thu, 5 Jul 2007 02:46:47 +0200
> Subject: [PATCH] SMS hacks.
>
> ---
> include/gsmd/gsmd.h | 4 +-
> include/gsmd/usock.h | 35 +++++++++-
> include/libgsmd/sms.h | 2 +-
> src/gsmd/atcmd.c | 4 +-
> src/gsmd/sms_cb.c | 19 ++++--
> src/gsmd/usock.c | 176 +++++++++++++++++++++++++++++++++++++++++----
> src/libgsmd/libgsmd_sms.c | 26 +++++--
> 8 files changed, 241 insertions(+), 29 deletions(-)
>
> diff --git a/include/gsmd/gsmd.h b/include/gsmd/gsmd.h
> index b4dfa62..8f1771e 100644
> --- a/include/gsmd/gsmd.h
> +++ b/include/gsmd/gsmd.h
> @@ -56,6 +56,7 @@ struct llparser {
> struct gsmd;
>
> #define GSMD_FLAG_V0 0x0001 /* V0 responses to be expected from TA */
> +#define GSMD_FLAG_SMS_FMT 0x0002 /* Use TEXT rather than PDU mode */
>
> struct gsmd {
> unsigned int flags;
> @@ -89,7 +90,8 @@ struct gsmd_user {
>
> extern int gsmdlog_init(const char *path);
> /* write a message to the daemons' logfile */
> -void __gsmd_log(int level, const char *file, int line, const char *function, const char *message, ...);
> +void __gsmd_log(int level, const char *file, int line, const char *function, const char *message, ...)
> + __attribute__ ((__format__ (__printf__, 5, 6)));
> /* macro for logging including filename and line number */
> #define gsmd_log(level, format, args ...) \
> __gsmd_log(level, __FILE__, __LINE__, __FUNCTION__, format, ## args)
> diff --git a/include/gsmd/usock.h b/include/gsmd/usock.h
> index 2032230..ad7ef27 100644
> --- a/include/gsmd/usock.h
> +++ b/include/gsmd/usock.h
> @@ -139,7 +139,7 @@ enum gsmd_sms_tp_rp {
> /* for SMS-SUBMIT, SMS-DELIVER */
> enum gsmd_sms_tp_udhi {
> GSMD_SMS_TP_UDHI_NO_HEADER = (0<<6),
> - GSMD_SMS_TP_UDHI_WTIH_HEADER = (1<<6),
> + GSMD_SMS_TP_UDHI_WITH_HEADER = (1<<6),
> };
>
> /* SMS delflg from 3GPP TS 07.05, Clause 3.5.4 */
> @@ -160,6 +160,34 @@ enum gsmd_msg_phonebook {
> GSMD_PHONEBOOK_GET_SUPPORT = 6,
> };
>
> +/* Type-of-Address, Numbering Plan Identification field */
> +enum gsmd_toa_npi {
> + GSMD_TOA_NPI_UNKNOWN = 0x0,
> + GSMD_TOA_NPI_ISDN = 0x1,
> + GSMD_TOA_NPI_DATA = 0x3,
> + GSMD_TOA_NPI_TELEX = 0x4,
> + GSMD_TOA_NPI_NATIONAL = 0x8,
> + GSMD_TOA_NPI_PRIVATE = 0x9,
> + GSMD_TOA_NPI_ERMES = 0xa,
> + GSMD_TOA_NPI_RESERVED = 0xf,
> +};
> +
> +/* Type-of-Address, Type-of-Number field */
> +enum gsmd_toa_ton {
> + GSMD_TOA_TON_UNKNOWN = (0<<4),
> + GSMD_TOA_TON_INTERNATIONAL = (1<<4),
> + GSMD_TOA_TON_NATIONAL = (2<<4),
> + GSMD_TOA_TON_NETWORK = (3<<4),
> + GSMD_TOA_TON_SUBSCRIBER = (4<<4),
> + GSMD_TOA_TON_ALPHANUMERIC = (5<<4),
> + GSMD_TOA_TON_ABBREVIATED = (6<<4),
> +};
> +
> +/* Type-of-Address, bit 7 always 1 */
> +enum gsmd_toa_reserved {
> + GSMD_TOA_RESERVED = (1<<7),
> +};
> +
> /* Length from 3GPP TS 04.08, Clause 10.5.4.7 */
>
> #define GSMD_ADDR_MAXLEN 32
> @@ -269,6 +297,11 @@ struct gsmd_sms_deliver {
> char user_data[140];
> } __attribute__ ((packed));
>
> +struct gsmd_sms_send {
> + struct gsmd_addr addr;
> + struct gsmd_sms payload;
> +};
> +
> /* Refer to GSM 07.07 subclause 8.12 */
> struct gsmd_phonebook_readrg {
> u_int8_t index1;
> diff --git a/include/libgsmd/sms.h b/include/libgsmd/sms.h
> index a07fc74..d449933 100644
> --- a/include/libgsmd/sms.h
> +++ b/include/libgsmd/sms.h
> @@ -83,7 +83,7 @@ extern int lgsmd_sms_delete(struct lgsm_handle *lh,
> extern int lgsmd_sms_send(struct lgsm_handle *lh, const struct lgsm_sms *sms);
>
> /* Write Message to Memory */
> -extern int lgsmd_sms_write(struct lgsm_handle *lh,
> +extern int lgsmd_sms_write(struct lgsm_handle *lh,
> const struct lgsm_sms_write *sms_write);
>
> /* Packing of 7-bit characters, refer to GSM 03.38 subclause 6.1.2.1.1 */
> diff --git a/src/gsmd/atcmd.c b/src/gsmd/atcmd.c
> index 44e215c..36ccb6e 100644
> --- a/src/gsmd/atcmd.c
> +++ b/src/gsmd/atcmd.c
> @@ -384,8 +384,8 @@ static int atcmd_select_cb(int fd, unsigned int what, void *data)
> if ((what & GSMD_FD_WRITE) && g->interpreter_ready) {
> struct gsmd_atcmd *pos, *pos2;
> llist_for_each_entry_safe(pos, pos2, &g->pending_atcmds, list) {
> - len = strlen(pos->buf);
> - rc = write(fd, pos->buf, strlen(pos->buf));
> + len = pos->buflen;
> + rc = write(fd, pos->buf, len);
> if (rc == 0) {
> gsmd_log(GSMD_ERROR, "write returns 0, aborting\n");
> break;
> diff --git a/src/gsmd/sms_cb.c b/src/gsmd/sms_cb.c
> index 330317c..ba7b290 100644
> --- a/src/gsmd/sms_cb.c
> +++ b/src/gsmd/sms_cb.c
> @@ -91,9 +91,6 @@ static int usock_cpms_cb(struct gsmd_atcmd *cmd, void *ctx, char *resp)
> if (!ucmd)
> return -ENOMEM;
>
> -
> -
> -
> ucmd->hdr.version = GSMD_PROTO_VERSION;
> ucmd->hdr.msg_type = GSMD_MSG_SMS;
> ucmd->hdr.msg_subtype = GSMD_SMS_GETMSG_STORAGE;
> @@ -188,14 +185,26 @@ static const struct gsmd_unsolocit gsm0705_unsolicit[] = {
> int sms_cb_init(struct gsmd *gsmd)
> {
> struct gsmd_atcmd *atcmd;
> + char buffer[10];
>
> atcmd = atcmd_fill("AT+CSMS=0", NULL, gu, 0);
> if (!atcmd)
> return -ENOMEM;
> atcmd_submit(gsmd, atcmd);
>
> - /* Switch into "text mode" (Section 3.2.3) */
> - atcdm = atcmd_fill("AT+CMGF=1", 9, &sms_cb_init_cb, gu, 0);
> + /* If text mode, set the encoding */
> + if (gu->gsmd->flags & GSMD_FLAG_SMS_FMT) {
> + atcdm = atcmd_fill("AT+CSCS=\"IRA\"", 13, NULL, gu, 0);
> + if (!atcmd)
> + return -ENOMEM;
> + atcmd_submit(gsmd, atcmd);
> + }
> +
> + /* Switch into desired mode (Section 3.2.3) */
> + snprintf(buffer, sizeof(buffer), "AT+CMGF=%i",
> + (gu->gsmd->flags & GSMD_FLAG_SMS_FMT) ?
> + GSMD_SMS_FMT_TEXT : GSMD_SMS_FMT_PDU);
> + atcdm = atcmd_fill(buffer, strlen(buffer), &sms_cb_init_cb, gu, 0);
> if (!atcmd)
> return -ENOMEM;
>
> diff --git a/src/gsmd/usock.c b/src/gsmd/usock.c
> index 0a29e86..b15f9c7 100644
> --- a/src/gsmd/usock.c
> +++ b/src/gsmd/usock.c
> @@ -100,7 +100,7 @@ static int usock_rcv_passthrough(struct gsmd_user *gu, struct gsmd_msg_hdr *gph,
>
> static int usock_rcv_event(struct gsmd_user *gu, struct gsmd_msg_hdr *gph, int len)
> {
> - u_int32_t *evtmask = (u_int32_t *) ((char *)gph + sizeof(*gph), gph->id);
> + u_int32_t *evtmask = (u_int32_t *) ((char *)gph + sizeof(*gph));
>
> if (len < sizeof(*gph) + sizeof(u_int32_t))
> return -EINVAL;
> @@ -470,18 +470,38 @@ static int sms_read_cb(struct gsmd_atcmd *cmd, void *ctx, char *resp)
>
> static int sms_send_cb(struct gsmd_atcmd *cmd, void *ctx, char *resp)
> {
> - struct gsmd_user *gu = ctx;
> + struct gsmd_atcmd *nextcmd = (struct gsmd_atcmd *) ctx;
> + struct gsmd_user *gu = (struct gsmd_user *) nextcmd->ctx;
> struct gsmd_ucmd *ucmd;
> -
> - ucmd = gsmd_ucmd_fill(strlen(resp)+1, GSMD_MSG_SMS,
> - GSMD_SMS_SEND, 0);
> +
> + /* The modem is asking for the message contents */
> + if (resp[0] == '>' && resp[1] == ' ')
> + return atcmd_submit(gu->gsmd, nextcmd);
> +
> + /* Something went wrong */
> + ucmd = gsmd_ucmd_fill(strlen(resp) + 1,
> + GSMD_MSG_SMS, GSMD_SMS_SEND, cmd->id);
> if (!ucmd)
> return -ENOMEM;
> -
> strcpy(ucmd->buf, resp);
> -
> usock_cmd_enqueue(ucmd, gu);
> + return 0;
> +}
> +
> +static int sms_send_next_cb(struct gsmd_atcmd *cmd, void *ctx, char *resp)
> +{
> + struct gsmd_user *gu = (struct gsmd_user *) ctx;
> + struct gsmd_ucmd *ucmd;
>
> + if (resp[0] == '>' && resp[1] == ' ')
> + return 0;
> +
> + ucmd = gsmd_ucmd_fill(strlen(resp) + 1,
> + GSMD_MSG_SMS, GSMD_SMS_SEND, cmd->id);
> + if (!ucmd)
> + return -ENOMEM;
> + strcpy(ucmd->buf, resp);
> + usock_cmd_enqueue(ucmd, gu);
> return 0;
> }
>
> @@ -519,18 +539,117 @@ static int sms_delete_cb(struct gsmd_atcmd *cmd, void *ctx, char *resp)
> return 0;
> }
>
> +int packing_7bit_character(char *src, char *dest)
> +{
> + int i,j = 0;
> + unsigned char ch1, ch2;
> + char tmp[2];
> + int shift = 0;
> +
> + *dest = '\0';
> +
> + for ( i=0; i<strlen(src); i++ ) {
> +
> + ch1 = src[i] & 0x7F;
> + ch1 = ch1 >> shift;
> + ch2 = src[(i+1)] & 0x7F;
> + ch2 = ch2 << (7-shift);
> +
> + ch1 = ch1 | ch2;
> +
> + j = strlen(dest);
> + sprintf(tmp, "%X", (ch1 >> 4));
> + dest[j++] = tmp[0];
> + sprintf(tmp, "%X", (ch1 & 0x0F));
> + dest[j++] = tmp[0];
> + dest[j++] = '\0';
> +
> + shift++;
> +
> + if ( 7 == shift ) {
> + shift = 0;
> + i++;
> + }
> + }
> +
> + return 0;
> +}
> +
> +/* Refer to GSM 03.40 subclause 9.2.3.3, for SMS-SUBMIT */
> +static int usock_pdu_make_smssubmit(char *dest, struct gsmd_sms_send *src)
> +{
> + u_int8_t header[10 + GSMD_ADDR_MAXLEN];
> + int pos = 0, i, coding7bit = 1;
> +
> + /* (Should be optional but some modems require it) SMSC Length octet
> + * is prepended. If omitted or zero, use SMSC stored in the phone. */
> + header[pos ++] = 0x00;
> +
> + header[pos ++] =
> + GSMD_SMS_TP_MTI_SUBMIT |
> + (0 << 2) | /* Reject Duplicates: 0 */
> + GSMD_SMS_TP_VPF_NOT_PRESENT |
> + GSMD_SMS_TP_SRR_NOT_REQUEST |
> + GSMD_SMS_TP_UDHI_NO_HEADER |
> + GSMD_SMS_TP_RP_NOT_SET;
> +
> + /* TP-Message-Reference - 00 lets the phone set the number itself */
> + header[pos ++] = 0x00;
> +
> + header[pos ++] = strlen(src->addr.number);
> + header[pos ++] = src->addr.type;
> + for (i = 0; src->addr.number[i]; i ++) {
> + header[pos] = src->addr.number[i ++] - '0';
> + if (src->addr.number[i])
> + header[pos ++] |= (src->addr.number[i] - '0') << 4;
> + else {
> + header[pos ++] |= 0xf0;
> + break;
> + }
> + }
> +
> + /* TP-Protocol-Identifier - 00 means implicit */
> + header[pos ++] = 0x00;
> +
> + /* TP-Data-Coding-Scheme - 00 for 7-bit default alphabet */
> + header[pos ++] = coding7bit ? 0x00 : 0x04;
> +
> + /* TP-Validity-Period, if present, would go here */
> +
> + header[pos ++] = src->payload.length;
> +
> + if (dest) {
> + for (i = 0; i < pos; i ++) {
> + sprintf(dest, "%02X", header[i]);
> + dest += 2;
> + }
> + if (coding7bit)
> + packing_7bit_character(src->payload.data, dest);
> + else
> + for (i = 0; i < src->payload.length; i ++) {
> + sprintf(dest, "%02X", src->payload.data[i]);
> + dest += 2;
> + }
> + }
> +
> + if (coding7bit)
> + return ((src->payload.length * 7 + 7) >> 3) + pos;
> + else
> + return src->payload.length + pos;
> +}
> +
> static int usock_rcv_sms(struct gsmd_user *gu, struct gsmd_msg_hdr *gph,
> int len)
> {
> /* FIXME: TEXT mode support!! */
> - struct gsmd_atcmd *cmd = NULL;
> + struct gsmd_atcmd *cmd = NULL, *nextcmd;
> struct gsmd_sms_delete *gsd;
> - struct gsmd_sms *gs;
> + struct gsmd_sms_send *gss;
> struct gsmd_sms_write *gsw;
> int *stat, *index;
> int atcmd_len;
> char buf[1024];
> -
> +
> switch (gph->msg_subtype) {
> case GSMD_SMS_LIST:
> /* FIXME: only support PDU mode!! */
> @@ -562,6 +681,37 @@ static int usock_rcv_sms(struct gsmd_user *gu, struct gsmd_msg_hdr *gph,
> return -ENOMEM;
> sprintf(cmd->buf, "AT+CMGR=%s", buf);
> break;
> + case GSMD_SMS_SEND:
> + if (len < sizeof(*gph) + sizeof(*gss))
> + return -EINVAL;
> + gss = (struct gsmd_sms_send *) ((void *) gph + sizeof(*gph));
> +
> + if (gu->gsmd->flags & GSMD_FLAG_SMS_FMT) {
> + nextcmd = atcmd_fill(gss->payload.data,
> + gss->payload.length + 2,
> + &sms_send_next_cb, gu, gph->id);
> + atcmd_len = sprintf(buf, "AT+CMGS=\"%s\"",
> + gss->addr.number);
> + } else {
> + atcmd_len = usock_pdu_make_smssubmit(buf, gss);
> + nextcmd = atcmd_fill(buf, atcmd_len * 2 + 2,
> + &sms_send_next_cb, gu, gph->id);
> + atcmd_len = sprintf(buf, "AT+CMGS=%i",
> + atcmd_len - 1);
> + }
> + /* ^Z ends the message */
> + nextcmd->buf[nextcmd->buflen - 2] = 26;
> +
> + cmd = atcmd_fill(buf, atcmd_len + 1, &sms_send_cb, nextcmd, 0);
> +
> + if (!cmd)
> + return -ENOMEM;
> +
> + gsmd_log(GSMD_DEBUG, "sms submitted with %s\n", buf);
> + break;
> + case GSMD_SMS_WRITE:
> + gsmd_log(GSMD_DEBUG, "sms write\n");
> + break;
> #if 0
> case GSMD_SMS_SEND:
> /* FIXME: only support PDU mode!! */
> @@ -609,8 +759,8 @@ static int usock_rcv_sms(struct gsmd_user *gu, struct gsmd_msg_hdr *gph,
> default:
> return -EINVAL;
> }
> -
> - gsmd_log(GSMD_DEBUG, "%s\n", cmd->buf);
> +
> + gsmd_log(GSMD_DEBUG, "%s\n", cmd ? cmd->buf : 0);
> if (cmd)
> return atcmd_submit(gu->gsmd, cmd);
> else
> @@ -866,7 +1016,7 @@ static usock_msg_handler *pcmd_type_handlers[__NUM_GSMD_MSGS] = {
> [GSMD_MSG_PIN] = &usock_rcv_pin,
> [GSMD_MSG_PHONE] = &usock_rcv_phone,
> [GSMD_MSG_NETWORK] = &usock_rcv_network,
> - [GSMD_MSG_SMS] = &usock_rcv_sms,
> + [GSMD_MSG_SMS] = &usock_rcv_sms,
> //[GSMD_MSG_PHONEBOOK] = &usock_rcv_phonebook,
> };
>
> diff --git a/src/libgsmd/libgsmd_sms.c b/src/libgsmd/libgsmd_sms.c
> index 261dca3..31c416b 100644
> --- a/src/libgsmd/libgsmd_sms.c
> +++ b/src/libgsmd/libgsmd_sms.c
> @@ -83,19 +83,33 @@ int lgsmd_sms_delete(struct lgsm_handle *lh,
> return 0;
> }
>
> -int lgsmd_sms_send(struct lgsm_handle *lh,
> - const struct lgsm_sms *sms)
> +#ifndef MIN
> +# define MIN(a,b) (((a) < (b)) ? (a) : (b))
> +#endif
> +
> +int lgsmd_sms_send(struct lgsm_handle *lh,
> + const struct lgsm_sms *sms)
> {
> /* FIXME: only support PDU mode */
> struct gsmd_msg_hdr *gmh;
> - struct gsmd_sms *gs;
> + struct gsmd_sms_send *gss;
> int rc;
>
> gmh = lgsm_gmh_fill(GSMD_MSG_SMS,
> - GSMD_SMS_SEND, sizeof(*gs));
> + GSMD_SMS_SEND, sizeof(*gss));
> if (!gmh)
> return -ENOMEM;
> - gs = (struct gsmd_sms *) gmh->data;
> + gss = (struct gsmd_sms_send *) gmh->data;
> +
> + gss->addr.type =
> + GSMD_TOA_NPI_ISDN |
> + GSMD_TOA_TON_UNKNOWN |
> + GSMD_TOA_RESERVED;
> + strncpy(gss->addr.number, sms->addr, sizeof(gss->addr.number));
> +
> + gss->payload.length =
> + MIN(strlen(sms->data), sizeof(gss->payload.data));
> + memcpy(gss->payload.data, sms->data, gss->payload.length);
>
> rc = lgsm_send(lh, gmh);
> if (rc < gmh->len + sizeof(*gmh)) {
> @@ -108,7 +122,7 @@ int lgsmd_sms_send(struct lgsm_handle *lh,
> return 0;
> }
>
> -int lgsmd_sms_write(struct lgsm_handle *lh,
> +int lgsmd_sms_write(struct lgsm_handle *lh,
> const struct lgsm_sms_write *sms_write)
> {
> /* FIXME: only support PDU mode */
>
More information about the gsmd-devel
mailing list