Home | History | Annotate | Download | only in smp
      1 /******************************************************************************
      2  *
      3  *  Copyright (C) 2008-2012 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 the implementation of the SMP interface used by
     22  *  applications that can run over an SMP.
     23  *
     24  ******************************************************************************/
     25 #include <string.h>
     26 
     27 #include "bt_target.h"
     28 #include "bt_utils.h"
     29 #if SMP_INCLUDED == TRUE
     30     #include "smp_int.h"
     31     #include "smp_api.h"
     32     #include "l2cdefs.h"
     33     #include "l2c_int.h"
     34     #include "btm_int.h"
     35     #include "hcimsgs.h"
     36 
     37     #include "btu.h"
     38     #include "p_256_ecc_pp.h"
     39 
     40 /*******************************************************************************
     41 **
     42 ** Function         SMP_Init
     43 **
     44 ** Description      This function initializes the SMP unit.
     45 **
     46 ** Returns          void
     47 **
     48 *******************************************************************************/
     49 void SMP_Init(void)
     50 {
     51     memset(&smp_cb, 0, sizeof(tSMP_CB));
     52 
     53 #if defined(SMP_INITIAL_TRACE_LEVEL)
     54     smp_cb.trace_level = SMP_INITIAL_TRACE_LEVEL;
     55 #else
     56     smp_cb.trace_level = BT_TRACE_LEVEL_NONE;    /* No traces */
     57 #endif
     58     SMP_TRACE_EVENT ("%s", __FUNCTION__);
     59 
     60     smp_l2cap_if_init();
     61     /* initialization of P-256 parameters */
     62     p_256_init_curve(KEY_LENGTH_DWORDS_P256);
     63 }
     64 
     65 
     66 /*******************************************************************************
     67 **
     68 ** Function         SMP_SetTraceLevel
     69 **
     70 ** Description      This function sets the trace level for SMP.  If called with
     71 **                  a value of 0xFF, it simply returns the current trace level.
     72 **
     73 **                  Input Parameters:
     74 **                      level:  The level to set the GATT tracing to:
     75 **                      0xff-returns the current setting.
     76 **                      0-turns off tracing.
     77 **                      >= 1-Errors.
     78 **                      >= 2-Warnings.
     79 **                      >= 3-APIs.
     80 **                      >= 4-Events.
     81 **                      >= 5-Debug.
     82 **
     83 ** Returns          The new or current trace level
     84 **
     85 *******************************************************************************/
     86 extern UINT8 SMP_SetTraceLevel (UINT8 new_level)
     87 {
     88     if (new_level != 0xFF)
     89         smp_cb.trace_level = new_level;
     90 
     91     return(smp_cb.trace_level);
     92 }
     93 
     94 
     95 /*******************************************************************************
     96 **
     97 ** Function         SMP_Register
     98 **
     99 ** Description      This function register for the SMP services callback.
    100 **
    101 ** Returns          void
    102 **
    103 *******************************************************************************/
    104 BOOLEAN SMP_Register (tSMP_CALLBACK *p_cback)
    105 {
    106     SMP_TRACE_EVENT ("SMP_Register state=%d", smp_cb.state);
    107 
    108     if (smp_cb.p_callback != NULL)
    109     {
    110         SMP_TRACE_ERROR ("SMP_Register: duplicate registration, overwrite it");
    111     }
    112     smp_cb.p_callback = p_cback;
    113 
    114     return(TRUE);
    115 
    116 }
    117 
    118 /*******************************************************************************
    119 **
    120 ** Function         SMP_Pair
    121 **
    122 ** Description      This function call to perform a SMP pairing with peer device.
    123 **                  Device support one SMP pairing at one time.
    124 **
    125 ** Parameters       bd_addr - peer device bd address.
    126 **
    127 ** Returns          None
    128 **
    129 *******************************************************************************/
    130 tSMP_STATUS SMP_Pair (BD_ADDR bd_addr)
    131 {
    132     tSMP_CB   *p_cb = &smp_cb;
    133     UINT8     status = SMP_PAIR_INTERNAL_ERR;
    134 
    135     SMP_TRACE_EVENT ("%s state=%d br_state=%d flag=0x%x ",
    136                       __FUNCTION__, p_cb->state, p_cb->br_state, p_cb->flags);
    137     if (p_cb->state != SMP_STATE_IDLE || p_cb->flags & SMP_PAIR_FLAGS_WE_STARTED_DD ||
    138         p_cb->smp_over_br)
    139     {
    140         /* pending security on going, reject this one */
    141         return SMP_BUSY;
    142     }
    143     else
    144     {
    145         p_cb->flags = SMP_PAIR_FLAGS_WE_STARTED_DD;
    146 
    147         memcpy (p_cb->pairing_bda, bd_addr, BD_ADDR_LEN);
    148 
    149         if (!L2CA_ConnectFixedChnl (L2CAP_SMP_CID, bd_addr))
    150         {
    151             SMP_TRACE_ERROR("%s: L2C connect fixed channel failed.", __FUNCTION__);
    152             smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &status);
    153             return status;
    154         }
    155 
    156         return SMP_STARTED;
    157     }
    158 }
    159 
    160 /*******************************************************************************
    161 **
    162 ** Function         SMP_BR_PairWith
    163 **
    164 ** Description      This function is called to start a SMP pairing over BR/EDR.
    165 **                  Device support one SMP pairing at one time.
    166 **
    167 ** Parameters       bd_addr - peer device bd address.
    168 **
    169 ** Returns          SMP_STARTED if pairing started, otherwise reason for failure.
    170 **
    171 *******************************************************************************/
    172 tSMP_STATUS SMP_BR_PairWith (BD_ADDR bd_addr)
    173 {
    174     tSMP_CB   *p_cb = &smp_cb;
    175     UINT8     status = SMP_PAIR_INTERNAL_ERR;
    176 
    177     SMP_TRACE_EVENT ("%s state=%d br_state=%d flag=0x%x ",
    178                       __func__, p_cb->state, p_cb->br_state, p_cb->flags);
    179 
    180     if (p_cb->state != SMP_STATE_IDLE ||
    181         p_cb->smp_over_br ||
    182         p_cb->flags & SMP_PAIR_FLAGS_WE_STARTED_DD)
    183     {
    184         /* pending security on going, reject this one */
    185         return SMP_BUSY;
    186     }
    187 
    188     p_cb->role = HCI_ROLE_MASTER;
    189     p_cb->flags = SMP_PAIR_FLAGS_WE_STARTED_DD;
    190     p_cb->smp_over_br = TRUE;
    191 
    192     memcpy (p_cb->pairing_bda, bd_addr, BD_ADDR_LEN);
    193 
    194     if (!L2CA_ConnectFixedChnl (L2CAP_SMP_BR_CID, bd_addr))
    195     {
    196         SMP_TRACE_ERROR("%s: L2C connect fixed channel failed.",__FUNCTION__);
    197         smp_br_state_machine_event(p_cb, SMP_BR_AUTH_CMPL_EVT, &status);
    198         return status;
    199     }
    200 
    201     return SMP_STARTED;
    202 }
    203 
    204 /*******************************************************************************
    205 **
    206 ** Function         SMP_PairCancel
    207 **
    208 ** Description      This function call to cancel a SMP pairing with peer device.
    209 **
    210 ** Parameters       bd_addr - peer device bd address.
    211 **
    212 ** Returns          TRUE - Pairining is cancelled
    213 **
    214 *******************************************************************************/
    215 BOOLEAN SMP_PairCancel (BD_ADDR bd_addr)
    216 {
    217     tSMP_CB   *p_cb = &smp_cb;
    218     UINT8     err_code = SMP_PAIR_FAIL_UNKNOWN;
    219     BOOLEAN   status = FALSE;
    220 
    221     BTM_TRACE_EVENT ("SMP_CancelPair state=%d flag=0x%x ", p_cb->state, p_cb->flags);
    222     if ( (p_cb->state != SMP_STATE_IDLE)  &&
    223          (!memcmp (p_cb->pairing_bda, bd_addr, BD_ADDR_LEN)) )
    224     {
    225         p_cb->is_pair_cancel = TRUE;
    226         SMP_TRACE_DEBUG("Cancel Pairing: set fail reason Unknown");
    227         smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &err_code);
    228         status = TRUE;
    229     }
    230 
    231     return status;
    232 }
    233 /*******************************************************************************
    234 **
    235 ** Function         SMP_SecurityGrant
    236 **
    237 ** Description      This function is called to grant security process.
    238 **
    239 ** Parameters       bd_addr - peer device bd address.
    240 **                  res     - result of the operation SMP_SUCCESS if success.
    241 **                            Otherwise, SMP_REPEATED_ATTEMPTS is too many attempts.
    242 **
    243 ** Returns          None
    244 **
    245 *******************************************************************************/
    246 void SMP_SecurityGrant(BD_ADDR bd_addr, UINT8 res)
    247 {
    248     SMP_TRACE_EVENT ("SMP_SecurityGrant ");
    249 
    250     if (smp_cb.smp_over_br)
    251     {
    252         if (smp_cb.br_state != SMP_BR_STATE_WAIT_APP_RSP ||
    253             smp_cb.cb_evt != SMP_SEC_REQUEST_EVT ||
    254             memcmp (smp_cb.pairing_bda, bd_addr, BD_ADDR_LEN))
    255         {
    256             return;
    257         }
    258 
    259         /* clear the SMP_SEC_REQUEST_EVT event after get grant */
    260         /* avoid generating duplicate pair request */
    261         smp_cb.cb_evt = 0;
    262         smp_br_state_machine_event(&smp_cb, SMP_BR_API_SEC_GRANT_EVT, &res);
    263         return;
    264     }
    265 
    266     if (smp_cb.state != SMP_STATE_WAIT_APP_RSP ||
    267         smp_cb.cb_evt != SMP_SEC_REQUEST_EVT ||
    268         memcmp (smp_cb.pairing_bda, bd_addr, BD_ADDR_LEN))
    269         return;
    270     /* clear the SMP_SEC_REQUEST_EVT event after get grant */
    271     /* avoid generate duplicate pair request */
    272     smp_cb.cb_evt = 0;
    273     smp_sm_event(&smp_cb, SMP_API_SEC_GRANT_EVT, &res);
    274 }
    275 
    276 /*******************************************************************************
    277 **
    278 ** Function         SMP_PasskeyReply
    279 **
    280 ** Description      This function is called after Security Manager submitted
    281 **                  passkey request to the application.
    282 **
    283 ** Parameters:      bd_addr      - Address of the device for which passkey was requested
    284 **                  res          - result of the operation SMP_SUCCESS if success
    285 **                  passkey - numeric value in the range of
    286 **                  BTM_MIN_PASSKEY_VAL(0) - BTM_MAX_PASSKEY_VAL(999999(0xF423F)).
    287 **
    288 *******************************************************************************/
    289 void SMP_PasskeyReply (BD_ADDR bd_addr, UINT8 res, UINT32 passkey)
    290 {
    291     tSMP_CB *p_cb = & smp_cb;
    292     UINT8   failure = SMP_PASSKEY_ENTRY_FAIL;
    293 
    294     SMP_TRACE_EVENT ("SMP_PasskeyReply: Key: %d  Result:%d",
    295                       passkey, res);
    296 
    297     /* If timeout already expired or has been canceled, ignore the reply */
    298     if (p_cb->cb_evt != SMP_PASSKEY_REQ_EVT)
    299     {
    300         SMP_TRACE_WARNING ("SMP_PasskeyReply() - Wrong State: %d", p_cb->state);
    301         return;
    302     }
    303 
    304     if (memcmp (bd_addr, p_cb->pairing_bda, BD_ADDR_LEN) != 0)
    305     {
    306         SMP_TRACE_ERROR ("SMP_PasskeyReply() - Wrong BD Addr");
    307         return;
    308     }
    309 
    310     if (btm_find_dev (bd_addr) == NULL)
    311     {
    312         SMP_TRACE_ERROR ("SMP_PasskeyReply() - no dev CB");
    313         return;
    314     }
    315 
    316     if (passkey > BTM_MAX_PASSKEY_VAL || res != SMP_SUCCESS)
    317     {
    318         SMP_TRACE_WARNING ("SMP_PasskeyReply() - Wrong key len: %d or passkey entry fail", passkey);
    319         /* send pairing failure */
    320         smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &failure);
    321 
    322     }
    323     else if (p_cb->selected_association_model == SMP_MODEL_SEC_CONN_PASSKEY_ENT)
    324     {
    325         smp_sm_event(&smp_cb, SMP_SC_KEY_READY_EVT, &passkey);
    326     }
    327     else
    328     {
    329         smp_convert_string_to_tk(p_cb->tk, passkey);
    330     }
    331 
    332     return;
    333 }
    334 
    335 /*******************************************************************************
    336 **
    337 ** Function         SMP_ConfirmReply
    338 **
    339 ** Description      This function is called after Security Manager submitted
    340 **                  numeric comparison request to the application.
    341 **
    342 ** Parameters:      bd_addr      - Address of the device with which numeric
    343 **                                 comparison was requested
    344 **                  res          - comparison result SMP_SUCCESS if success
    345 **
    346 *******************************************************************************/
    347 void SMP_ConfirmReply (BD_ADDR bd_addr, UINT8 res)
    348 {
    349     tSMP_CB *p_cb = & smp_cb;
    350     UINT8   failure = SMP_NUMERIC_COMPAR_FAIL;
    351 
    352     SMP_TRACE_EVENT ("%s: Result:%d", __FUNCTION__, res);
    353 
    354     /* If timeout already expired or has been canceled, ignore the reply */
    355     if (p_cb->cb_evt != SMP_NC_REQ_EVT)
    356     {
    357         SMP_TRACE_WARNING ("%s() - Wrong State: %d", __FUNCTION__,p_cb->state);
    358         return;
    359     }
    360 
    361     if (memcmp (bd_addr, p_cb->pairing_bda, BD_ADDR_LEN) != 0)
    362     {
    363         SMP_TRACE_ERROR ("%s() - Wrong BD Addr",__FUNCTION__);
    364         return;
    365     }
    366 
    367     if (btm_find_dev (bd_addr) == NULL)
    368     {
    369         SMP_TRACE_ERROR ("%s() - no dev CB",__FUNCTION__);
    370         return;
    371     }
    372 
    373     if (res != SMP_SUCCESS)
    374     {
    375         SMP_TRACE_WARNING ("%s() - Numeric Comparison fails",__FUNCTION__);
    376         /* send pairing failure */
    377         smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &failure);
    378     }
    379     else
    380     {
    381         smp_sm_event(p_cb, SMP_SC_NC_OK_EVT, NULL);
    382     }
    383 }
    384 
    385 /*******************************************************************************
    386 **
    387 ** Function         SMP_OobDataReply
    388 **
    389 ** Description      This function is called to provide the OOB data for
    390 **                  SMP in response to SMP_OOB_REQ_EVT
    391 **
    392 ** Parameters:      bd_addr     - Address of the peer device
    393 **                  res         - result of the operation SMP_SUCCESS if success
    394 **                  p_data      - simple pairing Randomizer  C.
    395 **
    396 *******************************************************************************/
    397 void SMP_OobDataReply(BD_ADDR bd_addr, tSMP_STATUS res, UINT8 len, UINT8 *p_data)
    398 {
    399     tSMP_CB *p_cb = & smp_cb;
    400     UINT8   failure = SMP_OOB_FAIL;
    401     tSMP_KEY        key;
    402 
    403     SMP_TRACE_EVENT ("%s State: %d  res:%d", __FUNCTION__, smp_cb.state, res);
    404 
    405     /* If timeout already expired or has been canceled, ignore the reply */
    406     if (p_cb->state != SMP_STATE_WAIT_APP_RSP || p_cb->cb_evt != SMP_OOB_REQ_EVT)
    407         return;
    408 
    409     if (res != SMP_SUCCESS || len == 0 || !p_data)
    410     {
    411         smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &failure);
    412     }
    413     else
    414     {
    415         if (len > BT_OCTET16_LEN)
    416             len = BT_OCTET16_LEN;
    417 
    418         memcpy(p_cb->tk, p_data, len);
    419 
    420         key.key_type    = SMP_KEY_TYPE_TK;
    421         key.p_data      = p_cb->tk;
    422 
    423         smp_sm_event(&smp_cb, SMP_KEY_READY_EVT, &key);
    424     }
    425 }
    426 
    427 /*******************************************************************************
    428 **
    429 ** Function         SMP_SecureConnectionOobDataReply
    430 **
    431 ** Description      This function is called to provide the SC OOB data for
    432 **                  SMP in response to SMP_SC_OOB_REQ_EVT
    433 **
    434 ** Parameters:      p_data      - pointer to the data
    435 **
    436 *******************************************************************************/
    437 void SMP_SecureConnectionOobDataReply(UINT8 *p_data)
    438 {
    439     tSMP_CB  *p_cb = &smp_cb;
    440 
    441     UINT8  failure = SMP_OOB_FAIL;
    442     tSMP_SC_OOB_DATA  *p_oob = (tSMP_SC_OOB_DATA *) p_data;
    443     if (!p_oob)
    444     {
    445         SMP_TRACE_ERROR("%s received no data",__FUNCTION__);
    446         smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &failure);
    447         return;
    448     }
    449 
    450     SMP_TRACE_EVENT ("%s req_oob_type: %d, loc_oob_data.present: %d, "
    451                        "peer_oob_data.present: %d",
    452                        __FUNCTION__, p_cb->req_oob_type, p_oob->loc_oob_data.present,
    453                        p_oob->peer_oob_data.present);
    454 
    455     if (p_cb->state != SMP_STATE_WAIT_APP_RSP || p_cb->cb_evt != SMP_SC_OOB_REQ_EVT)
    456         return;
    457 
    458     BOOLEAN  data_missing = FALSE;
    459     switch (p_cb->req_oob_type)
    460     {
    461         case SMP_OOB_PEER:
    462             if (!p_oob->peer_oob_data.present)
    463                 data_missing = TRUE;
    464             break;
    465         case SMP_OOB_LOCAL:
    466             if (!p_oob->loc_oob_data.present)
    467                 data_missing = TRUE;
    468             break;
    469         case SMP_OOB_BOTH:
    470             if (!p_oob->loc_oob_data.present || !p_oob->peer_oob_data.present)
    471                 data_missing = TRUE;
    472             break;
    473         default:
    474             SMP_TRACE_EVENT ("Unexpected OOB data type requested. Fail OOB");
    475             data_missing = TRUE;
    476             break;
    477     }
    478 
    479     if (data_missing)
    480     {
    481         smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &failure);
    482         return;
    483     }
    484 
    485     p_cb->sc_oob_data = *p_oob;
    486 
    487     smp_sm_event(&smp_cb, SMP_SC_OOB_DATA_EVT, p_data);
    488 }
    489 
    490 /*******************************************************************************
    491 **
    492 ** Function         SMP_Encrypt
    493 **
    494 ** Description      This function is called to encrypt the data with the specified
    495 **                  key
    496 **
    497 ** Parameters:      key                 - Pointer to key key[0] conatins the MSB
    498 **                  key_len             - key length
    499 **                  plain_text          - Pointer to data to be encrypted
    500 **                                        plain_text[0] conatins the MSB
    501 **                  pt_len              - plain text length
    502 **                  p_out                - output of the encrypted texts
    503 **
    504 **  Returns         Boolean - request is successful
    505 *******************************************************************************/
    506 BOOLEAN SMP_Encrypt (UINT8 *key, UINT8 key_len,
    507                      UINT8 *plain_text, UINT8 pt_len,
    508                      tSMP_ENC *p_out)
    509 
    510 {
    511     BOOLEAN status=FALSE;
    512     status = smp_encrypt_data(key, key_len, plain_text, pt_len, p_out);
    513     return status;
    514 }
    515 
    516 /*******************************************************************************
    517 **
    518 ** Function         SMP_KeypressNotification
    519 **
    520 ** Description      This function is called to notify Security Manager about Keypress Notification.
    521 **
    522 ** Parameters:     bd_addr      Address of the device to send keypress notification to
    523 **                 value        Keypress notification parameter value
    524 **
    525 *******************************************************************************/
    526 void SMP_KeypressNotification (BD_ADDR bd_addr, UINT8 value)
    527 {
    528     tSMP_CB   *p_cb = &smp_cb;
    529 
    530     SMP_TRACE_EVENT ("%s: Value: %d", __FUNCTION__,value);
    531 
    532     if (memcmp (bd_addr, p_cb->pairing_bda, BD_ADDR_LEN) != 0)
    533     {
    534         SMP_TRACE_ERROR ("%s() - Wrong BD Addr",__FUNCTION__);
    535         return;
    536     }
    537 
    538     if (btm_find_dev (bd_addr) == NULL)
    539     {
    540         SMP_TRACE_ERROR ("%s() - no dev CB",__FUNCTION__);
    541         return;
    542     }
    543 
    544     /* Keypress Notification is used by a device with KeyboardOnly IO capabilities */
    545     /* during the passkey entry protocol */
    546     if (p_cb->local_io_capability != SMP_IO_CAP_IN)
    547     {
    548         SMP_TRACE_ERROR ("%s() - wrong local IO capabilities %d",
    549                           __FUNCTION__, p_cb->local_io_capability);
    550         return;
    551     }
    552 
    553     if (p_cb->selected_association_model != SMP_MODEL_SEC_CONN_PASSKEY_ENT)
    554     {
    555         SMP_TRACE_ERROR ("%s() - wrong protocol %d", __FUNCTION__,
    556                          p_cb->selected_association_model);
    557         return;
    558     }
    559 
    560     smp_sm_event(p_cb, SMP_KEYPRESS_NOTIFICATION_EVENT, &value);
    561 }
    562 
    563 /*******************************************************************************
    564 **
    565 ** Function         SMP_CreateLocalSecureConnectionsOobData
    566 **
    567 ** Description      This function is called to start creation of local SC OOB
    568 **                  data set (tSMP_LOC_OOB_DATA).
    569 **
    570 ** Parameters:      bd_addr      - Address of the device to send OOB data block to
    571 **
    572 **  Returns         Boolean - TRUE: creation of local SC OOB data set started.
    573 *******************************************************************************/
    574 BOOLEAN SMP_CreateLocalSecureConnectionsOobData (tBLE_BD_ADDR *addr_to_send_to)
    575 {
    576     tSMP_CB *p_cb = &smp_cb;
    577     UINT8   *bd_addr;
    578 
    579     if (addr_to_send_to == NULL)
    580     {
    581         SMP_TRACE_ERROR ("%s addr_to_send_to is not provided",__FUNCTION__);
    582         return FALSE;
    583     }
    584 
    585     bd_addr = addr_to_send_to->bda;
    586 
    587     SMP_TRACE_EVENT ("%s addr type: %u,  BDA: %08x%04x,  state: %u, br_state: %u",
    588                       __FUNCTION__, addr_to_send_to->type,
    589                       (bd_addr[0]<<24)+(bd_addr[1]<<16)+(bd_addr[2]<<8) + bd_addr[3],
    590                       (bd_addr[4]<<8)+bd_addr[5],
    591                       p_cb->state,
    592                       p_cb->br_state);
    593 
    594     if ((p_cb->state != SMP_STATE_IDLE) || (p_cb->smp_over_br))
    595     {
    596         SMP_TRACE_WARNING ("%s creation of local OOB data set "\
    597             "starts only in IDLE state",__FUNCTION__);
    598         return FALSE;
    599     }
    600 
    601     p_cb->sc_oob_data.loc_oob_data.addr_sent_to = *addr_to_send_to;
    602     smp_sm_event(p_cb, SMP_CR_LOC_SC_OOB_DATA_EVT, NULL);
    603 
    604     return TRUE;
    605 }
    606 
    607 #endif /* SMP_INCLUDED */
    608