1 /******************************************************************************
2  *
3  *  Copyright (C) 2008-2014 Broadcom Corporation
4  *
5  *  Licensed under the Apache License, Version 2.0 (the "License");
6  *  you may not use this file except in compliance with the License.
7  *  You may obtain a copy of the License at:
8  *
9  *  http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  ******************************************************************************/
18 
19 /******************************************************************************
20  *
21  *  this file contains ATT protocol functions
22  *
23  ******************************************************************************/
24 
25 #include "bt_target.h"
26 
27 #if BLE_INCLUDED == TRUE
28 
29 #include "gatt_int.h"
30 #include "l2c_api.h"
31 
32 #define GATT_HDR_FIND_TYPE_VALUE_LEN    21
33 #define GATT_OP_CODE_SIZE   1
34 #define GATT_START_END_HANDLE_SIZE    4
35 
36 /**********************************************************************
37 **   ATT protocl message building utility                              *
38 ***********************************************************************/
39 /*******************************************************************************
40 **
41 ** Function         attp_build_mtu_exec_cmd
42 **
43 ** Description      Build a exchange MTU request
44 **
45 ** Returns          None.
46 **
47 *******************************************************************************/
attp_build_mtu_cmd(UINT8 op_code,UINT16 rx_mtu)48 BT_HDR *attp_build_mtu_cmd(UINT8 op_code, UINT16 rx_mtu)
49 {
50     UINT8 *p;
51     BT_HDR *p_buf =
52         (BT_HDR *)osi_malloc(sizeof(BT_HDR) + GATT_HDR_SIZE + L2CAP_MIN_OFFSET);
53 
54     p = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET;
55     UINT8_TO_STREAM(p, op_code);
56     UINT16_TO_STREAM(p, rx_mtu);
57 
58     p_buf->offset = L2CAP_MIN_OFFSET;
59     p_buf->len = GATT_HDR_SIZE; /* opcode + 2 bytes mtu */
60 
61     return p_buf;
62 }
63 /*******************************************************************************
64 **
65 ** Function         attp_build_exec_write_cmd
66 **
67 ** Description      Build a execute write request or response.
68 **
69 ** Returns          None.
70 **
71 *******************************************************************************/
attp_build_exec_write_cmd(UINT8 op_code,UINT8 flag)72 BT_HDR *attp_build_exec_write_cmd (UINT8 op_code, UINT8 flag)
73 {
74     BT_HDR      *p_buf = (BT_HDR *)osi_malloc(GATT_DATA_BUF_SIZE);
75     UINT8       *p;
76 
77     p = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET;
78 
79     p_buf->offset = L2CAP_MIN_OFFSET;
80     p_buf->len = GATT_OP_CODE_SIZE;
81 
82     UINT8_TO_STREAM(p, op_code);
83 
84     if (op_code == GATT_REQ_EXEC_WRITE) {
85         flag &= GATT_PREP_WRITE_EXEC;
86         UINT8_TO_STREAM (p, flag);
87         p_buf->len += 1;
88     }
89 
90     return p_buf;
91 }
92 
93 /*******************************************************************************
94 **
95 ** Function         attp_build_err_cmd
96 **
97 ** Description      Build a exchange MTU request
98 **
99 ** Returns          None.
100 **
101 *******************************************************************************/
attp_build_err_cmd(UINT8 cmd_code,UINT16 err_handle,UINT8 reason)102 BT_HDR *attp_build_err_cmd(UINT8 cmd_code, UINT16 err_handle, UINT8 reason)
103 {
104     UINT8 *p;
105     BT_HDR *p_buf = (BT_HDR *)osi_malloc(sizeof(BT_HDR) + L2CAP_MIN_OFFSET + 5);
106 
107     p = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET;
108     UINT8_TO_STREAM(p, GATT_RSP_ERROR);
109     UINT8_TO_STREAM(p, cmd_code);
110     UINT16_TO_STREAM(p, err_handle);
111     UINT8_TO_STREAM(p, reason);
112 
113     p_buf->offset = L2CAP_MIN_OFFSET;
114     /* GATT_HDR_SIZE (1B ERR_RSP op code+ 2B handle) + 1B cmd_op_code  + 1B status */
115     p_buf->len = GATT_HDR_SIZE + 1 + 1;
116 
117     return p_buf;
118 }
119 /*******************************************************************************
120 **
121 ** Function         attp_build_browse_cmd
122 **
123 ** Description      Build a read information request or read by type request
124 **
125 ** Returns          None.
126 **
127 *******************************************************************************/
attp_build_browse_cmd(UINT8 op_code,UINT16 s_hdl,UINT16 e_hdl,tBT_UUID uuid)128 BT_HDR *attp_build_browse_cmd(UINT8 op_code, UINT16 s_hdl, UINT16 e_hdl, tBT_UUID uuid)
129 {
130     const size_t payload_size = (GATT_OP_CODE_SIZE) + (GATT_START_END_HANDLE_SIZE) + (LEN_UUID_128);
131     BT_HDR *p_buf = (BT_HDR *)osi_malloc(sizeof(BT_HDR) + payload_size + L2CAP_MIN_OFFSET);
132 
133     UINT8 *p = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET;
134     /* Describe the built message location and size */
135     p_buf->offset = L2CAP_MIN_OFFSET;
136     p_buf->len = GATT_OP_CODE_SIZE + 4;
137 
138     UINT8_TO_STREAM(p, op_code);
139     UINT16_TO_STREAM(p, s_hdl);
140     UINT16_TO_STREAM(p, e_hdl);
141     p_buf->len += gatt_build_uuid_to_stream(&p, uuid);
142 
143     return p_buf;
144 }
145 
146 /*******************************************************************************
147 **
148 ** Function         attp_build_read_handles_cmd
149 **
150 ** Description      Build a read by type and value request.
151 **
152 ** Returns          pointer to the command buffer.
153 **
154 *******************************************************************************/
attp_build_read_by_type_value_cmd(UINT16 payload_size,tGATT_FIND_TYPE_VALUE * p_value_type)155 BT_HDR *attp_build_read_by_type_value_cmd (UINT16 payload_size, tGATT_FIND_TYPE_VALUE *p_value_type)
156 {
157     UINT8 *p;
158     UINT16 len = p_value_type->value_len;
159     BT_HDR *p_buf =
160         (BT_HDR *)osi_malloc(sizeof(BT_HDR) + payload_size + L2CAP_MIN_OFFSET);
161 
162     p = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET;
163     p_buf->offset = L2CAP_MIN_OFFSET;
164     p_buf->len = 5; /* opcode + s_handle + e_handle */
165 
166     UINT8_TO_STREAM(p, GATT_REQ_FIND_TYPE_VALUE);
167     UINT16_TO_STREAM(p, p_value_type->s_handle);
168     UINT16_TO_STREAM(p, p_value_type->e_handle);
169 
170     p_buf->len += gatt_build_uuid_to_stream(&p, p_value_type->uuid);
171 
172     if (p_value_type->value_len +  p_buf->len > payload_size)
173         len = payload_size - p_buf->len;
174 
175     memcpy(p, p_value_type->value, len);
176     p_buf->len += len;
177 
178     return p_buf;
179 }
180 
181 /*******************************************************************************
182 **
183 ** Function         attp_build_read_multi_cmd
184 **
185 ** Description      Build a read multiple request
186 **
187 ** Returns          None.
188 **
189 *******************************************************************************/
attp_build_read_multi_cmd(UINT16 payload_size,UINT16 num_handle,UINT16 * p_handle)190 BT_HDR *attp_build_read_multi_cmd(UINT16 payload_size, UINT16 num_handle, UINT16 *p_handle)
191 {
192     UINT8 *p, i = 0;
193     BT_HDR *p_buf =
194         (BT_HDR *)osi_malloc(sizeof(BT_HDR) + num_handle * 2 + 1 + L2CAP_MIN_OFFSET);
195 
196     p = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET;
197     p_buf->offset = L2CAP_MIN_OFFSET;
198     p_buf->len = 1;
199 
200     UINT8_TO_STREAM(p, GATT_REQ_READ_MULTI);
201 
202     for (i = 0; i < num_handle && p_buf->len + 2 <= payload_size; i ++) {
203       UINT16_TO_STREAM (p, *(p_handle + i));
204       p_buf->len += 2;
205     }
206 
207     return p_buf;
208 }
209 /*******************************************************************************
210 **
211 ** Function         attp_build_handle_cmd
212 **
213 ** Description      Build a read /read blob request
214 **
215 ** Returns          None.
216 **
217 *******************************************************************************/
attp_build_handle_cmd(UINT8 op_code,UINT16 handle,UINT16 offset)218 BT_HDR *attp_build_handle_cmd(UINT8 op_code, UINT16 handle, UINT16 offset)
219 {
220     UINT8 *p;
221     BT_HDR *p_buf =
222         (BT_HDR *)osi_malloc(sizeof(BT_HDR) + 5 + L2CAP_MIN_OFFSET);
223 
224     p = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET;
225     p_buf->offset = L2CAP_MIN_OFFSET;
226 
227     UINT8_TO_STREAM(p, op_code);
228     p_buf->len  = 1;
229 
230     UINT16_TO_STREAM(p, handle);
231     p_buf->len += 2;
232 
233     if (op_code == GATT_REQ_READ_BLOB) {
234         UINT16_TO_STREAM (p, offset);
235         p_buf->len += 2;
236     }
237 
238     return p_buf;
239 }
240 
241 /*******************************************************************************
242 **
243 ** Function         attp_build_opcode_cmd
244 **
245 ** Description      Build a  request/response with opcode only.
246 **
247 ** Returns          None.
248 **
249 *******************************************************************************/
attp_build_opcode_cmd(UINT8 op_code)250 BT_HDR *attp_build_opcode_cmd(UINT8 op_code)
251 {
252     UINT8 *p;
253     BT_HDR *p_buf =
254         (BT_HDR *)osi_malloc(sizeof(BT_HDR) + 1 + L2CAP_MIN_OFFSET);
255 
256     p = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET;
257     p_buf->offset = L2CAP_MIN_OFFSET;
258 
259     UINT8_TO_STREAM(p, op_code);
260     p_buf->len  = 1;
261 
262     return p_buf;
263 }
264 
265 /*******************************************************************************
266 **
267 ** Function         attp_build_value_cmd
268 **
269 ** Description      Build a attribute value request
270 **
271 ** Returns          None.
272 **
273 *******************************************************************************/
attp_build_value_cmd(UINT16 payload_size,UINT8 op_code,UINT16 handle,UINT16 offset,UINT16 len,UINT8 * p_data)274 BT_HDR *attp_build_value_cmd (UINT16 payload_size, UINT8 op_code, UINT16 handle,
275                               UINT16 offset, UINT16 len, UINT8 *p_data)
276 {
277     UINT8 *p, *pp, pair_len, *p_pair_len;
278     BT_HDR *p_buf =
279         (BT_HDR *)osi_malloc(sizeof(BT_HDR) + payload_size + L2CAP_MIN_OFFSET);
280 
281     p = pp = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET;
282     UINT8_TO_STREAM(p, op_code);
283     p_buf->offset = L2CAP_MIN_OFFSET;
284     p_buf->len = 1;
285 
286     if (op_code == GATT_RSP_READ_BY_TYPE) {
287         p_pair_len = p;
288         pair_len = len + 2;
289         UINT8_TO_STREAM (p, pair_len);
290         p_buf->len += 1;
291     }
292     if (op_code != GATT_RSP_READ_BLOB && op_code != GATT_RSP_READ) {
293         UINT16_TO_STREAM (p, handle);
294         p_buf->len += 2;
295     }
296 
297     if (op_code == GATT_REQ_PREPARE_WRITE ||op_code == GATT_RSP_PREPARE_WRITE) {
298         UINT16_TO_STREAM (p, offset);
299         p_buf->len += 2;
300     }
301 
302     if (len > 0 && p_data != NULL) {
303         /* ensure data not exceed MTU size */
304         if (payload_size - p_buf->len < len) {
305             len = payload_size - p_buf->len;
306             /* update handle value pair length */
307             if (op_code == GATT_RSP_READ_BY_TYPE)
308                 *p_pair_len = (len + 2);
309 
310             GATT_TRACE_WARNING("attribute value too long, to be truncated to %d", len);
311         }
312 
313         ARRAY_TO_STREAM(p, p_data, len);
314         p_buf->len += len;
315     }
316 
317     return p_buf;
318 }
319 
320 /*******************************************************************************
321 **
322 ** Function         attp_send_msg_to_l2cap
323 **
324 ** Description      Send message to L2CAP.
325 **
326 *******************************************************************************/
attp_send_msg_to_l2cap(tGATT_TCB * p_tcb,BT_HDR * p_toL2CAP)327 tGATT_STATUS attp_send_msg_to_l2cap(tGATT_TCB *p_tcb, BT_HDR *p_toL2CAP)
328 {
329     UINT16      l2cap_ret;
330 
331 
332     if (p_tcb->att_lcid == L2CAP_ATT_CID)
333         l2cap_ret = L2CA_SendFixedChnlData (L2CAP_ATT_CID, p_tcb->peer_bda, p_toL2CAP);
334     else
335         l2cap_ret = (UINT16) L2CA_DataWrite (p_tcb->att_lcid, p_toL2CAP);
336 
337     if (l2cap_ret == L2CAP_DW_FAILED)
338     {
339         GATT_TRACE_ERROR("ATT   failed to pass msg:0x%0x to L2CAP",
340             *((UINT8 *)(p_toL2CAP + 1) + p_toL2CAP->offset));
341         return GATT_INTERNAL_ERROR;
342     }
343     else if (l2cap_ret == L2CAP_DW_CONGESTED)
344     {
345         GATT_TRACE_DEBUG("ATT congested, message accepted");
346         return GATT_CONGESTED;
347     }
348     return GATT_SUCCESS;
349 }
350 
351 /*******************************************************************************
352 **
353 ** Function         attp_build_sr_msg
354 **
355 ** Description      Build ATT Server PDUs.
356 **
357 *******************************************************************************/
attp_build_sr_msg(tGATT_TCB * p_tcb,UINT8 op_code,tGATT_SR_MSG * p_msg)358 BT_HDR *attp_build_sr_msg(tGATT_TCB *p_tcb, UINT8 op_code, tGATT_SR_MSG *p_msg)
359 {
360     BT_HDR          *p_cmd = NULL;
361     UINT16          offset = 0;
362 
363     switch (op_code)
364     {
365     case GATT_RSP_READ_BLOB:
366     case GATT_RSP_PREPARE_WRITE:
367         GATT_TRACE_EVENT ("ATT_RSP_READ_BLOB/GATT_RSP_PREPARE_WRITE: len = %d offset = %d",
368                     p_msg->attr_value.len, p_msg->attr_value.offset);
369         offset = p_msg->attr_value.offset;
370 /* Coverity: [FALSE-POSITIVE error] intended fall through */
371 /* Missing break statement between cases in switch statement */
372         /* fall through */
373     case GATT_RSP_READ_BY_TYPE:
374     case GATT_RSP_READ:
375     case GATT_HANDLE_VALUE_NOTIF:
376     case GATT_HANDLE_VALUE_IND:
377         p_cmd = attp_build_value_cmd(p_tcb->payload_size,
378                                      op_code,
379                                      p_msg->attr_value.handle,
380                                      offset,
381                                      p_msg->attr_value.len,
382                                      p_msg->attr_value.value);
383         break;
384 
385     case GATT_RSP_WRITE:
386         p_cmd = attp_build_opcode_cmd(op_code);
387         break;
388 
389     case GATT_RSP_ERROR:
390         p_cmd = attp_build_err_cmd(p_msg->error.cmd_code, p_msg->error.handle, p_msg->error.reason);
391         break;
392 
393     case GATT_RSP_EXEC_WRITE:
394         p_cmd = attp_build_exec_write_cmd(op_code, 0);
395         break;
396 
397     case GATT_RSP_MTU:
398         p_cmd = attp_build_mtu_cmd(op_code, p_msg->mtu);
399         break;
400 
401     default:
402         GATT_TRACE_DEBUG("attp_build_sr_msg: unknown op code = %d", op_code);
403         break;
404     }
405 
406     if (!p_cmd)
407         GATT_TRACE_ERROR("No resources");
408 
409     return p_cmd;
410 }
411 
412 /*******************************************************************************
413 **
414 ** Function         attp_send_sr_msg
415 **
416 ** Description      This function sends the server response or indication message
417 **                  to client.
418 **
419 ** Parameter        p_tcb: pointer to the connecton control block.
420 **                  p_msg: pointer to message parameters structure.
421 **
422 ** Returns          GATT_SUCCESS if sucessfully sent; otherwise error code.
423 **
424 **
425 *******************************************************************************/
attp_send_sr_msg(tGATT_TCB * p_tcb,BT_HDR * p_msg)426 tGATT_STATUS attp_send_sr_msg (tGATT_TCB *p_tcb, BT_HDR *p_msg)
427 {
428     tGATT_STATUS     cmd_sent = GATT_NO_RESOURCES;
429 
430     if (p_tcb != NULL)
431     {
432         if (p_msg != NULL)
433         {
434             p_msg->offset = L2CAP_MIN_OFFSET;
435             cmd_sent = attp_send_msg_to_l2cap (p_tcb, p_msg);
436         }
437     }
438     return cmd_sent;
439 }
440 
441 /*******************************************************************************
442 **
443 ** Function         attp_cl_send_cmd
444 **
445 ** Description      Send a ATT command or enqueue it.
446 **
447 ** Returns          GATT_SUCCESS if command sent
448 **                  GATT_CONGESTED if command sent but channel congested
449 **                  GATT_CMD_STARTED if command queue up in GATT
450 **                  GATT_ERROR if command sending failure
451 **
452 *******************************************************************************/
attp_cl_send_cmd(tGATT_TCB * p_tcb,UINT16 clcb_idx,UINT8 cmd_code,BT_HDR * p_cmd)453 tGATT_STATUS attp_cl_send_cmd(tGATT_TCB *p_tcb, UINT16 clcb_idx, UINT8 cmd_code, BT_HDR *p_cmd)
454 {
455     tGATT_STATUS att_ret = GATT_SUCCESS;
456 
457     if (p_tcb != NULL)
458     {
459         cmd_code &= ~GATT_AUTH_SIGN_MASK;
460 
461         /* no pending request or value confirmation */
462         if (p_tcb->pending_cl_req == p_tcb->next_slot_inq ||
463             cmd_code == GATT_HANDLE_VALUE_CONF)
464         {
465             att_ret = attp_send_msg_to_l2cap(p_tcb, p_cmd);
466             if (att_ret == GATT_CONGESTED || att_ret == GATT_SUCCESS)
467             {
468                 /* do not enq cmd if handle value confirmation or set request */
469                 if (cmd_code != GATT_HANDLE_VALUE_CONF && cmd_code != GATT_CMD_WRITE)
470                 {
471                     gatt_start_rsp_timer (clcb_idx);
472                     gatt_cmd_enq(p_tcb, clcb_idx, FALSE, cmd_code, NULL);
473                 }
474             }
475             else
476                 att_ret = GATT_INTERNAL_ERROR;
477         }
478         else
479         {
480             att_ret = GATT_CMD_STARTED;
481             gatt_cmd_enq(p_tcb, clcb_idx, TRUE, cmd_code, p_cmd);
482         }
483     }
484     else
485         att_ret = GATT_ERROR;
486 
487     return att_ret;
488 }
489 /*******************************************************************************
490 **
491 ** Function         attp_send_cl_msg
492 **
493 ** Description      This function sends the client request or confirmation message
494 **                  to server.
495 **
496 ** Parameter        p_tcb: pointer to the connectino control block.
497 **                  clcb_idx: clcb index
498 **                  op_code: message op code.
499 **                  p_msg: pointer to message parameters structure.
500 **
501 ** Returns          GATT_SUCCESS if sucessfully sent; otherwise error code.
502 **
503 **
504 *******************************************************************************/
attp_send_cl_msg(tGATT_TCB * p_tcb,UINT16 clcb_idx,UINT8 op_code,tGATT_CL_MSG * p_msg)505 tGATT_STATUS attp_send_cl_msg (tGATT_TCB *p_tcb, UINT16 clcb_idx, UINT8 op_code, tGATT_CL_MSG *p_msg)
506 {
507     tGATT_STATUS     status = GATT_NO_RESOURCES;
508     BT_HDR          *p_cmd = NULL;
509     UINT16          offset = 0, handle;
510 
511     if (p_tcb != NULL)
512     {
513         switch (op_code)
514         {
515         case GATT_REQ_MTU:
516             if (p_msg->mtu <= GATT_MAX_MTU_SIZE)
517             {
518                 p_tcb->payload_size = p_msg->mtu;
519                 p_cmd = attp_build_mtu_cmd(GATT_REQ_MTU, p_msg->mtu);
520             }
521             else
522                 status = GATT_ILLEGAL_PARAMETER;
523             break;
524 
525         case GATT_REQ_FIND_INFO:
526         case GATT_REQ_READ_BY_TYPE:
527         case GATT_REQ_READ_BY_GRP_TYPE:
528             if (GATT_HANDLE_IS_VALID (p_msg->browse.s_handle) &&
529                 GATT_HANDLE_IS_VALID (p_msg->browse.e_handle)  &&
530                 p_msg->browse.s_handle <= p_msg->browse.e_handle)
531             {
532                 p_cmd = attp_build_browse_cmd(op_code,
533                                             p_msg->browse.s_handle,
534                                             p_msg->browse.e_handle,
535                                             p_msg->browse.uuid);
536             }
537             else
538                 status = GATT_ILLEGAL_PARAMETER;
539             break;
540 
541         case GATT_REQ_READ_BLOB:
542             offset = p_msg->read_blob.offset;
543             /* fall through */
544         case GATT_REQ_READ:
545             handle = (op_code == GATT_REQ_READ) ? p_msg->handle: p_msg->read_blob.handle;
546             /*  handle checking */
547             if (GATT_HANDLE_IS_VALID (handle))
548             {
549                 p_cmd = attp_build_handle_cmd(op_code, handle, offset);
550             }
551             else
552                 status = GATT_ILLEGAL_PARAMETER;
553             break;
554 
555         case GATT_HANDLE_VALUE_CONF:
556             p_cmd = attp_build_opcode_cmd(op_code);
557             break;
558 
559         case GATT_REQ_PREPARE_WRITE:
560             offset = p_msg->attr_value.offset;
561             /* fall through */
562         case GATT_REQ_WRITE:
563         case GATT_CMD_WRITE:
564         case GATT_SIGN_CMD_WRITE:
565             if (GATT_HANDLE_IS_VALID (p_msg->attr_value.handle))
566             {
567                 p_cmd = attp_build_value_cmd (p_tcb->payload_size,
568                                               op_code, p_msg->attr_value.handle,
569                                               offset,
570                                               p_msg->attr_value.len,
571                                               p_msg->attr_value.value);
572             }
573             else
574                 status = GATT_ILLEGAL_PARAMETER;
575             break;
576 
577         case GATT_REQ_EXEC_WRITE:
578             p_cmd = attp_build_exec_write_cmd(op_code, p_msg->exec_write);
579             break;
580 
581         case GATT_REQ_FIND_TYPE_VALUE:
582             p_cmd = attp_build_read_by_type_value_cmd(p_tcb->payload_size, &p_msg->find_type_value);
583             break;
584 
585         case GATT_REQ_READ_MULTI:
586             p_cmd = attp_build_read_multi_cmd(p_tcb->payload_size,
587                                               p_msg->read_multi.num_handles,
588                                               p_msg->read_multi.handles);
589             break;
590 
591         default:
592             break;
593         }
594 
595         if (p_cmd != NULL)
596             status = attp_cl_send_cmd(p_tcb, clcb_idx, op_code, p_cmd);
597 
598     }
599     else
600     {
601         GATT_TRACE_ERROR("Peer device not connected");
602     }
603 
604     return status;
605 }
606 #endif
607