|
|
@@ -33,6 +33,42 @@
|
|
|
#define GATT_MTU_REQ_MIN_LEN 2
|
|
|
|
|
|
|
|
|
+
|
|
|
+/*******************************************************************************
|
|
|
+**
|
|
|
+** Function gatt_send_packet
|
|
|
+**
|
|
|
+** Description This function is called to send gatt packets directly
|
|
|
+
|
|
|
+**
|
|
|
+** Returns status
|
|
|
+**
|
|
|
+*******************************************************************************/
|
|
|
+tGATT_STATUS gatt_send_packet (tGATT_TCB *p_tcb, UINT8 *p_data, UINT16 len)
|
|
|
+{
|
|
|
+ BT_HDR *p_msg = NULL;
|
|
|
+ UINT8 *p_m = NULL;
|
|
|
+ UINT16 buf_len;
|
|
|
+ tGATT_STATUS status;
|
|
|
+
|
|
|
+ if (len > p_tcb->payload_size){
|
|
|
+ return GATT_ILLEGAL_PARAMETER;
|
|
|
+ }
|
|
|
+
|
|
|
+ buf_len = (UINT16)(sizeof(BT_HDR) + p_tcb->payload_size + L2CAP_MIN_OFFSET);
|
|
|
+ if ((p_msg = (BT_HDR *)GKI_getbuf(buf_len)) == NULL) {
|
|
|
+ return GATT_NO_RESOURCES;
|
|
|
+ }
|
|
|
+
|
|
|
+ memset(p_msg, 0, buf_len);
|
|
|
+ p_msg->len = len;
|
|
|
+ p_m = (UINT8 *)(p_msg + 1) + L2CAP_MIN_OFFSET;
|
|
|
+ memcpy(p_m, p_data, len);
|
|
|
+
|
|
|
+ status = attp_send_sr_msg(p_tcb, p_msg);
|
|
|
+ return status;
|
|
|
+}
|
|
|
+
|
|
|
/*******************************************************************************
|
|
|
**
|
|
|
** Function gatt_sr_enqueue_cmd
|
|
|
@@ -300,7 +336,11 @@ void gatt_process_exec_write_req (tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, U
|
|
|
UINT32 trans_id = 0;
|
|
|
tGATT_IF gatt_if;
|
|
|
UINT16 conn_id;
|
|
|
-
|
|
|
+ UINT16 queue_num = 0;
|
|
|
+ BOOLEAN is_prepare_write_valid = FALSE;
|
|
|
+ BOOLEAN is_need_dequeue_sr_cmd = FALSE;
|
|
|
+ tGATT_PREPARE_WRITE_RECORD *prepare_record = NULL;
|
|
|
+ tGATT_PREPARE_WRITE_QUEUE_DATA * queue_data = NULL;
|
|
|
UNUSED(len);
|
|
|
|
|
|
#if GATT_CONFORMANCE_TESTING == TRUE
|
|
|
@@ -319,11 +359,60 @@ void gatt_process_exec_write_req (tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, U
|
|
|
/* mask the flag */
|
|
|
flag &= GATT_PREP_WRITE_EXEC;
|
|
|
|
|
|
+ prepare_record = &(p_tcb->prepare_write_record);
|
|
|
+ queue_num = prepare_record->queue._count;
|
|
|
+
|
|
|
|
|
|
- /* no prep write is queued */
|
|
|
+ //if received prepare_write packets include stack_rsp and app_rsp,
|
|
|
+ //stack respond to execute_write only when stack_rsp handle has invalid_offset
|
|
|
+ //or invalid_length error;
|
|
|
+ //app need to respond to execute_write if it has received app_rsp handle packets
|
|
|
+ if (((prepare_record->error_code_app == GATT_SUCCESS) &&
|
|
|
+ (prepare_record->total_num == queue_num))
|
|
|
+ || (flag == GATT_PREP_WRITE_CANCEL)){
|
|
|
+ tGATT_EXEC_WRITE_RSP gatt_exec_write_rsp;
|
|
|
+ gatt_exec_write_rsp.op_code = GATT_RSP_EXEC_WRITE;
|
|
|
+ gatt_send_packet(p_tcb, (UINT8 *)(&gatt_exec_write_rsp), sizeof(gatt_exec_write_rsp));
|
|
|
+ gatt_dequeue_sr_cmd(p_tcb);
|
|
|
+ if (flag != GATT_PREP_WRITE_CANCEL){
|
|
|
+ is_prepare_write_valid = TRUE;
|
|
|
+ }
|
|
|
+ GATT_TRACE_DEBUG("Send execute_write_rsp\n");
|
|
|
+ } else if ((prepare_record->error_code_app == GATT_SUCCESS) &&
|
|
|
+ (prepare_record->total_num > queue_num)){
|
|
|
+ //No error for stack_rsp's handles and there exist some app_rsp's handles,
|
|
|
+ //so exec_write_rsp depends to app's response; but stack_rsp's data is valid
|
|
|
+ //TODO: there exist problem if stack_rsp's data is valid but app_rsp's data is not valid.
|
|
|
+ is_prepare_write_valid = TRUE;
|
|
|
+ } else if(prepare_record->total_num < queue_num) {
|
|
|
+ GATT_TRACE_ERROR("Error in %s, line=%d, prepare write total number (%d) \
|
|
|
+ should not smaller than prepare queue number (%d)\n", \
|
|
|
+ __func__, __LINE__,prepare_record->total_num, queue_num);
|
|
|
+ } else if (prepare_record->error_code_app != GATT_SUCCESS){
|
|
|
+ GATT_TRACE_DEBUG("Send error code for execute_write, code=0x%x\n", prepare_record->error_code_app);
|
|
|
+ is_need_dequeue_sr_cmd = (prepare_record->total_num == queue_num) ? TRUE : FALSE;
|
|
|
+ gatt_send_error_rsp(p_tcb, prepare_record->error_code_app, GATT_REQ_EXEC_WRITE, 0, is_need_dequeue_sr_cmd);
|
|
|
+ }
|
|
|
+
|
|
|
+ //dequeue prepare write data
|
|
|
+ while(GKI_getfirst(&(prepare_record->queue))) {
|
|
|
+ queue_data = GKI_dequeue(&(prepare_record->queue));
|
|
|
+ if (is_prepare_write_valid){
|
|
|
+ if((queue_data->p_attr->p_value != NULL) && (queue_data->p_attr->p_value->attr_val.attr_val != NULL)){
|
|
|
+ memcpy(queue_data->p_attr->p_value->attr_val.attr_val+queue_data->offset, queue_data->value, queue_data->len);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ GKI_freebuf(queue_data);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* according to ble spec, even if there is no prep write queued,
|
|
|
+ * need to respond execute_write_response
|
|
|
+ * Note: exec_write_rsp callback should be called after all data has been written*/
|
|
|
if (!gatt_sr_is_prep_cnt_zero(p_tcb)) {
|
|
|
- trans_id = gatt_sr_enqueue_cmd(p_tcb, op_code, 0);
|
|
|
- gatt_sr_copy_prep_cnt_to_cback_cnt(p_tcb);
|
|
|
+ if (prepare_record->total_num > queue_num){
|
|
|
+ trans_id = gatt_sr_enqueue_cmd(p_tcb, op_code, 0);
|
|
|
+ gatt_sr_copy_prep_cnt_to_cback_cnt(p_tcb);
|
|
|
+ }
|
|
|
|
|
|
for (i = 0; i < GATT_MAX_APPS; i++) {
|
|
|
if (p_tcb->prep_cnt[i]) {
|
|
|
@@ -336,10 +425,10 @@ void gatt_process_exec_write_req (tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, U
|
|
|
p_tcb->prep_cnt[i] = 0;
|
|
|
}
|
|
|
}
|
|
|
- } else { /* nothing needs to be executed , send response now */
|
|
|
- GATT_TRACE_ERROR("gatt_process_exec_write_req: no prepare write pending");
|
|
|
- gatt_send_error_rsp(p_tcb, GATT_ERROR, GATT_REQ_EXEC_WRITE, 0, FALSE);
|
|
|
}
|
|
|
+
|
|
|
+ prepare_record->total_num = 0;
|
|
|
+ prepare_record->error_code_app = GATT_SUCCESS;
|
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
|
@@ -987,54 +1076,29 @@ void gatts_process_read_by_type_req(tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len,
|
|
|
void gatts_process_write_req (tGATT_TCB *p_tcb, UINT8 i_rcb, UINT16 handle,
|
|
|
UINT8 op_code, UINT16 len, UINT8 *p_data)
|
|
|
{
|
|
|
- UINT16 buf_len = (UINT16)(sizeof(BT_HDR) + p_tcb->payload_size + L2CAP_MIN_OFFSET);
|
|
|
tGATTS_DATA sr_data;
|
|
|
UINT32 trans_id;
|
|
|
tGATT_STATUS status;
|
|
|
- UINT8 sec_flag, key_size, *p = p_data, *p_m;
|
|
|
+ UINT8 sec_flag, key_size, *p = p_data;
|
|
|
tGATT_SR_REG *p_sreg;
|
|
|
UINT16 conn_id, offset = 0;
|
|
|
- BT_HDR *p_msg = NULL;
|
|
|
- memset(&sr_data, 0, sizeof(tGATTS_DATA));
|
|
|
|
|
|
- if ((p_msg = (BT_HDR *)GKI_getbuf(buf_len)) == NULL) {
|
|
|
- GATT_TRACE_ERROR("gatts_process_write_req failed. no resources.\n");
|
|
|
- }
|
|
|
+ memset(&sr_data, 0, sizeof(tGATTS_DATA));
|
|
|
+ sr_data.write_req.need_rsp = FALSE;
|
|
|
|
|
|
- memset(p_msg, 0, buf_len);
|
|
|
- p_m = (UINT8 *)(p_msg + 1) + L2CAP_MIN_OFFSET;
|
|
|
- *p_m ++ = op_code + 1;
|
|
|
- p_msg->len = 1;
|
|
|
- buf_len = p_tcb->payload_size - 1;
|
|
|
-
|
|
|
switch (op_code) {
|
|
|
- case GATT_REQ_PREPARE_WRITE:
|
|
|
- sr_data.write_req.is_prep = TRUE;
|
|
|
- STREAM_TO_UINT16(sr_data.write_req.offset, p);
|
|
|
- UINT16_TO_STREAM(p_m, sr_data.write_req.is_prep);
|
|
|
- offset = sr_data.write_req.offset;
|
|
|
- len -= 2;
|
|
|
- /* fall through */
|
|
|
case GATT_SIGN_CMD_WRITE:
|
|
|
if (op_code == GATT_SIGN_CMD_WRITE) {
|
|
|
- GATT_TRACE_DEBUG("Write CMD with data sigining" );
|
|
|
+ GATT_TRACE_DEBUG("Write CMD with data signing" );
|
|
|
len -= GATT_AUTH_SIGN_LEN;
|
|
|
}
|
|
|
/* fall through */
|
|
|
case GATT_CMD_WRITE:
|
|
|
case GATT_REQ_WRITE:
|
|
|
- if (op_code == GATT_REQ_WRITE || op_code == GATT_REQ_PREPARE_WRITE) {
|
|
|
- sr_data.write_req.need_rsp = TRUE;
|
|
|
- if(op_code == GATT_REQ_PREPARE_WRITE){
|
|
|
- memcpy(p_m, p, len);
|
|
|
- p_msg->len += len;
|
|
|
- }
|
|
|
- }
|
|
|
sr_data.write_req.handle = handle;
|
|
|
sr_data.write_req.len = len;
|
|
|
if (len != 0 && p != NULL) {
|
|
|
memcpy (sr_data.write_req.value, p, len);
|
|
|
-
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
@@ -1059,40 +1123,180 @@ void gatts_process_write_req (tGATT_TCB *p_tcb, UINT8 i_rcb, UINT16 handle,
|
|
|
conn_id = GATT_CREATE_CONN_ID(p_tcb->tcb_idx, p_sreg->gatt_if);
|
|
|
status = gatts_write_attr_value_by_handle(gatt_cb.sr_reg[i_rcb].p_db,
|
|
|
handle, offset, p, len);
|
|
|
- if((sr_data.write_req.need_rsp == TRUE) && (status == GATT_APP_RSP)){
|
|
|
+ if((op_code == GATT_REQ_WRITE) && (status == GATT_APP_RSP)){
|
|
|
sr_data.write_req.need_rsp = TRUE;
|
|
|
status = GATT_PENDING;
|
|
|
}
|
|
|
|
|
|
- else{
|
|
|
- sr_data.write_req.need_rsp = FALSE;
|
|
|
- }
|
|
|
-
|
|
|
gatt_sr_send_req_callback(conn_id,
|
|
|
trans_id,
|
|
|
GATTS_REQ_TYPE_WRITE,
|
|
|
&sr_data);
|
|
|
+ } else {
|
|
|
+ GATT_TRACE_ERROR("Error in %s, line=%d, max pending command, send error\n", __func__, __LINE__);
|
|
|
+ status = GATT_BUSY; /* max pending command, application error */
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- if (status == GATT_SUCCESS) {
|
|
|
- attp_send_sr_msg(p_tcb, p_msg);
|
|
|
- gatt_dequeue_sr_cmd(p_tcb);
|
|
|
- } else {
|
|
|
- GKI_freebuf(p_msg);
|
|
|
+ /* response should be sent only for write_request */
|
|
|
+ if ((op_code == GATT_REQ_WRITE) && (sr_data.write_req.need_rsp == FALSE)){
|
|
|
+ if (status == GATT_SUCCESS){
|
|
|
+ tGATT_WRITE_REQ_RSP gatt_write_req_rsp;
|
|
|
+ gatt_write_req_rsp.op_code = GATT_RSP_WRITE;
|
|
|
+ gatt_send_packet(p_tcb, (UINT8 *)(&gatt_write_req_rsp), sizeof(gatt_write_req_rsp));
|
|
|
+ gatt_dequeue_sr_cmd(p_tcb);
|
|
|
+ } else if (status != GATT_PENDING){
|
|
|
+ /* note: in case of GATT_BUSY, will respond this application error to remote device */
|
|
|
+ gatt_send_error_rsp (p_tcb, status, op_code, handle, TRUE);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/*******************************************************************************
|
|
|
+ **
|
|
|
+ ** Function gatts_attr_process_preapre_write
|
|
|
+ **
|
|
|
+ ** Description This function is called to process the prepare write request
|
|
|
+ ** from client.
|
|
|
+ **
|
|
|
+ ** Returns void
|
|
|
+ **
|
|
|
+ *******************************************************************************/
|
|
|
+void gatt_attr_process_prepare_write (tGATT_TCB *p_tcb, UINT8 i_rcb, UINT16 handle,
|
|
|
+ UINT8 op_code, UINT16 len, UINT8 *p_data)
|
|
|
+{
|
|
|
+ tGATT_STATUS status;
|
|
|
+ tGATT_PREPARE_WRITE_QUEUE_DATA * queue_data = NULL;
|
|
|
+ tGATT_ATTR16 *p_attr;
|
|
|
+ tGATT_ATTR16 *p_attr_temp;
|
|
|
+ tGATTS_DATA sr_data;
|
|
|
+ UINT32 trans_id = 0;
|
|
|
+ UINT8 sec_flag, key_size, *p = p_data;
|
|
|
+ tGATT_SR_REG *p_sreg;
|
|
|
+ UINT16 conn_id, offset = 0;
|
|
|
+ tGATT_SVC_DB *p_db;
|
|
|
+ BOOLEAN is_need_prepare_write_rsp = FALSE;
|
|
|
+ BOOLEAN is_need_queue_data = FALSE;
|
|
|
+ tGATT_PREPARE_WRITE_RECORD *prepare_record = NULL;
|
|
|
+ memset(&sr_data, 0, sizeof(tGATTS_DATA));
|
|
|
+
|
|
|
+ //get offset from p_data
|
|
|
+ STREAM_TO_UINT16(offset, p);
|
|
|
+ len -= 2;
|
|
|
+ p_sreg = &gatt_cb.sr_reg[i_rcb];
|
|
|
+ conn_id = GATT_CREATE_CONN_ID(p_tcb->tcb_idx, p_sreg->gatt_if);
|
|
|
+ //prepare_record = &(prepare_write_record);
|
|
|
+ prepare_record = &(p_tcb->prepare_write_record);
|
|
|
+
|
|
|
+ gatt_sr_get_sec_info(p_tcb->peer_bda,
|
|
|
+ p_tcb->transport,
|
|
|
+ &sec_flag,
|
|
|
+ &key_size);
|
|
|
+
|
|
|
+ status = gatts_write_attr_perm_check (gatt_cb.sr_reg[i_rcb].p_db,
|
|
|
+ op_code,
|
|
|
+ handle,
|
|
|
+ sr_data.write_req.offset,
|
|
|
+ p,
|
|
|
+ len,
|
|
|
+ sec_flag,
|
|
|
+ key_size);
|
|
|
+
|
|
|
+ if (status == GATT_SUCCESS){
|
|
|
+ if ((trans_id = gatt_sr_enqueue_cmd(p_tcb, op_code, handle)) != 0) {
|
|
|
+ p_db = gatt_cb.sr_reg[i_rcb].p_db;
|
|
|
+ if (p_db && p_db->p_attr_list) {
|
|
|
+ p_attr = (tGATT_ATTR16 *)p_db->p_attr_list;
|
|
|
+ while (p_attr && handle >= p_attr->handle) {
|
|
|
+ if (p_attr->handle == handle ) {
|
|
|
+ p_attr_temp = p_attr;
|
|
|
+ if (p_attr->control.auto_rsp == GATT_RSP_BY_APP) {
|
|
|
+ status = GATT_APP_RSP;
|
|
|
+ } else if (p_attr->p_value != NULL &&
|
|
|
+ offset > p_attr->p_value->attr_val.attr_max_len) {
|
|
|
+ status = GATT_INVALID_OFFSET;
|
|
|
+ is_need_prepare_write_rsp = TRUE;
|
|
|
+ is_need_queue_data = TRUE;
|
|
|
+ } else if (p_attr->p_value != NULL &&
|
|
|
+ ((offset + len) > p_attr->p_value->attr_val.attr_max_len)){
|
|
|
+ status = GATT_INVALID_ATTR_LEN;
|
|
|
+ is_need_prepare_write_rsp = TRUE;
|
|
|
+ is_need_queue_data = TRUE;
|
|
|
+ } else if (p_attr->p_value == NULL) {
|
|
|
+ LOG_ERROR("Error in %s, attribute of handle 0x%x not allocate value buffer\n",
|
|
|
+ __func__, handle);
|
|
|
+ status = GATT_ESP_ERROR;
|
|
|
+ } else {
|
|
|
+ //valid prepare write request, need to send response and queue the data
|
|
|
+ //status: GATT_SUCCESS
|
|
|
+ is_need_prepare_write_rsp = TRUE;
|
|
|
+ is_need_queue_data = TRUE;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ p_attr = (tGATT_ATTR16 *)p_attr->p_next;
|
|
|
+ }
|
|
|
}
|
|
|
+ } else{
|
|
|
+ status = GATT_ESP_ERROR;
|
|
|
+ GATT_TRACE_ERROR("Error in %s, Line %d: GATT BUSY\n", __func__, __LINE__);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
+ if (is_need_queue_data){
|
|
|
+ queue_data = (tGATT_PREPARE_WRITE_QUEUE_DATA *)GKI_getbuf(len + sizeof(tGATT_PREPARE_WRITE_QUEUE_DATA));
|
|
|
+ if (queue_data == NULL){
|
|
|
+ status = GATT_PREPARE_Q_FULL;
|
|
|
} else {
|
|
|
- GATT_TRACE_ERROR("max pending command, send error\n");
|
|
|
- status = GATT_BUSY; /* max pending command, application error */
|
|
|
+ queue_data->p_attr = p_attr_temp;
|
|
|
+ queue_data->len = len;
|
|
|
+ queue_data->handle = handle;
|
|
|
+ queue_data->offset = offset;
|
|
|
+ memcpy(queue_data->value, p, len);
|
|
|
+ GKI_enqueue(&(prepare_record->queue), queue_data);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (is_need_prepare_write_rsp){
|
|
|
+ //send prepare write response
|
|
|
+ if (queue_data != NULL){
|
|
|
+ queue_data->op_code = op_code + 1;
|
|
|
+ //5: op_code 1 + handle 2 + offset 2
|
|
|
+ tGATT_STATUS rsp_send_status = gatt_send_packet(p_tcb, &(queue_data->op_code), queue_data->len + 5);
|
|
|
+ gatt_sr_update_prep_cnt(p_tcb, p_sreg->gatt_if, TRUE, FALSE);
|
|
|
+ gatt_dequeue_sr_cmd(p_tcb);
|
|
|
+
|
|
|
+ if (rsp_send_status != GATT_SUCCESS){
|
|
|
+ LOG_ERROR("Error in %s, line=%d, fail to send prepare_write_rsp, status=0x%x\n",
|
|
|
+ __func__, __LINE__, rsp_send_status);
|
|
|
+ }
|
|
|
+ } else{
|
|
|
+ LOG_ERROR("Error in %s, line=%d, queue_data should not be NULL here, fail to send prepare_write_rsp\n",
|
|
|
+ __func__, __LINE__);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /* in theroy BUSY is not possible(should already been checked), protected check */
|
|
|
- if (status != GATT_PENDING && status != GATT_BUSY && status != GATT_SUCCESS &&
|
|
|
- (op_code == GATT_REQ_PREPARE_WRITE || op_code == GATT_REQ_WRITE)) {
|
|
|
- gatt_send_error_rsp (p_tcb, status, op_code, handle, FALSE);
|
|
|
- gatt_dequeue_sr_cmd(p_tcb);
|
|
|
+ if ((status == GATT_APP_RSP) || (is_need_prepare_write_rsp)){
|
|
|
+ prepare_record->total_num++;
|
|
|
+ memset(&sr_data, 0, sizeof(sr_data));
|
|
|
+ sr_data.write_req.is_prep = TRUE;
|
|
|
+ sr_data.write_req.handle = handle;
|
|
|
+ sr_data.write_req.offset = offset;
|
|
|
+ sr_data.write_req.len = len;
|
|
|
+ sr_data.write_req.need_rsp = (status == GATT_APP_RSP) ? TRUE : FALSE;
|
|
|
+ memcpy(sr_data.write_req.value, p, len);
|
|
|
+ gatt_sr_send_req_callback(conn_id, trans_id, GATTS_REQ_TYPE_WRITE, &sr_data);
|
|
|
+ } else{
|
|
|
+ gatt_send_error_rsp(p_tcb, status, GATT_REQ_PREPARE_WRITE, handle, TRUE);
|
|
|
}
|
|
|
- return;
|
|
|
+
|
|
|
+ if ((prepare_record->error_code_app == GATT_SUCCESS)
|
|
|
+ && ((status == GATT_INVALID_OFFSET) || (status == GATT_INVALID_ATTR_LEN))){
|
|
|
+ prepare_record->error_code_app = status;
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
|
@@ -1226,9 +1430,11 @@ void gatts_process_attribute_req (tGATT_TCB *p_tcb, UINT8 op_code,
|
|
|
case GATT_REQ_WRITE: /* write char/char descriptor value */
|
|
|
case GATT_CMD_WRITE:
|
|
|
case GATT_SIGN_CMD_WRITE:
|
|
|
- case GATT_REQ_PREPARE_WRITE:
|
|
|
gatts_process_write_req(p_tcb, i, handle, op_code, len, p);
|
|
|
break;
|
|
|
+
|
|
|
+ case GATT_REQ_PREPARE_WRITE:
|
|
|
+ gatt_attr_process_prepare_write (p_tcb, i, handle, op_code, len, p);
|
|
|
default:
|
|
|
break;
|
|
|
}
|