1. 前言
当BLE设备作为GATT客户端(GATT Client)连接到GATT服务器(GATT Server)后,它通常需要执行发现过程以了解服务器的GATT数据库结构。这包括服务(Services)、特征(Characteristics)和描述符等。bta_gattc_start_discover
函数正是用于启动这一过程的。
2. 工作流程
- 连接建立:首先,GATT客户端需要与GATT服务器建立连接。通过调用BlueDroid
BTA_GATTC_Open
来完成。 - 启动发现:连接建立后,GATT客户端会调用
bta_gattc_start_discover
来启动发现过程。这个函数可能会触发一系列的GATT请求,如“Read By Group Type Request”来请求主服务(Primary Services)的列表。 - 处理响应:当GATT服务器响应这些请求时,客户端会收到相应的GATT响应。这些响应由BlueDroid堆栈中的函数(如
gatt_client_handle_server_rsp
)处理,并根据需要进行进一步的操作。 - 缓存结果:发现过程中获取的信息(如服务列表、特征列表等)会被缓存起来,以便客户端后续使用。
- 完成发现:当所有必要的发现操作都完成后,会触发一个完成回调(如
bta_gattc_disc_cmpl_cback
),表示发现过程已经结束。
3. 源码分析
3.1. GATT实例
为能更直观理解,引入一个gatt实例。
[Service]
UUID: 0x1801
PRIMARY SERVICE
[Unknown Characteristic]
UUID: 0x2a05
Properties: INDICATE
---
[Service]
UUID: 0x1800
PRIMARY SERVICE
[Unknown Characteristic]
UUID: 0x2a00
Properties: READ
[Unknown Characteristic]
UUID: 0x2a01
Properties: READ
[Unknown Characteristic]
UUID: 0x2aa6
Properties: READ
---
[Service]
UUID: 0x5356
PRIMARY SERVICE
[Unknown Characteristic]
UUID: 0xfd02
Properties: READ
[Unknown Characteristic]
UUID: 0xfd03
Properties: NOTIFY
Descriptors
[Client Characteristic Configuration]
UUID: 0x2902
Properties: NOTIFY
[Unknown Characteristic]
UUID: 0xfd01
Properties: READ, WRITE, NOTIFY
对应这gatt,当发现结束后,bta_gattc_explore_srvc_finished(...)中bta_gattc_display_cache_server()输出的logcat。
<================Start Server Cache =============>
Service: handle=0x0001, end_handle=0x0005, uuid=00001801-0000-1000-8000-00805f9b34fb
Characteristic: declaration_handle=0x0002, value_handle=0x0003, uuid=00002a05-0000-1000-8000-00805f9b34fb, prop=0x20
Service: handle=0x0014, end_handle=0x001c, uuid=00001800-0000-1000-8000-00805f9b34fb
Characteristic: declaration_handle=0x0015, value_handle=0x0016, uuid=00002a00-0000-1000-8000-00805f9b34fb, prop=0x02
Characteristic: declaration_handle=0x0017, value_handle=0x0018, uuid=00002a01-0000-1000-8000-00805f9b34fb, prop=0x02
Characteristic: declaration_handle=0x0019, value_handle=0x001a, uuid=00002aa6-0000-1000-8000-00805f9b34fb, prop=0x02
Service: handle=0x0028, end_handle=0xffff, uuid=00005356-0000-1000-8000-00805f9b34fb
Characteristic: declaration_handle=0x0029, value_handle=0x002a, uuid=0000fd02-0000-1000-8000-00805f9b34fb, prop=0x02
Characteristic: declaration_handle=0x002b, value_handle=0x002c, uuid=0000fd03-0000-1000-8000-00805f9b34fb, prop=0x10
Descriptor: handle=0x002d, uuid=00002902-0000-1000-8000-00805f9b34fb
Characteristic: declaration_handle=0x002e, value_handle=0x002f, uuid=0000fd01-0000-1000-8000-00805f9b34fb, prop=0x1a
<================End Server Cache =============>
-
进入discover时的bta状态是BTA_GATTC_DISCOVER_ST,经过discover后,进入BTA_GATTC_CONN_ST。
-
discover任务是从ble peripheral获取service、characteristic、descriptor,存储在BLE HAL内存:p_clcb->p_srcb。过程中不会回调JNI函数。
-
discover流程:
-
一次req/resp搜索出所有primary service,然后对每个service依次进行单一service发现流程。
-
单一service发现。一次Req/Resp搜索include service;然后一次Req/Resp搜出所有Characteristic;
-
对每个char,一次Req/Resp搜出Descriptors。
-
-
执行完一步操作后,会调用gatt_end_operation。该函数的主要功能是触发下一步操作,而这是通过回调bta_gattc_disc_cmpl_cback。
-
会把discover到的gatt db存储到cache文件,像/data/misc/bluetooth/gatt_cache_XXX,前提是配对了该peripheral,即条件“btm_sec_is_a_bonded_dev(p_srvc_cb->server_bda)”返回true。以目前常见的手机连接ble peripernal,是不配对的,即不会生成cache文件。
对示例,1 + (2+1) + (2+3) + (2+3) = 14,须14个packet req/resp。以下是每次req/resp对应的功能,最后一列是gatt_client_handle_server_rsp中执行的函数。
搜出3个service,每个service有s_handle、e_handle、uuid。gatt_client_handle_server_rsp中执行的是gatt_process_error_rsp。
[#0单一service发现]
搜索include service,结果没有。gatt_process_error_rsp。
搜索所有Characteristic,有1个。gatt_process_read_by_type_rsp。
搜索#0号Char中的descriptor,结果没有。gatt_process_read_by_type_rsp。
[#1单一service发现]
搜索include service,结果没有。gatt_process_error_rsp。
[#1单一service发现]搜索所有Characteristic,有3个。gatt_process_read_by_type_rsp。
[#1单一service发现]搜索#0号Char中的descriptor,结果没有。gatt_process_error_rsp。
[#1单一service发现]搜索#1号Char中的descriptor,结果没有。gatt_process_error_rsp。
[#1单一service发现]搜索#2号Char中的descriptor,结果没有。gatt_process_error_rsp。
[#2单一service发现]
搜索include service,结果没有。gatt_process_error_rsp。
[#2单一service发现]搜索所有Characteristic,有3个。gatt_process_read_by_type_rsp。
[#2单一service发现]搜索#0号Char中的descriptor,结果没有。gatt_process_error_rsp。
[#2单一service发现]搜索#1号Char中的descriptor,有一个。gatt_process_read_info_rsp。
[#2单一service发现]搜索#2号Char中的descriptor,结果没有。gatt_process_error_rsp。
接下开始bta_gattc_start_discover过程解析。
3.2. bta_gattc_start_discover
/packages/modules/Bluetooth/system/bta/gatt/bta_gattc_act.cc
/** Start a discovery on server */
void bta_gattc_start_discover(tBTA_GATTC_CLCB* p_clcb,
UNUSED_ATTR const tBTA_GATTC_DATA* p_data) {
log::verbose("conn_id:{} p_clcb->p_srcb->state:{}",
loghex(p_clcb->bta_conn_id), p_clcb->p_srcb->state);
if (((p_clcb->p_q_cmd == NULL ||
p_clcb->auto_update == BTA_GATTC_REQ_WAITING) &&
p_clcb->p_srcb->state == BTA_GATTC_SERV_IDLE) ||
p_clcb->p_srcb->state == BTA_GATTC_SERV_DISC)
/* no pending operation, start discovery right away */
{
p_clcb->auto_update = BTA_GATTC_NO_SCHEDULE; // 表示没有自动更新的计划
if (p_clcb->p_srcb == NULL) {
log::error("unknown device, can not start discovery");
return;
}
/* set all srcb related clcb into discovery ST */
// 复位一些标记位和数据, 包括把p_clcb->state改为BTA_GATTC_DISCOVER_ST
bta_gattc_set_discover_st(p_clcb->p_srcb);
// Before clear mask, set is_svc_chg to
// 1. true, invoked by service changed indication
// 2. false, invoked by connect API
bool is_svc_chg = p_clcb->p_srcb->srvc_hdl_chg; //这个值决定了服务发现是由服务变更指示触发的,还是由连接API触发的
/* clear the service change mask */
p_clcb->p_srcb->srvc_hdl_chg = false;
p_clcb->p_srcb->update_count = 0;
p_clcb->p_srcb->state = BTA_GATTC_SERV_DISC_ACT;
p_clcb->p_srcb->disc_blocked_waiting_on_version = false;
auto cache_support =
GetRobustCachingSupport(p_clcb, p_clcb->p_srcb->gatt_database);
if (cache_support == RobustCachingSupport::W4_REMOTE_VERSION) {
log::info(
"Pausing service discovery till remote version is read conn_id:{}",
p_clcb->bta_conn_id);
p_clcb->p_srcb->disc_blocked_waiting_on_version = true;
p_clcb->p_srcb->blocked_conn_id = p_clcb->bta_conn_id;
return;
}
bta_gattc_continue_with_version_and_cache_known(p_clcb, cache_support,
is_svc_chg);
}
/* pending operation, wait until it finishes */
else {
p_clcb->auto_update = BTA_GATTC_DISC_WAITING;
if (p_clcb->p_srcb->state == BTA_GATTC_SERV_IDLE)
p_clcb->state = BTA_GATTC_CONN_ST; /* set clcb state */
}
}
bta_gattc_start_discoverBLE是客户端在建立与服务器的连接后,用于启动启动对远端蓝牙设备的服务发现过程。根据当前的状态和条件决定是否立即开始服务发现,或者等待之前的操作完成后再进行。同时,它也处理了与服务变更和缓存支持相关的逻辑。
继续服务发现流程:如果不需要等待对端版本信息,则调用bta_gattc_continue_with_version_and_cache_known继续服务发现流程,根据缓存支持和是否由服务变更触发来决定后续操作。
3.3.bta_gattc_continue_with_version_and_cache_known
/packages/modules/Bluetooth/system/bta/gatt/bta_gattc_act.cc
void bta_gattc_continue_with_version_and_cache_known(
tBTA_GATTC_CLCB* p_clcb, RobustCachingSupport cache_support,
bool is_svc_chg) {
if (cache_support == RobustCachingSupport::UNSUPPORTED ||
(IS_FLAG_ENABLED(skip_unknown_robust_caching) &&
cache_support == RobustCachingSupport::UNKNOWN)) {
// Skip initial DB hash read if no DB hash is known, or if
// we have strong reason (due to interop,
// or a prior discovery) to believe that it is unsupported.
p_clcb->p_srcb->srvc_hdl_db_hash = false; //表示不需要处理数据库哈希
}
/* read db hash if db hash characteristic exists */
if (bta_gattc_is_robust_caching_enabled() &&
p_clcb->p_srcb->srvc_hdl_db_hash &&
bta_gattc_read_db_hash(p_clcb, is_svc_chg)) {
log::info("pending service discovery, read db hash first conn_id:{}",
loghex(p_clcb->bta_conn_id));
p_clcb->p_srcb->srvc_hdl_db_hash = false;
return;
}
bta_gattc_start_discover_internal(p_clcb);
}
-
鲁棒缓存是一种优化机制,允许GATTC在重新连接时快速恢复之前的服务发现结果,而无需重新发现所有服务。但是,这需要GATT服务器支持该特性,并提供数据库哈希值以便验证缓存的有效性。
-
读取数据库哈希是可选的,取决于对端GATT服务器的支持情况、配置选项以及是否检测到服务变更。
-
如果决定读取数据库哈希,则服务发现过程会暂时挂起,直到数据库哈希读取完成并验证缓存的有效性。
3.4.bta_gattc_start_discover_internal
packages/modules/Bluetooth/system/bta/gatt/bta_gattc_act.cc
void bta_gattc_start_discover_internal(tBTA_GATTC_CLCB* p_clcb) {
if (p_clcb->transport == BT_TRANSPORT_LE)
L2CA_LockBleConnParamsForServiceDiscovery(p_clcb->p_srcb->server_bda, true);//锁定连接参数,以便在服务发现期间保持稳定的连接质量。这个步骤是可选的,取决于蓝牙堆栈的实现和配置
//初始化与远程服务器相关的缓存。这个缓存用于存储服务发现的结果,以便在后续的连接中快速恢复。
bta_gattc_init_cache(p_clcb->p_srcb);
p_clcb->status = bta_gattc_discover_pri_service(
p_clcb->bta_conn_id, p_clcb->p_srcb, GATT_DISC_SRVC_ALL); //启动主要服务的发现过程
if (p_clcb->status != GATT_SUCCESS) {
log::error("discovery on server failed");
bta_gattc_reset_discover_st(p_clcb->p_srcb, p_clcb->status);
} else
p_clcb->disc_active = true; //表示当前正在进行服务发现
}
最终执行了bta_gattc_discover_pri_service,其中参数disc_type是GATT_DISC_SRVC_ALL,指示GATTC启动一个过程来发现远程 GATTS上所有的主要服务(Primary Services)。
在BLE中,服务是数据的集合,这些数据通过一组特征(Characteristics)来暴露给 GATTC。主要服务是指那些可以在服务发现过程中被直接发现的服务,而次要服务(Secondary Services)则通常被包含在某个主要服务内部,并需要额外的步骤来发现。
GATT_DISC_SRVC_ALL 是一个发现类型,它告诉 GATTC使用 GATT Discover All Primary Services by UUID 过程(如果 UUID 设置为 NULL,则表示不特定于任何 UUID)来查找远程服务器上的所有主要服务。这个过程通常涉及发送一个包含特定 PDU的GATT请求给对端服务器,然后等待服务器响应包含服务列表的响应。
一旦 GATTC收到这些服务的列表,它就可以进一步发现每个服务的特征(Characteristics)和描述符(Descriptors),从而能够读取、写入或订阅这些服务的特定数据。
注意,尽管 GATT_DISC_SRVC_ALL 指示发现所有服务,但远程服务器可能不包含任何服务,或者出于安全或隐私原因,某些服务可能对特定的 GATTC不可见。
3.5.bta_gattc_discover_pri_service
/packages/modules/Bluetooth/system/bta/gatt/bta_gattc_cache.cc
/** Start primary service discovery */
tGATT_STATUS bta_gattc_discover_pri_service(uint16_t conn_id,
tBTA_GATTC_SERV* p_server_cb,
tGATT_DISC_TYPE disc_type) {
tBTA_GATTC_CLCB* p_clcb = bta_gattc_find_clcb_by_conn_id(conn_id);
if (!p_clcb) return GATT_ERROR;
if (p_clcb->transport == BT_TRANSPORT_LE) {
return GATTC_Discover(conn_id, disc_type, 0x0001, 0xFFFF);
}
// only for Classic transport
return bta_gattc_sdp_service_disc(conn_id, p_server_cb);
}
对于BLE连接,调用 GATTC_Discover发送服务发现请求。这里disc_type为GATT_DISC_SRVC_ALL。
3.6.GATTC_Discover
/packages/modules/Bluetooth/system/stack/gatt/gatt_api.cc
tGATT_STATUS GATTC_Discover(uint16_t conn_id, tGATT_DISC_TYPE disc_type,
uint16_t start_handle, uint16_t end_handle) {
return GATTC_Discover(conn_id, disc_type, start_handle, end_handle,
Uuid::kEmpty);
}
//4参数版本调用5参数时,第5个参数传Uuid::kEmpty。
tGATT_STATUS GATTC_Discover(uint16_t conn_id, tGATT_DISC_TYPE disc_type,
uint16_t start_handle, uint16_t end_handle,
const Uuid& uuid) {
tGATT_IF gatt_if = GATT_GET_GATT_IF(conn_id);
uint8_t tcb_idx = GATT_GET_TCB_IDX(conn_id);
tGATT_TCB* p_tcb = gatt_get_tcb_by_idx(tcb_idx);
tGATT_REG* p_reg = gatt_get_regcb(gatt_if);
if ((p_tcb == NULL) || (p_reg == NULL) || (disc_type >= GATT_DISC_MAX)) {
log::error("Illegal param: disc_type={} conn_id={}", disc_type,
loghex(conn_id));
return GATT_ILLEGAL_PARAMETER;
}
if (!GATT_HANDLE_IS_VALID(start_handle) ||
!GATT_HANDLE_IS_VALID(end_handle) ||
/* search by type does not have a valid UUID param */
(disc_type == GATT_DISC_SRVC_BY_UUID && uuid.IsEmpty())) {
log::warn(
"Illegal parameter conn_id={}, disc_type={}, s_handle={}, e_handle={}",
loghex(conn_id), disc_type, loghex(start_handle), loghex(end_handle));
return GATT_ILLEGAL_PARAMETER;
}
tGATT_CLCB* p_clcb = gatt_clcb_alloc(conn_id);
if (!p_clcb) {
log::warn("No resources conn_id={}, disc_type={}, s_handle={}, e_handle={}",
loghex(conn_id), disc_type, loghex(start_handle),
loghex(end_handle));
return GATT_NO_RESOURCES;
}
// 这个opcode并非ATT协议里面的opcode,只是代码实现的一种抽象表达,常用于在GATT RSP的处理中做逻辑判断
p_clcb->operation = GATTC_OPTYPE_DISCOVERY;
// 这个实际对应于ATT的各种opcode
p_clcb->op_subtype = disc_type;
p_clcb->s_handle = start_handle;
p_clcb->e_handle = end_handle;
p_clcb->uuid = uuid;
log::info("conn_id={}, disc_type={}, s_handle={}, e_handle={}",
loghex(conn_id), disc_type, loghex(start_handle),
loghex(end_handle));
gatt_act_discovery(p_clcb);
return GATT_SUCCESS;
}
GATTC_Discover负责准备和发送发现请求,并管理相关的资源。调用 gatt_act_discovery函数来实际执行服务发现过程。
3.6.1. gatt_act_discovery
/packages/modules/Bluetooth/system/stack/gatt/gatt_cl.cc
void gatt_act_discovery(tGATT_CLCB* p_clcb) {
uint8_t op_code = disc_type_to_att_opcode[p_clcb->op_subtype];
if (p_clcb->s_handle > p_clcb->e_handle || p_clcb->s_handle == 0) { //首先检查起始句柄是否大于结束句柄或起始句柄是否为0
log::debug("Completed GATT discovery of all handle ranges");
gatt_end_operation(p_clcb, GATT_SUCCESS, NULL); //已经完成了所有句柄范围的GATT发现,结束操作,并返回成功状态
return;
}
tGATT_CL_MSG cl_req; //用于存储发现请求的信息,包括起始句柄、结束句柄,以及如果操作子类型指定了UUID,则还会设置相应的UUID
memset(&cl_req, 0, sizeof(tGATT_CL_MSG));
cl_req.browse.s_handle = p_clcb->s_handle;
cl_req.browse.e_handle = p_clcb->e_handle;
if (disc_type_to_uuid[p_clcb->op_subtype] != 0) {
cl_req.browse.uuid =
bluetooth::Uuid::From16Bit(disc_type_to_uuid[p_clcb->op_subtype]);
}
if (p_clcb->op_subtype ==
GATT_DISC_SRVC_BY_UUID) /* fill in the FindByTypeValue request info*/
{
...
}
tGATT_STATUS st = attp_send_cl_msg(*p_clcb->p_tcb, p_clcb, op_code, &cl_req);
if (st != GATT_SUCCESS && st != GATT_CMD_STARTED) {
log::warn("Unable to send ATT message");
gatt_end_operation(p_clcb, GATT_ERROR, NULL);
}
}
根据客户端控制块p_clcb中的信息来构建并发送一个合适的ATT请求并调用attp_send_cl_msg发送给远程GATT服务器进行一次发现操作。参数指定了要发现是什么内容。最后会通过L2cap 的ATT通道下发下去。通过调用attp_send_cl_msg函数发送发现请求。
3.6.2. attp_send_cl_msg
/packages/modules/Bluetooth/system/stack/gatt/att_protocol.cc
tGATT_STATUS attp_send_cl_msg(tGATT_TCB& tcb, tGATT_CLCB* p_clcb,
uint8_t op_code, tGATT_CL_MSG* p_msg) {
BT_HDR* p_cmd = NULL;
uint16_t offset = 0, handle;
if (!p_clcb) {
log::error("Missing p_clcb");
return GATT_ILLEGAL_PARAMETER;
}
uint16_t payload_size = gatt_tcb_get_payload_size(tcb, p_clcb->cid);
if (payload_size == 0) {
log::error("Cannot send request (op: 0x{:02x}) due to payload size = 0, {}",
op_code, ADDRESS_TO_LOGGABLE_CSTR(tcb.peer_bda));
return GATT_NO_RESOURCES;
}
switch (op_code) {
...
case GATT_REQ_FIND_INFO:
case GATT_REQ_READ_BY_TYPE:
case GATT_REQ_READ_BY_GRP_TYPE:
if (!GATT_HANDLE_IS_VALID(p_msg->browse.s_handle) ||
!GATT_HANDLE_IS_VALID(p_msg->browse.e_handle) ||
p_msg->browse.s_handle > p_msg->browse.e_handle) {
log::warn("GATT message has invalid handle op_code:{}", op_code);
return GATT_ILLEGAL_PARAMETER;
}
p_cmd = attp_build_browse_cmd(op_code, p_msg->browse.s_handle,
p_msg->browse.e_handle, p_msg->browse.uuid);
break;
...
default:
break;
}
if (p_cmd == NULL) {
log::warn(
"Unable to build proper GATT message to send to peer device op_code:{}",
op_code);
return GATT_NO_RESOURCES;
}
return attp_cl_send_cmd(tcb, p_clcb, op_code, p_cmd);
}
负责将高层的GATT操作请求转换为ATT协议命令,并通过蓝牙连接发送给远程设备,实现GATT客户端与服务器之间的通信。
-
根据操作码构建命令:
-
使用switch语句根据op_code的值选择相应的命令构建函数。
-
对于每个操作码,都会检查消息中的句柄(handle)和其他参数是否有效。
-
调用相应的构建函数(如attp_build_mtu_cmd、attp_build_browse_cmd等)来构建ATT命令。
-
-
命令构建失败处理:如果无法构建命令(即p_cmd为NULL),返回 return GATT_NO_RESOURCES错误码。
-
发送命令:如果命令成功构建,则调用attp_cl_send_cmd函数将命令通过蓝牙连接发送给远程设备。
3.6.3.attp_cl_send_cmd(GATT_REQ_READ_BY_GRP_TYPE)
/packages/modules/Bluetooth/system/stack/gatt/att_protocol.cc
static tGATT_STATUS attp_cl_send_cmd(tGATT_TCB& tcb, tGATT_CLCB* p_clcb,
uint8_t cmd_code, BT_HDR* p_cmd) {
cmd_code &= ~GATT_AUTH_SIGN_MASK; //清除命令码中的认证签名位,确保只处理命令的基本部分
if (gatt_tcb_is_cid_busy(tcb, p_clcb->cid) &&
cmd_code != GATT_HANDLE_VALUE_CONF) {
if (gatt_cmd_enq(tcb, p_clcb, true, cmd_code, p_cmd)) {
log::debug("Enqueued ATT command {} conn_id=0x{:04x}, cid={}",
fmt::ptr(p_clcb), p_clcb->conn_id, p_clcb->cid);
return GATT_CMD_STARTED;
}
log::error("{}, cid 0x{:02x} already disconnected",
ADDRESS_TO_LOGGABLE_CSTR(tcb.peer_bda), p_clcb->cid);
return GATT_INTERNAL_ERROR;
}
log::debug(
"Sending ATT command to l2cap cid:0x{:04x} eatt_channels:{} transport:{}",
p_clcb->cid, tcb.eatt, bt_transport_text(tcb.transport));
tGATT_STATUS att_ret = attp_send_msg_to_l2cap(tcb, p_clcb->cid, p_cmd);
if (att_ret != GATT_CONGESTED && att_ret != GATT_SUCCESS) {
log::warn(
"Unable to send ATT command to l2cap layer {} conn_id=0x{:04x}, cid={}",
fmt::ptr(p_clcb), p_clcb->conn_id, p_clcb->cid);
return GATT_INTERNAL_ERROR;
}
if (cmd_code == GATT_HANDLE_VALUE_CONF || cmd_code == GATT_CMD_WRITE) {
return att_ret;//这些命令不需要额外的响应计时器或请求队列处理。
}
log::debug("Starting ATT response timer {} conn_id=0x{:04x}, cid={}",
fmt::ptr(p_clcb), p_clcb->conn_id, p_clcb->cid);
gatt_start_rsp_timer(p_clcb);//启动一个响应计时器以等待对端设备的响应
if (!gatt_cmd_enq(tcb, p_clcb, false, cmd_code, NULL)) {
log::error(
"Could not queue sent request. {}, cid 0x{:02x} already disconnected",
ADDRESS_TO_LOGGABLE_CSTR(tcb.peer_bda), p_clcb->cid);
return GATT_INTERNAL_ERROR;
}
return att_ret;
}
处理了命令的发送逻辑,包括命令的排队、发送以及后续处理。
-
清除认证签名位;
-
CID忙碌检查:如果当前CID忙碌且命令码不是GATT_HANDLE_VALUE_CONF(句柄值确认),则尝试将命令入队。如果入队成功,则返回GATT_CMD_STARTED;如果CID已经断开连接,则返回GATT_INTERNAL_ERROR。
-
发送命令到L2CAP:如果CID不忙碌或命令是GATT_HANDLE_VALUE_CONF,则调用attp_send_msg_to_l2cap将命令发送到L2CAP层。如果发送成功或仅因拥塞而未能发送(GATT_CONGESTED),则继续后续处理;否则,返回GATT_INTERNAL_ERROR。
-
启动响应计时器;
-
请求入队:尝试将已发送的请求(不包括命令内容,因为已经发送)入队。如果入队失败(通常意味着CID已经断开连接),返回GATT_INTERNAL_ERROR。
3.6.4.attp_send_msg_to_l2cap
/packages/modules/Bluetooth/system/stack/gatt/att_protocol.cc
tGATT_STATUS attp_send_msg_to_l2cap(tGATT_TCB& tcb, uint16_t lcid,
BT_HDR* p_toL2CAP) {
uint16_t l2cap_ret;
if (lcid == L2CAP_ATT_CID) { //ATT固定通道是一个特殊的L2CAP通道,用于ATT协议的数据传输。
log::debug("Sending ATT message on att fixed channel");
l2cap_ret = L2CA_SendFixedChnlData(lcid, tcb.peer_bda, p_toL2CAP);
} else {
log::debug("Sending ATT message on lcid:{}", lcid);
l2cap_ret = (uint16_t)L2CA_DataWrite(lcid, p_toL2CAP);
}
if (l2cap_ret == L2CAP_DW_FAILED) {
log::error("failed to write data to L2CAP");
return GATT_INTERNAL_ERROR;
} else if (l2cap_ret == L2CAP_DW_CONGESTED) {
log::verbose("ATT congested, message accepted");
return GATT_CONGESTED;
}
return GATT_SUCCESS;
}
attp_send_msg_to_l2cap负责将ATT(Attribute Protocol)消息发送到L2CAP(Logical Link Control and Adaptation Protocol)层。确保了ATT消息能够正确地发送到L2CAP层,进而通过蓝牙物理层发送到远端设备。
-
判断LCID类型;
-
发送数据:
-
如果lcid是L2CAP_ATT_CID,则使用L2CA_SendFixedChnlData函数将ATT消息发送到固定通道。
-
否则,使用L2CA_DataWrite函数将ATT消息发送到指定的LCID。
-
-
处理L2CAP返回值:
-
如果返回L2CAP_DW_FAILED,表示数据写入L2CAP层失败,返回GATT_INTERNAL_ERROR。
-
如果返回L2CAP_DW_CONGESTED,表示L2CAP层当前拥塞,但数据已被接受并排队等待发送。返回GATT_CONGESTED。
-
如果以上两种情况都不满足,即数据成功发送到L2CAP层,则返回GATT_SUCCESS。
-
3.6.5.L2CA_SendFixedChnlData
/packages/modules/Bluetooth/system/stack/l2cap/l2c_api.cc
uint16_t L2CA_SendFixedChnlData(uint16_t fixed_cid, const RawAddress& rem_bda,
BT_HDR* p_buf) {
tL2C_LCB* p_lcb;
tBT_TRANSPORT transport = BT_TRANSPORT_BR_EDR;
if (fixed_cid >= L2CAP_ATT_CID && fixed_cid <= L2CAP_SMP_CID)
transport = BT_TRANSPORT_LE;
if ((fixed_cid < L2CAP_FIRST_FIXED_CHNL) ||
(fixed_cid > L2CAP_LAST_FIXED_CHNL) ||
(l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb ==
NULL)) {
log::warn("No service registered or invalid CID: 0x{:04x}", fixed_cid);
osi_free(p_buf);
return (L2CAP_DW_FAILED);
}
if (!BTM_IsDeviceUp()) {
log::warn("Controller is not ready CID: 0x{:04x}", fixed_cid);
osi_free(p_buf);
return (L2CAP_DW_FAILED);
}
p_lcb = l2cu_find_lcb_by_bd_addr(rem_bda, transport);
if (p_lcb == NULL || p_lcb->link_state == LST_DISCONNECTING) {
/* if link is disconnecting, also report data sending failure */
log::warn("Link is disconnecting or does not exist CID: 0x{:04x}",
fixed_cid);
osi_free(p_buf);
return (L2CAP_DW_FAILED);
}
tL2C_BLE_FIXED_CHNLS_MASK peer_channel_mask;
// Select peer channels mask to use depending on transport
if (transport == BT_TRANSPORT_LE)
peer_channel_mask = l2cb.l2c_ble_fixed_chnls_mask;
else
peer_channel_mask = p_lcb->peer_chnl_mask[0];
if ((peer_channel_mask & (1 << fixed_cid)) == 0) {
log::warn("Peer does not support fixed channel CID: 0x{:04x}", fixed_cid);
osi_free(p_buf);
return (L2CAP_DW_FAILED);
}
p_buf->event = 0;
p_buf->layer_specific = L2CAP_FLUSHABLE_CH_BASED;
if (!p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]) {
if (!l2cu_initialize_fixed_ccb(p_lcb, fixed_cid)) {
log::warn("No channel control block found for CID: 0x{:4x}", fixed_cid);
osi_free(p_buf);
return (L2CAP_DW_FAILED);
}
}
if (p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]->cong_sent) {
log::warn(
"Unable to send data due to congestion CID: 0x{:04x} "
"xmit_hold_q.count: {} buff_quota: {}",
fixed_cid,
fixed_queue_length(
p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]
->xmit_hold_q),
p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]->buff_quota);
osi_free(p_buf);
return (L2CAP_DW_FAILED);
}
log::debug("Enqueued data for CID: 0x{:04x} len:{}", fixed_cid, p_buf->len);
l2c_enqueue_peer_data(p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL],
p_buf);
l2c_link_check_send_pkts(p_lcb, 0, NULL);
// If there is no dynamic CCB on the link, restart the idle timer each time
// something is sent
if (p_lcb->in_use && p_lcb->link_state == LST_CONNECTED &&
!p_lcb->ccb_queue.p_first_ccb) {
l2cu_no_dynamic_ccbs(p_lcb);
}
if (p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]->cong_sent) {
log::debug("Link congested for CID: 0x{:04x}", fixed_cid);
return (L2CAP_DW_CONGESTED);
}
return (L2CAP_DW_SUCCESS);
}
L2CA_SendFixedChnlData负责在蓝牙的L2CAP层上发送固定通道(Fixed Channel)的数据。
-
确定传输类型;
-
验证CID有效性;
-
检查控制器状态;
-
查找LCB;
-
检查通道支持:检查远端设备是否支持该固定通道。如果不支持,则释放p_buf并返回失败。
-
准备数据;
-
初始化或检查CCB:对于指定的固定通道,检查是否有对应的通道控制块(CCB)存在。如果不存在,则尝试初始化。如果初始化失败或CCB已标记为拥塞,则释放p_buf并返回失败。
-
入队数据:将p_buf加入到对应固定通道的发送队列中。
-
检查并发送数据:调用l2c_link_check_send_pkts函数检查并尝试发送队列中的数据。
-
重启空闲定时器:如果连接上没有动态CCB,并且连接状态为已连接,则在每次发送数据时重启空闲定时器。
接收到应答后,ble协议栈会调用gatt_client_handle_server_rsp。
3.7.gatt_client_handle_server_rsp
fixed_reg.pL2CA_FixedData_Cb = gatt_le_data_ind;
/packages/modules/Bluetooth/system/stack/gatt/gatt_cl.cc
/** This function is called to handle the server response to client */
void gatt_client_handle_server_rsp(tGATT_TCB& tcb, uint16_t cid,
uint8_t op_code, uint16_t len,
uint8_t* p_data) {
log::verbose("opcode: {} cid{}", loghex(op_code), cid);
uint16_t payload_size = gatt_tcb_get_payload_size(tcb, cid);
if (op_code == GATT_HANDLE_VALUE_IND || op_code == GATT_HANDLE_VALUE_NOTIF ||
op_code == GATT_HANDLE_MULTI_VALUE_NOTIF) {
if (len >= payload_size) {
log::error("invalid indicate pkt size: {}, PDU size: {}", len + 1,
payload_size);
return;
}
gatt_process_notification(tcb, cid, op_code, len, p_data);
return;
}
uint8_t cmd_code = 0;
tGATT_CLCB* p_clcb = gatt_cmd_dequeue(tcb, cid, &cmd_code);
if (!p_clcb) {
log::warn("ATT - clcb already not in use, ignoring response");
gatt_cl_send_next_cmd_inq(tcb);
return;
}
uint8_t rsp_code = gatt_cmd_to_rsp_code(cmd_code);
if (!p_clcb) {
log::warn("ATT - clcb already not in use, ignoring response");
gatt_cl_send_next_cmd_inq(tcb);
return;
}
if (rsp_code != op_code && op_code != GATT_RSP_ERROR) {
log::warn(
"ATT - Ignore wrong response. Receives ({:02x}) Request({:02x}) "
"Ignored",
op_code, rsp_code);
return;
}
gatt_stop_rsp_timer(p_clcb);
p_clcb->retry_count = 0;
/* the size of the message may not be bigger than the local max PDU size*/
/* The message has to be smaller than the agreed MTU, len does not count
* op_code */
if (len >= payload_size) {
log::error("invalid response pkt size: {}, PDU size: {}", len + 1,
payload_size);
gatt_end_operation(p_clcb, GATT_ERROR, NULL);
} else {
switch (op_code) {
...
case GATT_RSP_READ_BY_TYPE:
case GATT_RSP_READ_BY_GRP_TYPE: //0x11
gatt_process_read_by_type_rsp(tcb, p_clcb, op_code, len, p_data);
break;
...
default:
log::error("Unknown opcode = {:x}", op_code);
break;
}
}
gatt_cl_send_next_cmd_inq(tcb);
}
注意当中"Opcode: Read By Group Type Response (0x11)",对应gatt_client_handle_server_rsp中代码“op_code==GATT_RSP_READ_BY_GRP_TYPE”,于是执行gatt_process_read_by_type_rsp。
3.7.1. gatt_process_read_by_type_rsp
/packages/modules/Bluetooth/system/stack/gatt/gatt_cl.cc
void gatt_process_read_by_type_rsp(tGATT_TCB& tcb, tGATT_CLCB* p_clcb,
uint8_t op_code, uint16_t len,
uint8_t* p_data) {
tGATT_DISC_RES result;
tGATT_DISC_VALUE record_value;
uint8_t *p = p_data, value_len, handle_len = 2;
uint16_t handle = 0;
/* discovery procedure and no callback function registered */
if (((!p_clcb->p_reg) || (!p_clcb->p_reg->app_cb.p_disc_res_cb)) &&
(p_clcb->operation == GATTC_OPTYPE_DISCOVERY))
return;
if (len < GATT_READ_BY_TYPE_RSP_MIN_LEN) {
log::error("Illegal ReadByType/ReadByGroupType Response length, discard");
gatt_end_operation(p_clcb, GATT_INVALID_PDU, NULL);
return;
}
STREAM_TO_UINT8(value_len, p);
uint16_t payload_size = gatt_tcb_get_payload_size(tcb, p_clcb->cid);
if ((value_len > (payload_size - 2)) || (value_len > (len - 1))) {
/* this is an error case that server's response containing a value length
which is larger than MTU-2
or value_len > message total length -1 */
log::error(
"Discard response op_code={} vale_len={} > (MTU-2={} or msg_len-1={})",
op_code, value_len, (payload_size - 2), (len - 1));
gatt_end_operation(p_clcb, GATT_ERROR, NULL);
return;
}
if (op_code == GATT_RSP_READ_BY_GRP_TYPE) handle_len = 4;
value_len -= handle_len; /* subtract the handle pairs bytes */
len -= 1;
while (len >= (handle_len + value_len)) {
// 假设peripheral有三个service,那“DISCOVERY && SRVC_ALL”时,此while将循环三次。
// 每个rsp,前两个字节都有handle。对“DISCOVERY && SRVC_ALL”,它就是s_handle。
STREAM_TO_UINT16(handle, p);
if (!GATT_HANDLE_IS_VALID(handle)) {
gatt_end_operation(p_clcb, GATT_INVALID_HANDLE, NULL);
return;
}
memset(&result, 0, sizeof(tGATT_DISC_RES));
memset(&record_value, 0, sizeof(tGATT_DISC_VALUE));
result.handle = handle;
result.type =
bluetooth::Uuid::From16Bit(disc_type_to_uuid[p_clcb->op_subtype]);
/* discover all services */
if (p_clcb->operation == GATTC_OPTYPE_DISCOVERY &&
p_clcb->op_subtype == GATT_DISC_SRVC_ALL &&
op_code == GATT_RSP_READ_BY_GRP_TYPE) {
// 发现阶段。目的是获取该peripherl有哪些service,此处的两个字节是e_handle。
STREAM_TO_UINT16(handle, p);
if (!GATT_HANDLE_IS_VALID(handle)) {
gatt_end_operation(p_clcb, GATT_INVALID_HANDLE, NULL);
return;
} else {
// 读出的e_handle存储到record_value.group_value.e_handle。
// record_value.group_value.service_type则存储着此个service的uuid。
record_value.group_value.e_handle = handle;
if (!gatt_parse_uuid_from_cmd(&record_value.group_value.service_type,
value_len, &p)) {
log::error("discover all service response parsing failure");
break;
}
}
}
/* discover included service */
else if (p_clcb->operation == GATTC_OPTYPE_DISCOVERY &&
p_clcb->op_subtype == GATT_DISC_INC_SRVC) {
if (value_len < 4) {
log::error("Illegal Response length, must be at least 4.");
gatt_end_operation(p_clcb, GATT_INVALID_PDU, NULL);
return;
}
STREAM_TO_UINT16(record_value.incl_service.s_handle, p);
STREAM_TO_UINT16(record_value.incl_service.e_handle, p);
if (!GATT_HANDLE_IS_VALID(record_value.incl_service.s_handle) ||
!GATT_HANDLE_IS_VALID(record_value.incl_service.e_handle)) {
gatt_end_operation(p_clcb, GATT_INVALID_HANDLE, NULL);
return;
}
if (value_len == 6) {
uint16_t tmp;
STREAM_TO_UINT16(tmp, p);
record_value.incl_service.service_type =
bluetooth::Uuid::From16Bit(tmp);
} else if (value_len == 4) {
p_clcb->s_handle = record_value.incl_service.s_handle;
p_clcb->read_uuid128.wait_for_read_rsp = true;
p_clcb->read_uuid128.next_disc_start_hdl = handle + 1;
memcpy(&p_clcb->read_uuid128.result, &result, sizeof(result));
memcpy(&p_clcb->read_uuid128.result.value, &record_value,
sizeof(result.value));
p_clcb->op_subtype |= 0x90;
gatt_act_read(p_clcb, 0);
return;
} else {
log::error("INCL_SRVC failed with invalid data value_len={}",
value_len);
gatt_end_operation(p_clcb, GATT_INVALID_PDU, (void*)p);
return;
}
}
/* read by type */
else if (p_clcb->operation == GATTC_OPTYPE_READ &&
p_clcb->op_subtype == GATT_READ_BY_TYPE) {
p_clcb->counter = len - 2;
p_clcb->s_handle = handle;
if (p_clcb->counter == (payload_size - 4)) {
p_clcb->op_subtype = GATT_READ_BY_HANDLE;
if (!p_clcb->p_attr_buf)
p_clcb->p_attr_buf = (uint8_t*)osi_malloc(GATT_MAX_ATTR_LEN);
if (p_clcb->counter <= GATT_MAX_ATTR_LEN) {
memcpy(p_clcb->p_attr_buf, p, p_clcb->counter);
gatt_act_read(p_clcb, p_clcb->counter);
} else {
gatt_end_operation(p_clcb, GATT_INTERNAL_ERROR, (void*)p);
}
} else {
gatt_end_operation(p_clcb, GATT_SUCCESS, (void*)p);
}
return;
} else /* discover characteristic */
{ // 单一service发现流程、从该service读取所含的characterisitic。
// p_clcb->operation == GATTC_OPTYPE_DISCOVERY && p_clcb->op_subtype == GATT_READ_CHAR_VALUE
if (value_len < 3) {
log::error("Illegal Response length, must be at least 3.");
gatt_end_operation(p_clcb, GATT_INVALID_PDU, NULL);
return;
}
STREAM_TO_UINT8(record_value.dclr_value.char_prop, p);
STREAM_TO_UINT16(record_value.dclr_value.val_handle, p);
if (!GATT_HANDLE_IS_VALID(record_value.dclr_value.val_handle)) {
gatt_end_operation(p_clcb, GATT_INVALID_HANDLE, NULL);
return;
}
if (!gatt_parse_uuid_from_cmd(&record_value.dclr_value.char_uuid,
(uint16_t)(value_len - 3), &p)) {
gatt_end_operation(p_clcb, GATT_SUCCESS, NULL);
/* invalid format, and skip the result */
return;
}
/* UUID not matching */
if (!p_clcb->uuid.IsEmpty() &&
!record_value.dclr_value.char_uuid.IsEmpty() &&
record_value.dclr_value.char_uuid != p_clcb->uuid) {
len -= (value_len + 2);
continue; /* skip the result, and look for next one */
}
if (p_clcb->operation == GATTC_OPTYPE_READ)
/* UUID match for read characteristic value */
{
/* only read the first matching UUID characteristic value, and
discard the rest results */
p_clcb->s_handle = record_value.dclr_value.val_handle;
p_clcb->op_subtype |= 0x80;
gatt_act_read(p_clcb, 0);
return;
}
}
len -= (value_len + handle_len);
/* result is (handle, 16bits UUID) pairs */
memcpy(&result.value, &record_value, sizeof(result.value));
/* send callback if is discover procedure */
if (p_clcb->operation == GATTC_OPTYPE_DISCOVERY &&
p_clcb->p_reg->app_cb.p_disc_res_cb)
// 把此得发现到的部分内容汇集到gatt db。
// p_clcb->p_reg->app_cb.p_disc_res_cb这个函数指针指向bta_gattc_cache.c中实现的bta_gattc_disc_res_cback
(*p_clcb->p_reg->app_cb.p_disc_res_cb)(
p_clcb->conn_id, static_cast<tGATT_DISC_TYPE>(p_clcb->op_subtype),
&result);
}
// “GATTC_OPTYPE_DISCOVERY && GATT_DISC_SRVC_ALL”时,handle是最后一个service时的e_handle,
// 它的值总是65535,加1后65536,由于s_handle是16位,65536会被归到0。
p_clcb->s_handle = (handle == 0) ? 0 : (handle + 1);
if (p_clcb->operation == GATTC_OPTYPE_DISCOVERY) {
/* initiate another request */
gatt_act_discovery(p_clcb); // 继续进行发现,直到err response
} else /* read characteristic value */
{
gatt_act_read(p_clcb, 0);
}
}
在发现阶段(p_clcb->operation == GATTC_OPTYPE_DISCOVERY),gatt_process_read_by_type_rsp可归纳为依次执行两个任务。
-
一个用于解析收到RSP内容的while循环。每解析出一部分,就回调p_clcb->p_reg->app_cb.p_disc_res_cb,把这部分数据汇集到GATT数据库。
-
调用gatt_act_discovery(p_clcb)。触发后绪发现过程。
先说gatt_process_read_by_type_rsp第一个任务。每解析出一部分,像图中的第一条“Attribute Data, Handle: 0x0001, ...”,就会调用p_disc_res_cb指向的回调函数,这个函数指针指向bta_gattc_cache.c中实现的bta_gattc_disc_res_cback。
a.bta_gattc_disc_res_cback
/packages/modules/Bluetooth/system/bta/gatt/bta_gattc_cache.cc
/** callback function to GATT client stack */
void bta_gattc_disc_res_cback(uint16_t conn_id, tGATT_DISC_TYPE disc_type,
tGATT_DISC_RES* p_data) {
tBTA_GATTC_CLCB* p_clcb = bta_gattc_find_clcb_by_conn_id(conn_id);
tBTA_GATTC_SERV* p_srvc_cb = bta_gattc_find_scb_by_cid(conn_id);
if (!p_srvc_cb || !p_clcb || p_clcb->state != BTA_GATTC_DISCOVER_ST) return;
switch (disc_type) {
case GATT_DISC_SRVC_ALL:
case GATT_DISC_SRVC_BY_UUID:
//p_srvc_cb->pending_discovery类型是gatt::DatabaseBuilder,就是此次发现要生成的最终gatt数据库。AddService方法是向该数据库添加一个service,换句话说,把此次发现到的部分数据补充到gatt数据库。注意:只是补充到p_srvc_cb->pending_discovery这个变量,不会写到cache文件,要读出整个gatt数据库后才写入。
p_srvc_cb->pending_discovery.AddService(
p_data->handle, p_data->value.group_value.e_handle,
p_data->value.group_value.service_type, true);
break;
...
case GATT_DISC_MAX:
default:
log::error("Received illegal discovery item");
break;
}
}
根据发现结果的类型(如服务、包含的服务、特征或描述符)来更新内部的服务缓存。
分析补充gatt数据库,再看gatt_process_read_by_type_rsp第二个任务,执行gatt_act_discovery(p_clcb)。上面已贴过这函数,并且说了功能是调用attp_send_cl_msg,把参数p_clcb指定的要发现什么内容,通过L2cap的ATT通道发向peripheral。这里有个问题,此时p_clcb存储的还是发现所有servier这个任务(disc_type==GATT_DISC_SRVC_ALL),这个发现任务已经成功执行,难道还要再次执行?——当然不会,这就要看gatt_act_discovery一个“隐藏”功能,当“p_clcb->s_handle == 0”时,执行的是调用gatt_end_operation。
b. gatt_act_discovery
在gatt_process_read_by_type_rsp,“GATTC_OPTYPE_DISCOVERY && GATT_DISC_SRVC_ALL”时,handle是最后一个service时的e_handle,它的值总是65535,加1后65536,由于s_handle是16位,65536会被归到0。于是满足了“p_clcb->s_handle == 0”条件,执行的是调用gatt_end_operation。
3.7.2. gatt_end_operation
/packages/modules/Bluetooth/system/stack/gatt/gatt_utils.cc
void gatt_end_operation(tGATT_CLCB* p_clcb, tGATT_STATUS status, void* p_data) {
tGATT_CL_COMPLETE cb_data;
tGATT_CMPL_CBACK* p_cmpl_cb =
(p_clcb->p_reg) ? p_clcb->p_reg->app_cb.p_cmpl_cb : NULL;
tGATTC_OPTYPE op = p_clcb->operation;
tGATT_DISC_TYPE disc_type = GATT_DISC_MAX;
tGATT_DISC_CMPL_CB* p_disc_cmpl_cb =
(p_clcb->p_reg) ? p_clcb->p_reg->app_cb.p_disc_cmpl_cb : NULL;
uint16_t conn_id;
uint8_t operation;
log::verbose("status={} op={} subtype={}", status, p_clcb->operation,
p_clcb->op_subtype);
memset(&cb_data.att_value, 0, sizeof(tGATT_VALUE));
if (p_cmpl_cb != NULL && p_clcb->operation != 0) {
if (p_clcb->operation == GATTC_OPTYPE_READ) {
cb_data.att_value.handle = p_clcb->s_handle;
cb_data.att_value.len = p_clcb->counter;
if (cb_data.att_value.len > GATT_MAX_ATTR_LEN) {
log::warn("Large cb_data.att_value, size={}", cb_data.att_value.len);
cb_data.att_value.len = GATT_MAX_ATTR_LEN;
}
if (p_data && p_clcb->counter)
memcpy(cb_data.att_value.value, p_data, cb_data.att_value.len);
}
if (p_clcb->operation == GATTC_OPTYPE_WRITE) {
memset(&cb_data.att_value, 0, sizeof(tGATT_VALUE));
cb_data.handle = cb_data.att_value.handle = p_clcb->s_handle;
if (p_clcb->op_subtype == GATT_WRITE_PREPARE) {
if (p_data) {
cb_data.att_value = *((tGATT_VALUE*)p_data);
} else {
log::verbose("Rcv Prepare write rsp but no data");
}
}
}
if (p_clcb->operation == GATTC_OPTYPE_CONFIG)
cb_data.mtu = p_clcb->p_tcb->payload_size;
if (p_clcb->operation == GATTC_OPTYPE_DISCOVERY) {
disc_type = static_cast<tGATT_DISC_TYPE>(p_clcb->op_subtype);
}
}
osi_free_and_reset((void**)&p_clcb->p_attr_buf);
operation = p_clcb->operation;
conn_id = p_clcb->conn_id;
gatt_stop_rsp_timer(p_clcb);
gatt_clcb_dealloc(p_clcb);
if (p_disc_cmpl_cb && (op == GATTC_OPTYPE_DISCOVERY))
(*p_disc_cmpl_cb)(conn_id, disc_type, status);
else if (p_cmpl_cb && op)
(*p_cmpl_cb)(conn_id, op, status, &cb_data);
else
log::warn("not sent out op={} p_disc_cmpl_cb:{} p_cmpl_cb:{}", operation,
fmt::ptr(p_disc_cmpl_cb), fmt::ptr(p_cmpl_cb));
}
gatt_end_operation用于结束一次发现,并启动下一次发现。在发现阶段(p == GATTC_OPTYPE_DISCOVERY),它会调用p_clcb->p_reg->app_cb.p_disc_cmpl_cb,这个函数指针指向bta_gattc_disc_cmpl_cback。
正是在这函数,会调用GATTC_Discover,从而改变p_clcb的operation、op_subtype、s_handle、e_handle、uuid,并把发现请求发现peripheral。
3.7.3. bta_gattc_disc_cmpl_cback(GATT_DISC_SRVC_ALL)
/packages/modules/Bluetooth/system/bta/gatt/bta_gattc_cache.cc
void bta_gattc_disc_cmpl_cback(uint16_t conn_id, tGATT_DISC_TYPE disc_type,
tGATT_STATUS status) {
tBTA_GATTC_CLCB* p_clcb = bta_gattc_find_clcb_by_conn_id(conn_id);
tBTA_GATTC_SERV* p_srvc_cb = bta_gattc_find_scb_by_cid(conn_id);
if (p_clcb && (status != GATT_SUCCESS || p_clcb->status != GATT_SUCCESS)) {
if (status == GATT_SUCCESS) p_clcb->status = status;
// if db out of sync is received, try to start service discovery if possible
if (bta_gattc_is_robust_caching_enabled() &&
status == GATT_DATABASE_OUT_OF_SYNC) {
if (p_srvc_cb &&
p_srvc_cb->srvc_disc_count < BTA_GATTC_DISCOVER_RETRY_COUNT) {
p_srvc_cb->srvc_disc_count++;
p_clcb->auto_update = BTA_GATTC_DISC_WAITING;
} else {
log::error("retry limit exceeds for db out of sync, conn_id={}",
conn_id);
}
}
bta_gattc_sm_execute(p_clcb, BTA_GATTC_DISCOVER_CMPL_EVT, NULL);
return;
}
if (!p_srvc_cb) return;
switch (disc_type) {
case GATT_DISC_SRVC_ALL:
case GATT_DISC_SRVC_BY_UUID:
// definition of all services are discovered, now it's time to discover
// their content
#if (BTA_GATT_DEBUG == TRUE)
bta_gattc_display_explore_record(p_srvc_cb->pending_discovery);
#endif
// 已搜索出pri services,接下搜索第一个service的incldue service
bta_gattc_explore_next_service(conn_id, p_srvc_cb);
break;
case GATT_DISC_INC_SRVC: {
auto& service = p_srvc_cb->pending_discovery.CurrentlyExploredService();
/* start discovering characteristic */
// 已搜索出该service的include service,接着搜索该service的chara
GATTC_Discover(conn_id, GATT_DISC_CHAR, service.first, service.second);
break;
}
case GATT_DISC_CHAR: {
#if (BTA_GATT_DEBUG == TRUE)
bta_gattc_display_explore_record(p_srvc_cb->pending_discovery);
#endif
// 已搜索出该service的chara,接下搜索第一个chara的characteristic descriptor,
// 如果搜索失败,那么搜索下一个char dsp
bta_gattc_start_disc_char_dscp(conn_id, p_srvc_cb);
break;
}
case GATT_DISC_CHAR_DSCPT:
/* start discovering next characteristic for char descriptor */
// 已搜索出该chara的descriptor,接着搜索下一个chara的char descriptor,
// 如果全部搜索完成,那么搜索下一个服务
bta_gattc_start_disc_char_dscp(conn_id, p_srvc_cb);
break;
case GATT_DISC_MAX:
default:
log::error("Received illegal discovery item");
break;
}
}
“DISCOVERY && SRVC_ALL”时,首先执行bta_gattc_display_explore_record,这是一个调试辅助函数,logcat出的service,以下是一个示例
I/bt_stack: <================Start Explore Queue =============> I/bt_stack: Service: handle=0x0001, end_handle=0x0005, uuid=00001801-0000-1000-8000-00805f9b34fb I/bt_stack: Service: handle=0x0014, end_handle=0x001c, uuid=00001800-0000-1000-8000-00805f9b34fb I/bt_stack: Service: handle=0x0028, end_handle=0xffff, uuid=00005356-0000-1000-8000-00805f9b34fb I/bt_stack: <================ End Explore Queue =============>
logcat后,执行bta_gattc_explore_next_service:start exploring next service, or finish discovery if no more services left。留意下它最后会调用bta_gattc_explore_srvc_finished,当已发现整个gatt数据库后,便会执行到它。
2.7.4.bta_gattc_explore_next_service
/packages/modules/Bluetooth/system/bta/gatt/bta_gattc_cache.cc
/** start exploring next service, or finish discovery if no more services left
*/
static void bta_gattc_explore_next_service(uint16_t conn_id,
tBTA_GATTC_SERV* p_srvc_cb) {
tBTA_GATTC_CLCB* p_clcb = bta_gattc_find_clcb_by_conn_id(conn_id);
if (!p_clcb) {
log::error("unknown conn_id={}", loghex(conn_id));
return;
}
//判断GATT数据库内是否还有未完成发现的service
if (p_srvc_cb->pending_discovery.StartNextServiceExploration()) {
const auto& service =
p_srvc_cb->pending_discovery.CurrentlyExploredService(); //获取当前正在探索的服务信息
log::verbose("Start service discovery");
/* start discovering included services */
//使用 GATTC_Discover函数发起对包含服务的发现(GATT_DISC_INC_SRVC),指定服务的起始和结束句柄。
GATTC_Discover(conn_id, GATT_DISC_INC_SRVC, service.first, service.second);
return;
}
// No more services to discover
// As part of service discovery, read the values of "Characteristic Extended
// Properties" descriptor
const auto& descriptors =
p_srvc_cb->pending_discovery.DescriptorHandlesToRead(); //检查是否有特征扩展属性描述符的句柄需要读取
if (!descriptors.empty()) {
// set request field to READ_EXT_PROP_DESC
p_clcb->request_during_discovery =
BTA_GATTC_DISCOVER_REQ_READ_EXT_PROP_DESC;
//如果存在需要读取的句柄,并且设备不支持同时读取多个句柄(p_srvc_cb->read_multiple_not_supported 为 true)或只有一个句柄需要读取,则通过 GATTC_Read函数以单个句柄的方式读取描述符的值。
if (p_srvc_cb->read_multiple_not_supported || descriptors.size() == 1) {
tGATT_READ_PARAM read_param{.by_handle = {.auth_req = GATT_AUTH_REQ_NONE,
.handle = descriptors.front()}};
GATTC_Read(conn_id, GATT_READ_BY_HANDLE, &read_param);
// asynchronous continuation in bta_gattc_op_cmpl_during_discovery
return;
}
// TODO(jpawlowski): as a limit we should use MTU/2 rather than
// GATT_MAX_READ_MULTI_HANDLES
/* each descriptor contains just 2 bytes, so response size is same as
* request size */
//如果设备支持同时读取多个句柄且存在多个句柄需要读取,则计算需要读取的句柄数量(不超过 10),并使用 GATTC_Read 函数以多个句柄的方式读取描述符的值。
size_t num_handles =
std::min(descriptors.size(), (size_t)GATT_MAX_READ_MULTI_HANDLES);
tGATT_READ_PARAM read_param;
memset(&read_param, 0, sizeof(tGATT_READ_PARAM));
read_param.read_multiple.num_handles = num_handles;
read_param.read_multiple.auth_req = GATT_AUTH_REQ_NONE;
memcpy(&read_param.read_multiple.handles, descriptors.data(),
sizeof(uint16_t) * num_handles);
GATTC_Read(conn_id, GATT_READ_MULTIPLE, &read_param);
// asynchronous continuation in bta_gattc_op_cmpl_during_discovery
return;
}
bta_gattc_explore_srvc_finished(conn_id, p_srvc_cb); //标记服务发现过程已完成
}
负责在 Bluetooth GATTC的服务发现过程中,根据当前的状态(是否还有服务需要探索、是否有描述符需要读取)来执行相应的操作,并在完成所有发现任务后结束服务发现过程。这个过程中,通过与Bluetooth 栈的交互(使用 GATTC_Discover和 GATTC_Read函数)来实际执行发现和读取操作。
-
探索下一个服务;
-
读取特征扩展属性描述符的值;
-
异步完成处理:无论是单个句柄读取还是多个句柄读取,操作都是异步的。当读取操作完成时,会触发 bta_gattc_op_cmpl_during_discovery函数的调用,以处理读取结果。
-
完成服务发现。
首先调用pending_discovery.StartNextServiceExploration(),判断gatt数据库内是否还有未完成发现的service,有未完成的就返回true,否则false。
a.DatabaseBuilder::StartNextServiceExploration
/packages/modules/Bluetooth/system/bta/gatt/database_builder.cc
bool DatabaseBuilder::StartNextServiceExploration() {
while (!services_to_discover.empty()) { //循环遍历服务列表
auto handle_range = services_to_discover.begin();
pending_service = *handle_range;
services_to_discover.erase(handle_range); //取出并移除服务范围
// Empty service declaration, nothing to explore, skip to next.
//检查pending_service的起始句柄和结束句柄是否相同。如果相同,表示这是一个空的服务声明(即没有实际的服务数据或范围),因此函数会跳过这个服务,继续检查下一个服务(通过循环的下一次迭代)。
if (pending_service.first == pending_service.second) continue;
pending_characteristic = HANDLE_MIN;
return true;
}
return false;
}
该函数的目的是从待发现的服务列表中(services_to_discover)取出下一个服务范围(由起始句柄和结束句柄定义),准备进行探索。
-
循环遍历服务列表;
-
取出并移除服务范围;
-
检查服务范围的有效性;
-
准备探索下一个特征。
判断逻辑主要用了个变量:std::set<std::pair<uint16_t, uint16_t>> services_to_discover;
之前的bta_gattc_disc_res_cback中AddService,会执行services_to_discover.insert({handle, end_handle}),handle是s_handle。
StartNextServiceExploration从services_to_discover取出service,这是一个等待进一步发现的service。把它赋给pending_server,并把该service从services_to_discover删除。
已发现完整个pending_server,继续调用这里的StartNextServiceExploration,让第二个service成为pending_server。
持续工作,当示例的3个service都发现结束后,那时services_to_discover已经空了,StartNextServiceExploration返回false,表示已发现完所有service。
bta_gattc_explore_next_service调用 p_srvc_cb->pending_discovery.StartNextServiceExploration返回的就是那个从services_to_discover移出、等待进一步发现的pending_server。对应到此时示例,那就是(handle=0x0001, end_handle=0x0009)的service。
得到待发现的pending_server后,这将开始新一轮单一service发现流程,这个流程首先是搜索include服务。分析到这里,贴上完整的gatt发现流程。
搜索出所有pri service,然后对每个service依次进行单一service发现流程。(一对请求应答)
单一Service发现流程。先搜索include service;然后搜出所有UUID: Characteristic;对每个char,搜Descriptor。
GATTC_Discover(conn_id, GATT_DISC_INC_SRVC, ...),这将开始新一轮单一service发现流程中的第一步:搜索include service。上面已分析过GATTC_Discover,于于收到GATT_DISC_INC_SRVC对应应答后怎么处理,后绪的发现chara,chara内的descriptor,如法炮制,这里不再一一分析了。
搜完最后一个service的最后一个descriptor后会发生什么?——还是会调用bta_gattc_explore_next_service,此时StartNextServiceExploration返回false,后面pending_discovery.DescriptorHandlesToRead()返回空,会执行bta_gattc_explore_srvc_finished。
2.7.5.bta_gattc_explore_srvc_finished
static void bta_gattc_explore_srvc_finished(uint16_t conn_id,
tBTA_GATTC_SERV* p_srvc_cb) {
tBTA_GATTC_CLCB* p_clcb = bta_gattc_find_clcb_by_conn_id(conn_id);
if (!p_clcb) {
log::error("unknown conn_id={}", loghex(conn_id));
return;
}
/* no service found at all, the end of server discovery*/
log::info("service discovery finished");
p_srvc_cb->gatt_database = p_srvc_cb->pending_discovery.Build(); //构建并更新GATT数据库
#if (BTA_GATT_DEBUG == TRUE)
bta_gattc_display_cache_server(p_srvc_cb->gatt_database);
#endif
/* save cache to NV */
p_clcb->p_srcb->state = BTA_GATTC_SERV_SAVE; //表示接下来需要保存服务数据
// If robust caching is not enabled, use original design
if (!bta_gattc_is_robust_caching_enabled()) {
if (btm_sec_is_a_bonded_dev(p_srvc_cb->server_bda)) {
bta_gattc_cache_write(p_clcb->p_srcb->server_bda,
p_clcb->p_srcb->gatt_database); //将GATT数据库写入缓存
}
} else { //启用健壮缓存处理GATT数据库
// If robust caching is enabled, do something optimized
Octet16 hash = p_clcb->p_srcb->gatt_database.Hash();
bool success = bta_gattc_hash_write(hash, p_clcb->p_srcb->gatt_database); //将哈希值和数据库写入缓存
// If the device is trusted, link the addr file to hash file
if (success && btm_sec_is_a_bonded_dev(p_srvc_cb->server_bda)) {
log::debug("Linking db hash to address {}",
p_clcb->p_srcb->server_bda.ToRedactedStringForLogging());
bta_gattc_cache_link(p_clcb->p_srcb->server_bda, hash); //将设备地址与哈希值链接起来,以便于快速检索
}
// After success, reset the count.
log::debug(
"service discovery succeed, reset count to zero, conn_id=0x{:04x}",
conn_id);
p_srvc_cb->srvc_disc_count = 0;
}
bta_gattc_reset_discover_st(p_clcb->p_srcb, GATT_SUCCESS); //重置发现状态,表示服务发现过程成功完成。
}
蓝牙GATTC服务发现完成后的处理函数主要负责处理服务发现过程结束后的逻辑,包括更新服务缓存、根据是否启用健壮缓存(robust caching)来优化存储过程,以及重置相关状态。
对connectGatt导致的bta_gattc_disc_cmpl,参数p_srvc_cb和p_clcb->p_srcb是一样的,指向同一个tBTA_GATTC_SERV。bta_gattc_explore_srvc_finished依次执行三个任务。
-
p_srvc_cb->gatt_database = p_srvc_cb->pending_discovery.Build()。pending_discovery存储着发现过程中生成的gatt数据库,调用Build()生成要用于后绪操作的gatt_database,Database。在Build(),同时会清空pending_discovery。
-
判断该periperhal是否配对过,如果是,调用bta_gattc_cache_write存储cache到文件。对通常的ios、android连接,是没有配对过的。cache文件名示例:/data/misc/bluetooth/gatt_cache_XXX,文件名中的XXX是mac地址。
-
调用bta_gattc_reset_discover_st,执行状态机,状态最终变换BTA_GATTC_CONN_ST。
前两个较简单,让看下第三步bta_gattc_reset_discover_st。
2.7.5.1. bta_gattc_reset_discover_st
/packages/modules/Bluetooth/system/bta/gatt/bta_gattc_act.cc
/** when a SRCB finished discovery, tell all related clcb */
void bta_gattc_reset_discover_st(tBTA_GATTC_SERV* p_srcb, tGATT_STATUS status) {
for (uint8_t i = 0; i < BTA_GATTC_CLCB_MAX; i++) {
if (bta_gattc_cb.clcb[i].p_srcb == p_srcb) {
bta_gattc_cb.clcb[i].status = status;
bta_gattc_sm_execute(&bta_gattc_cb.clcb[i], BTA_GATTC_DISCOVER_CMPL_EVT,
NULL);
}
}
}
2.7.5.2. bta_gattc_sm_execute(BTA_GATTC_DISCOVER_CMPL_EVT)
/packages/modules/Bluetooth/system/bta/gatt/bta_gattc_main.cc
bool bta_gattc_sm_execute(tBTA_GATTC_CLCB* p_clcb, uint16_t event,
const tBTA_GATTC_DATA* p_data) {
tBTA_GATTC_ST_TBL state_table;
uint8_t action;
int i;
bool rt = true;
tBTA_GATTC_STATE in_state = p_clcb->state;
uint16_t in_event = event;
#if (BTA_GATT_DEBUG == TRUE)
log::verbose("State 0x{:02x} [{}], Event 0x{:x}[{}]", in_state,
gattc_state_code(in_state), in_event, gattc_evt_code(in_event));
#else
log::verbose("State 0x{:02x}, Event 0x{:x}", in_state, in_event);
#endif
/* look up the state table for the current state */
state_table = bta_gattc_st_tbl[p_clcb->state];
event &= 0x00FF;
/* set next state */
p_clcb->state = state_table[event][BTA_GATTC_NEXT_STATE];
/* execute action functions */
for (i = 0; i < BTA_GATTC_ACTIONS; i++) {
action = state_table[event][i];
if (action != BTA_GATTC_IGNORE) {
(*bta_gattc_action[action])(p_clcb, p_data);
if (bta_gattc_is_data_queued(p_clcb, p_data)) {
/* buffer is queued, don't free in the bta dispatcher.
* we free it ourselves when a completion event is received.
*/
rt = false;
}
} else {
break;
}
}
#if (BTA_GATT_DEBUG == TRUE)
if (in_state != p_clcb->state) {
log::verbose("GATTC State Change: [{}] -> [{}] after Event [{}]",
gattc_state_code(in_state), gattc_state_code(p_clcb->state),
gattc_evt_code(in_event));
}
#else
log::verbose("GATTC State Change: 0x{:02x} -> 0x{:02x} after Event 0x{:x}",
in_state, p_clcb->state, in_event);
#endif
return rt;
}
针对此刻的状态转换,bta的state是st_discovery,意味着状态表是bta_gattc_st_discover,收到BTA_GATTC_DISCOVER_CMPL_EVT事件,查表可知action是BTA_GATTC_DISC_CMPL,下一个状态是BTA_GATTC_CONN_ST。action是BTA_GATTC_OPEN时执行的函数是bta_gattc_disc_cmpl。
2.7.5.3.bta_gattc_disc_cmpl
/packages/modules/Bluetooth/system/bta/gatt/bta_gattc_act.cc
/** discovery on server is finished */
void bta_gattc_disc_cmpl(tBTA_GATTC_CLCB* p_clcb,
UNUSED_ATTR const tBTA_GATTC_DATA* p_data) {
const tBTA_GATTC_DATA* p_q_cmd = p_clcb->p_q_cmd;
log::verbose("conn_id={}", loghex(p_clcb->bta_conn_id));
if (p_clcb->transport == BT_TRANSPORT_LE) {
L2CA_LockBleConnParamsForServiceDiscovery(p_clcb->p_srcb->server_bda,
false); //解锁之前可能因服务发现而锁定的连接参数
}
p_clcb->p_srcb->state = BTA_GATTC_SERV_IDLE; //表示服务发现已经完成且当前处于空闲状态
p_clcb->disc_active = false; //表示当前没有活动的服务发现操作
if (p_clcb->status != GATT_SUCCESS) {
/* clean up cache */
if (p_clcb->p_srcb) {
p_clcb->p_srcb->gatt_database.Clear();
}
/* used to reset cache in application */
bta_gattc_cache_reset(p_clcb->p_srcb->server_bda);
}
if (p_clcb->p_srcb) {
p_clcb->p_srcb->pending_discovery.Clear(); //清理待处理的服务发现请求
}
if (p_clcb->auto_update == BTA_GATTC_DISC_WAITING) { //表示有自动更新的需求
/* start discovery again */
p_clcb->auto_update = BTA_GATTC_REQ_WAITING;
bta_gattc_sm_execute(p_clcb, BTA_GATTC_INT_DISCOVER_EVT, NULL); //启动新的服务发现过程
}
/* get any queued command to proceed */
else if (p_q_cmd != NULL) {
// p_q_cmd往往是NULL。如果不是NULL,极可能是p_q_cmd->hdr.event==BTA_GATTC_API_SEARCH_EVT,
// 意味着app调用了BluetoothGatt.discoverServices
p_clcb->p_q_cmd = NULL;
/* execute pending operation of link block still present */
if (L2CA_IsLinkEstablished(p_clcb->p_srcb->server_bda, p_clcb->transport)) {
bta_gattc_sm_execute(p_clcb, p_q_cmd->hdr.event, p_q_cmd);
}
/* if the command executed requeued the cmd, we don't
* want to free the underlying buffer that's being
* referenced by p_clcb->p_q_cmd
*/
if (!bta_gattc_is_data_queued(p_clcb, p_q_cmd)) {
osi_free_and_reset((void**)&p_q_cmd);
}
} else {
bta_gattc_continue(p_clcb); //继续处理其他可能的操作(如处理其他排队的命令或执行其他GATTC操作)
}
if (p_clcb->p_rcb->p_cback) {
tBTA_GATTC bta_gattc;
bta_gattc.remote_bda = p_clcb->p_srcb->server_bda;
(*p_clcb->p_rcb->p_cback)(BTA_GATTC_SRVC_DISC_DONE_EVT, &bta_gattc); //通知上层应用服务发现已完成
}
}
bta_gattc_disc_cmpl是处理GATTC在GATTS上完成服务发现的回调函数。主要负责处理服务发现完成后的各种逻辑,包括状态更新、缓存清理、自动更新服务的处理、以及通知上层应用服务发现的结果。
p_q_cmd往往是NULL,bta_gattc_start_discover就结束了。p_q_cmd什么时候不是NULL?——discover需要花点时间,它是连接(btif_gattc_open)的一部分,而在结束discover之前,app极可能已收到newState是 BluetoothProfile.STATE_CONNECTED的onConnectionStateChange,并调用了BluetoothGatt操作,像discoverServices()。一旦discover没完成就调用discoverServices(),执行bta_gattc_disc_cmpl时,p_q_cmd便就存储着discoverServices()。此时p_q_cmd->hdr.event==BTA_GATTC_API_SEARCH_EVT,这时要调用bta_gattc_sm_execute,启动收到BTA_GATTC_API_SEARCH_EVT事件时状态机。
2.7.6. bta_gattc_cback(BTA_GATTC_SRVC_DISC_DONE_EVT)
/packages/modules/Bluetooth/system/btif/src/btif_gatt_client.cc
static void bta_gattc_cback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) {
log::debug("gatt client callback event:{} [{}]",
gatt_client_event_text(event), event);
bt_status_t status =
btif_transfer_context(btif_gattc_upstreams_evt, (uint16_t)event,
(char*)p_data, sizeof(tBTA_GATTC), NULL);
ASSERTC(status == BT_STATUS_SUCCESS, "Context transfer failed!", status);
}
2.7.6.btif_gattc_upstreams_evt(BTA_GATTC_SRVC_DISC_DONE_EVT)
/packages/modules/Bluetooth/system/btif/src/btif_gatt_client.cc
static void btif_gattc_upstreams_evt(uint16_t event, char* p_param) {
log::debug("Event {} [{}]",
gatt_client_event_text(static_cast<tBTA_GATTC_EVT>(event)), event);
tBTA_GATTC* p_data = (tBTA_GATTC*)p_param;
switch (event) {
...
case BTA_GATTC_ACL_EVT:
case BTA_GATTC_DEREG_EVT:
case BTA_GATTC_SEARCH_RES_EVT:
case BTA_GATTC_CANCEL_OPEN_EVT:
case BTA_GATTC_SRVC_DISC_DONE_EVT:
log::debug("Ignoring event ({})", event);
break;
...
default:
log::error("Unhandled event ({})!", event);
break;
}
}
BTA_GATTC_SRVC_DISC_DONE_EVT事件在当前上下文中不需要特别处理。
标签:clcb,handle,GATT,gattc,discover,gatt,源码,bta From: https://blog.csdn.net/weixin_37800531/article/details/141726554