/****************************************************************************** * * Copyright (C) 1999-2012 Broadcom Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ******************************************************************************/ /****************************************************************************** * * This file contains the main SDP functions * ******************************************************************************/ #include #include #include #include "bt_target.h" #include "gki.h" #include "l2cdefs.h" #include "hcidefs.h" #include "hcimsgs.h" #include "l2c_api.h" #include "l2cdefs.h" #include "btu.h" #include "btm_api.h" #include "sdp_api.h" #include "sdpint.h" /********************************************************************************/ /* G L O B A L S D P D A T A */ /********************************************************************************/ #if SDP_DYNAMIC_MEMORY == FALSE tSDP_CB sdp_cb; #endif /********************************************************************************/ /* L O C A L F U N C T I O N P R O T O T Y P E S */ /********************************************************************************/ static void sdp_connect_ind (BD_ADDR bd_addr, UINT16 l2cap_cid, UINT16 psm, UINT8 l2cap_id); static void sdp_config_ind (UINT16 l2cap_cid, tL2CAP_CFG_INFO *p_cfg); static void sdp_config_cfm (UINT16 l2cap_cid, tL2CAP_CFG_INFO *p_cfg); static void sdp_disconnect_ind (UINT16 l2cap_cid, BOOLEAN ack_needed); static void sdp_data_ind (UINT16 l2cap_cid, BT_HDR *p_msg); #if SDP_CLIENT_ENABLED == TRUE static void sdp_connect_cfm (UINT16 l2cap_cid, UINT16 result); static void sdp_disconnect_cfm (UINT16 l2cap_cid, UINT16 result); #else #define sdp_connect_cfm NULL #define sdp_disconnect_cfm NULL #endif /******************************************************************************* ** ** Function sdp_init ** ** Description This function initializes the SDP unit. ** ** Returns void ** *******************************************************************************/ void sdp_init (void) { /* Clears all structures and local SDP database (if Server is enabled) */ memset (&sdp_cb, 0, sizeof (tSDP_CB)); /* Initialize the L2CAP configuration. We only care about MTU and flush */ sdp_cb.l2cap_my_cfg.mtu_present = TRUE; sdp_cb.l2cap_my_cfg.mtu = SDP_MTU_SIZE; sdp_cb.l2cap_my_cfg.flush_to_present = TRUE; sdp_cb.l2cap_my_cfg.flush_to = SDP_FLUSH_TO; sdp_cb.max_attr_list_size = SDP_MTU_SIZE - 16; sdp_cb.max_recs_per_search = SDP_MAX_DISC_SERVER_RECS; #if SDP_SERVER_ENABLED == TRUE /* Register with Security Manager for the specific security level */ if (!BTM_SetSecurityLevel (FALSE, SDP_SERVICE_NAME, BTM_SEC_SERVICE_SDP_SERVER, SDP_SECURITY_LEVEL, SDP_PSM, 0, 0)) { SDP_TRACE_ERROR0 ("Security Registration Server failed"); return; } #endif #if SDP_CLIENT_ENABLED == TRUE /* Register with Security Manager for the specific security level */ if (!BTM_SetSecurityLevel (TRUE, SDP_SERVICE_NAME, BTM_SEC_SERVICE_SDP_SERVER, SDP_SECURITY_LEVEL, SDP_PSM, 0, 0)) { SDP_TRACE_ERROR0 ("Security Registration for Client failed"); return; } #endif #if defined(SDP_INITIAL_TRACE_LEVEL) sdp_cb.trace_level = SDP_INITIAL_TRACE_LEVEL; #else sdp_cb.trace_level = BT_TRACE_LEVEL_NONE; /* No traces */ #endif sdp_cb.reg_info.pL2CA_ConnectInd_Cb = sdp_connect_ind; sdp_cb.reg_info.pL2CA_ConnectCfm_Cb = sdp_connect_cfm; sdp_cb.reg_info.pL2CA_ConnectPnd_Cb = NULL; sdp_cb.reg_info.pL2CA_ConfigInd_Cb = sdp_config_ind; sdp_cb.reg_info.pL2CA_ConfigCfm_Cb = sdp_config_cfm; sdp_cb.reg_info.pL2CA_DisconnectInd_Cb = sdp_disconnect_ind; sdp_cb.reg_info.pL2CA_DisconnectCfm_Cb = sdp_disconnect_cfm; sdp_cb.reg_info.pL2CA_QoSViolationInd_Cb = NULL; sdp_cb.reg_info.pL2CA_DataInd_Cb = sdp_data_ind; sdp_cb.reg_info.pL2CA_CongestionStatus_Cb = NULL; sdp_cb.reg_info.pL2CA_TxComplete_Cb = NULL; /* Now, register with L2CAP */ if (!L2CA_Register (SDP_PSM, &sdp_cb.reg_info)) { SDP_TRACE_ERROR0 ("SDP Registration failed"); } } #if (defined(SDP_DEBUG) && SDP_DEBUG == TRUE) /******************************************************************************* ** ** Function sdp_set_max_attr_list_size ** ** Description This function sets the max attribute list size to use ** ** Returns void ** *******************************************************************************/ UINT16 sdp_set_max_attr_list_size (UINT16 max_size) { if (max_size > (sdp_cb.l2cap_my_cfg.mtu - 16) ) max_size = sdp_cb.l2cap_my_cfg.mtu - 16; sdp_cb.max_attr_list_size = max_size; return sdp_cb.max_attr_list_size; } #endif /******************************************************************************* ** ** Function sdp_connect_ind ** ** Description This function handles an inbound connection indication ** from L2CAP. This is the case where we are acting as a ** server. ** ** Returns void ** *******************************************************************************/ static void sdp_connect_ind (BD_ADDR bd_addr, UINT16 l2cap_cid, UINT16 psm, UINT8 l2cap_id) { #if SDP_SERVER_ENABLED == TRUE tCONN_CB *p_ccb; /* Allocate a new CCB. Return if none available. */ if ((p_ccb = sdpu_allocate_ccb()) == NULL) return; /* Transition to the next appropriate state, waiting for config setup. */ p_ccb->con_state = SDP_STATE_CFG_SETUP; /* Save the BD Address and Channel ID. */ memcpy (&p_ccb->device_address[0], bd_addr, sizeof (BD_ADDR)); p_ccb->connection_id = l2cap_cid; /* Send response to the L2CAP layer. */ L2CA_ConnectRsp (bd_addr, l2cap_id, l2cap_cid, L2CAP_CONN_OK, L2CAP_CONN_OK); { tL2CAP_CFG_INFO cfg = sdp_cb.l2cap_my_cfg; if (cfg.fcr_present) { SDP_TRACE_DEBUG6("sdp_connect_ind: mode %u, txwinsz %u, max_trans %u, rtrans_tout %u, mon_tout %u, mps %u", cfg.fcr.mode, cfg.fcr.tx_win_sz, cfg.fcr.max_transmit, cfg.fcr.rtrans_tout,cfg.fcr.mon_tout, cfg.fcr.mps); } if ((!L2CA_ConfigReq (l2cap_cid, &cfg)) && cfg.fcr_present && cfg.fcr.mode != L2CAP_FCR_BASIC_MODE) { /* FCR not desired; try again in basic mode */ cfg.fcr.mode = L2CAP_FCR_BASIC_MODE; cfg.fcr_present = FALSE; L2CA_ConfigReq (l2cap_cid, &cfg); } } SDP_TRACE_EVENT1 ("SDP - Rcvd L2CAP conn ind, sent config req, CID 0x%x", p_ccb->connection_id); #else /* No server */ /* Reject the connection */ L2CA_ConnectRsp (bd_addr, l2cap_id, l2cap_cid, L2CAP_CONN_NO_PSM, 0); #endif } #if SDP_CLIENT_ENABLED == TRUE /******************************************************************************* ** ** Function sdp_connect_cfm ** ** Description This function handles the connect confirm events ** from L2CAP. This is the case when we are acting as a ** client and have sent a connect request. ** ** Returns void ** *******************************************************************************/ static void sdp_connect_cfm (UINT16 l2cap_cid, UINT16 result) { tCONN_CB *p_ccb; tL2CAP_CFG_INFO cfg; /* Find CCB based on CID */ if ((p_ccb = sdpu_find_ccb_by_cid (l2cap_cid)) == NULL) { SDP_TRACE_WARNING1 ("SDP - Rcvd conn cnf for unknown CID 0x%x", l2cap_cid); return; } /* If the connection response contains success status, then */ /* Transition to the next state and startup the timer. */ if ((result == L2CAP_CONN_OK) && (p_ccb->con_state == SDP_STATE_CONN_SETUP)) { p_ccb->con_state = SDP_STATE_CFG_SETUP; cfg = sdp_cb.l2cap_my_cfg; if (cfg.fcr_present) { SDP_TRACE_DEBUG6("sdp_connect_cfm: mode %u, txwinsz %u, max_trans %u, rtrans_tout %u, mon_tout %u, mps %u", cfg.fcr.mode, cfg.fcr.tx_win_sz, cfg.fcr.max_transmit, cfg.fcr.rtrans_tout,cfg.fcr.mon_tout, cfg.fcr.mps); } if ((!L2CA_ConfigReq (l2cap_cid, &cfg)) && cfg.fcr_present && cfg.fcr.mode != L2CAP_FCR_BASIC_MODE) { /* FCR not desired; try again in basic mode */ cfg.fcr_present = FALSE; cfg.fcr.mode = L2CAP_FCR_BASIC_MODE; L2CA_ConfigReq (l2cap_cid, &cfg); } SDP_TRACE_EVENT1 ("SDP - got conn cnf, sent cfg req, CID: 0x%x", p_ccb->connection_id); } else { SDP_TRACE_WARNING2 ("SDP - Rcvd conn cnf with error: 0x%x CID 0x%x", result, p_ccb->connection_id); /* Tell the user if he has a callback */ if (p_ccb->p_cb || p_ccb->p_cb2) { UINT16 err = -1; if ((result == HCI_ERR_HOST_REJECT_SECURITY) || (result == HCI_ERR_AUTH_FAILURE) || (result == HCI_ERR_PAIRING_NOT_ALLOWED) || (result == HCI_ERR_PAIRING_WITH_UNIT_KEY_NOT_SUPPORTED) || (result == HCI_ERR_KEY_MISSING)) err = SDP_SECURITY_ERR; else if (result == HCI_ERR_HOST_REJECT_DEVICE) err = SDP_CONN_REJECTED; else err = SDP_CONN_FAILED; if(p_ccb->p_cb) (*p_ccb->p_cb)(err); else if(p_ccb->p_cb2) (*p_ccb->p_cb2)(err, p_ccb->user_data); } sdpu_release_ccb (p_ccb); } } #endif /* SDP_CLIENT_ENABLED == TRUE */ /******************************************************************************* ** ** Function sdp_config_ind ** ** Description This function processes the L2CAP configuration indication ** event. ** ** Returns void ** *******************************************************************************/ static void sdp_config_ind (UINT16 l2cap_cid, tL2CAP_CFG_INFO *p_cfg) { tCONN_CB *p_ccb; /* Find CCB based on CID */ if ((p_ccb = sdpu_find_ccb_by_cid (l2cap_cid)) == NULL) { SDP_TRACE_WARNING1 ("SDP - Rcvd L2CAP cfg ind, unknown CID: 0x%x", l2cap_cid); return; } /* Remember the remote MTU size */ if (!p_cfg->mtu_present) { /* use min(L2CAP_DEFAULT_MTU,SDP_MTU_SIZE) for GKI buffer size reasons */ p_ccb->rem_mtu_size = (L2CAP_DEFAULT_MTU > SDP_MTU_SIZE)?SDP_MTU_SIZE:L2CAP_DEFAULT_MTU; } else { if (p_cfg->mtu > SDP_MTU_SIZE) p_ccb->rem_mtu_size = SDP_MTU_SIZE; else p_ccb->rem_mtu_size = p_cfg->mtu; } /* For now, always accept configuration from the other side */ p_cfg->flush_to_present = FALSE; p_cfg->mtu_present = FALSE; p_cfg->result = L2CAP_CFG_OK; /* Check peer config request against our rfcomm configuration */ if (p_cfg->fcr_present) { /* Reject the window size if it is bigger than we want it to be */ if (p_cfg->fcr.mode != L2CAP_FCR_BASIC_MODE) { if (sdp_cb.l2cap_my_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE && p_cfg->fcr.tx_win_sz > sdp_cb.l2cap_my_cfg.fcr.tx_win_sz) { p_cfg->fcr.tx_win_sz = sdp_cb.l2cap_my_cfg.fcr.tx_win_sz; p_cfg->result = L2CAP_CFG_UNACCEPTABLE_PARAMS; SDP_TRACE_DEBUG0("sdp_config_ind(CONFIG) -> Please try again with SMALLER TX WINDOW"); } /* Reject if locally we want basic and they don't */ if (sdp_cb.l2cap_my_cfg.fcr.mode == L2CAP_FCR_BASIC_MODE) { /* Ask for a new setup */ p_cfg->fcr.mode = L2CAP_FCR_BASIC_MODE; p_cfg->result = L2CAP_CFG_UNACCEPTABLE_PARAMS; SDP_TRACE_DEBUG0("sdp_config_ind(CONFIG) -> Please try again with BASIC mode"); } /* Remain in configure state and give the peer our desired configuration */ if (p_cfg->result != L2CAP_CFG_OK) { SDP_TRACE_WARNING1 ("SDP - Rcvd cfg ind, Unacceptable Parameters sent cfg cfm, CID: 0x%x", l2cap_cid); L2CA_ConfigRsp (l2cap_cid, p_cfg); return; } } else /* We agree with peer's request */ p_cfg->fcr_present = FALSE; } L2CA_ConfigRsp (l2cap_cid, p_cfg); SDP_TRACE_EVENT1 ("SDP - Rcvd cfg ind, sent cfg cfm, CID: 0x%x", l2cap_cid); p_ccb->con_flags |= SDP_FLAGS_HIS_CFG_DONE; if (p_ccb->con_flags & SDP_FLAGS_MY_CFG_DONE) { p_ccb->con_state = SDP_STATE_CONNECTED; if (p_ccb->con_flags & SDP_FLAGS_IS_ORIG) sdp_disc_connected (p_ccb); else /* Start inactivity timer */ btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_SDP, SDP_INACT_TIMEOUT); } } /******************************************************************************* ** ** Function sdp_config_cfm ** ** Description This function processes the L2CAP configuration confirmation ** event. ** ** Returns void ** *******************************************************************************/ static void sdp_config_cfm (UINT16 l2cap_cid, tL2CAP_CFG_INFO *p_cfg) { tCONN_CB *p_ccb; SDP_TRACE_EVENT2 ("SDP - Rcvd cfg cfm, CID: 0x%x Result: %d", l2cap_cid, p_cfg->result); /* Find CCB based on CID */ if ((p_ccb = sdpu_find_ccb_by_cid (l2cap_cid)) == NULL) { SDP_TRACE_WARNING1 ("SDP - Rcvd L2CAP cfg ind, unknown CID: 0x%x", l2cap_cid); return; } /* For now, always accept configuration from the other side */ if (p_cfg->result == L2CAP_CFG_OK) { p_ccb->con_flags |= SDP_FLAGS_MY_CFG_DONE; if (p_ccb->con_flags & SDP_FLAGS_HIS_CFG_DONE) { p_ccb->con_state = SDP_STATE_CONNECTED; if (p_ccb->con_flags & SDP_FLAGS_IS_ORIG) sdp_disc_connected (p_ccb); else /* Start inactivity timer */ btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_SDP, SDP_INACT_TIMEOUT); } } else { /* If peer has rejected FCR and suggested basic then try basic */ if (p_cfg->fcr_present) { tL2CAP_CFG_INFO cfg = sdp_cb.l2cap_my_cfg; cfg.fcr_present = FALSE; L2CA_ConfigReq (l2cap_cid, &cfg); /* Remain in configure state */ return; } #if SDP_CLIENT_ENABLED == TRUE sdp_disconnect(p_ccb, SDP_CFG_FAILED); #endif } } /******************************************************************************* ** ** Function sdp_disconnect_ind ** ** Description This function handles a disconnect event from L2CAP. If ** requested to, we ack the disconnect before dropping the CCB ** ** Returns void ** *******************************************************************************/ static void sdp_disconnect_ind (UINT16 l2cap_cid, BOOLEAN ack_needed) { tCONN_CB *p_ccb; /* Find CCB based on CID */ if ((p_ccb = sdpu_find_ccb_by_cid (l2cap_cid)) == NULL) { SDP_TRACE_WARNING1 ("SDP - Rcvd L2CAP disc, unknown CID: 0x%x", l2cap_cid); return; } if (ack_needed) L2CA_DisconnectRsp (l2cap_cid); SDP_TRACE_EVENT1 ("SDP - Rcvd L2CAP disc, CID: 0x%x", l2cap_cid); #if SDP_CLIENT_ENABLED == TRUE /* Tell the user if he has a callback */ if (p_ccb->p_cb) (*p_ccb->p_cb) ((UINT16) ((p_ccb->con_state == SDP_STATE_CONNECTED) ? SDP_SUCCESS : SDP_CONN_FAILED)); else if (p_ccb->p_cb2) (*p_ccb->p_cb2) ((UINT16) ((p_ccb->con_state == SDP_STATE_CONNECTED) ? SDP_SUCCESS : SDP_CONN_FAILED), p_ccb->user_data); #endif sdpu_release_ccb (p_ccb); } /******************************************************************************* ** ** Function sdp_data_ind ** ** Description This function is called when data is received from L2CAP. ** if we are the originator of the connection, we are the SDP ** client, and the received message is queued up for the client. ** ** If we are the destination of the connection, we are the SDP ** server, so the message is passed to the server processing ** function. ** ** Returns void ** *******************************************************************************/ static void sdp_data_ind (UINT16 l2cap_cid, BT_HDR *p_msg) { tCONN_CB *p_ccb; /* Find CCB based on CID */ if ((p_ccb = sdpu_find_ccb_by_cid (l2cap_cid)) != NULL) { if (p_ccb->con_state == SDP_STATE_CONNECTED) { if (p_ccb->con_flags & SDP_FLAGS_IS_ORIG) sdp_disc_server_rsp (p_ccb, p_msg); else sdp_server_handle_client_req (p_ccb, p_msg); } else { SDP_TRACE_WARNING2 ("SDP - Ignored L2CAP data while in state: %d, CID: 0x%x", p_ccb->con_state, l2cap_cid); } } else { SDP_TRACE_WARNING1 ("SDP - Rcvd L2CAP data, unknown CID: 0x%x", l2cap_cid); } GKI_freebuf (p_msg); } #if SDP_CLIENT_ENABLED == TRUE /******************************************************************************* ** ** Function sdp_conn_originate ** ** Description This function is called from the API to originate a ** connection. ** ** Returns void ** *******************************************************************************/ tCONN_CB* sdp_conn_originate (UINT8 *p_bd_addr) { tCONN_CB *p_ccb; UINT16 cid; /* Allocate a new CCB. Return if none available. */ if ((p_ccb = sdpu_allocate_ccb()) == NULL) { SDP_TRACE_WARNING0 ("SDP - no spare CCB for orig"); return (NULL); } SDP_TRACE_EVENT0 ("SDP - Originate started"); /* We are the originator of this connection */ p_ccb->con_flags |= SDP_FLAGS_IS_ORIG; /* Save the BD Address and Channel ID. */ memcpy (&p_ccb->device_address[0], p_bd_addr, sizeof (BD_ADDR)); /* Transition to the next appropriate state, waiting for connection confirm. */ p_ccb->con_state = SDP_STATE_CONN_SETUP; // btla-specific ++ #ifndef ANDROID_APP_INCLUDED /* Skip for Android: Do not need to set out_service for sdp, since sdp does not use sec. Prevents over-writing service_rec of a connection already in progress */ BTM_SetOutService(p_bd_addr, BTM_SEC_SERVICE_SDP_SERVER, 0); #endif // btla-specific -- cid = L2CA_ConnectReq (SDP_PSM, p_bd_addr); /* Check if L2CAP started the connection process */ if (cid != 0) { p_ccb->connection_id = cid; return (p_ccb); } else { SDP_TRACE_WARNING0 ("SDP - Originate failed"); sdpu_release_ccb (p_ccb); return (NULL); } } /******************************************************************************* ** ** Function sdp_disconnect ** ** Description This function disconnects a connection. ** ** Returns void ** *******************************************************************************/ void sdp_disconnect (tCONN_CB*p_ccb, UINT16 reason) { #if (defined(SDP_BROWSE_PLUS) && SDP_BROWSE_PLUS == TRUE) /* If we are browsing for multiple UUIDs ... */ if ((p_ccb->con_state == SDP_STATE_CONNECTED) && (p_ccb->con_flags & SDP_FLAGS_IS_ORIG) && ((reason == SDP_SUCCESS) || (reason == SDP_NO_RECS_MATCH))) { /* If the browse found something, do no more searching */ if ((p_ccb->cur_uuid_idx == 0) && (p_ccb->p_db->p_first_rec)) p_ccb->cur_uuid_idx = p_ccb->p_db->num_uuid_filters; while (++p_ccb->cur_uuid_idx < p_ccb->p_db->num_uuid_filters) { /* Check we have not already found the UUID (maybe through browse) */ if ((p_ccb->p_db->uuid_filters[p_ccb->cur_uuid_idx].len == 2) && (SDP_FindServiceInDb (p_ccb->p_db, p_ccb->p_db->uuid_filters[p_ccb->cur_uuid_idx].uu.uuid16, NULL))) continue; if ((p_ccb->p_db->uuid_filters[p_ccb->cur_uuid_idx].len > 2) && (SDP_FindServiceUUIDInDb (p_ccb->p_db, &p_ccb->p_db->uuid_filters[p_ccb->cur_uuid_idx], NULL))) continue; p_ccb->cur_handle = 0; SDP_TRACE_EVENT1 ("SDP - looking for for more, CID: 0x%x", p_ccb->connection_id); sdp_disc_connected (p_ccb); return; } } if ((reason == SDP_NO_RECS_MATCH) && (p_ccb->p_db->p_first_rec)) reason = SDP_SUCCESS; #endif SDP_TRACE_EVENT1 ("SDP - disconnect CID: 0x%x", p_ccb->connection_id); /* Check if we have a connection ID */ if (p_ccb->connection_id != 0) { L2CA_DisconnectReq (p_ccb->connection_id); p_ccb->disconnect_reason = reason; } /* If at setup state, we may not get callback ind from L2CAP */ /* Call user callback immediately */ if (p_ccb->con_state == SDP_STATE_CONN_SETUP) { /* Tell the user if he has a callback */ if (p_ccb->p_cb) (*p_ccb->p_cb) (reason); else if (p_ccb->p_cb2) (*p_ccb->p_cb2) (reason, p_ccb->user_data); sdpu_release_ccb (p_ccb); } } /******************************************************************************* ** ** Function sdp_disconnect_cfm ** ** Description This function handles a disconnect confirm event from L2CAP. ** ** Returns void ** *******************************************************************************/ static void sdp_disconnect_cfm (UINT16 l2cap_cid, UINT16 result) { tCONN_CB *p_ccb; /* Find CCB based on CID */ if ((p_ccb = sdpu_find_ccb_by_cid (l2cap_cid)) == NULL) { SDP_TRACE_WARNING1 ("SDP - Rcvd L2CAP disc cfm, unknown CID: 0x%x", l2cap_cid); return; } SDP_TRACE_EVENT1 ("SDP - Rcvd L2CAP disc cfm, CID: 0x%x", l2cap_cid); /* Tell the user if he has a callback */ if (p_ccb->p_cb) (*p_ccb->p_cb) (p_ccb->disconnect_reason); else if (p_ccb->p_cb2) (*p_ccb->p_cb2) (p_ccb->disconnect_reason, p_ccb->user_data); sdpu_release_ccb (p_ccb); } #endif /* SDP_CLIENT_ENABLED == TRUE */ /******************************************************************************* ** ** Function sdp_conn_timeout ** ** Description This function processes a timeout. Currently, we simply send ** a disconnect request to L2CAP. ** ** Returns void ** *******************************************************************************/ void sdp_conn_timeout (tCONN_CB*p_ccb) { SDP_TRACE_EVENT2 ("SDP - CCB timeout in state: %d CID: 0x%x", p_ccb->con_state, p_ccb->connection_id); L2CA_DisconnectReq (p_ccb->connection_id); #if SDP_CLIENT_ENABLED == TRUE /* Tell the user if he has a callback */ if (p_ccb->p_cb) (*p_ccb->p_cb) (SDP_CONN_FAILED); else if (p_ccb->p_cb2) (*p_ccb->p_cb2) (SDP_CONN_FAILED, p_ccb->user_data); #endif sdpu_release_ccb (p_ccb); }