[RFC] SMS support

andrzej zaborowski balrogg at gmail.com
Thu Jul 5 03:35:22 CEST 2007


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
-------------- next part --------------
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 */
-- 
1.4.4.3



More information about the gsmd-devel mailing list