/*
 * NimBLEAdvertisedDevice.cpp
 *
 *  Created: on Jan 24 2020
 *      Author H2zero
 *
 * Originally:
 *
 *  BLEAdvertisedDevice.cpp
 *
 *  Created on: Jul 3, 2017
 *      Author: kolban
 */

#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)

#include "NimBLEDevice.h"
#include "NimBLEAdvertisedDevice.h"
#include "NimBLEUtils.h"
#include "NimBLELog.h"

#include <climits>

static const char* LOG_TAG = "NimBLEAdvertisedDevice";


/**
 * @brief Constructor
 */
NimBLEAdvertisedDevice::NimBLEAdvertisedDevice() :
    m_payload(62,0)
{
    m_advType          = 0;
    m_rssi             = -9999;
    m_callbackSent     = false;
    m_timestamp        = 0;
    m_advLength        = 0;
} // NimBLEAdvertisedDevice


/**
 * @brief Get the address of the advertising device.
 * @return The address of the advertised device.
 */
NimBLEAddress NimBLEAdvertisedDevice::getAddress() {
    return m_address;
} // getAddress


/**
 * @brief Get the advertisement type.
 * @return The advertising type the device is reporting:
 * * BLE_HCI_ADV_TYPE_ADV_IND            (0) - indirect advertising
 * * BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_HD  (1) - direct advertising - high duty cycle
 * * BLE_HCI_ADV_TYPE_ADV_SCAN_IND       (2) - indirect scan response
 * * BLE_HCI_ADV_TYPE_ADV_NONCONN_IND    (3) - indirect advertising - not connectable
 * * BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_LD  (4) - direct advertising - low duty cycle
 */
uint8_t NimBLEAdvertisedDevice::getAdvType() {
    return m_advType;
} // getAdvType


/**
 * @brief Get the advertisement flags.
 * @return The advertisement flags, a bitmask of:
 * BLE_HS_ADV_F_DISC_LTD (0x01) - limited discoverability
 * BLE_HS_ADV_F_DISC_GEN (0x02) - general discoverability
 * BLE_HS_ADV_F_BREDR_UNSUP - BR/EDR not supported
 */
uint8_t NimBLEAdvertisedDevice::getAdvFlags() {
    size_t data_loc = 0;

    if(findAdvField(BLE_HS_ADV_TYPE_FLAGS, 0, &data_loc) > 0) {
        ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc];
        if(field->length == BLE_HS_ADV_FLAGS_LEN + 1) {
            return *field->value;
        }
    }
    return 0;
} // getAdvFlags

/**
 * @brief Get the appearance.
 *
 * A %BLE device can declare its own appearance.  The appearance is how it would like to be shown to an end user
 * typically in the form of an icon.
 *
 * @return The appearance of the advertised device.
 */
uint16_t NimBLEAdvertisedDevice::getAppearance() {
    size_t data_loc = 0;

    if(findAdvField(BLE_HS_ADV_TYPE_APPEARANCE, 0, &data_loc) > 0) {
        ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc];
        if(field->length == BLE_HS_ADV_APPEARANCE_LEN + 1) {
            return *field->value | *(field->value + 1) << 8;
        }
    }

    return 0;
} // getAppearance


/**
 * @brief Get the advertisement interval.
 * @return The advertisement interval in 0.625ms units.
 */
uint16_t NimBLEAdvertisedDevice::getAdvInterval() {
    size_t data_loc = 0;

    if(findAdvField(BLE_HS_ADV_TYPE_ADV_ITVL, 0, &data_loc) > 0) {
        ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc];
        if(field->length == BLE_HS_ADV_ADV_ITVL_LEN + 1) {
            return *field->value | *(field->value + 1) << 8;
        }
    }

    return 0;
} // getAdvInterval


/**
 * @brief Get the preferred min connection interval.
 * @return The preferred min connection interval in 1.25ms units.
 */
uint16_t NimBLEAdvertisedDevice::getMinInterval() {
    size_t data_loc = 0;

    if(findAdvField(BLE_HS_ADV_TYPE_SLAVE_ITVL_RANGE, 0, &data_loc) > 0) {
        ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc];
        if(field->length == BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN + 1) {
            return *field->value | *(field->value + 1) << 8;
        }
    }

    return 0;
} // getMinInterval


/**
 * @brief Get the preferred max connection interval.
 * @return The preferred max connection interval in 1.25ms units.
 */
uint16_t NimBLEAdvertisedDevice::getMaxInterval() {
    size_t data_loc = 0;

    if(findAdvField(BLE_HS_ADV_TYPE_SLAVE_ITVL_RANGE, 0, &data_loc) > 0) {
        ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc];
        if(field->length == BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN + 1) {
            return *(field->value + 2) | *(field->value + 3) << 8;
        }
    }

    return 0;
} // getMaxInterval


/**
 * @brief Get the manufacturer data.
 * @param [in] index The index of the of the manufacturer data set to get.
 * @return The manufacturer data.
 */
std::string NimBLEAdvertisedDevice::getManufacturerData(uint8_t index) {
    size_t data_loc = 0;
    index++;

    if(findAdvField(BLE_HS_ADV_TYPE_MFG_DATA, index, &data_loc) > 0) {
        ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc];
        if(field->length > 1) {
            return std::string((char*)field->value, field->length - 1);
        }
    }

    return "";
} // getManufacturerData


/**
 * @brief Get the count of manufacturer data sets.
 * @return The number of manufacturer data sets.
 */
uint8_t NimBLEAdvertisedDevice::getManufacturerDataCount() {
    return findAdvField(BLE_HS_ADV_TYPE_MFG_DATA);
} // getManufacturerDataCount


/**
 * @brief Get the URI from the advertisement.
 * @return The URI data.
 */
std::string NimBLEAdvertisedDevice::getURI() {
    size_t data_loc = 0;

    if(findAdvField(BLE_HS_ADV_TYPE_URI, 0, &data_loc) > 0) {
        ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc];
        if(field->length > 1) {
            return std::string((char*)field->value, field->length - 1);
        }
    }

    return "";
} // getURI

/**
 * @brief Get the data from any type available in the advertisement
 * @param [in] type The advertised data type BLE_HS_ADV_TYPE
 * @return The data available under the type `type`
*/
std::string NimBLEAdvertisedDevice::getPayloadByType(uint16_t type) {
    size_t data_loc = 0;

    if(findAdvField(type, 0, &data_loc) > 0) {
        ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc];
        if(field->length > 1) {
            return std::string((char*)field->value, field->length - 1);
        }
    }

    return "";
} // getPayloadByType


/**
 * @brief Get the advertised name.
 * @return The name of the advertised device.
 */
std::string NimBLEAdvertisedDevice::getName() {
    size_t data_loc = 0;

    if(findAdvField(BLE_HS_ADV_TYPE_COMP_NAME, 0, &data_loc) > 0 ||
       findAdvField(BLE_HS_ADV_TYPE_INCOMP_NAME, 0, &data_loc) > 0)
    {
        ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc];
        if(field->length > 1) {
            return std::string((char*)field->value, field->length - 1);
        }
    }

    return "";
} // getName


/**
 * @brief Get the RSSI.
 * @return The RSSI of the advertised device.
 */
int NimBLEAdvertisedDevice::getRSSI() {
    return m_rssi;
} // getRSSI


/**
 * @brief Get the scan object that created this advertised device.
 * @return The scan object.
 */
NimBLEScan* NimBLEAdvertisedDevice::getScan() {
    return NimBLEDevice::getScan();
} // getScan


/**
 * @brief Get the number of target addresses.
 * @return The number of addresses.
 */
uint8_t NimBLEAdvertisedDevice::getTargetAddressCount() {
    uint8_t count = 0;

    count = findAdvField(BLE_HS_ADV_TYPE_PUBLIC_TGT_ADDR);
    count += findAdvField(BLE_HS_ADV_TYPE_RANDOM_TGT_ADDR);

    return count;
}


/**
 * @brief Get the target address at the index.
 * @param [in] index The index of the target address.
 * @return The target address.
 */
NimBLEAddress NimBLEAdvertisedDevice::getTargetAddress(uint8_t index) {
    ble_hs_adv_field *field = nullptr;
    uint8_t count = 0;
    size_t data_loc = ULONG_MAX;

    index++;
    count = findAdvField(BLE_HS_ADV_TYPE_PUBLIC_TGT_ADDR, index, &data_loc);

    if (count < index) {
        index -= count;
        count = findAdvField(BLE_HS_ADV_TYPE_RANDOM_TGT_ADDR, index, &data_loc);
    }

    if(count > 0 && data_loc != ULONG_MAX) {
        field = (ble_hs_adv_field *)&m_payload[data_loc];
        if(field->length < index *  BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN) {
            index -= count - field->length / BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN;
        }
        if(field->length > index * BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN) {
            return NimBLEAddress(field->value + (index - 1) * BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN);
        }
    }

    return NimBLEAddress("");
}


/**
 * @brief Get the service data.
 * @param [in] index The index of the service data requested.
 * @return The advertised service data or empty string if no data.
 */
std::string NimBLEAdvertisedDevice::getServiceData(uint8_t index) {
    ble_hs_adv_field *field = nullptr;
    uint8_t bytes;
    size_t data_loc = findServiceData(index, &bytes);

    if(data_loc != ULONG_MAX) {
        field = (ble_hs_adv_field *)&m_payload[data_loc];
        if(field->length > bytes) {
            return std::string((char*)(field->value + bytes), field->length - bytes - 1);
        }
    }

    return "";
} //getServiceData


/**
 * @brief Get the service data.
 * @param [in] uuid The uuid of the service data requested.
 * @return The advertised service data or empty string if no data.
 */
std::string NimBLEAdvertisedDevice::getServiceData(const NimBLEUUID &uuid) {
    ble_hs_adv_field *field = nullptr;
    uint8_t bytes;
    uint8_t index = 0;
    size_t  data_loc = findServiceData(index, &bytes);
    size_t  plSize = m_payload.size() - 2;
    uint8_t uuidBytes = uuid.bitSize() / 8;

    while(data_loc < plSize) {
        field = (ble_hs_adv_field *)&m_payload[data_loc];
        if(bytes == uuidBytes && NimBLEUUID(field->value, bytes, false) == uuid) {
           return std::string((char*)(field->value + bytes), field->length - bytes - 1);
        }

        index++;
        data_loc = findServiceData(index, &bytes);
    }

    NIMBLE_LOGI(LOG_TAG, "No service data found");
    return "";
} //getServiceData


/**
 * @brief Get the UUID of the service data at the index.
 * @param [in] index The index of the service data UUID requested.
 * @return The advertised service data UUID or an empty UUID if not found.
 */
NimBLEUUID NimBLEAdvertisedDevice::getServiceDataUUID(uint8_t index) {
    ble_hs_adv_field *field = nullptr;
    uint8_t bytes;
    size_t data_loc = findServiceData(index, &bytes);

    if(data_loc != ULONG_MAX) {
        field = (ble_hs_adv_field *)&m_payload[data_loc];
        if(field->length >= bytes) {
            return NimBLEUUID(field->value, bytes, false);
        }
    }

    return NimBLEUUID("");
} // getServiceDataUUID


/**
 * @brief Find the service data at the index.
 * @param [in] index The index of the service data to find.
 * @param [in] bytes A pointer to storage for the number of the bytes in the UUID.
 * @return The index in the vector where the data is located, ULONG_MAX if not found.
 */
size_t NimBLEAdvertisedDevice::findServiceData(uint8_t index, uint8_t *bytes) {
    size_t data_loc = 0;
    uint8_t found = 0;

    *bytes = 0;
    index++;
    found = findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID16, index, &data_loc);
    if(found == index) {
        *bytes = 2;
        return data_loc;
    }

    index -= found;
    found = findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID32, index, &data_loc);
    if(found == index) {
        *bytes = 4;
        return data_loc;
    }

    index -= found;
    found = findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID128, index, &data_loc);
    if(found == index) {
        *bytes = 16;
        return data_loc;
    }

    return ULONG_MAX;
}


/**
 * @brief Get the count of advertised service data UUIDS
 * @return The number of service data UUIDS in the vector.
 */
uint8_t NimBLEAdvertisedDevice::getServiceDataCount() {
    uint8_t count = 0;

    count += findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID16);
    count += findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID32);
    count += findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID128);

    return count;
} // getServiceDataCount


/**
 * @brief Get the Service UUID.
 * @param [in] index The index of the service UUID requested.
 * @return The Service UUID of the advertised service, or an empty UUID if not found.
 */
NimBLEUUID NimBLEAdvertisedDevice::getServiceUUID(uint8_t index) {
    uint8_t count = 0;
    size_t data_loc = 0;
    uint8_t uuidBytes = 0;
    uint8_t type = BLE_HS_ADV_TYPE_INCOMP_UUIDS16;
    ble_hs_adv_field *field = nullptr;

    index++;

    do {
        count = findAdvField(type, index, &data_loc);
        if(count >= index) {
            if(type < BLE_HS_ADV_TYPE_INCOMP_UUIDS32) {
                uuidBytes = 2;
            } else if(type < BLE_HS_ADV_TYPE_INCOMP_UUIDS128) {
                uuidBytes = 4;
            } else {
                uuidBytes = 16;
            }
            break;

        } else {
            type++;
            index -= count;
        }

    } while(type <= BLE_HS_ADV_TYPE_COMP_UUIDS128);

    if(uuidBytes > 0) {
        field = (ble_hs_adv_field *)&m_payload[data_loc];
        // In the case of more than one field of service uuid's we need to adjust
        // the index to account for the uuids of the previous fields.
        if(field->length < index * uuidBytes) {
            index -= count - field->length / uuidBytes;
        }

        if(field->length > uuidBytes * index) {
            return NimBLEUUID(field->value + uuidBytes * (index - 1), uuidBytes, false);
        }
    }

    return NimBLEUUID("");
} // getServiceUUID


/**
 * @brief Get the number of services advertised
 * @return The count of services in the advertising packet.
 */
uint8_t NimBLEAdvertisedDevice::getServiceUUIDCount() {
    uint8_t count = 0;

    count += findAdvField(BLE_HS_ADV_TYPE_INCOMP_UUIDS16);
    count += findAdvField(BLE_HS_ADV_TYPE_COMP_UUIDS16);
    count += findAdvField(BLE_HS_ADV_TYPE_INCOMP_UUIDS32);
    count += findAdvField(BLE_HS_ADV_TYPE_COMP_UUIDS32);
    count += findAdvField(BLE_HS_ADV_TYPE_INCOMP_UUIDS128);
    count += findAdvField(BLE_HS_ADV_TYPE_COMP_UUIDS128);

    return count;
} // getServiceUUIDCount


/**
 * @brief Check advertised services for existence of the required UUID
 * @param [in] uuid The service uuid to look for in the advertisement.
 * @return Return true if service is advertised
 */
bool NimBLEAdvertisedDevice::isAdvertisingService(const NimBLEUUID &uuid) {
    size_t count = getServiceUUIDCount();
    for(size_t i = 0; i < count; i++) {
        if(uuid == getServiceUUID(i)) {
            return true;
        }
    }

    return false;
} // isAdvertisingService


/**
 * @brief Get the TX Power.
 * @return The TX Power of the advertised device.
 */
int8_t NimBLEAdvertisedDevice::getTXPower() {
    size_t data_loc = 0;

    if(findAdvField(BLE_HS_ADV_TYPE_TX_PWR_LVL, 0, &data_loc) > 0) {
        ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc];
        if(field->length == BLE_HS_ADV_TX_PWR_LVL_LEN + 1) {
            return *(int8_t*)field->value;
        }
    }

    return -99;
} // getTXPower


/**
 * @brief Does this advertisement have preferred connection parameters?
 * @return True if connection parameters are present.
 */
bool NimBLEAdvertisedDevice::haveConnParams() {
    return findAdvField(BLE_HS_ADV_TYPE_SLAVE_ITVL_RANGE) > 0;
} // haveConnParams


/**
 * @brief Does this advertisement have have the advertising interval?
 * @return True if the advertisement interval is present.
 */
bool NimBLEAdvertisedDevice::haveAdvInterval() {
    return findAdvField(BLE_HS_ADV_TYPE_ADV_ITVL) > 0;
} // haveAdvInterval


/**
 * @brief Does this advertisement have an appearance value?
 * @return True if there is an appearance value present.
 */
bool NimBLEAdvertisedDevice::haveAppearance() {
    return findAdvField(BLE_HS_ADV_TYPE_APPEARANCE) > 0;
} // haveAppearance


/**
 * @brief Does this advertisement have manufacturer data?
 * @return True if there is manufacturer data present.
 */
bool NimBLEAdvertisedDevice::haveManufacturerData() {
    return findAdvField(BLE_HS_ADV_TYPE_MFG_DATA) > 0;
} // haveManufacturerData


/**
 * @brief Does this advertisement have a URI?
 * @return True if there is a URI present.
 */
bool NimBLEAdvertisedDevice::haveURI() {
    return findAdvField(BLE_HS_ADV_TYPE_URI) > 0;
} // haveURI

/**
 * @brief Does this advertisement have a adv type `type`?
 * @return True if there is a `type` present.
*/
bool NimBLEAdvertisedDevice::haveType(uint16_t type) {
    return findAdvField(type) > 0;
}


/**
 * @brief Does the advertisement contain a target address?
 * @return True if an address is present.
 */
bool NimBLEAdvertisedDevice::haveTargetAddress() {
    return findAdvField(BLE_HS_ADV_TYPE_PUBLIC_TGT_ADDR) > 0 ||
           findAdvField(BLE_HS_ADV_TYPE_RANDOM_TGT_ADDR) > 0;
}


/**
 * @brief Does this advertisement have a name value?
 * @return True if there is a name value present.
 */
bool NimBLEAdvertisedDevice::haveName() {
    return findAdvField(BLE_HS_ADV_TYPE_COMP_NAME) > 0 ||
           findAdvField(BLE_HS_ADV_TYPE_INCOMP_NAME) > 0;
} // haveName


/**
 * @brief Does this advertisement have a signal strength value?
 * @return True if there is a signal strength value present.
 */
bool NimBLEAdvertisedDevice::haveRSSI() {
    return m_rssi != -9999;
} // haveRSSI


/**
 * @brief Does this advertisement have a service data value?
 * @return True if there is a service data value present.
 */
bool NimBLEAdvertisedDevice::haveServiceData() {
    return getServiceDataCount() > 0;
} // haveServiceData


/**
 * @brief Does this advertisement have a service UUID value?
 * @return True if there is a service UUID value present.
 */
bool NimBLEAdvertisedDevice::haveServiceUUID() {
    return getServiceUUIDCount() > 0;
} // haveServiceUUID


/**
 * @brief Does this advertisement have a transmission power value?
 * @return True if there is a transmission power value present.
 */
bool NimBLEAdvertisedDevice::haveTXPower() {
    return findAdvField(BLE_HS_ADV_TYPE_TX_PWR_LVL) > 0;
} // haveTXPower


#if CONFIG_BT_NIMBLE_EXT_ADV
/**
 * @brief Get the set ID of the extended advertisement.
 * @return The set ID.
 */
uint8_t NimBLEAdvertisedDevice::getSetId() {
    return m_sid;
} // getSetId


/**
 * @brief Get the primary PHY used by this advertisement.
 * @return The PHY type, one of:
 *  * BLE_HCI_LE_PHY_1M
 *  * BLE_HCI_LE_PHY_CODED
 */
uint8_t NimBLEAdvertisedDevice::getPrimaryPhy() {
    return m_primPhy;
} // getPrimaryPhy


/**
 * @brief Get the primary PHY used by this advertisement.
 * @return The PHY type, one of:
 *  * BLE_HCI_LE_PHY_1M
 *  * BLE_HCI_LE_PHY_2M
 *  * BLE_HCI_LE_PHY_CODED
 */
uint8_t NimBLEAdvertisedDevice::getSecondaryPhy() {
    return m_secPhy;
} // getSecondaryPhy


/**
 * @brief Get the periodic interval of the advertisement.
 * @return The periodic advertising interval, 0 if not periodic advertising.
 */
uint16_t NimBLEAdvertisedDevice::getPeriodicInterval() {
    return m_periodicItvl;
} // getPeriodicInterval
#endif


uint8_t NimBLEAdvertisedDevice::findAdvField(uint8_t type, uint8_t index, size_t * data_loc) {
    ble_hs_adv_field *field = nullptr;
    size_t  length = m_payload.size();
    size_t  data   = 0;
    uint8_t count  = 0;

    if (length < 3) {
        return count;
    }

    while (length > 2) {
        field = (ble_hs_adv_field*)&m_payload[data];

        if (field->length >= length) {
            return count;
        }

        if (field->type == type) {
            switch (type) {
                case BLE_HS_ADV_TYPE_INCOMP_UUIDS16:
                case BLE_HS_ADV_TYPE_COMP_UUIDS16:
                    count += field->length / 2;
                    break;

                case BLE_HS_ADV_TYPE_INCOMP_UUIDS32:
                case BLE_HS_ADV_TYPE_COMP_UUIDS32:
                    count += field->length / 4;
                    break;

                case BLE_HS_ADV_TYPE_INCOMP_UUIDS128:
                case BLE_HS_ADV_TYPE_COMP_UUIDS128:
                    count += field->length / 16;
                    break;

                case BLE_HS_ADV_TYPE_PUBLIC_TGT_ADDR:
                case BLE_HS_ADV_TYPE_RANDOM_TGT_ADDR:
                    count += field->length / 6;
                    break;

                default:
                    count++;
                    break;
            }

            if (data_loc != nullptr) {
                if (index == 0 || count >= index) {
                    break;
                }
            }
        }

        length -= 1 + field->length;
        data += 1 + field->length;
    }

    if (data_loc != nullptr && field != nullptr) {
        *data_loc = data;
    }

    return count;
}


/**
 * @brief Set the address of the advertised device.
 * @param [in] address The address of the advertised device.
 */
void NimBLEAdvertisedDevice::setAddress(NimBLEAddress address) {
    m_address = address;
} // setAddress


/**
 * @brief Set the adFlag for this device.
 * @param [in] advType The advertisement flag data from the advertisement.
 */
void NimBLEAdvertisedDevice::setAdvType(uint8_t advType, bool isLegacyAdv) {
    m_advType = advType;
#if CONFIG_BT_NIMBLE_EXT_ADV
    m_isLegacyAdv = isLegacyAdv;
#else
    (void)isLegacyAdv;
#endif
} // setAdvType


/**
 * @brief Set the RSSI for this device.
 * @param [in] rssi The RSSI of the discovered device.
 */
void NimBLEAdvertisedDevice::setRSSI(int rssi) {
    m_rssi = rssi;
} // setRSSI


/**
 * @brief Create a string representation of this device.
 * @return A string representation of this device.
 */
std::string NimBLEAdvertisedDevice::toString() {
    std::string res = "Name: " + getName() + ", Address: " + getAddress().toString();

    if (haveAppearance()) {
        char val[6];
        snprintf(val, sizeof(val), "%d", getAppearance());
        res += ", appearance: ";
        res += val;
    }

    if (haveManufacturerData()) {
        char *pHex = NimBLEUtils::buildHexData(nullptr, (uint8_t*)getManufacturerData().data(), getManufacturerData().length());
        res += ", manufacturer data: ";
        res += pHex;
        free(pHex);
    }

    if (haveServiceUUID()) {
        res += ", serviceUUID: " + getServiceUUID().toString();
    }

    if (haveTXPower()) {
        char val[5];
        snprintf(val, sizeof(val), "%d", getTXPower());
        res += ", txPower: ";
        res += val;
    }

    if (haveServiceData()) {
        uint8_t count = getServiceDataCount();
        res += "\nService Data:";
        for(uint8_t i = 0; i < count; i++) {
            res += "\nUUID: " + std::string(getServiceDataUUID(i));
            res += ", Data: " + getServiceData(i);
        }
    }

    return res;

} // toString


/**
 * @brief Get the payload advertised by the device.
 * @return The advertisement payload.
 */
uint8_t* NimBLEAdvertisedDevice::getPayload() {
    return &m_payload[0];
} // getPayload


/**
 * @brief Stores the payload of the advertised device in a vector.
 * @param [in] payload The advertisement payload.
 * @param [in] length The length of the payload in bytes.
 * @param [in] append Indicates if the the data should be appended (scan response).
 */
void NimBLEAdvertisedDevice::setPayload(const uint8_t *payload, uint8_t length, bool append) {
    if(!append) {
        m_advLength = length;
        m_payload.assign(payload, payload + length);
    } else {
        m_payload.insert(m_payload.end(), payload, payload + length);
    }
}


/**
 * @brief Get the length of the advertisement data in the payload.
 * @return The number of bytes in the payload that is from the advertisement.
 */
uint8_t NimBLEAdvertisedDevice::getAdvLength() {
    return m_advLength;
}


/**
 * @brief Get the advertised device address type.
 * @return The device address type:
 * * BLE_ADDR_PUBLIC      (0x00)
 * * BLE_ADDR_RANDOM      (0x01)
 * * BLE_ADDR_PUBLIC_ID   (0x02)
 * * BLE_ADDR_RANDOM_ID   (0x03)
 */
uint8_t NimBLEAdvertisedDevice::getAddressType() {
    return m_address.getType();
} // getAddressType


/**
 * @brief Get the timeStamp of when the device last advertised.
 * @return The timeStamp of when the device was last seen.
 */
time_t NimBLEAdvertisedDevice::getTimestamp() {
    return m_timestamp;
} // getTimestamp


/**
 * @brief Get the length of the payload advertised by the device.
 * @return The size of the payload in bytes.
 */
size_t NimBLEAdvertisedDevice::getPayloadLength() {
    return m_payload.size();
} // getPayloadLength


/**
 * @brief Check if this device is advertising as connectable.
 * @return True if the device is connectable.
 */
bool NimBLEAdvertisedDevice::isConnectable() {
#if CONFIG_BT_NIMBLE_EXT_ADV
    if (m_isLegacyAdv) {
        return m_advType == BLE_HCI_ADV_RPT_EVTYPE_ADV_IND ||
               m_advType == BLE_HCI_ADV_RPT_EVTYPE_DIR_IND;
    }
#endif
    return (m_advType & BLE_HCI_ADV_CONN_MASK) ||
           (m_advType & BLE_HCI_ADV_DIRECT_MASK);
} // isConnectable


/**
 * @brief Check if this advertisement is a legacy or extended type
 * @return True if legacy (Bluetooth 4.x), false if extended (bluetooth 5.x).
 */
bool NimBLEAdvertisedDevice::isLegacyAdvertisement() {
#if CONFIG_BT_NIMBLE_EXT_ADV
    return m_isLegacyAdv;
# else
    return true;
#endif
} // isLegacyAdvertisement

#endif /* CONFIG_BT_ENABLED  && CONFIG_BT_NIMBLE_ROLE_CENTRAL */

