http://bugzilla.openmoko.org/cgi-bin/bugzilla/show_bug.cgi?id=107

Author: Andrzej Zaborowski <balrog@zabor.org>
Date:   Thu Jul 24 23:45:11 2007 +0200

Operator selection.

This adds gsmd/libgsmd/libgmsd-tool commands to:
 * query available operators,
 * register to given operator,
 * register automatically (like gsmd did until now),
 * deregister,
 * get current operator name,
 * query signal quality,
 * query current connection state (a bit hacky, but it's the only way
   I found to satisfy the synchronous prototype in misc.h).

The operator cache is not used as it wouldn't give any benefit here. Retrieving
the list of present operators takes very long but there doesn't seem to exist
any way around it and all other phones I used also take that long.

The libgmsd call for registration to an automatically chosen operator now takes
a parameter of a different type so all libgsmd clients need to be updated (this
patch updates libgsmd-tool already, but not libmokogsmd which is not a part of
gsmd project).
---
 include/gsmd/usock.h          |   24 +++++++-
 include/libgsmd/misc.h        |   33 +++++------
 src/gsmd/usock.c              |  128 +++++++++++++++++++++++++++++++++--------
 src/libgsmd/lgsm_internals.h  |    2 +
 src/libgsmd/libgsmd_event.c   |    7 ++
 src/libgsmd/libgsmd_network.c |   45 ++++++++++++--
 src/util/shell.c              |   77 +++++++++++++++++++++++--
 7 files changed, 263 insertions(+), 53 deletions(-)

Index: gsm/include/gsmd/usock.h
===================================================================
--- gsm.orig/include/gsmd/usock.h	2007-08-15 00:32:24.000000000 +0800
+++ gsm/include/gsmd/usock.h	2007-08-15 00:32:29.000000000 +0800
@@ -66,7 +66,9 @@
 	GSMD_NETWORK_VMAIL_GET	= 3,
 	GSMD_NETWORK_VMAIL_SET	= 4,
 	GSMD_NETWORK_OPER_GET	= 5,
-	GSMD_NETWORK_CIND_GET	= 6,
+	GSMD_NETWORK_OPER_LIST	= 6,
+	GSMD_NETWORK_CIND_GET	= 7,
+	GSMD_NETWORK_DEREGISTER	= 8,
 };
 
 enum gsmd_msg_sms {
@@ -358,6 +360,26 @@
 	u_int8_t tlength;
 } __attribute__ ((packed));
 
+/* Operator status from 3GPP TS 07.07, Clause 7.3 */
+enum gsmd_oper_status {
+	GSMD_OPER_UNKNOWN,
+	GSMD_OPER_AVAILABLE,
+	GSMD_OPER_CURRENT,
+	GSMD_OPER_FORBIDDEN,
+};
+
+/* Theoretically numeric operator code is five digits long but some
+ * operators apparently use six digit codes.  */
+typedef char gsmd_oper_numeric[6];
+
+struct gsmd_msg_oper {
+	enum gsmd_oper_status stat;
+	int is_last;
+	char opname_longalpha[16];
+	char opname_shortalpha[8];
+	gsmd_oper_numeric opname_num;
+};
+
 struct gsmd_msg_hdr {
 	u_int8_t version;
 	u_int8_t msg_type;
Index: gsm/include/libgsmd/misc.h
===================================================================
--- gsm.orig/include/libgsmd/misc.h	2007-08-15 00:32:24.000000000 +0800
+++ gsm/include/libgsmd/misc.h	2007-08-15 00:32:29.000000000 +0800
@@ -10,21 +10,6 @@
 
 extern int lgsm_phone_power(struct lgsm_handle *lh, int power);
 
-enum lgsm_netreg_state {
-	LGSM_NETREG_ST_NOTREG		= 0,
-	LGSM_NETREG_ST_REG_HOME		= 1,
-	LGSM_NETREG_ST_NOTREG_SEARCH	= 2,
-	LGSM_NETREG_ST_DENIED		= 3,
-	LGSM_NETREG_ST_UNKNOWN		= 4,
-	LGSM_NETREG_ST_REG_ROAMING	= 5,
-};
-
-/* Get the current network registration status */
-extern int lgsm_get_netreg_state(struct lgsm_handle *lh,
-				 enum lgsm_netreg_state *state);
-
-extern int lgsm_netreg_register(struct lgsm_handle *lh, int oper);
-
 enum lgsm_info_type {
 	LGSM_INFO_TYPE_NONE		= 0,
 	LGSM_INFO_TYPE_MANUF		= 1,
@@ -58,8 +43,24 @@
 			      struct lgsm_addr *addr);
 
 /* Operator Selection, Network Registration */
-/* TBD */
+extern int lgsm_oper_get(struct lgsm_handle *lh);
+extern int lgsm_opers_get(struct lgsm_handle *lh);
+extern int lgsm_netreg_register(struct lgsm_handle *lh,
+		gsmd_oper_numeric oper);
+extern int lgsm_netreg_deregister(struct lgsm_handle *lh);
 
+enum lgsm_netreg_state {
+	LGSM_NETREG_ST_NOTREG		= 0,
+	LGSM_NETREG_ST_REG_HOME		= 1,
+	LGSM_NETREG_ST_NOTREG_SEARCH	= 2,
+	LGSM_NETREG_ST_DENIED		= 3,
+	LGSM_NETREG_ST_UNKNOWN		= 4,
+	LGSM_NETREG_ST_REG_ROAMING	= 5,
+};
+
+/* Get the current network registration status */
+extern int lgsm_get_netreg_state(struct lgsm_handle *lh,
+				 enum lgsm_netreg_state *state);
 
 /* CLIP, CLIR, COLP, Call Forwarding, Call Waiting, Call Deflecting */
 /* TBD */
Index: gsm/src/gsmd/usock.c
===================================================================
--- gsm.orig/src/gsmd/usock.c	2007-08-15 00:32:24.000000000 +0800
+++ gsm/src/gsmd/usock.c	2007-08-15 00:32:29.000000000 +0800
@@ -349,7 +349,7 @@
 		return -ENOMEM;
 	
 	gsq = (struct gsmd_signal_quality *) ucmd->buf;
-	gsq->rssi = atoi(resp);
+	gsq->rssi = atoi(resp + 6);
 	comma = strchr(resp, ',');
 	if (!comma) {
 		talloc_free(ucmd);
@@ -362,42 +362,104 @@
 	return 0;
 }
 
-#define GSMD_OPER_MAXLEN	16
 static int network_oper_cb(struct gsmd_atcmd *cmd, void *ctx, char *resp)
 {
 	struct gsmd_user *gu = ctx;
 	struct gsmd_ucmd *ucmd;
-	char *comma, *opname;
-	
-	ucmd = gsmd_ucmd_fill(GSMD_OPER_MAXLEN+1, GSMD_MSG_NETWORK,
-			      GSMD_NETWORK_OPER_GET, 0);
+	const char *end, *opname;
+	int format, s;
+
+	/* Format: <mode>[,<format>,<oper>] */
+	/* In case we're not registered, return an empty string.  */
+	if (sscanf(resp, "+COPS: %*i,%i,\"%n", &format, &s) <= 0)
+		end = opname = resp;
+	else {
+		/* If the phone returned the opname in a short or numeric
+		 * format, then it probably doesn't know the operator's full
+		 * name or doesn't support it.  Return any information we
+		 * have in this case.  */
+		if (format != 0)
+			gsmd_log(GSMD_NOTICE, "+COPS response in a format "
+					" different than long alphanumeric - "
+					" returning as is!\n");
+		opname = resp + s;
+		end = strchr(opname, '"');
+		if (!end)
+			return -EINVAL;
+	}
+
+	ucmd = gsmd_ucmd_fill(end - opname + 1, GSMD_MSG_NETWORK,
+			GSMD_NETWORK_OPER_GET, 0);
 	if (!ucmd)
 		return -ENOMEM;
 
-	/* Format: <mode>[, <format>, <oper>] */
-	comma = strchr(resp, ',');
-	if (!comma)
-		goto out_err;
+	memcpy(ucmd->buf, opname, end - opname);
+	ucmd->buf[end - opname] = '\0';
 
-	if (atoi(comma+1) != 0) {
-		gsmd_log(GSMD_NOTICE, "COPS format !=0 not supported yet!\n");
-		goto out_err;
-	}
-	comma = strchr(resp, ',');
-	if (!comma || *(comma+1) != '"')
-		goto out_err;
-	opname = comma+2;
+	usock_cmd_enqueue(ucmd, gu);
+
+	return 0;
+}
 
-	memcpy(ucmd->buf, opname, strlen(opname-1));
-	ucmd->buf[strlen(opname)] = '\0';
+static int network_opers_parse(const char *str, struct gsmd_msg_oper out[])
+{
+	int len = 0;
+	int stat, n;
+	if (strncmp(str, "+COPS: ", 7))
+		goto final;
+	str += 7;
+
+	while (*str == '(') {
+		if (out) {
+			out->is_last = 0;
+			if (sscanf(str,
+						"(%i,\"%16[^\"]\","
+						"\"%8[^\"]\",\"%6[0-9]\")%n",
+						&stat,
+						out->opname_longalpha,
+						out->opname_shortalpha,
+						out->opname_num,
+						&n) < 4)
+				goto final;
+			out->stat = stat;
+		} else
+			if (sscanf(str,
+						"(%*i,\"%*[^\"]\","
+						"\"%*[^\"]\",\"%*[0-9]\")%n",
+						&n) < 0)
+				goto final;
+		if (n < 10 || str[n - 1] != ')')
+			goto final;
+		if (str[n] == ',')
+			n ++;
+		str += n;
+		len ++;
+		if (out)
+			out ++;
+	}
+final:
+	if (out)
+		out->is_last = 1;
+	return len;
+}
 
+static int network_opers_cb(struct gsmd_atcmd *cmd, void *ctx, char *resp)
+{
+	struct gsmd_user *gu = ctx;
+	struct gsmd_ucmd *ucmd;
+	int len;
+
+	len = network_opers_parse(resp, 0);
+
+	ucmd = gsmd_ucmd_fill(sizeof(struct gsmd_msg_oper) * (len + 1),
+			GSMD_MSG_NETWORK, GSMD_NETWORK_OPER_LIST, 0);
+	if (!ucmd)
+		return -ENOMEM;
+
+	network_opers_parse(resp, (struct gsmd_msg_oper *) ucmd->buf);
 	usock_cmd_enqueue(ucmd, gu);
 
 	return 0;
-
-out_err:
-	talloc_free(ucmd);
-	return -EIO;
 }
 
 static int usock_rcv_network(struct gsmd_user *gu, struct gsmd_msg_hdr *gph, 
@@ -405,11 +467,21 @@
 {
 	struct gsmd_atcmd *cmd;
 	struct gsmd_voicemail *vmail = (struct gsmd_voicemail *) gph->data;
+	gsmd_oper_numeric *oper = (gsmd_oper_numeric *) gph->data;
+	char buffer[15 + sizeof(gsmd_oper_numeric)];
+	int cmdlen;
 
 	switch (gph->msg_subtype) {
 	case GSMD_NETWORK_REGISTER:
-		cmd = atcmd_fill("AT+COPS=0", 9+1,
-				 &null_cmd_cb, gu, 0);
+		if ((*oper)[0])
+			cmdlen = sprintf(buffer, "AT+COPS=1,2,\"%.*s\"",
+					sizeof(gsmd_oper_numeric), oper);
+		else
+			cmdlen = sprintf(buffer, "AT+COPS=0");
+		cmd = atcmd_fill(buffer, cmdlen + 1, &null_cmd_cb, gu, 0);
+		break;
+	case GSMD_NETWORK_DEREGISTER:
+		cmd = atcmd_fill("AT+COPS=2", 9+1, &null_cmd_cb, gu, 0);
 		break;
 	case GSMD_NETWORK_VMAIL_GET:
 		cmd = atcmd_fill("AT+CSVM?", 8+1, &network_vmail_cb, gu, 0);
@@ -421,8 +493,14 @@
 		cmd = atcmd_fill("AT+CSQ", 6+1, &network_sigq_cb, gu, 0);
 		break;
 	case GSMD_NETWORK_OPER_GET:
+		/* Set long alphanumeric format */
+		atcmd_submit(gu->gsmd, atcmd_fill("AT+COPS=3,0", 11+1,
+					&null_cmd_cb, gu, 0));
 		cmd = atcmd_fill("AT+COPS?", 8+1, &network_oper_cb, gu, 0);
 		break;
+	case GSMD_NETWORK_OPER_LIST:
+		cmd = atcmd_fill("AT+COPS=?", 9+1, &network_opers_cb, gu, 0);
+		break;
 	default:
 		return -EINVAL;
 	}
Index: gsm/src/libgsmd/lgsm_internals.h
===================================================================
--- gsm.orig/src/libgsmd/lgsm_internals.h	2007-08-15 00:32:24.000000000 +0800
+++ gsm/src/libgsmd/lgsm_internals.h	2007-08-15 00:32:29.000000000 +0800
@@ -2,10 +2,12 @@
 #define _LGSM_INTERNALS_H
 
 #include <gsmd/usock.h>
+#include <libgsmd/misc.h>
 
 struct lgsm_handle {
 	int fd;
 	lgsm_msg_handler *handler[__NUM_GSMD_MSGS];
+	enum lgsm_netreg_state netreg_state;
 };
 
 int lgsm_send(struct lgsm_handle *lh, struct gsmd_msg_hdr *gmh);
Index: gsm/src/libgsmd/libgsmd_event.c
===================================================================
--- gsm.orig/src/libgsmd/libgsmd_event.c	2007-08-15 00:32:24.000000000 +0800
+++ gsm/src/libgsmd/libgsmd_event.c	2007-08-15 00:32:29.000000000 +0800
@@ -63,6 +63,12 @@
 	    gmh->msg_subtype >= __NUM_GSMD_EVT)
 		return -EINVAL;
 
+	switch (gmh->msg_subtype) {
+	case GSMD_EVT_NETREG:
+		lh->netreg_state = aux->u.netreg.state;
+		break;
+	}
+
 	if (evt_handlers[gmh->msg_subtype])
 		return evt_handlers[gmh->msg_subtype](lh, gmh->msg_subtype, aux);
 	else
@@ -71,6 +77,7 @@
 
 int lgsm_evt_init(struct lgsm_handle *lh)
 {
+	lh->netreg_state = LGSM_NETREG_ST_NOTREG;
 	return lgsm_register_handler(lh, GSMD_MSG_EVENT, &evt_demux_msghandler);
 }
 
Index: gsm/src/libgsmd/libgsmd_network.c
===================================================================
--- gsm.orig/src/libgsmd/libgsmd_network.c	2007-08-15 00:32:24.000000000 +0800
+++ gsm/src/libgsmd/libgsmd_network.c	2007-08-15 00:32:29.000000000 +0800
@@ -32,18 +32,49 @@
 
 #include "lgsm_internals.h"
 
-int lgsm_netreg_register(struct lgsm_handle *lh, int oper)
+/* Get the current network registration status */
+int lgsm_get_netreg_state(struct lgsm_handle *lh,
+		enum lgsm_netreg_state *state)
 {
-	/* FIXME: implement oper selection */
-	return lgsm_send_simple(lh, GSMD_MSG_NETWORK, GSMD_NETWORK_REGISTER);
+	*state = lh->netreg_state;
 }
 
-int lgsm_signal_quality(struct lgsm_handle *lh)
+int lgsm_oper_get(struct lgsm_handle *lh)
 {
-	return lgsm_send_simple(lh, GSMD_MSG_NETWORK, GSMD_NETWORK_SIGQ_GET);
+	return lgsm_send_simple(lh, GSMD_MSG_NETWORK, GSMD_NETWORK_OPER_GET);
 }
 
-int lgsmd_operator_name(struct lgsm_handle *lh)
+int lgsm_opers_get(struct lgsm_handle *lh)
 {
-	return lgsm_send_simple(lh, GSMD_MSG_NETWORK, GSMD_NETWORK_OPER_GET);
+	return lgsm_send_simple(lh, GSMD_MSG_NETWORK, GSMD_NETWORK_OPER_LIST);
+}
+
+int lgsm_netreg_register(struct lgsm_handle *lh, gsmd_oper_numeric oper)
+{
+	struct gsmd_msg_hdr *gmh;
+
+	gmh = lgsm_gmh_fill(GSMD_MSG_NETWORK, GSMD_NETWORK_REGISTER,
+			sizeof(gsmd_oper_numeric));
+	if (!gmh)
+		return -ENOMEM;
+
+	memcpy(gmh->data, oper, sizeof(gsmd_oper_numeric));
+
+	if (lgsm_send(lh, gmh) < gmh->len + sizeof(*gmh)) {
+		lgsm_gmh_free(gmh);
+		return -EIO;
+	}
+
+	lgsm_gmh_free(gmh);
+	return 0;
+}
+
+int lgsm_netreg_deregister(struct lgsm_handle *lh)
+{
+	return lgsm_send_simple(lh, GSMD_MSG_NETWORK, GSMD_NETWORK_DEREGISTER);
+}
+
+int lgsm_signal_quality(struct lgsm_handle *lh)
+{
+	return lgsm_send_simple(lh, GSMD_MSG_NETWORK, GSMD_NETWORK_SIGQ_GET);
 }
Index: gsm/src/util/shell.c
===================================================================
--- gsm.orig/src/util/shell.c	2007-08-15 00:32:24.000000000 +0800
+++ gsm/src/util/shell.c	2007-08-15 00:32:29.000000000 +0800
@@ -171,6 +171,54 @@
 	}
 }
 
+/* this is the handler for responses to network/operator commands */
+static int net_msghandler(struct lgsm_handle *lh, struct gsmd_msg_hdr *gmh)
+{
+	const struct gsmd_signal_quality *sq = (struct gsmd_signal_quality *)
+		((void *) gmh + sizeof(*gmh));
+	const char *oper = (char *) gmh + sizeof(*gmh);
+	const struct gsmd_msg_oper *opers = (struct gsmd_msg_oper *)
+		((void *) gmh + sizeof(*gmh));
+	static const char *oper_stat[] = {
+		[GSMD_OPER_UNKNOWN] = "of unknown status",
+		[GSMD_OPER_AVAILABLE] = "available",
+		[GSMD_OPER_CURRENT] = "our current operator",
+		[GSMD_OPER_FORBIDDEN] = "forbidden",
+	};
+
+	switch (gmh->msg_subtype) {
+	case GSMD_NETWORK_SIGQ_GET:
+		if (sq->rssi == 99)
+			printf("Signal undetectable\n");
+		else
+			printf("Signal quality %i dBm\n", -113 + sq->rssi * 2);
+		if (sq->ber == 99)
+			printf("Error rate undetectable\n");
+		else
+			printf("Bit error rate %i\n", sq->ber);
+		break;
+	case GSMD_NETWORK_OPER_GET:
+		if (oper[0])
+			printf("Our current operator is %s\n", oper);
+		else
+			printf("No current operator\n");
+		break;
+	case GSMD_NETWORK_OPER_LIST:
+		for (; !opers->is_last; opers ++)
+			printf("%8.*s   %16.*s,   %.*s for short, is %s\n",
+					sizeof(opers->opname_num),
+					opers->opname_num,
+					sizeof(opers->opname_longalpha),
+					opers->opname_longalpha,
+					sizeof(opers->opname_shortalpha),
+					opers->opname_shortalpha,
+					oper_stat[opers->stat]);
+		break;
+	default:
+		return -EINVAL;
+	}
+}
+
 static int shell_help(void)
 {
 	printf( "\tA\tAnswer incoming call\n"
@@ -178,8 +226,12 @@
 		"\tH\tHangup call\n"
 		"\tO\tPower On\n"
 		"\to\tPower Off\n"
-		"\tR\tRegister Network\n"
+		"\tr\tRegister to network\n"
+		"\tR\tRegister to given operator (R=number)\n"
 		"\tU\tUnregister from netowrk\n"
+		"\tP\tPrint current operator\n"
+		"\tL\tDetect available operators\n"
+		"\tQ\tRead signal quality\n"
 		"\tT\tSend DTMF Tone\n"
 		"\tpd\tPB Delete (pb=index)\n"
 		"\tpr\tPB Read (pr=index)\n"
@@ -208,6 +260,7 @@
 	lgsm_register_handler(lgsmh, GSMD_MSG_PASSTHROUGH, &pt_msghandler);
 	lgsm_register_handler(lgsmh, GSMD_MSG_PHONEBOOK, &pb_msghandler);
 	lgsm_register_handler(lgsmh, GSMD_MSG_SMS, &sms_msghandler);
+	lgsm_register_handler(lgsmh, GSMD_MSG_NETWORK, &net_msghandler);
 
 	fcntl(0, F_SETFD, O_NONBLOCK);
 	fcntl(lgsm_fd(lgsmh), F_SETFD, O_NONBLOCK);
@@ -272,12 +325,28 @@
 			} else if (!strcmp(buf, "o")) {
 				printf("Power-Off\n");
 				lgsm_phone_power(lgsmh, 0);
-			} else if (!strcmp(buf, "R")) {
+			} else if (!strcmp(buf, "r")) {
 				printf("Register\n");
-				lgsm_netreg_register(lgsmh, 0);
+				lgsm_netreg_register(lgsmh, "\0     ");
+			} else if (buf[0] == 'R') {
+				printf("Register to operator\n");
+				ptr = strchr(buf, '=');
+				if (!ptr || strlen(ptr) < 6)
+					printf("No.\n");
+				else
+					lgsm_netreg_register(lgsmh, ptr + 1);
 			} else if (!strcmp(buf, "U")) {
 				printf("Unregister\n");
-				lgsm_netreg_register(lgsmh, 2);
+				lgsm_netreg_deregister(lgsmh);
+			} else if (!strcmp(buf, "P")) {
+				printf("Read current opername\n");
+				lgsm_oper_get(lgsmh);
+			} else if (!strcmp(buf, "L")) {
+				printf("List operators\n");
+				lgsm_opers_get(lgsmh);
+			} else if (!strcmp(buf, "Q")) {
+				printf("Signal strength\n");
+				lgsm_signal_quality(lgsmh);
 			} else if (!strcmp(buf, "q")) {
 				exit(0);
 			} else if (buf[0] == 'T') {
