/** * @file * @author chipsea * @brief * @version 0.1 * @date 2020-11-30 * @copyright Copyright (c) 2020, CHIPSEA Co., Ltd. * @note */ /************************************************************************************************** Filename: gapgattserver.c Revised: Revision: Description: GAP Attribute Server **************************************************************************************************/ /********************************************************************* * INCLUDES */ #include "sdk_config.h" #include "bcomdef.h" #include "OSAL.h" #include "gap.h" #include "gapgattserver.h" #include "att.h" #include "gatt.h" #include "gatt_uuid.h" #include "gattservapp.h" /********************************************************************* * MACROS */ /********************************************************************* * CONSTANTS */ // Position of device name in attribute table #define GAP_DEVICE_NAME_POS 2 #define GAP_APPEARANCE_POS 4 #define GAP_PRIVACY_FLAG_POS 6 /********************************************************************* * TYPEDEFS */ /********************************************************************* * GLOBAL VARIABLES */ /********************************************************************* * EXTERNAL VARIABLES */ /********************************************************************* * EXTERNAL FUNCTIONS */ /********************************************************************* * LOCAL VARIABLES */ static const ggsAppCBs_t *ggs_AppCBs = NULL; #if defined ( TESTMODES ) static uint16 paramValue = 0; #endif /********************************************************************* * Profile Attributes - variables */ // GAP Service static CONST gattAttrType_t gapService = { ATT_BT_UUID_SIZE, gapServiceUUID }; // Device Name Characteristic Properties static uint8 deviceNameCharProps = GATT_PROP_READ; // Device Name attribute (0 - 248 octets) - extra octet for null-terminate char static uint8 deviceName[GAP_DEVICE_NAME_LEN+1] = { 0 }; // Appearance Characteristic Properties static uint8 appearanceCharProps = GATT_PROP_READ; // Appearance attribute (2-octet enumerated value as defined by Bluetooth Assigned Numbers document) static uint16 appearance = GAP_APPEARE_UNKNOWN; #if ( HOST_CONFIG & PERIPHERAL_CFG ) #if defined (GAP_PRIVACY) || defined (GAP_PRIVACY_RECONNECT) // Peripheral Privacy Flag Characteristic Properties static uint8 periPrivacyFlagCharProps = GATT_PROP_READ; // Peripheral Privacy Flag attribute (1 octet) static uint8 periPrivacyFlag = GAP_PRIVACY_DISABLED; #endif // GAP_PRIVACY || GAP_PRIVACY_RECONNECT #if defined (GAP_PRIVACY_RECONNECT) // Reconnection Address Characteristic Properties static uint8 reconnectAddrCharProps = GATT_PROP_WRITE; // Reconnection Address attribute (6 octets) static uint8 reconnectAddr[B_ADDR_LEN] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; #endif // GAP_PRIVACY_RECONNECT // Peripheral Preferred Connection Parameters Characteristic Properties static uint8 periConnParamCharProps = GATT_PROP_READ; // Peripheral Preferred Connection Parameters attribute (8 octets) static gapPeriConnectParams_t periConnParameters = { 80, 160, 0, 1000 }; #endif // PERIPHERAL_CFG /********************************************************************* * Profile Attributes - Table */ // GAP Attribute Table static gattAttribute_t gapAttrTbl[] = { // Generic Access Profile { { ATT_BT_UUID_SIZE, primaryServiceUUID }, /* type */ GATT_PERMIT_READ, /* permissions */ 0, /* handle */ (uint8 *)&gapService /* pValue */ }, // Characteristic Declaration { { ATT_BT_UUID_SIZE, characterUUID }, GATT_PERMIT_READ, 0, &deviceNameCharProps }, // Device Name attribute { { ATT_BT_UUID_SIZE, deviceNameUUID }, GATT_PERMIT_READ, 0, deviceName }, // Characteristic Declaration { { ATT_BT_UUID_SIZE, characterUUID }, GATT_PERMIT_READ, 0, &appearanceCharProps }, // Icon attribute { { ATT_BT_UUID_SIZE, appearanceUUID }, GATT_PERMIT_READ, 0, (uint8 *)&appearance }, #if ( HOST_CONFIG & PERIPHERAL_CFG ) #if defined (GAP_PRIVACY) || defined (GAP_PRIVACY_RECONNECT) // Characteristic Declaration { { ATT_BT_UUID_SIZE, characterUUID }, GATT_PERMIT_READ, 0, &periPrivacyFlagCharProps }, // Peripheral Privacy Flag attribute { { ATT_BT_UUID_SIZE, periPrivacyFlagUUID }, GATT_PERMIT_READ, 0, (uint8 *)&periPrivacyFlag }, #endif // GAP_PRIVACY || GAP_PRIVACY_RECONNECT #if defined (GAP_PRIVACY_RECONNECT) // Characteristic Declaration { { ATT_BT_UUID_SIZE, characterUUID }, GATT_PERMIT_READ, 0, &reconnectAddrCharProps }, // Reconnection Address attribute { { ATT_BT_UUID_SIZE, reconnectAddrUUID }, GATT_PERMIT_AUTHEN_WRITE, 0, reconnectAddr }, #endif // GAP_PRIVACY_RECONNECT // Characteristic Declaration { { ATT_BT_UUID_SIZE, characterUUID }, GATT_PERMIT_READ, 0, &periConnParamCharProps }, // Peripheral Preferred Connection Parameters attribute { { ATT_BT_UUID_SIZE, periConnParamUUID }, GATT_PERMIT_READ, 0, (uint8 *)&periConnParameters }, #endif // PERIPHERAL_CFG }; /********************************************************************* * LOCAL FUNCTIONS */ static void ggs_SetAttrWPermit( uint8 wPermit, uint8 *pPermissions, uint8 *pCharProps ); /********************************************************************* * PUBLIC FUNCTIONS */ // GGS Callback functions static uint8 ggs_ReadAttrCB( uint16 connHandle, gattAttribute_t *pAttr, uint8 *pValue, uint16 *pLen, uint16 offset, uint8 maxLen ); static bStatus_t ggs_WriteAttrCB( uint16 connHandle, gattAttribute_t *pAttr, uint8 *pValue, uint16 len, uint16 offset ); /********************************************************************* * PROFILE CALLBACKS */ // GAP Service Callbacks CONST gattServiceCBs_t gapServiceCBs = { ggs_ReadAttrCB, // Read callback function pointer ggs_WriteAttrCB, // Write callback function pointer NULL // Authorization callback function pointer }; /********************************************************************* * @fn GGS_SetParameter * * @brief Set a GAP GATT Server parameter. * * @param param - Profile parameter ID * @param len - length of data to right * @param value - pointer to data to write. This is dependent on * the parameter ID and WILL be cast to the appropriate * data type (example: data type of uint16 will be cast to * uint16 pointer). * * @return bStatus_t */ bStatus_t GGS_SetParameter( uint8 param, uint8 len, void *value ) { bStatus_t ret = SUCCESS; switch ( param ) { case GGS_DEVICE_NAME_ATT: // Always leave room for null-terminate char if ( len <= GAP_DEVICE_NAME_LEN ) { VOID osal_memset( deviceName, 0, GAP_DEVICE_NAME_LEN+1 ); VOID osal_memcpy( deviceName, value, len ); } else { ret = bleInvalidRange; } break; case GGS_APPEARANCE_ATT: if ( len == sizeof ( uint16 ) ) { appearance = *((uint16*)value); } else { ret = bleInvalidRange; } break; #if ( HOST_CONFIG & PERIPHERAL_CFG ) #if defined (GAP_PRIVACY) || defined (GAP_PRIVACY_RECONNECT) case GGS_PERI_PRIVACY_FLAG_ATT: if ( len == sizeof ( uint8 ) ) { periPrivacyFlag = *((uint8*)value); } else { ret = bleInvalidRange; } break; case GGS_PERI_PRIVACY_FLAG_PROPS: if ( len == sizeof ( uint8 ) ) { periPrivacyFlagCharProps = *((uint8*)value); } else { ret = bleInvalidRange; } break; case GGS_W_PERMIT_PRIVACY_FLAG_ATT: if ( len == sizeof ( uint8 ) ) { uint8 wPermit = *(uint8 *)value; // Optionally Writeable with Authentication if ( !gattPermitWrite( wPermit ) ) { ggs_SetAttrWPermit( wPermit, &(gapAttrTbl[GAP_PRIVACY_FLAG_POS].permissions), gapAttrTbl[GAP_PRIVACY_FLAG_POS-1].pValue ); } else { ret = bleInvalidRange; } } else { ret = bleInvalidRange; } break; #endif // GAP_PRIVACY || GAP_PRIVACY_RECONNECT #if defined (GAP_PRIVACY_RECONNECT) case GGS_RECONNCT_ADDR_ATT: if ( len == B_ADDR_LEN ) { VOID osal_memcpy( reconnectAddr, value, len ); } else { ret = bleInvalidRange; } break; #endif // GAP_PRIVACY_RECONNECT case GGS_PERI_CONN_PARAM_ATT: if ( len == sizeof(gapPeriConnectParams_t) ) { periConnParameters = *((gapPeriConnectParams_t *)(value)); } else { ret = bleInvalidRange; } break; #endif // PERIPHERAL_CFG case GGS_W_PERMIT_DEVICE_NAME_ATT: if ( len == sizeof ( uint8 ) ) { // Optionally Writeable ggs_SetAttrWPermit( *(uint8 *)value, &(gapAttrTbl[GAP_DEVICE_NAME_POS].permissions), gapAttrTbl[GAP_DEVICE_NAME_POS-1].pValue ); } else { ret = bleInvalidRange; } break; case GGS_W_PERMIT_APPEARANCE_ATT: if ( len == sizeof ( uint8 ) ) { // Optionally Writeable ggs_SetAttrWPermit( *(uint8 *)value, &(gapAttrTbl[GAP_APPEARANCE_POS].permissions), gapAttrTbl[GAP_APPEARANCE_POS-1].pValue ); } else { ret = bleInvalidRange; } break; default: ret = INVALIDPARAMETER; break; } return ( ret ); } /********************************************************************* * @fn GGS_GetParameter * * @brief Get a GAP GATT Server parameter. * * @param param - Profile parameter ID * @param value - pointer to data to put. This is dependent on * the parameter ID and WILL be cast to the appropriate * data type (example: data type of uint16 will be cast to * uint16 pointer). * * @return bStatus_t */ bStatus_t GGS_GetParameter( uint8 param, void *value ) { bStatus_t ret = SUCCESS; switch ( param ) { case GGS_DEVICE_NAME_ATT: VOID osal_memcpy( value, deviceName, GAP_DEVICE_NAME_LEN ); break; case GGS_APPEARANCE_ATT: *((uint16*)value) = appearance; break; #if ( HOST_CONFIG & PERIPHERAL_CFG ) #if defined (GAP_PRIVACY) || defined (GAP_PRIVACY_RECONNECT) case GGS_PERI_PRIVACY_FLAG_ATT: *((uint8*)value) = periPrivacyFlag; break; case GGS_PERI_PRIVACY_FLAG_PROPS: *((uint8*)value) = periPrivacyFlagCharProps; break; #endif // GAP_PRIVACY || GAP_PRIVACY_RECONNECT #if defined (GAP_PRIVACY_RECONNECT) case GGS_RECONNCT_ADDR_ATT: VOID osal_memcpy( value, reconnectAddr, B_ADDR_LEN ); break; #endif // GAP_PRIVACY_RECONNECT case GGS_PERI_CONN_PARAM_ATT: *((gapPeriConnectParams_t *)(value)) = periConnParameters; break; #endif // PERIPHERAL_CFG default: ret = INVALIDPARAMETER; break; } return ( ret ); } /********************************************************************* * @fn GGS_SetParamValue * * @brief Set a GGS Parameter value. Use this function to change * the default GGS parameter values. * * @param value - new GGS param value * * @return void */ void GGS_SetParamValue( uint16 value ) { #if defined ( TESTMODES ) uint8 wpermit; paramValue = value; switch ( value ) { case GGS_TESTMODE_OFF: wpermit = 0; VOID GGS_SetParameter( GGS_W_PERMIT_DEVICE_NAME_ATT, sizeof( uint8 ), (void*)&wpermit ); VOID GGS_SetParameter( GGS_W_PERMIT_APPEARANCE_ATT, sizeof( uint8 ), (void*)&wpermit ); VOID GGS_SetParameter( GGS_W_PERMIT_PRIVACY_FLAG_ATT, sizeof( uint8 ), (void*)&wpermit ); break; case GGS_TESTMODE_W_PERMIT_DEVICE_NAME: wpermit = GATT_PERMIT_WRITE; VOID GGS_SetParameter( GGS_W_PERMIT_DEVICE_NAME_ATT, sizeof( uint8 ), (void*)&wpermit ); break; case GGS_TESTMODE_W_PERMIT_APPEARANCE: wpermit = GATT_PERMIT_WRITE; VOID GGS_SetParameter( GGS_W_PERMIT_APPEARANCE_ATT, sizeof( uint8 ), (void*)&wpermit ); break; case GGS_TESTMODE_W_PERMIT_PRIVACY_FLAG: wpermit = GATT_PERMIT_AUTHEN_WRITE; VOID GGS_SetParameter( GGS_W_PERMIT_PRIVACY_FLAG_ATT, sizeof( uint8 ), (void*)&wpermit ); break; default: break; } #else VOID value; #endif } /********************************************************************* * @fn GGS_GetParamValue * * @brief Get a GGS Parameter value. * * @param none * * @return GGS Parameter value */ uint16 GGS_GetParamValue( void ) { #if defined ( TESTMODES ) return ( paramValue ); #else return ( 0 ); #endif } /********************************************************************* * LOCAL FUNCTION PROTOTYPES */ /********************************************************************* * @fn GGS_AddService * * @brief Add function for the GAP GATT Service. * * @param services - services to add. This is a bit map and can * contain more than one service. * * @return SUCCESS: Service added successfully. * INVALIDPARAMETER: Invalid service field. * FAILURE: Not enough attribute handles available. * bleMemAllocError: Memory allocation error occurred. */ bStatus_t GGS_AddService( uint32 services ) { uint8 status = SUCCESS; if ( services & GAP_SERVICE ) { // Register GAP attribute list and CBs with GATT Server Server App status = GATTServApp_RegisterService( gapAttrTbl, GATT_NUM_ATTRS( gapAttrTbl ), &gapServiceCBs ); } return ( status ); } /****************************************************************************** * @fn GATTServApp_DelService * * @brief Delete function for the GAP GATT Service. * * @param services - services to delete. This is a bit map and can * contain more than one service. * * @return SUCCESS: Service deleted successfully. * FAILURE: Service not found. */ bStatus_t GGS_DelService( uint32 services ) { uint8 status = SUCCESS; if ( services & GAP_SERVICE ) { // Deregister GAP attribute list and CBs from GATT Server Application status = GATTServApp_DeregisterService( GATT_SERVICE_HANDLE( gapAttrTbl ), NULL ); } return ( status ); } /********************************************************************* * @fn GGS_RegisterAppCBs * * @brief Registers the application callback function. * * Note: Callback registration is needed only when the * Device Name is made writable. The application * will be notified when the Device Name is changed * over the air. * * @param appCallbacks - pointer to application callbacks. * * @return none */ void GGS_RegisterAppCBs( ggsAppCBs_t *appCallbacks ) { ggs_AppCBs = appCallbacks; } /********************************************************************* * @fn ggs_SetAttrWPermit * * @brief Update attribute Write access permissions and characteristic * properties for over-the-air write operations. * * @param wPermit - write acces permissions * @param pPermissions - pointer to attribute permissions * @param pCharProps - pointer to characteristic properties * * @return none */ static void ggs_SetAttrWPermit( uint8 wPermit, uint8 *pPermissions, uint8 *pCharProps ) { // Update attribute Write access permissions if ( gattPermitWrite( wPermit ) ) { *pPermissions |= GATT_PERMIT_WRITE; } else { *pPermissions &= ~GATT_PERMIT_WRITE; } if ( gattPermitAuthenWrite( wPermit ) ) { *pPermissions |= GATT_PERMIT_AUTHEN_WRITE; } else { *pPermissions &= ~GATT_PERMIT_AUTHEN_WRITE; } if ( gattPermitAuthorWrite( wPermit ) ) { *pPermissions |= GATT_PERMIT_AUTHOR_WRITE; } else { *pPermissions &= ~GATT_PERMIT_AUTHOR_WRITE; } // Update attribute Write characteristic properties if ( gattPermitWrite( wPermit ) || gattPermitAuthenWrite( wPermit ) || gattPermitAuthorWrite( wPermit ) ) { *pCharProps |= (GATT_PROP_WRITE_NO_RSP | GATT_PROP_WRITE); } else if ( wPermit == 0 ) { // Attribute not Writable *pCharProps &= ~(GATT_PROP_WRITE_NO_RSP | GATT_PROP_WRITE); } } /********************************************************************* * @fn ggs_ReadAttrCB * * @brief Read an attribute. * * @param connHandle - connection message was received on * @param pAttr - pointer to attribute * @param pValue - pointer to data to be read * @param pLen - length of data to be read * @param offset - offset of the first octet to be read * @param maxLen - maximum length of data to be read * * @return Success or Failure */ static uint8 ggs_ReadAttrCB( uint16 connHandle, gattAttribute_t *pAttr, uint8 *pValue, uint16 *pLen, uint16 offset, uint8 maxLen ) { uint16 uuid; bStatus_t status = SUCCESS; VOID connHandle; // Not needed for now! // Make sure it's not a blob operation if ( offset > 0 ) { return ( ATT_ERR_ATTR_NOT_LONG ); } if ( pAttr->type.len == ATT_BT_UUID_SIZE ) { // 16-bit UUID uuid = BUILD_UINT16( pAttr->type.uuid[0], pAttr->type.uuid[1]); switch ( uuid ) { case DEVICE_NAME_UUID: { uint8 len = osal_strlen( (char *)(pAttr->pValue) ); // If the attribute value is longer than maxLen then maxLen // octets shall be included in this response. if ( len > maxLen ) { len = maxLen; } *pLen = len; VOID osal_memcpy( pValue, pAttr->pValue, len ); } break; case APPEARANCE_UUID: { uint16 value = *((uint16 *)(pAttr->pValue)); *pLen = 2; pValue[0] = LO_UINT16( value ); pValue[1] = HI_UINT16( value ); } break; case RECONNECT_ADDR_UUID: *pLen = B_ADDR_LEN; VOID osal_memcpy( pValue, pAttr->pValue, B_ADDR_LEN ); break; case PERI_PRIVACY_FLAG_UUID: *pLen = 1; *pValue = *pAttr->pValue; break; case PERI_CONN_PARAM_UUID: if ( pAttr->pValue != NULL ) { gapPeriConnectParams_t *pConnectParam = (gapPeriConnectParams_t *)(pAttr->pValue); *pLen = 8; pValue[0] = LO_UINT16( pConnectParam->intervalMin ); pValue[1] = HI_UINT16( pConnectParam->intervalMin ); pValue[2] = LO_UINT16( pConnectParam->intervalMax ); pValue[3] = HI_UINT16( pConnectParam->intervalMax ); pValue[4] = LO_UINT16( pConnectParam->latency ); pValue[5] = HI_UINT16( pConnectParam->latency ); pValue[6] = LO_UINT16( pConnectParam->timeout ); pValue[7] = HI_UINT16( pConnectParam->timeout ); } else { *pLen = 0; } break; default: // Should never get here! *pLen = 0; status = ATT_ERR_INVALID_HANDLE; break; } } else { // 128-bit UUID *pLen = 0; status = ATT_ERR_INVALID_HANDLE; } return ( status ); } /********************************************************************* * @fn ggs_ValidateWriteAttrCB * * @brief Validate and Write attribute data * * @param connHandle - connection message was received on * @param pAttr - pointer to attribute * @param pValue - pointer to data to be written * @param len - length of data * @param offset - offset of the first octet to be written * * @return Success or Failure */ static bStatus_t ggs_WriteAttrCB( uint16 connHandle, gattAttribute_t *pAttr, uint8 *pValue, uint16 len, uint16 offset ) { bStatus_t status = SUCCESS; VOID connHandle; // Not needed for now! if ( pAttr->type.len == ATT_BT_UUID_SIZE ) { // 16-bit UUID uint16 uuid = BUILD_UINT16( pAttr->type.uuid[0], pAttr->type.uuid[1]); switch ( uuid ) { case DEVICE_NAME_UUID: // Validate the long value { uint8 curLen = osal_strlen( (char *)(pAttr->pValue) ); // If the value offset is greater than the current length of the // attribute value then an Error Response shall be sent with the // error code Invalid Offset. if ( offset <= curLen ) { // Always leave room for null-terminate char if ( ( offset + len ) > GAP_DEVICE_NAME_LEN ) { // Appliction error status = ATT_ERR_INVALID_VALUE_SIZE; } } else { status = ATT_ERR_INVALID_OFFSET; } } // Write the long value if ( status == SUCCESS ) { VOID osal_memcpy( &(pAttr->pValue[offset]), pValue, len ); offset += len; pAttr->pValue[offset] = '\0'; // Notify application if ( ggs_AppCBs && ggs_AppCBs->pfnAttrValueChange ) { ggs_AppCBs->pfnAttrValueChange( GGS_DEVICE_NAME_ID ); } } break; case APPEARANCE_UUID: // Validate the value if ( offset == 0 ) { if ( len != 2 ) { status = ATT_ERR_INVALID_VALUE_SIZE; } } else { status = ATT_ERR_ATTR_NOT_LONG; } // Write the value if ( status == SUCCESS ) { uint16 *pCurValue = (uint16 *)pAttr->pValue; *pCurValue = BUILD_UINT16( pValue[0], pValue[1] ); // Notify application if ( ggs_AppCBs && ggs_AppCBs->pfnAttrValueChange ) { ggs_AppCBs->pfnAttrValueChange( GGS_APPEARANCE_ID ); } } break; case RECONNECT_ADDR_UUID: // Validate the value - writable by a bonded device #if ( HOST_CONFIG & PERIPHERAL_CFG ) #if defined (GAP_PRIVACY) || defined (GAP_PRIVACY_RECONNECT) if ( periPrivacyFlag == GAP_PRIVACY_DISABLED ) { status = ATT_ERR_WRITE_NOT_PERMITTED; } else #endif // GAP_PRIVACY || GAP_PRIVACY_RECONNECT #endif // PERIPHERAL_CFG if ( offset == 0 ) { if ( len != B_ADDR_LEN ) { status = ATT_ERR_INVALID_VALUE_SIZE; } } else { status = ATT_ERR_ATTR_NOT_LONG; } // Write the value if ( status == SUCCESS ) { VOID osal_memcpy( pAttr->pValue, pValue, B_ADDR_LEN ); } break; case PERI_PRIVACY_FLAG_UUID: // Validate the value - writable by a bonded device #if ( HOST_CONFIG & PERIPHERAL_CFG ) #if defined (GAP_PRIVACY) || defined (GAP_PRIVACY_RECONNECT) if ( (periPrivacyFlagCharProps & GATT_PROP_WRITE) == 0 ) { status = ATT_ERR_WRITE_NOT_PERMITTED; } else #endif // GAP_PRIVACY || GAP_PRIVACY_RECONNECT #endif // PERIPHERAL_CFG if ( offset == 0 ) { if ( len == 1 ) { // Validate characteristic configuration bit field if ( ( *pValue != GAP_PRIVACY_DISABLED ) && ( *pValue != GAP_PRIVACY_ENABLED ) ) { status = ATT_ERR_INVALID_VALUE; } } else { status = ATT_ERR_INVALID_VALUE_SIZE; } } else { status = ATT_ERR_ATTR_NOT_LONG; } // Write the value if ( status == SUCCESS ) { *pAttr->pValue = *pValue; } break; default: // Should never get here! status = ATT_ERR_INVALID_HANDLE; } } else { // 128-bit UUID status = ATT_ERR_INVALID_HANDLE; } return ( status ); } /********************************************************************* *********************************************************************/