Skip to content
Snippets Groups Projects
chan_mobile.c 122 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	bdaddr_t bdaddr;
    	uuid_t svc_uuid;
    	uint32_t range = 0x0000ffff;
    	sdp_list_t *response_list, *search_list, *attrid_list;
    	int status, port;
    	sdp_list_t *proto_list;
    	sdp_record_t *sdprec;
    
    	str2ba(addr, &bdaddr);
    	port = 0;
    	session = sdp_connect(BDADDR_ANY, &bdaddr, SDP_RETRY_IF_BUSY);
    	if (!session) {
    		ast_debug(1, "sdp_connect() failed on device %s.\n", addr);
    		return 0;
    	}
    
    	sdp_uuid32_create(&svc_uuid, profile);
    	search_list = sdp_list_append(0, &svc_uuid);
    	attrid_list = sdp_list_append(0, &range);
    	response_list = 0x00;
    	status = sdp_service_search_attr_req(session, search_list, SDP_ATTR_REQ_RANGE, attrid_list, &response_list);
    	if (status == 0) {
    		if (response_list) {
    			sdprec = (sdp_record_t *) response_list->data;
    			proto_list = 0x00;
    			if (sdp_get_access_protos(sdprec, &proto_list) == 0) {
    				port = sdp_get_proto_port(proto_list, RFCOMM_UUID);
    				sdp_list_free(proto_list, 0);
    			}
    			sdp_record_free(sdprec);
    			sdp_list_free(response_list, 0);
    		} else
    			ast_debug(1, "No responses returned for device %s.\n", addr);
    	} else
    		ast_debug(1, "sdp_service_search_attr_req() failed on device %s.\n", addr);
    
    	sdp_list_free(search_list, 0);
    	sdp_list_free(attrid_list, 0);
    	sdp_close(session);
    
    	return port;
    
    }
    
    static sdp_session_t *sdp_register(void)
    {
    	uint32_t service_uuid_int[] = {0, 0, 0, GENERIC_AUDIO_SVCLASS_ID};
    	uint8_t rfcomm_channel = 1;
    	const char *service_name = "Asterisk PABX";
    	const char *service_dsc = "Asterisk PABX";
    	const char *service_prov = "Asterisk";
    
    	uuid_t root_uuid, l2cap_uuid, rfcomm_uuid, svc_uuid, svc_class1_uuid, svc_class2_uuid;
    	sdp_list_t  *l2cap_list = 0, *rfcomm_list = 0, *root_list = 0, *proto_list = 0, *access_proto_list = 0, *svc_uuid_list = 0;
    	sdp_data_t *channel = 0;
    
    	sdp_session_t *session = 0;
    
    	sdp_record_t *record = sdp_record_alloc();
    
    	sdp_uuid128_create(&svc_uuid, &service_uuid_int);
    	sdp_set_service_id(record, svc_uuid);
    
    	sdp_uuid32_create(&svc_class1_uuid, GENERIC_AUDIO_SVCLASS_ID);
    	sdp_uuid32_create(&svc_class2_uuid, HEADSET_PROFILE_ID);
    
    	svc_uuid_list = sdp_list_append(0, &svc_class1_uuid);
    	svc_uuid_list = sdp_list_append(svc_uuid_list, &svc_class2_uuid);
    	sdp_set_service_classes(record, svc_uuid_list);
    
    	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
    	root_list = sdp_list_append(0, &root_uuid);
    	sdp_set_browse_groups( record, root_list );
    
    	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
    	l2cap_list = sdp_list_append(0, &l2cap_uuid);
    	proto_list = sdp_list_append(0, l2cap_list);
    
    	sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
    	channel = sdp_data_alloc(SDP_UINT8, &rfcomm_channel);
    	rfcomm_list = sdp_list_append(0, &rfcomm_uuid);
    	sdp_list_append(rfcomm_list, channel);
    	sdp_list_append(proto_list, rfcomm_list);
    
    	access_proto_list = sdp_list_append(0, proto_list);
    	sdp_set_access_protos(record, access_proto_list);
    
    	sdp_set_info_attr(record, service_name, service_prov, service_dsc);
    
    	if (!(session = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, SDP_RETRY_IF_BUSY)))
    		ast_log(LOG_WARNING, "Failed to connect sdp and create session.\n");
    
    	else {
    		if (sdp_record_register(session, record, 0) < 0) {
    			ast_log(LOG_WARNING, "Failed to sdp_record_register error: %d\n", errno);
    			return NULL;
    		}
    	}
    
    
    	sdp_data_free(channel);
    	sdp_list_free(rfcomm_list, 0);
    	sdp_list_free(root_list, 0);
    	sdp_list_free(access_proto_list, 0);
    	sdp_list_free(svc_uuid_list, 0);
    
    	return session;
    
    }
    
    /*
    
    	Thread routines
    
    */
    
    /*!
     * \brief Handle the BRSF response.
     * \param pvt a mbl_pvt structure
     * \param buf a null terminated buffer containing an AT message
     * \retval 0 success
     * \retval -1 error
     */
    static int handle_response_brsf(struct mbl_pvt *pvt, char *buf)
    {
    	struct msg_queue_entry *entry;
    	if ((entry = msg_queue_head(pvt)) && entry->expected == AT_BRSF) {
    		if (hfp_parse_brsf(pvt->hfp, buf)) {
    			ast_debug(1, "[%s] error parsing BRSF\n", pvt->id);
    			goto e_return;
    		}
    
    		if (msg_queue_push(pvt, AT_OK, AT_BRSF)) {
    			ast_debug(1, "[%s] error handling BRSF\n", pvt->id);
    			goto e_return;
    		}
    
    		msg_queue_free_and_pop(pvt);
    	} else if (entry) {
    
    		ast_debug(1, "[%s] received unexpected AT message 'BRSF' when expecting %s, ignoring\n", pvt->id, at_msg2str(entry->expected));
    
    		ast_debug(1, "[%s] received unexpected AT message 'BRSF'\n", pvt->id);
    
    	}
    
    	return 0;
    
    e_return:
    	msg_queue_free_and_pop(pvt);
    	return -1;
    }
    
    /*!
     * \brief Handle the CIND response.
     * \param pvt a mbl_pvt structure
     * \param buf a null terminated buffer containing an AT message
     * \retval 0 success
     * \retval -1 error
     */
    static int handle_response_cind(struct mbl_pvt *pvt, char *buf)
    {
    	struct msg_queue_entry *entry;
    	if ((entry = msg_queue_head(pvt)) && entry->expected == AT_CIND) {
    		switch (entry->response_to) {
    		case AT_CIND_TEST:
    			if (hfp_parse_cind_test(pvt->hfp, buf) || msg_queue_push(pvt, AT_OK, AT_CIND_TEST)) {
    				ast_debug(1, "[%s] error performing CIND test\n", pvt->id);
    				goto e_return;
    			}
    			break;
    		case AT_CIND:
    			if (hfp_parse_cind(pvt->hfp, buf) || msg_queue_push(pvt, AT_OK, AT_CIND)) {
    				ast_debug(1, "[%s] error getting CIND state\n", pvt->id);
    				goto e_return;
    			}
    			break;
    		default:
    			ast_debug(1, "[%s] error getting CIND state\n", pvt->id);
    			goto e_return;
    		}
    		msg_queue_free_and_pop(pvt);
    	} else if (entry) {
    
    		ast_debug(1, "[%s] received unexpected AT message 'CIND' when expecting %s, ignoring\n", pvt->id, at_msg2str(entry->expected));
    
    		ast_debug(1, "[%s] received unexpected AT message 'CIND'\n", pvt->id);
    
    	}
    
    	return 0;
    
    e_return:
    	msg_queue_free_and_pop(pvt);
    	return -1;
    }
    
    /*!
     * \brief Handle OK AT messages.
     * \param pvt a mbl_pvt structure
     * \param buf a null terminated buffer containing an AT message
     * \retval 0 success
     * \retval -1 error
     */
    static int handle_response_ok(struct mbl_pvt *pvt, char *buf)
    {
    	struct msg_queue_entry *entry;
    	if ((entry = msg_queue_head(pvt)) && entry->expected == AT_OK) {
    		switch (entry->response_to) {
    
    
    		case AT_BRSF:
    			ast_debug(1, "[%s] BSRF sent successfully\n", pvt->id);
    
    			/* If this is a blackberry do CMER now, otherwise
    			 * continue with CIND as normal. */
    			if (pvt->blackberry) {
    				if (hfp_send_cmer(pvt->hfp, 1) || msg_queue_push(pvt, AT_OK, AT_CMER)) {
    					ast_debug(1, "[%s] error sending CMER\n", pvt->id);
    					goto e_return;
    				}
    			} else {
    				if (hfp_send_cind_test(pvt->hfp) || msg_queue_push(pvt, AT_CIND, AT_CIND_TEST)) {
    					ast_debug(1, "[%s] error sending CIND test\n", pvt->id);
    					goto e_return;
    				}
    			}
    			break;
    		case AT_CIND_TEST:
    			ast_debug(1, "[%s] CIND test sent successfully\n", pvt->id);
    
    			ast_debug(2, "[%s] call: %d\n", pvt->id, pvt->hfp->cind_map.call);
    			ast_debug(2, "[%s] callsetup: %d\n", pvt->id, pvt->hfp->cind_map.callsetup);
    
    			ast_debug(2, "[%s] service: %d\n", pvt->id, pvt->hfp->cind_map.service);
    
    
    			if (hfp_send_cind(pvt->hfp) || msg_queue_push(pvt, AT_CIND, AT_CIND)) {
    				ast_debug(1, "[%s] error requesting CIND state\n", pvt->id);
    				goto e_return;
    			}
    			break;
    		case AT_CIND:
    			ast_debug(1, "[%s] CIND sent successfully\n", pvt->id);
    
    			/* check if a call is active */
    			if (pvt->hfp->cind_state[pvt->hfp->cind_map.call]) {
    				ast_verb(3, "Bluetooth Device %s has a call in progress - delaying connection.\n", pvt->id);
    				goto e_return;
    			}
    
    			/* If this is NOT a blackberry proceed with CMER,
    			 * otherwise send CLIP. */
    			if (!pvt->blackberry) {
    				if (hfp_send_cmer(pvt->hfp, 1) || msg_queue_push(pvt, AT_OK, AT_CMER)) {
    					ast_debug(1, "[%s] error sending CMER\n", pvt->id);
    					goto e_return;
    				}
    			} else {
    				if (hfp_send_clip(pvt->hfp, 1) || msg_queue_push(pvt, AT_OK, AT_CLIP)) {
    					ast_debug(1, "[%s] error enabling calling line notification\n", pvt->id);
    					goto e_return;
    				}
    			}
    			break;
    		case AT_CMER:
    			ast_debug(1, "[%s] CMER sent successfully\n", pvt->id);
    
    			/* If this is a blackberry proceed with the CIND test,
    			 * otherwise send CLIP. */
    			if (pvt->blackberry) {
    				if (hfp_send_cind_test(pvt->hfp) || msg_queue_push(pvt, AT_CIND, AT_CIND_TEST)) {
    					ast_debug(1, "[%s] error sending CIND test\n", pvt->id);
    					goto e_return;
    				}
    			} else {
    				if (hfp_send_clip(pvt->hfp, 1) || msg_queue_push(pvt, AT_OK, AT_CLIP)) {
    					ast_debug(1, "[%s] error enabling calling line notification\n", pvt->id);
    					goto e_return;
    				}
    			}
    			break;
    		case AT_CLIP:
    			ast_debug(1, "[%s] caling line indication enabled\n", pvt->id);
    
    			if (hfp_send_ecam(pvt->hfp) || msg_queue_push(pvt, AT_OK, AT_ECAM)) {
    				ast_debug(1, "[%s] error enabling Sony Ericsson call monitoring extensions\n", pvt->id);
    				goto e_return;
    			}
    
    			break;
    		case AT_ECAM:
    			ast_debug(1, "[%s] Sony Ericsson call monitoring is active on device\n", pvt->id);
    
    			if (hfp_send_vgs(pvt->hfp, 15) || msg_queue_push(pvt, AT_OK, AT_VGS)) {
    				ast_debug(1, "[%s] error synchronizing gain settings\n", pvt->id);
    				goto e_return;
    			}
    
    			pvt->timeout = -1;
    			pvt->hfp->initialized = 1;
    			ast_verb(3, "Bluetooth Device %s initialized and ready.\n", pvt->id);
    
    			break;
    		case AT_VGS:
    			ast_debug(1, "[%s] volume level synchronization successful\n", pvt->id);
    
    			/* set the SMS operating mode to text mode */
    
    			if (pvt->has_sms) {
    				if (hfp_send_cmgf(pvt->hfp, 1) || msg_queue_push(pvt, AT_OK, AT_CMGF)) {
    					ast_debug(1, "[%s] error setting CMGF\n", pvt->id);
    					goto e_return;
    				}
    
    			}
    			break;
    		case AT_CMGF:
    			ast_debug(1, "[%s] sms text mode enabled\n", pvt->id);
    			/* turn on SMS new message indication */
    			if (hfp_send_cnmi(pvt->hfp) || msg_queue_push(pvt, AT_OK, AT_CNMI)) {
    				ast_debug(1, "[%s] error setting CNMI\n", pvt->id);
    				goto e_return;
    			}
    			break;
    		case AT_CNMI:
    			ast_debug(1, "[%s] sms new message indication enabled\n", pvt->id);
    			pvt->has_sms = 1;
    			break;
    
    
    		case AT_A:
    			ast_debug(1, "[%s] answer sent successfully\n", pvt->id);
    			pvt->needchup = 1;
    			break;
    		case AT_D:
    			ast_debug(1, "[%s] dial sent successfully\n", pvt->id);
    			pvt->needchup = 1;
    			pvt->outgoing = 1;
    			mbl_queue_control(pvt, AST_CONTROL_PROGRESS);
    			break;
    		case AT_CHUP:
    			ast_debug(1, "[%s] successful hangup\n", pvt->id);
    			break;
    		case AT_CMGS:
    			ast_debug(1, "[%s] successfully sent sms message\n", pvt->id);
    			pvt->outgoing_sms = 0;
    			break;
    		case AT_VTS:
    			ast_debug(1, "[%s] digit sent successfully\n", pvt->id);
    			break;
    
    		case AT_CUSD:
    			ast_debug(1, "[%s] CUSD code sent successfully\n", pvt->id);
    			break;
    
    			ast_debug(1, "[%s] received OK for unhandled request: %s\n", pvt->id, at_msg2str(entry->response_to));
    
    			break;
    		}
    		msg_queue_free_and_pop(pvt);
    	} else if (entry) {
    
    		ast_debug(1, "[%s] received AT message 'OK' when expecting %s, ignoring\n", pvt->id, at_msg2str(entry->expected));
    
    		ast_debug(1, "[%s] received unexpected AT message 'OK'\n", pvt->id);
    
    	}
    	return 0;
    
    e_return:
    	msg_queue_free_and_pop(pvt);
    	return -1;
    }
    
    /*!
     * \brief Handle ERROR AT messages.
     * \param pvt a mbl_pvt structure
     * \param buf a null terminated buffer containing an AT message
     * \retval 0 success
     * \retval -1 error
     */
    static int handle_response_error(struct mbl_pvt *pvt, char *buf)
    {
    	struct msg_queue_entry *entry;
    	if ((entry = msg_queue_head(pvt))
    			&& (entry->expected == AT_OK
    			|| entry->expected == AT_ERROR
    			|| entry->expected == AT_CMS_ERROR
    			|| entry->expected == AT_CMGR
    			|| entry->expected == AT_SMS_PROMPT)) {
    		switch (entry->response_to) {
    
    
    		case AT_BRSF:
    			ast_debug(1, "[%s] error reading BSRF\n", pvt->id);
    			goto e_return;
    		case AT_CIND_TEST:
    			ast_debug(1, "[%s] error during CIND test\n", pvt->id);
    			goto e_return;
    		case AT_CIND:
    			ast_debug(1, "[%s] error requesting CIND state\n", pvt->id);
    			goto e_return;
    		case AT_CMER:
    			ast_debug(1, "[%s] error during CMER request\n", pvt->id);
    			goto e_return;
    		case AT_CLIP:
    			ast_debug(1, "[%s] error enabling calling line indication\n", pvt->id);
    			goto e_return;
    		case AT_VGS:
    			ast_debug(1, "[%s] volume level synchronization failed\n", pvt->id);
    
    
    			/* this is not a fatal error, let's continue with initialization */
    
    
    			/* set the SMS operating mode to text mode */
    			if (hfp_send_cmgf(pvt->hfp, 1) || msg_queue_push(pvt, AT_OK, AT_CMGF)) {
    				ast_debug(1, "[%s] error setting CMGF\n", pvt->id);
    				goto e_return;
    			}
    			break;
    		case AT_CMGF:
    
    			ast_debug(1, "[%s] error setting CMGF\n", pvt->id);
    			ast_debug(1, "[%s] no SMS support\n", pvt->id);
    			break;
    		case AT_CNMI:
    
    			ast_debug(1, "[%s] error setting CNMI\n", pvt->id);
    			ast_debug(1, "[%s] no SMS support\n", pvt->id);
    
    			break;
    		case AT_ECAM:
    			ast_debug(1, "[%s] Mobile does not support Sony Ericsson extensions\n", pvt->id);
    
    			/* this is not a fatal error, let's continue with the initialization */
    
    			if (hfp_send_vgs(pvt->hfp, 15) || msg_queue_push(pvt, AT_OK, AT_VGS)) {
    				ast_debug(1, "[%s] error synchronizing gain settings\n", pvt->id);
    				goto e_return;
    			}
    
    			pvt->timeout = -1;
    			pvt->hfp->initialized = 1;
    			ast_verb(3, "Bluetooth Device %s initialized and ready.\n", pvt->id);
    
    
    
    		case AT_A:
    			ast_debug(1, "[%s] answer failed\n", pvt->id);
    			mbl_queue_hangup(pvt);
    			break;
    		case AT_D:
    			ast_debug(1, "[%s] dial failed\n", pvt->id);
    			pvt->needchup = 0;
    			mbl_queue_control(pvt, AST_CONTROL_CONGESTION);
    			break;
    		case AT_CHUP:
    			ast_debug(1, "[%s] error sending hangup, disconnecting\n", pvt->id);
    			goto e_return;
    		case AT_CMGR:
    			ast_debug(1, "[%s] error reading sms message\n", pvt->id);
    			pvt->incoming_sms = 0;
    			break;
    		case AT_CMGS:
    			ast_debug(1, "[%s] error sending sms message\n", pvt->id);
    			pvt->outgoing_sms = 0;
    			break;
    		case AT_VTS:
    			ast_debug(1, "[%s] error sending digit\n", pvt->id);
    			break;
    
    		case AT_CUSD:
    			ast_verb(0, "[%s] error sending CUSD command\n", pvt->id);
    			break;
    
    			ast_debug(1, "[%s] received ERROR for unhandled request: %s\n", pvt->id, at_msg2str(entry->response_to));
    
    			break;
    		}
    		msg_queue_free_and_pop(pvt);
    	} else if (entry) {
    
    		ast_debug(1, "[%s] received AT message 'ERROR' when expecting %s, ignoring\n", pvt->id, at_msg2str(entry->expected));
    
    		ast_debug(1, "[%s] received unexpected AT message 'ERROR'\n", pvt->id);
    
    	}
    
    	return 0;
    
    e_return:
    	msg_queue_free_and_pop(pvt);
    	return -1;
    }
    
    /*!
     * \brief Handle AT+CIEV messages.
     * \param pvt a mbl_pvt structure
     * \param buf a null terminated buffer containing an AT message
     * \retval 0 success
     * \retval -1 error
     */
    static int handle_response_ciev(struct mbl_pvt *pvt, char *buf)
    {
    	int i;
    	switch (hfp_parse_ciev(pvt->hfp, buf, &i)) {
    	case HFP_CIND_CALL:
    		switch (i) {
    		case HFP_CIND_CALL_NONE:
    			ast_debug(1, "[%s] line disconnected\n", pvt->id);
    			if (pvt->owner) {
    				ast_debug(1, "[%s] hanging up owner\n", pvt->id);
    				if (mbl_queue_hangup(pvt)) {
    					ast_log(LOG_ERROR, "[%s] error queueing hangup, disconnectiong...\n", pvt->id);
    					return -1;
    				}
    			}
    			pvt->needchup = 0;
    			pvt->needcallerid = 0;
    			pvt->incoming = 0;
    			pvt->outgoing = 0;
    			break;
    		case HFP_CIND_CALL_ACTIVE:
    			if (pvt->outgoing) {
    				ast_debug(1, "[%s] remote end answered\n", pvt->id);
    				mbl_queue_control(pvt, AST_CONTROL_ANSWER);
    			} else if (pvt->incoming && pvt->answered) {
    				ast_setstate(pvt->owner, AST_STATE_UP);
    			} else if (pvt->incoming) {
    				/* user answered from handset, disconnecting */
    				ast_verb(3, "[%s] user answered bluetooth device from handset, disconnecting\n", pvt->id);
    				mbl_queue_hangup(pvt);
    				return -1;
    			}
    			break;
    		}
    		break;
    
    	case HFP_CIND_CALLSETUP:
    		switch (i) {
    		case HFP_CIND_CALLSETUP_NONE:
    			if (pvt->hfp->cind_state[pvt->hfp->cind_map.call] != HFP_CIND_CALL_ACTIVE) {
    				if (pvt->owner) {
    
    					if (pvt->hfp->sent_alerting == 1) {
    						handle_response_busy(pvt);
    					}
    
    					if (mbl_queue_hangup(pvt)) {
    						ast_log(LOG_ERROR, "[%s] error queueing hangup, disconnectiong...\n", pvt->id);
    						return -1;
    					}
    				}
    				pvt->needchup = 0;
    				pvt->needcallerid = 0;
    				pvt->incoming = 0;
    				pvt->outgoing = 0;
    			}
    			break;
    		case HFP_CIND_CALLSETUP_INCOMING:
    			ast_debug(1, "[%s] incoming call, waiting for caller id\n", pvt->id);
    			pvt->needcallerid = 1;
    			pvt->incoming = 1;
    			break;
    		case HFP_CIND_CALLSETUP_OUTGOING:
    			if (pvt->outgoing) {
    
    				pvt->hfp->sent_alerting = 0;
    
    				ast_debug(1, "[%s] outgoing call\n", pvt->id);
    			} else {
    				ast_verb(3, "[%s] user dialed from handset, disconnecting\n", pvt->id);
    				return -1;
    			}
    			break;
    		case HFP_CIND_CALLSETUP_ALERTING:
    			if (pvt->outgoing) {
    				ast_debug(1, "[%s] remote alerting\n", pvt->id);
    				mbl_queue_control(pvt, AST_CONTROL_RINGING);
    
    				pvt->hfp->sent_alerting = 1;
    
    			}
    			break;
    		}
    		break;
    	case HFP_CIND_NONE:
    		ast_debug(1, "[%s] error parsing CIND: %s\n", pvt->id, buf);
    		break;
    	}
    	return 0;
    }
    
    /*!
     * \brief Handle AT+CLIP messages.
     * \param pvt a mbl_pvt structure
     * \param buf a null terminated buffer containing an AT message
     * \retval 0 success
     * \retval -1 error
     */
    static int handle_response_clip(struct mbl_pvt *pvt, char *buf)
    {
    	char *clip;
    	struct msg_queue_entry *msg;
    	struct ast_channel *chan;
    
    	if ((msg = msg_queue_head(pvt)) && msg->expected == AT_CLIP) {
    		msg_queue_free_and_pop(pvt);
    
    		pvt->needcallerid = 0;
    		if (!(clip = hfp_parse_clip(pvt->hfp, buf))) {
    			ast_debug(1, "[%s] error parsing CLIP: %s\n", pvt->id, buf);
    		}
    
    
    		if (!(chan = mbl_new(AST_STATE_RING, pvt, clip, NULL, NULL))) {
    
    			ast_log(LOG_ERROR, "[%s] unable to allocate channel for incoming call\n", pvt->id);
    			hfp_send_chup(pvt->hfp);
    			msg_queue_push(pvt, AT_OK, AT_CHUP);
    			return -1;
    		}
    
    		/* from this point on, we need to send a chup in the event of a
    		 * hangup */
    		pvt->needchup = 1;
    
    		if (ast_pbx_start(chan)) {
    			ast_log(LOG_ERROR, "[%s] unable to start pbx on incoming call\n", pvt->id);
    			mbl_ast_hangup(pvt);
    			return -1;
    		}
    	}
    
    	return 0;
    }
    
    /*!
     * \brief Handle RING messages.
     * \param pvt a mbl_pvt structure
     * \param buf a null terminated buffer containing an AT message
     * \retval 0 success
     * \retval -1 error
     */
    static int handle_response_ring(struct mbl_pvt *pvt, char *buf)
    {
    	if (pvt->needcallerid) {
    		ast_debug(1, "[%s] got ring while waiting for caller id\n", pvt->id);
    		return msg_queue_push(pvt, AT_CLIP, AT_UNKNOWN);
    	} else {
    		return 0;
    	}
    }
    
    /*!
     * \brief Handle AT+CMTI messages.
     * \param pvt a mbl_pvt structure
     * \param buf a null terminated buffer containing an AT message
     * \retval 0 success
     * \retval -1 error
     */
    static int handle_response_cmti(struct mbl_pvt *pvt, char *buf)
    {
    	int index = hfp_parse_cmti(pvt->hfp, buf);
    	if (index > 0) {
    		ast_debug(1, "[%s] incoming sms message\n", pvt->id);
    
    		if (hfp_send_cmgr(pvt->hfp, index)
    				|| msg_queue_push(pvt, AT_CMGR, AT_CMGR)) {
    			ast_debug(1, "[%s] error sending CMGR to retrieve SMS message\n", pvt->id);
    			return -1;
    		}
    
    		pvt->incoming_sms = 1;
    		return 0;
    	} else {
    		ast_debug(1, "[%s] error parsing incoming sms message alert, disconnecting\n", pvt->id);
    		return -1;
    	}
    }
    
    /*!
     * \brief Handle AT+CMGR messages.
     * \param pvt a mbl_pvt structure
     * \param buf a null terminated buffer containing an AT message
     * \retval 0 success
     * \retval -1 error
     */
    static int handle_response_cmgr(struct mbl_pvt *pvt, char *buf)
    {
    	char *from_number = NULL, *text = NULL;
    	struct ast_channel *chan;
    	struct msg_queue_entry *msg;
    
    	if ((msg = msg_queue_head(pvt)) && msg->expected == AT_CMGR) {
    		msg_queue_free_and_pop(pvt);
    
    
    		if (hfp_parse_cmgr(pvt->hfp, buf, &from_number, &text)) {
    
    			ast_debug(1, "[%s] error parsing sms message, disconnecting\n", pvt->id);
    			return -1;
    		}
    
    
    		ast_debug(1, "[%s] successfully read sms message\n", pvt->id);
    		pvt->incoming_sms = 0;
    
    
    		/* XXX this channel probably does not need to be associated with this pvt */
    
    		if (!(chan = mbl_new(AST_STATE_DOWN, pvt, NULL, NULL, NULL))) {
    
    			ast_debug(1, "[%s] error creating sms message channel, disconnecting\n", pvt->id);
    			return -1;
    		}
    
    
    		ast_channel_exten_set(chan, "sms");
    
    		pbx_builtin_setvar_helper(chan, "SMSSRC", from_number);
    		pbx_builtin_setvar_helper(chan, "SMSTXT", text);
    
    		if (ast_pbx_start(chan)) {
    			ast_log(LOG_ERROR, "[%s] unable to start pbx on incoming sms\n", pvt->id);
    			mbl_ast_hangup(pvt);
    		}
    	} else {
    		ast_debug(1, "[%s] got unexpected +CMGR message, ignoring\n", pvt->id);
    	}
    
    	return 0;
    }
    
    /*!
     * \brief Send an SMS message from the queue.
     * \param pvt a mbl_pvt structure
     * \param buf a null terminated buffer containing an AT message
     * \retval 0 success
     * \retval -1 error
     */
    static int handle_sms_prompt(struct mbl_pvt *pvt, char *buf)
    {
    	struct msg_queue_entry *msg;
    	if (!(msg = msg_queue_head(pvt))) {
    		ast_debug(1, "[%s] error, got sms prompt with no pending sms messages\n", pvt->id);
    		return 0;
    	}
    
    	if (msg->expected != AT_SMS_PROMPT) {
    		ast_debug(1, "[%s] error, got sms prompt but no pending sms messages\n", pvt->id);
    		return 0;
    	}
    
    	if (hfp_send_sms_text(pvt->hfp, msg->data)
    			|| msg_queue_push(pvt, AT_OK, AT_CMGS)) {
    		msg_queue_free_and_pop(pvt);
    		ast_debug(1, "[%s] error sending sms message\n", pvt->id);
    		return 0;
    	}
    
    	msg_queue_free_and_pop(pvt);
    	return 0;
    }
    
    
    /*!
     * \brief Handle CUSD messages.
     * \param pvt a mbl_pvt structure
     * \param buf a null terminated buffer containing an AT message
     * \retval 0 success
     * \retval -1 error
     */
    static int handle_response_cusd(struct mbl_pvt *pvt, char *buf)
    {
    	char *cusd;
    
    	if (!(cusd = hfp_parse_cusd(pvt->hfp, buf))) {
    		ast_verb(0, "[%s] error parsing CUSD: %s\n", pvt->id, buf);
    		return 0;
    	}
    
    	ast_verb(0, "[%s] CUSD response: %s\n", pvt->id, cusd);
    
    	return 0;
    }
    
    
    /*!
     * \brief Handle BUSY messages.
     * \param pvt a mbl_pvt structure
     * \retval 0 success
     * \retval -1 error
     */
    static int handle_response_busy(struct mbl_pvt *pvt)
    {
    	pvt->hangupcause = AST_CAUSE_USER_BUSY;
    	pvt->needchup = 1;
    	mbl_queue_control(pvt, AST_CONTROL_BUSY);
    	return 0;
    }
    
    /*!
     * \brief Handle NO DIALTONE messages.
     * \param pvt a mbl_pvt structure
     * \param buf a null terminated buffer containing an AT message
     * \retval 0 success
     * \retval -1 error
     */
    static int handle_response_no_dialtone(struct mbl_pvt *pvt, char *buf)
    {
    	ast_verb(1, "[%s] mobile reports NO DIALTONE\n", pvt->id);
    	pvt->needchup = 1;
    	mbl_queue_control(pvt, AST_CONTROL_CONGESTION);
    	return 0;
    }
    
    /*!
     * \brief Handle NO CARRIER messages.
     * \param pvt a mbl_pvt structure
     * \param buf a null terminated buffer containing an AT message
     * \retval 0 success
     * \retval -1 error
     */
    static int handle_response_no_carrier(struct mbl_pvt *pvt, char *buf)
    {
    	ast_verb(1, "[%s] mobile reports NO CARRIER\n", pvt->id);
    	pvt->needchup = 1;
    	mbl_queue_control(pvt, AST_CONTROL_CONGESTION);
    	return 0;
    }
    
    
    
    static void *do_monitor_phone(void *data)
    {
    	struct mbl_pvt *pvt = (struct mbl_pvt *)data;
    	struct hfp_pvt *hfp = pvt->hfp;
    
    	int t;
    	at_message_t at_msg;
    	struct msg_queue_entry *entry;
    
    
    	/* Note: At one point the initialization procedure was neatly contained
    	 * in the hfp_init() function, but that initialization method did not
    	 * work with non standard devices.  As a result, the initialization
    
    	 * procedure is not spread throughout the event handling loop.
    	 */
    
    
    	/* start initialization with the BRSF request */
    
    	ast_mutex_lock(&pvt->lock);
    	pvt->timeout = 10000;
    	if (hfp_send_brsf(hfp, &hfp_our_brsf)  || msg_queue_push(pvt, AT_BRSF, AT_BRSF)) {
    		ast_debug(1, "[%s] error sending BRSF\n", hfp->owner->id);
    		goto e_cleanup;
    	}
    	ast_mutex_unlock(&pvt->lock);
    
    	while (!check_unloading()) {
    		ast_mutex_lock(&pvt->lock);
    		t = pvt->timeout;
    		ast_mutex_unlock(&pvt->lock);
    
    		if (!rfcomm_wait(pvt->rfcomm_socket, &t)) {
    			ast_debug(1, "[%s] timeout waiting for rfcomm data, disconnecting\n", pvt->id);
    			ast_mutex_lock(&pvt->lock);
    			if (!hfp->initialized) {
    				if ((entry = msg_queue_head(pvt))) {
    					switch (entry->response_to) {
    					case AT_CIND_TEST:
    						if (pvt->blackberry)
    							ast_debug(1, "[%s] timeout during CIND test\n", hfp->owner->id);
    						else
    							ast_debug(1, "[%s] timeout during CIND test, try setting 'blackberry=yes'\n", hfp->owner->id);
    						break;
    					case AT_CMER:
    						if (pvt->blackberry)
    							ast_debug(1, "[%s] timeout after sending CMER, try setting 'blackberry=no'\n", hfp->owner->id);
    						else
    							ast_debug(1, "[%s] timeout after sending CMER\n", hfp->owner->id);
    						break;
    					default:
    						ast_debug(1, "[%s] timeout while waiting for %s in response to %s\n", pvt->id, at_msg2str(entry->expected), at_msg2str(entry->response_to));
    						break;
    					}
    				}
    			}
    			ast_mutex_unlock(&pvt->lock);
    			goto e_cleanup;
    		}
    
    		if ((at_msg = at_read_full(hfp->rsock, buf, sizeof(buf))) < 0) {
    
    			ast_debug(1, "[%s] error reading from device: %s (%d)\n", pvt->id, strerror(errno), errno);
    
    			break;
    		}
    
    		ast_debug(1, "[%s] %s\n", pvt->id, buf);
    
    		switch (at_msg) {
    		case AT_BRSF:
    			ast_mutex_lock(&pvt->lock);
    			if (handle_response_brsf(pvt, buf)) {
    				ast_mutex_unlock(&pvt->lock);
    				goto e_cleanup;
    			}
    			ast_mutex_unlock(&pvt->lock);
    			break;
    		case AT_CIND:
    			ast_mutex_lock(&pvt->lock);
    			if (handle_response_cind(pvt, buf)) {
    				ast_mutex_unlock(&pvt->lock);
    				goto e_cleanup;
    			}
    			ast_mutex_unlock(&pvt->lock);
    			break;
    		case AT_OK:
    			ast_mutex_lock(&pvt->lock);
    			if (handle_response_ok(pvt, buf)) {
    				ast_mutex_unlock(&pvt->lock);
    				goto e_cleanup;
    			}
    			ast_mutex_unlock(&pvt->lock);
    			break;
    		case AT_CMS_ERROR:
    		case AT_ERROR:
    			ast_mutex_lock(&pvt->lock);
    			if (handle_response_error(pvt, buf)) {
    				ast_mutex_unlock(&pvt->lock);
    				goto e_cleanup;
    			}
    			ast_mutex_unlock(&pvt->lock);
    			break;
    		case AT_RING:
    			ast_mutex_lock(&pvt->lock);
    			if (handle_response_ring(pvt, buf)) {
    				ast_mutex_unlock(&pvt->lock);
    				goto e_cleanup;
    			}
    			ast_mutex_unlock(&pvt->lock);
    			break;
    		case AT_CIEV:
    			ast_mutex_lock(&pvt->lock);
    			if (handle_response_ciev(pvt, buf)) {
    				ast_mutex_unlock(&pvt->lock);
    				goto e_cleanup;
    			}
    			ast_mutex_unlock(&pvt->lock);
    			break;
    		case AT_CLIP:
    			ast_mutex_lock(&pvt->lock);
    			if (handle_response_clip(pvt, buf)) {
    				ast_mutex_unlock(&pvt->lock);
    				goto e_cleanup;
    			}
    			ast_mutex_unlock(&pvt->lock);
    			break;
    		case AT_CMTI:
    			ast_mutex_lock(&pvt->lock);
    			if (handle_response_cmti(pvt, buf)) {
    				ast_mutex_unlock(&pvt->lock);
    				goto e_cleanup;
    			}
    			ast_mutex_unlock(&pvt->lock);
    			break;
    		case AT_CMGR:
    			ast_mutex_lock(&pvt->lock);
    			if (handle_response_cmgr(pvt, buf)) {
    				ast_mutex_unlock(&pvt->lock);
    				goto e_cleanup;
    			}
    			ast_mutex_unlock(&pvt->lock);
    			break;
    		case AT_SMS_PROMPT:
    			ast_mutex_lock(&pvt->lock);
    			if (handle_sms_prompt(pvt, buf)) {
    				ast_mutex_unlock(&pvt->lock);
    				goto e_cleanup;
    			}
    			ast_mutex_unlock(&pvt->lock);
    			break;
    
    		case AT_CUSD:
    			ast_mutex_lock(&pvt->lock);
    			if (handle_response_cusd(pvt, buf)) {
    				ast_mutex_unlock(&pvt->lock);
    				goto e_cleanup;
    			}
    			ast_mutex_unlock(&pvt->lock);
    			break;
    
    		case AT_BUSY:
    			ast_mutex_lock(&pvt->lock);
    			if (handle_response_busy(pvt)) {
    				ast_mutex_unlock(&pvt->lock);
    				goto e_cleanup;
    			}
    			ast_mutex_unlock(&pvt->lock);
    			break;
    		case AT_NO_DIALTONE:
    			ast_mutex_lock(&pvt->lock);
    			if (handle_response_no_dialtone(pvt, buf)) {
    				ast_mutex_unlock(&pvt->lock);
    				goto e_cleanup;
    			}
    			ast_mutex_unlock(&pvt->lock);
    			break;
    		case AT_NO_CARRIER:
    			ast_mutex_lock(&pvt->lock);
    			if (handle_response_no_carrier(pvt, buf)) {
    				ast_mutex_unlock(&pvt->lock);
    				goto e_cleanup;
    			}
    			ast_mutex_unlock(&pvt->lock);
    			break;
    		case AT_ECAM:
    			ast_mutex_lock(&pvt->lock);
    			if (hfp_parse_ecav(hfp, buf) == 7) {
    				if (handle_response_busy(pvt)) {
    					ast_mutex_unlock(&pvt->lock);
    					goto e_cleanup;
    				}
    			}
    			ast_mutex_unlock(&pvt->lock);
    			break;
    
    		case AT_UNKNOWN:
    			ast_debug(1, "[%s] ignoring unknown message: %s\n", pvt->id, buf);
    			break;
    		case AT_PARSE_ERROR:
    			ast_debug(1, "[%s] error parsing message\n", pvt->id);
    			goto e_cleanup;
    		case AT_READ_ERROR:
    
    			ast_debug(1, "[%s] error reading from device: %s (%d)\n", pvt->id, strerror(errno), errno);