Skip to content
Snippets Groups Projects
chan_mobile.c 125 KiB
Newer Older
  • Learn to ignore specific revisions
  •  * \param prefix the prefix to match
     */
    static int at_match_prefix(char *buf, char *prefix)
    {
    	return !strncmp(buf, prefix, strlen(prefix));
    }
    
    /*!
    
    Josh Soref's avatar
    Josh Soref committed
     * \brief Read an AT message and classify it.
    
     * \param rsock an rfcomm socket
     * \param buf the buffer to store the result in
     * \param count the size of the buffer or the maximum number of characters to read
     * \return the type of message received, in addition buf will contain the
     * message received and will be null terminated
     * \see at_read()
     */
    static at_message_t at_read_full(int rsock, char *buf, size_t count)
    {
    	ssize_t s;
    	if ((s = rfcomm_read(rsock, buf, count - 1)) < 1)
    		return s;
    	buf[s] = '\0';
    
    	if (!strcmp("OK", buf)) {
    		return AT_OK;
    	} else if (!strcmp("ERROR", buf)) {
    		return AT_ERROR;
    	} else if (!strcmp("RING", buf)) {
    		return AT_RING;
    	} else if (!strcmp("AT+CKPD=200", buf)) {
    		return AT_CKPD;
    	} else if (!strcmp("> ", buf)) {
    		return AT_SMS_PROMPT;
    	} else if (at_match_prefix(buf, "+CMTI:")) {
    		return AT_CMTI;
    	} else if (at_match_prefix(buf, "+CIEV:")) {
    		return AT_CIEV;
    	} else if (at_match_prefix(buf, "+BRSF:")) {
    		return AT_BRSF;
    	} else if (at_match_prefix(buf, "+CIND:")) {
    		return AT_CIND;
    	} else if (at_match_prefix(buf, "+CLIP:")) {
    		return AT_CLIP;
    	} else if (at_match_prefix(buf, "+CMGR:")) {
    		return AT_CMGR;
    	} else if (at_match_prefix(buf, "+VGM:")) {
    		return AT_VGM;
    	} else if (at_match_prefix(buf, "+VGS:")) {
    		return AT_VGS;
    	} else if (at_match_prefix(buf, "+CMS ERROR:")) {
    		return AT_CMS_ERROR;
    	} else if (at_match_prefix(buf, "AT+VGM=")) {
    		return AT_VGM;
    	} else if (at_match_prefix(buf, "AT+VGS=")) {
    		return AT_VGS;
    
    	} else if (at_match_prefix(buf, "+CUSD:")) {
    		return AT_CUSD;
    
    	} else if (at_match_prefix(buf, "BUSY")) {
    		return AT_BUSY;
    	} else if (at_match_prefix(buf, "NO DIALTONE")) {
    		return AT_NO_DIALTONE;
    	} else if (at_match_prefix(buf, "NO CARRIER")) {
    		return AT_NO_CARRIER;
    	} else if (at_match_prefix(buf, "*ECAV:")) {
    		return AT_ECAM;
    
    	} else {
    		return AT_UNKNOWN;
    	}
    }
    
    /*!
     * \brief Get the string representation of the given AT message.
     * \param msg the message to process
     * \return a string describing the given message
     */
    static inline const char *at_msg2str(at_message_t msg)
    {
    	switch (msg) {
    	/* errors */
    	case AT_PARSE_ERROR:
    		return "PARSE ERROR";
    	case AT_READ_ERROR:
    		return "READ ERROR";
    	default:
    	case AT_UNKNOWN:
    		return "UNKNOWN";
    	/* at responses */
    	case AT_OK:
    		return "OK";
    	case AT_ERROR:
    		return "ERROR";
    	case AT_RING:
    		return "RING";
    	case AT_BRSF:
    		return "AT+BRSF";
    	case AT_CIND:
    		return "AT+CIND";
    	case AT_CIEV:
    		return "AT+CIEV";
    	case AT_CLIP:
    		return "AT+CLIP";
    	case AT_CMTI:
    		return "AT+CMTI";
    	case AT_CMGR:
    		return "AT+CMGR";
    	case AT_SMS_PROMPT:
    		return "SMS PROMPT";
    	case AT_CMS_ERROR:
    		return "+CMS ERROR";
    
    	case AT_BUSY:
    		return "BUSY";
    	case AT_NO_DIALTONE:
    		return "NO DIALTONE";
    	case AT_NO_CARRIER:
    		return "NO CARRIER";
    
    	/* at commands */
    	case AT_A:
    		return "ATA";
    	case AT_D:
    		return "ATD";
    	case AT_CHUP:
    		return "AT+CHUP";
    	case AT_CKPD:
    		return "AT+CKPD";
    	case AT_CMGS:
    		return "AT+CMGS";
    	case AT_VGM:
    		return "AT+VGM";
    	case AT_VGS:
    		return "AT+VGS";
    	case AT_VTS:
    		return "AT+VTS";
    	case AT_CMGF:
    		return "AT+CMGF";
    	case AT_CNMI:
    		return "AT+CNMI";
    	case AT_CMER:
    		return "AT+CMER";
    	case AT_CIND_TEST:
    		return "AT+CIND=?";
    
    	case AT_ECAM:
    		return "AT*ECAM";
    
     /*!
     * \brief Parse a ECAV event.
     * \param hfp an hfp_pvt struct
     * \param buf the buffer to parse (null terminated)
     * \return -1 on error (parse error) or a ECAM value on success
     *
    
     * Example:
     * \verbatim *ECAV: <ccid>,<ccstatus>,<calltype>[,<processid>]
                        [,exitcause][,<number>,<type>] \endverbatim
    
     * Example indicating busy:
     * \verbatim *ECAV: 1,7,1 \endverbatim
    
     */
    static int hfp_parse_ecav(struct hfp_pvt *hfp, char *buf)
    {
    	int ccid = 0;
    	int ccstatus = 0;
    	int calltype = 0;
    
    	if (!sscanf(buf, "*ECAV: %2d,%2d,%2d", &ccid, &ccstatus, &calltype)) {
    		ast_debug(1, "[%s] error parsing ECAV event '%s'\n", hfp->owner->id, buf);
    		return -1;
    	}
    
    	return ccstatus;
    }
    
    /*!
    
    Josh Soref's avatar
    Josh Soref committed
     * \brief Enable Sony Ericsson extensions / indications.
    
     * \param hfp an hfp_pvt struct
     */
    static int hfp_send_ecam(struct hfp_pvt *hfp)
    {
    	return rfcomm_write(hfp->rsock, "AT*ECAM=1\r");
    }
    
    
    /*!
     * \brief Parse a CIEV event.
     * \param hfp an hfp_pvt struct
     * \param buf the buffer to parse (null terminated)
     * \param value a pointer to an int to store the event value in (can be NULL)
     * \return 0 on error (parse error, or unknown event) or a HFP_CIND_* value on
     * success
     */
    static int hfp_parse_ciev(struct hfp_pvt *hfp, char *buf, int *value)
    {
    	int i, v;
    	if (!value)
    		value = &v;
    
    	if (!sscanf(buf, "+CIEV: %d,%d", &i, value)) {
    		ast_debug(2, "[%s] error parsing CIEV event '%s'\n", hfp->owner->id, buf);
    		return HFP_CIND_NONE;
    	}
    
    
    	if (i >= ARRAY_LEN(hfp->cind_state)) {
    
    		ast_debug(2, "[%s] CIEV event index too high (%s)\n", hfp->owner->id, buf);
    		return HFP_CIND_NONE;
    	}
    
    	hfp->cind_state[i] = *value;
    	return hfp->cind_index[i];
    }
    
    /*!
     * \brief Parse a CLIP event.
     * \param hfp an hfp_pvt struct
     * \param buf the buffer to parse (null terminated)
    
     * \note buf will be modified when the CID string is parsed
    
     * \return a cidinfo structure pointing to the cnam and cnum
     * data in buf.  On parse errors, either or both pointers
     * will point to null strings
    
    static struct cidinfo hfp_parse_clip(struct hfp_pvt *hfp, char *buf)
    
    	int i;
    	int tokens[6];
    	char *cnamtmp;
    	char delim = ' ';	/* First token terminates with space */
    	int invalid = 0;	/* Number of invalid chars in cnam */
    	struct cidinfo cidinfo = { NULL, NULL };
    
    
    	/* parse clip info in the following format:
    	 * +CLIP: "123456789",128,...
    	 */
    
    	ast_debug(3, "[%s] hfp_parse_clip is processing \"%s\"\n", hfp->owner->id, buf);
    	tokens[0] = 0;		/* First token starts in position 0 */
    	for (i = 1; i < ARRAY_LEN(tokens); i++) {
    		tokens[i] = parse_next_token(buf, tokens[i - 1], delim);
    		delim = ',';	/* Subsequent tokens terminate with comma */
    	}
    	ast_debug(3, "[%s] hfp_parse_clip found tokens: 0=%s, 1=%s, 2=%s, 3=%s, 4=%s, 5=%s\n",
    		hfp->owner->id, &buf[tokens[0]], &buf[tokens[1]], &buf[tokens[2]],
    		&buf[tokens[3]], &buf[tokens[4]], &buf[tokens[5]]);
    
    	/* Clean up cnum, and make sure it is legitimate since it is untrusted. */
    	cidinfo.cnum = ast_strip_quoted(&buf[tokens[1]], "\"", "\"");
    	if (!ast_isphonenumber(cidinfo.cnum)) {
    		ast_debug(1, "[%s] hfp_parse_clip invalid cidinfo.cnum data \"%s\" - deleting\n",
    			hfp->owner->id, cidinfo.cnum);
    		cidinfo.cnum = "";
    	}
    
    	/*
    	 * Some docs say tokens 2 and 3 including the commas are optional.
    	 * If absent, that would move CNAM back to token 3.
    	 */
    	cidinfo.cnam = &buf[tokens[5]];	/* Assume it's in token 5 */
    	if (buf[tokens[5]] == '\0' && buf[tokens[4]] == '\0') {
    		/* Tokens 4 and 5 are empty.  See if token 3 looks like CNAM (starts with ") */
    		i = tokens[3];
    		while (buf[i] == ' ') {		/* Find the first non-blank */
    			i++;
    		}
    		if (buf[i] == '"') {
    			/* Starts with quote.  Use this for CNAM. */
    			cidinfo.cnam = &buf[i];
    
    	/* Clean up CNAM. */
    	cidinfo.cnam = ast_strip_quoted(cidinfo.cnam, "\"", "\"");
    	for (cnamtmp = cidinfo.cnam; *cnamtmp != '\0'; cnamtmp++) {
    		if (!strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789-,abcdefghijklmnopqrstuvwxyz_", *cnamtmp)) {
    			*cnamtmp = '_';	/* Invalid.  Replace with underscore. */
    			invalid++;
    		}
    
    	if (invalid) {
    		ast_debug(2, "[%s] hfp_parse_clip replaced %d invalid byte(s) in cnam data\n",
    			hfp->owner->id, invalid);
    	}
    	ast_debug(2, "[%s] hfp_parse_clip returns cnum=%s and cnam=%s\n",
    		hfp->owner->id, cidinfo.cnum, cidinfo.cnam);
    
    	return cidinfo;
    }
    
    /*!
     * \brief Terminate current token and return an index to start of the next token.
     * \param string the null-terminated string being parsed (will be altered!)
     * \param start where the current token starts
     * \param delim the token termination delimiter.  \0 is also considered a terminator.
     * \return index of the next token.  May be the same as this token if the string is
     * exhausted.
     */
    static int parse_next_token(char string[], const int start, const char delim)
    {
    	int index;
    	int quoting = 0;
    
    	for (index = start; string[index] != 0; index++) {
    		if ((string[index] == delim) && !quoting ) {
    			/* Found the delimiter, outside of quotes.  This is the end of the token. */
    			string[index] = '\0';	/* Terminate this token. */
    			index++;		/* Point the index to the start of the next token. */
    			break;			/* We're done. */
    		} else if (string[index] == '"' && !quoting) {
    			/* Found a beginning quote mark.  Remember it. */
    			quoting = 1;
    		} else if (string[index] == '"' ) {
    			/* Found the end quote mark. */
    			quoting = 0;
    		}
    	}
    	return index;
    
    }
    
    /*!
     * \brief Parse a CMTI notification.
     * \param hfp an hfp_pvt struct
     * \param buf the buffer to parse (null terminated)
    
     * \note buf will be modified when the CMTI message is parsed
    
     * \return -1 on error (parse error) or the index of the new sms message
     */
    static int hfp_parse_cmti(struct hfp_pvt *hfp, char *buf)
    {
    	int index = -1;
    
    	/* parse cmti info in the following format:
    
    	 * +CMTI: <mem>,<index>
    
    	 */
    	if (!sscanf(buf, "+CMTI: %*[^,],%d", &index)) {
    		ast_debug(2, "[%s] error parsing CMTI event '%s'\n", hfp->owner->id, buf);
    		return -1;
    	}
    
    	return index;
    }
    
    /*!
     * \brief Parse a CMGR message.
     * \param hfp an hfp_pvt struct
     * \param buf the buffer to parse (null terminated)
     * \param from_number a pointer to a char pointer which will store the from
     * number
     * \param text a pointer to a char pointer which will store the message text
    
     * \note buf will be modified when the CMGR message is parsed
    
     * \retval -1 parse error
     * \retval 0 success
     */
    static int hfp_parse_cmgr(struct hfp_pvt *hfp, char *buf, char **from_number, char **text)
    {
    	int i, state;
    	size_t s;
    
    	/* parse cmgr info in the following format:
    	 * +CMGR: <msg status>,"+123456789",...\r\n
    	 * <message text>
    	 */
    	state = 0;
    	s = strlen(buf);
    
    	for (i = 0; i < s && state != 6; i++) {
    
    		switch (state) {
    		case 0: /* search for start of the number section (,) */
    			if (buf[i] == ',') {
    				state++;
    			}
    			break;
    		case 1: /* find the opening quote (") */
    			if (buf[i] == '"') {
    				state++;
    			}
    
    		case 2: /* mark the start of the number */
    			if (from_number) {
    				*from_number = &buf[i];
    				state++;
    			}
    			/* fall through */
    		case 3: /* search for the end of the number (") */
    			if (buf[i] == '"') {
    				buf[i] = '\0';
    				state++;
    			}
    			break;
    		case 4: /* search for the start of the message text (\n) */
    			if (buf[i] == '\n') {
    				state++;
    			}
    			break;
    		case 5: /* mark the start of the message text */
    			if (text) {
    				*text = &buf[i];
    				state++;
    			}
    			break;
    		}
    	}
    
    	if (state != 6) {
    		return -1;
    	}
    
    	return 0;
    }
    
    
    /*!
     * \brief Parse a CUSD answer.
     * \param hfp an hfp_pvt struct
     * \param buf the buffer to parse (null terminated)
    
     * \note buf will be modified when the CUSD string is parsed
    
     * \return NULL on error (parse error) or a pointer to the cusd message
     * information in buf
     */
    static char *hfp_parse_cusd(struct hfp_pvt *hfp, char *buf)
    {
    
    	int i, message_start, message_end;
    
    	char *cusd;
    	size_t s;
    
    	/* parse cusd message in the following format:
    	 * +CUSD: 0,"100,00 EURO, valid till 01.01.2010, you are using tariff "Mega Tariff". More informations *111#."
    	 */
    	message_start = 0;
    	message_end = 0;
    	s = strlen(buf);
    
    	/* Find the start of the message (") */
    	for (i = 0; i < s; i++) {
    		if (buf[i] == '"') {
    			message_start = i + 1;
    			break;
    		}
    	}
    
    	if (message_start == 0 || message_start >= s) {
    		return NULL;
    	}
    
    	/* Find the end of the message (") */
    	for (i = s; i > 0; i--) {
    		if (buf[i] == '"') {
    			message_end = i;
    			break;
    		}
    	}
    
    	if (message_end == 0) {
    		return NULL;
    	}
    
    	if (message_start >= message_end) {
    		return NULL;
    	}
    
    	cusd = &buf[message_start];
    	buf[message_end] = '\0';
    
    	return cusd;
    }
    
    
    /*!
     * \brief Convert a hfp_hf struct to a BRSF int.
     * \param hf an hfp_hf brsf object
     * \return an integer representing the given brsf struct
     */
    static int hfp_brsf2int(struct hfp_hf *hf)
    {
    	int brsf = 0;
    
    	brsf |= hf->ecnr ? HFP_HF_ECNR : 0;
    	brsf |= hf->cw ? HFP_HF_CW : 0;
    	brsf |= hf->cid ? HFP_HF_CID : 0;
    	brsf |= hf->voice ? HFP_HF_VOICE : 0;
    	brsf |= hf->volume ? HFP_HF_VOLUME : 0;
    	brsf |= hf->status ? HFP_HF_STATUS : 0;
    	brsf |= hf->control ? HFP_HF_CONTROL : 0;
    
    	return brsf;
    }
    
    /*!
     * \brief Convert a BRSF int to an hfp_ag struct.
     * \param brsf a brsf integer
     * \param ag a AG (hfp_ag) brsf object
     * \return a pointer to the given hfp_ag object populated with the values from
     * the given brsf integer
     */
    static struct hfp_ag *hfp_int2brsf(int brsf, struct hfp_ag *ag)
    {
    	ag->cw = brsf & HFP_AG_CW ? 1 : 0;
    	ag->ecnr = brsf & HFP_AG_ECNR ? 1 : 0;
    	ag->voice = brsf & HFP_AG_VOICE ? 1 : 0;
    	ag->ring = brsf & HFP_AG_RING ? 1 : 0;
    	ag->tag = brsf & HFP_AG_TAG ? 1 : 0;
    	ag->reject = brsf & HFP_AG_REJECT ? 1 : 0;
    	ag->status = brsf & HFP_AG_STATUS ? 1 : 0;
    	ag->control = brsf & HFP_AG_CONTROL ? 1 : 0;
    	ag->errors = brsf & HFP_AG_ERRORS ? 1 : 0;
    
    	return ag;
    }
    
    
    /*!
     * \brief Send a BRSF request.
     * \param hfp an hfp_pvt struct
     * \param brsf an hfp_hf brsf struct
     *
     * \retval 0 on success
     * \retval -1 on error
     */
    static int hfp_send_brsf(struct hfp_pvt *hfp, struct hfp_hf *brsf)
    {
    	char cmd[32];
    	snprintf(cmd, sizeof(cmd), "AT+BRSF=%d\r", hfp_brsf2int(brsf));
    	return rfcomm_write(hfp->rsock, cmd);
    }
    
    /*!
     * \brief Send the CIND read command.
     * \param hfp an hfp_pvt struct
     */
    static int hfp_send_cind(struct hfp_pvt *hfp)
    {
    	return rfcomm_write(hfp->rsock, "AT+CIND?\r");
    }
    
    /*!
     * \brief Send the CIND test command.
     * \param hfp an hfp_pvt struct
     */
    static int hfp_send_cind_test(struct hfp_pvt *hfp)
    {
    	return rfcomm_write(hfp->rsock, "AT+CIND=?\r");
    }
    
    /*!
     * \brief Enable or disable indicator events reporting.
     * \param hfp an hfp_pvt struct
     * \param status enable or disable events reporting (should be 1 or 0)
     */
    static int hfp_send_cmer(struct hfp_pvt *hfp, int status)
    {
    	char cmd[32];
    	snprintf(cmd, sizeof(cmd), "AT+CMER=3,0,0,%d\r", status ? 1 : 0);
    	return rfcomm_write(hfp->rsock, cmd);
    }
    
    /*!
     * \brief Send the current speaker gain level.
     * \param hfp an hfp_pvt struct
     * \param value the value to send (must be between 0 and 15)
     */
    static int hfp_send_vgs(struct hfp_pvt *hfp, int value)
    {
    	char cmd[32];
    	snprintf(cmd, sizeof(cmd), "AT+VGS=%d\r", value);
    	return rfcomm_write(hfp->rsock, cmd);
    }
    
    #if 0
    /*!
     * \brief Send the current microphone gain level.
     * \param hfp an hfp_pvt struct
     * \param value the value to send (must be between 0 and 15)
     */
    static int hfp_send_vgm(struct hfp_pvt *hfp, int value)
    {
    	char cmd[32];
    	snprintf(cmd, sizeof(cmd), "AT+VGM=%d\r", value);
    	return rfcomm_write(hfp->rsock, cmd);
    }
    #endif
    
    /*!
     * \brief Enable or disable calling line identification.
     * \param hfp an hfp_pvt struct
     * \param status enable or disable calling line identification (should be 1 or
     * 0)
     */
    static int hfp_send_clip(struct hfp_pvt *hfp, int status)
    {
    	char cmd[32];
    	snprintf(cmd, sizeof(cmd), "AT+CLIP=%d\r", status ? 1 : 0);
    	return rfcomm_write(hfp->rsock, cmd);
    }
    
    /*!
     * \brief Send a DTMF command.
     * \param hfp an hfp_pvt struct
     * \param digit the dtmf digit to send
     * \return the result of rfcomm_write() or -1 on an invalid digit being sent
     */
    static int hfp_send_dtmf(struct hfp_pvt *hfp, char digit)
    {
    	char cmd[10];
    
    	switch(digit) {
    	case '0':
    	case '1':
    	case '2':
    	case '3':
    	case '4':
    	case '5':
    	case '6':
    	case '7':
    	case '8':
    	case '9':
    	case '*':
    	case '#':
    		snprintf(cmd, sizeof(cmd), "AT+VTS=%c\r", digit);
    		return rfcomm_write(hfp->rsock, cmd);
    	default:
    		return -1;
    	}
    }
    
    /*!
     * \brief Set the SMS mode.
     * \param hfp an hfp_pvt struct
     * \param mode the sms mode (0 = PDU, 1 = Text)
     */
    static int hfp_send_cmgf(struct hfp_pvt *hfp, int mode)
    {
    	char cmd[32];
    	snprintf(cmd, sizeof(cmd), "AT+CMGF=%d\r", mode);
    	return rfcomm_write(hfp->rsock, cmd);
    }
    
    /*!
     * \brief Setup SMS new message indication.
     * \param hfp an hfp_pvt struct
     */
    static int hfp_send_cnmi(struct hfp_pvt *hfp)
    {
    	return rfcomm_write(hfp->rsock, "AT+CNMI=2,1,0,0,0\r");
    }
    
    /*!
     * \brief Read an SMS message.
     * \param hfp an hfp_pvt struct
     * \param index the location of the requested message
     */
    static int hfp_send_cmgr(struct hfp_pvt *hfp, int index)
    {
    	char cmd[32];
    	snprintf(cmd, sizeof(cmd), "AT+CMGR=%d\r", index);
    	return rfcomm_write(hfp->rsock, cmd);
    }
    
    /*!
     * \brief Start sending an SMS message.
     * \param hfp an hfp_pvt struct
     * \param number the destination of the message
     */
    static int hfp_send_cmgs(struct hfp_pvt *hfp, const char *number)
    {
    	char cmd[64];
    	snprintf(cmd, sizeof(cmd), "AT+CMGS=\"%s\"\r", number);
    	return rfcomm_write(hfp->rsock, cmd);
    }
    
    /*!
     * \brief Send the text of an SMS message.
     * \param hfp an hfp_pvt struct
     * \param message the text of the message
     */
    static int hfp_send_sms_text(struct hfp_pvt *hfp, const char *message)
    {
    	char cmd[162];
    	snprintf(cmd, sizeof(cmd), "%.160s\x1a", message);
    	return rfcomm_write(hfp->rsock, cmd);
    }
    
    /*!
     * \brief Send AT+CHUP.
     * \param hfp an hfp_pvt struct
     */
    static int hfp_send_chup(struct hfp_pvt *hfp)
    {
    	return rfcomm_write(hfp->rsock, "AT+CHUP\r");
    }
    
    /*!
     * \brief Send ATD.
     * \param hfp an hfp_pvt struct
     * \param number the number to send
     */
    static int hfp_send_atd(struct hfp_pvt *hfp, const char *number)
    {
    	char cmd[64];
    	snprintf(cmd, sizeof(cmd), "ATD%s;\r", number);
    	return rfcomm_write(hfp->rsock, cmd);
    }
    
    /*!
     * \brief Send ATA.
     * \param hfp an hfp_pvt struct
     */
    static int hfp_send_ata(struct hfp_pvt *hfp)
    {
    	return rfcomm_write(hfp->rsock, "ATA\r");
    }
    
    
    /*!
     * \brief Send CUSD.
     * \param hfp an hfp_pvt struct
     * \param code the CUSD code to send
     */
    static int hfp_send_cusd(struct hfp_pvt *hfp, const char *code)
    {
    	char cmd[128];
    	snprintf(cmd, sizeof(cmd), "AT+CUSD=1,\"%s\",15\r", code);
    	return rfcomm_write(hfp->rsock, cmd);
    }
    
    
    /*!
     * \brief Parse BRSF data.
     * \param hfp an hfp_pvt struct
     * \param buf the buffer to parse (null terminated)
     */
    static int hfp_parse_brsf(struct hfp_pvt *hfp, const char *buf)
    {
    	int brsf;
    
    	if (!sscanf(buf, "+BRSF:%d", &brsf))
    		return -1;
    
    	hfp_int2brsf(brsf, &hfp->brsf);
    
    	return 0;
    }
    
    /*!
     * \brief Parse and store the given indicator.
     * \param hfp an hfp_pvt struct
     * \param group the indicator group
     * \param indicator the indicator to parse
     */
    static int hfp_parse_cind_indicator(struct hfp_pvt *hfp, int group, char *indicator)
    {
    	int value;
    
    	/* store the current indicator */
    
    	if (group >= ARRAY_LEN(hfp->cind_state)) {
    
    		ast_debug(1, "ignoring CIND state '%s' for group %d, we only support up to %d indicators\n", indicator, group, (int) sizeof(hfp->cind_state));
    		return -1;
    	}
    
    	if (!sscanf(indicator, "%d", &value)) {
    		ast_debug(1, "error parsing CIND state '%s' for group %d\n", indicator, group);
    		return -1;
    	}
    
    	hfp->cind_state[group] = value;
    	return 0;
    }
    
    /*!
     * \brief Read the result of the AT+CIND? command.
     * \param hfp an hfp_pvt struct
     * \param buf the buffer to parse (null terminated)
     * \note hfp_send_cind_test() and hfp_parse_cind_test() should be called at
     * least once before this function is called.
     */
    static int hfp_parse_cind(struct hfp_pvt *hfp, char *buf)
    {
    	int i, state, group;
    	size_t s;
    	char *indicator = NULL;
    
    	/* parse current state of all of our indicators.  The list is in the
    	 * following format:
    	 * +CIND: 1,0,2,0,0,0,0
    	 */
    	group = 0;
    	state = 0;
    	s = strlen(buf);
    	for (i = 0; i < s; i++) {
    		switch (state) {
    		case 0: /* search for start of the status indicators (a space) */
    			if (buf[i] == ' ') {
    				group++;
    				state++;
    			}
    			break;
    		case 1: /* mark this indicator */
    			indicator = &buf[i];
    			state++;
    			break;
    		case 2: /* search for the start of the next indicator (a comma) */
    			if (buf[i] == ',') {
    				buf[i] = '\0';
    
    				hfp_parse_cind_indicator(hfp, group, indicator);
    
    				group++;
    				state = 1;
    			}
    			break;
    		}
    	}
    
    	/* store the last indicator */
    	if (state == 2)
    		hfp_parse_cind_indicator(hfp, group, indicator);
    
    	return 0;
    }
    
    /*!
     * \brief Parse the result of the AT+CIND=? command.
     * \param hfp an hfp_pvt struct
     * \param buf the buffer to parse (null terminated)
     */
    static int hfp_parse_cind_test(struct hfp_pvt *hfp, char *buf)
    {
    	int i, state, group;
    	size_t s;
    
    	char *indicator = NULL;
    
    
    	hfp->nocallsetup = 1;
    
    	/* parse the indications list.  It is in the follwing format:
    	 * +CIND: ("ind1",(0-1)),("ind2",(0-5))
    	 */
    	group = 0;
    	state = 0;
    	s = strlen(buf);
    	for (i = 0; i < s; i++) {
    		switch (state) {
    		case 0: /* search for start of indicator block */
    			if (buf[i] == '(') {
    				group++;
    				state++;
    			}
    			break;
    		case 1: /* search for '"' in indicator block */
    			if (buf[i] == '"') {
    				state++;
    			}
    			break;
    		case 2: /* mark the start of the indicator name */
    			indicator = &buf[i];
    			state++;
    			break;
    		case 3: /* look for the end of the indicator name */
    			if (buf[i] == '"') {
    				buf[i] = '\0';
    				state++;
    			}
    			break;
    		case 4: /* find the start of the value range */
    			if (buf[i] == '(') {
    				state++;
    			}
    			break;
    		case 5: /* mark the start of the value range */
    			state++;
    			break;
    		case 6: /* find the end of the value range */
    			if (buf[i] == ')') {
    				buf[i] = '\0';
    				state++;
    			}
    			break;
    		case 7: /* process the values we found */
    			if (group < sizeof(hfp->cind_index)) {
    				if (!strcmp(indicator, "service")) {
    					hfp->cind_map.service = group;
    					hfp->cind_index[group] = HFP_CIND_SERVICE;
    				} else if (!strcmp(indicator, "call")) {
    					hfp->cind_map.call = group;
    					hfp->cind_index[group] = HFP_CIND_CALL;
    				} else if (!strcmp(indicator, "callsetup")) {
    					hfp->nocallsetup = 0;
    					hfp->cind_map.callsetup = group;
    					hfp->cind_index[group] = HFP_CIND_CALLSETUP;
    				} else if (!strcmp(indicator, "call_setup")) { /* non standard call setup identifier */
    					hfp->nocallsetup = 0;
    					hfp->cind_map.callsetup = group;
    					hfp->cind_index[group] = HFP_CIND_CALLSETUP;
    				} else if (!strcmp(indicator, "callheld")) {
    					hfp->cind_map.callheld = group;
    					hfp->cind_index[group] = HFP_CIND_CALLHELD;
    				} else if (!strcmp(indicator, "signal")) {
    					hfp->cind_map.signal = group;
    					hfp->cind_index[group] = HFP_CIND_SIGNAL;
    				} else if (!strcmp(indicator, "roam")) {
    					hfp->cind_map.roam = group;
    					hfp->cind_index[group] = HFP_CIND_ROAM;
    				} else if (!strcmp(indicator, "battchg")) {
    					hfp->cind_map.battchg = group;
    					hfp->cind_index[group] = HFP_CIND_BATTCHG;
    				} else {
    					hfp->cind_index[group] = HFP_CIND_UNKNOWN;
    					ast_debug(2, "ignoring unknown CIND indicator '%s'\n", indicator);
    				}
    			} else {
    					ast_debug(1, "can't store indicator %d (%s), we only support up to %d indicators", group, indicator, (int) sizeof(hfp->cind_index));
    			}
    
    			state = 0;
    			break;
    		}
    	}
    
    	hfp->owner->no_callsetup = hfp->nocallsetup;
    
    	return 0;
    }
    
    
    /*
     * Bluetooth Headset Profile helpers
     */
    
    /*!
     * \brief Send an OK AT response.
     * \param rsock the rfcomm socket to use
     */
    static int hsp_send_ok(int rsock)
    {
    	return rfcomm_write(rsock, "\r\nOK\r\n");
    }
    
    /*!
     * \brief Send an ERROR AT response.
     * \param rsock the rfcomm socket to use
     */
    static int hsp_send_error(int rsock)
    {
    	return rfcomm_write(rsock, "\r\nERROR\r\n");
    }
    
    /*!
     * \brief Send a speaker gain unsolicited AT response
     * \param rsock the rfcomm socket to use
     * \param gain the speaker gain value
     */
    static int hsp_send_vgs(int rsock, int gain)
    {
    	char cmd[32];
    	snprintf(cmd, sizeof(cmd), "\r\n+VGS=%d\r\n", gain);
    	return rfcomm_write(rsock, cmd);
    }
    
    /*!
     * \brief Send a microphone gain unsolicited AT response
     * \param rsock the rfcomm socket to use
     * \param gain the microphone gain value
     */
    static int hsp_send_vgm(int rsock, int gain)
    {
    	char cmd[32];
    	snprintf(cmd, sizeof(cmd), "\r\n+VGM=%d\r\n", gain);
    	return rfcomm_write(rsock, cmd);
    }
    
    /*!
     * \brief Send a RING unsolicited AT response.
     * \param rsock the rfcomm socket to use
     */
    static int hsp_send_ring(int rsock)
    {
    	return rfcomm_write(rsock, "\r\nRING\r\n");
    }
    
    /*
     * message queue functions
     */
    
    /*!
     * \brief Add an item to the back of the queue.
     * \param pvt a mbl_pvt structure
    
     * \param expect the msg we expect to receive
    
     * \param response_to the message that was sent to generate the expected
     * response
     */
    static int msg_queue_push(struct mbl_pvt *pvt, at_message_t expect, at_message_t response_to)
    {
    	struct msg_queue_entry *msg;
    	if (!(msg = ast_calloc(1, sizeof(*msg)))) {
    		return -1;
    	}
    	msg->expected = expect;
    	msg->response_to = response_to;
    
    	AST_LIST_INSERT_TAIL(&pvt->msg_queue, msg, entry);
    	return 0;
    }
    
    /*!
     * \brief Add an item to the back of the queue with data.
     * \param pvt a mbl_pvt structure
    
     * \param expect the msg we expect to receive