/*
 * Copyright 2024 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ble_ad_parse.h"

#include "applib/applib_malloc.auto.h"

#include "syscall/syscall.h"
#include "system/passert.h"

#include "util/math.h"
#include "util/net.h"

#include <btutil/bt_uuid.h>

// -----------------------------------------------------------------------------
//! Internal parsed advertisement data structures.

// -----------------------------------------------------------------------------
//! AD TYPE Values as specified by the Bluetooth 4.0 Spec.
//! See "Appendix C (Normative): EIR and AD Formats" in Core_v4.0.pdf
typedef enum {
  BLEAdTypeFlags                     = 0x01,
  BLEAdTypeService16BitUUIDPartial   = 0x02,
  BLEAdTypeService16BitUUIDComplete  = 0x03,
  BLEAdTypeService32BitUUIDPartial   = 0x04,
  BLEAdTypeService32BitUUIDComplete  = 0x05,
  BLEAdTypeService128BitUUIDPartial  = 0x06,
  BLEAdTypeService128BitUUIDComplete = 0x07,

  BLEAdTypeLocalNameShortened        = 0x08,
  BLEAdTypeLocalNameComplete         = 0x09,

  BLEAdTypeTxPowerLevel              = 0x0a,

  BLEAdTypeManufacturerSpecific      = 0xff,
} BLEAdType;

// -----------------------------------------------------------------------------
//! AD DATA element header
typedef struct __attribute__((__packed__)) {
  uint8_t length;
  BLEAdType type:8;
} BLEAdElementHeader;

typedef struct __attribute__((__packed__)) {
  BLEAdElementHeader header;
  uint8_t data[];
} BLEAdElement;

// -----------------------------------------------------------------------------
// Consuming BLEAdData:
// -----------------------------------------------------------------------------

// -----------------------------------------------------------------------------
//! Internal parser callback. Gets called for a parsed Service UUIDs element.
//! @param uuids The array with Service UUIDs
//! @param count The number of Service UUIDs the array contains
//! @param cb_data Pointer to arbitrary client data as passed to the parse call.
//! @return true to continue parsing or false to stop after returning.
typedef bool (*BLEAdParseServicesCallback)(const Uuid uuids[], uint8_t count,
                                           void *cb_data);

// -----------------------------------------------------------------------------
//! Internal parser callback. Gets called for a parsed Local Name element.
//! @param local_name_bytes This is a *NON* zero terminated UTF-8 string.
//! @param length The length of local_name_bytes
//! @param cb_data Pointer to arbitrary client data as passed to the parse call.
//! @return true to continue parsing or false to stop after returning.
typedef bool (*BLEAdParseLocalNameCallback)(const uint8_t *local_name_bytes,
                                            uint8_t length, void *cb_data);

// -----------------------------------------------------------------------------
//! Internal parser callback. Gets called for a parsed TX Power Level element.
//! @param tx_power_level The TX Power Level value.
//! @param cb_data Pointer to arbitrary client data as passed to the parse call.
//! @return true to continue parsing or false to stop after returning.
typedef bool (*BLEAdParseTXPowerLevelCallback)(int8_t tx_power_level,
                                               void *cb_data);

// -----------------------------------------------------------------------------
//! Internal parser callback. Gets called for a Manufacturer Specific data elem.
//! @param company_id The Company ID
//! @param data The Manufacturer Specific data
//! @param length The length in bytes of data
//! @param cb_data Pointer to arbitrary client data as passed to the parse call.
//! @return true to continue parsing or false to stop after returning.
typedef bool (*BLEAdParseManufacturerSpecificCallback)(uint16_t company_id,
                                                       const uint8_t *data,
                                                       uint8_t length,
                                                       void *cb_data);

typedef struct {
  BLEAdParseServicesCallback services_cb;
  BLEAdParseLocalNameCallback local_name_cb;
  BLEAdParseTXPowerLevelCallback tx_power_level_cb;
  BLEAdParseManufacturerSpecificCallback manufacturer_cb;
} BLEAdParseCallbacks;

// -----------------------------------------------------------------------------
//! Parser for Services List data elements.
static bool parse_services_list(const BLEAdElement *elem,
                                const BLEAdParseCallbacks *callbacks,
                                void *cb_data) {
  const uint8_t uuid_type = elem->header.type / 2;
  // Most common is probably 128-bit UUID, then 16-bit, then 32-bit:
  const size_t uuid_width = (uuid_type == 3) ? 16 : ((uuid_type == 1) ? 2 : 4);
  const size_t num_uuids = (elem->header.length - 1 /* Type byte */)
                                                                  / uuid_width;

  if (!num_uuids) {
    return true; // continue parsing
  }

  Uuid uuids[num_uuids];

  // Iterate through the list, expanding each UUID to 128-bit equivalents,
  // then copying them into the uuids[] array:
  const uint8_t *uuid_data = elem->data;
  for (size_t i = 0; i < num_uuids; ++i) {
    switch (uuid_type) {
      case 1: { // 16 bit
        uint16_t u16 = *(uint16_t *) uuid_data;
        uuids[i] = bt_uuid_expand_16bit(u16);
        uuid_data += sizeof(uint16_t);
        break;
      }
      case 2: { // 32 bit
        uint32_t u32 = *(uint32_t *) uuid_data;
        uuids[i] = bt_uuid_expand_32bit(u32);
        uuid_data += sizeof(uint32_t);
        break;
      }
      case 3: // 128-bit
        uuids[i] = UuidMakeFromLEBytes(uuid_data);
        uuid_data += sizeof(Uuid);
        break;
    }
  }

  // Call back to client with parsed data:
  return callbacks->services_cb(uuids, num_uuids, cb_data);
}

// -----------------------------------------------------------------------------
//! Parser for Local Name data element.
static bool parse_local_name(const BLEAdElement *elem,
                             const BLEAdParseCallbacks *callbacks,
                             void *cb_data) {
  // Length of the raw string:
  const uint8_t raw_length = elem->header.length - 1 /* -1 Type byte */;
  if (!raw_length) {
    return true; // continue parsing
  }

  // Call back to client with parsed data:
  return callbacks->local_name_cb(elem->data, raw_length, cb_data);
}

// -----------------------------------------------------------------------------
//! Parser for TX Power Level data element.
static bool parse_power_level(const BLEAdElement *elem,
                              const BLEAdParseCallbacks *callbacks,
                              void *cb_data) {
  if (elem->header.length != 2) {
    // In case the length is not what it should be, do not add data element.
    return true; // continue parsing
  }

  const int8_t tx_power_level = *(int8_t *)elem->data;

  // Call back to client with parsed data:
  return callbacks->tx_power_level_cb(tx_power_level, cb_data);
}

// -----------------------------------------------------------------------------
//! Parser for Manufacturer Specific data element.

static bool parse_manufact_spec(const BLEAdElement *elem,
                                const BLEAdParseCallbacks *callbacks,
                                void *cb_data) {

  if (elem->header.length < 3) {
    // The first 2 octets should be the Company Identifier Code
    // (+1 for Type byte)
    return true;
  }

  if (callbacks->manufacturer_cb) {
    // Little-endian:
    const uint16_t *company_id = (uint16_t *) elem->data;

    // Call back to client with parsed data:
    const uint8_t manufacturer_data_size =
        elem->header.length - sizeof(*company_id) - 1 /* -1 Type Byte */;
    return callbacks->manufacturer_cb(ltohs(*company_id),
                                      elem->data + sizeof(*company_id),
                                      manufacturer_data_size,
                                      cb_data);
  }

  return true; // continue parsing
}

// -----------------------------------------------------------------------------
//! @param ad_data The advertising data and scan response data to parse.
//! @param callbacks Callbacks for each of the types of data the client wants
//! to receive parse callbacks for. You can leave a callback NULL if you are not
//! interested in receiving callbacks for that type of data.
//! @param cb_data Pointer to client data that is passed into the callback.
static void ble_ad_parse_ad_data(const BLEAdData *ad_data,
                                 const BLEAdParseCallbacks *callbacks,
                                 void *cb_data) {
  const uint8_t *cursor = ad_data->data;
  const uint8_t *end = cursor + ad_data->ad_data_length +
                            ad_data->scan_resp_data_length;
  while (cursor < end) {

    const BLEAdElement *elem = (const BLEAdElement *) cursor;
    if (elem->header.length == 0) {
      // We've hit a padding zero. We should be done, or this packet is corrupt.
      return;
    }

    if (cursor + elem->header.length + 1 /* +1 length byte */ > end) {
      return; // corrupted
    }

    bool (*parse_func)(const BLEAdElement *,
                       const BLEAdParseCallbacks *, void *) = NULL;

    switch (elem->header.type) {
      case BLEAdTypeService16BitUUIDPartial ... BLEAdTypeService128BitUUIDComplete:
        if (callbacks->services_cb) {
          parse_func = parse_services_list;
        }
        break;

      case BLEAdTypeLocalNameShortened ... BLEAdTypeLocalNameComplete:
        if (callbacks->local_name_cb) {
          parse_func = parse_local_name;
        }
        break;

      case BLEAdTypeTxPowerLevel:
        if (callbacks->tx_power_level_cb) {
          parse_func = parse_power_level;
        }
        break;

      case BLEAdTypeManufacturerSpecific:
        if (callbacks->manufacturer_cb) {
          parse_func = parse_manufact_spec;
        }
        break;

      default: // parse_func == NULL
        break;

    } // switch()

    if (parse_func) {
      if (!parse_func(elem, callbacks, cb_data)) {
        // The callback indicated we should not continue parsing
        return;
      }
    }

    // The Length byte itself is not counted, so +1:
    cursor += elem->header.length + 1;
  }
}

// -----------------------------------------------------------------------------
//! ble_ad_includes_service() wrapper and helper function:

struct IncludesServiceCtx {
  const Uuid *service_uuid;
  bool included;
};

static bool includes_service_parse_cb(const Uuid uuids[], uint8_t count,
                                      void *cb_data) {
  struct IncludesServiceCtx *ctx = (struct IncludesServiceCtx *) cb_data;
  for (int i = 0; i < count; ++i) {
    if (uuid_equal(ctx->service_uuid, &uuids[i])) {
      // Found!
      ctx->included = true;
      return false; // stop parsing
    }
  }
  return true; // continue parsing
}

bool ble_ad_includes_service(const BLEAdData *ad, const Uuid *service_uuid) {
   struct IncludesServiceCtx ctx = {
    .service_uuid = service_uuid,
    .included = false,
  };
  const BLEAdParseCallbacks callbacks = (const BLEAdParseCallbacks) {
    .services_cb = includes_service_parse_cb,
  };
  ble_ad_parse_ad_data(ad, &callbacks, &ctx);
  return ctx.included;
}

// -----------------------------------------------------------------------------
//! ble_ad_copy_service_uuids() wrapper and helper function:

struct CopyServiceUUIDsCtx {
  Uuid *uuids_out;
  const uint8_t max;
  uint8_t copied;
  uint8_t total;
};

static bool copy_services_parse_cb(const Uuid uuids[], uint8_t count,
                                   void *cb_data) {
  struct CopyServiceUUIDsCtx *ctx = (struct CopyServiceUUIDsCtx *)cb_data;
  for (int i = 0; i < count; ++i) {
    if (ctx->copied < ctx->max) {
      // Still space left, so copy:
      const Uuid *uuid = &uuids[i];
      memcpy(&ctx->uuids_out[ctx->copied++], uuid, sizeof(Uuid));
    }
    ++ctx->total;
  }
  return false; // stop parsing, only one Services UUID element allowed by spec
}

uint8_t ble_ad_copy_service_uuids(const BLEAdData *ad,
                                  Uuid *uuids_out,
                                  uint8_t num_uuids) {
  struct CopyServiceUUIDsCtx ctx = {
    .uuids_out = uuids_out,
    .max = num_uuids,
  };
  const BLEAdParseCallbacks callbacks = {
    .services_cb = copy_services_parse_cb,
  };
  ble_ad_parse_ad_data(ad, &callbacks, &ctx);
  return ctx.total;
}

// -----------------------------------------------------------------------------
//! ble_ad_get_tx_power_level() wrapper and helper function:

struct TxPowerLevelCtx {
  int8_t *tx_power_level_out;
  bool included;
};

static bool tx_power_level_cb(int8_t tx_power_level,
                              void *cb_data) {
  struct TxPowerLevelCtx *ctx = (struct TxPowerLevelCtx *)cb_data;
  *ctx->tx_power_level_out = tx_power_level;
  ctx->included = true;
  return false; // stop parsing
}

bool ble_ad_get_tx_power_level(const BLEAdData *ad,
                               int8_t *tx_power_level_out) {
  struct TxPowerLevelCtx ctx = {
    .tx_power_level_out = tx_power_level_out,
    .included = false,
  };
  const BLEAdParseCallbacks callbacks = {
    .tx_power_level_cb = tx_power_level_cb,
  };
  ble_ad_parse_ad_data(ad, &callbacks, &ctx);
  return ctx.included;
}

// -----------------------------------------------------------------------------
//! ble_ad_copy_local_name() wrapper and helper function:

struct LocalNameCtx {
  char *buffer;
  const uint8_t size;
  size_t copied_size;
};

static bool copy_local_name_parse_cb(const uint8_t *local_name_bytes,
                                     uint8_t length, void *cb_data) {
  struct LocalNameCtx *ctx = (struct LocalNameCtx *)cb_data;
  const uint8_t copied_size = MIN(ctx->size, length + 1 /* zero terminator */);

  memcpy(ctx->buffer, local_name_bytes, copied_size - 1);
  ctx->buffer[copied_size - 1] = 0; // zero terminator
  ctx->copied_size = copied_size;

  return false; // stop parsing
}

size_t ble_ad_copy_local_name(const BLEAdData *ad, char *buffer, size_t size) {
  struct LocalNameCtx ctx = {
    .buffer = buffer,
    .size = MIN(size, 0xff),
    .copied_size = 0,
  };
  const BLEAdParseCallbacks callbacks = {
    .local_name_cb = copy_local_name_parse_cb,
  };
  ble_ad_parse_ad_data(ad, &callbacks, &ctx);
  return ctx.copied_size;
}

// -----------------------------------------------------------------------------
//! ble_ad_get_raw_data_size() wrapper and helper function:

size_t ble_ad_get_raw_data_size(const BLEAdData *ad) {
  return ad->ad_data_length + ad->scan_resp_data_length;
}

// -----------------------------------------------------------------------------
//! ble_ad_copy_raw_data() wrapper and helper function:

size_t ble_ad_copy_raw_data(const BLEAdData *ad, uint8_t *buffer, size_t size) {
  const size_t size_to_copy = ble_ad_get_raw_data_size(ad);
  if (size < size_to_copy) {
    return 0;
  }
  memcpy(buffer, ad->data, size_to_copy);
  return size_to_copy;
}

// -----------------------------------------------------------------------------
//! ble_ad_get_manufacturer_specific_data() wrapper and helper function:

struct ManufacturerSpecificCtx {
  uint16_t company_id;
  uint8_t *buffer;
  const uint8_t size;
  size_t copied_size;
};

static bool copy_manufacturer_specific_parse_cb(uint16_t company_id,
                                                const uint8_t *data,
                                                uint8_t length,
                                                void *cb_data) {

  struct ManufacturerSpecificCtx *ctx =
      (struct ManufacturerSpecificCtx *) cb_data;
  const uint8_t copied_size = MIN(ctx->size, length);

  memcpy(ctx->buffer, data, copied_size);
  ctx->copied_size = copied_size;
  ctx->company_id = company_id;

  return false; // stop parsing
}

size_t ble_ad_copy_manufacturer_specific_data(const BLEAdData *ad,
                                              uint16_t *company_id,
                                              uint8_t *buffer, size_t size) {
  struct ManufacturerSpecificCtx ctx = {
    .size = size,
    .buffer = buffer,
  };
  const BLEAdParseCallbacks callbacks = {
    .manufacturer_cb = copy_manufacturer_specific_parse_cb,
  };
  ble_ad_parse_ad_data(ad, &callbacks, &ctx);
  if (company_id) {
    *company_id = ctx.company_id;
  }
  return ctx.copied_size;
}

// -----------------------------------------------------------------------------
// Creating BLEAdData:
// -----------------------------------------------------------------------------

//! Magic high bit used as scan_resp_data_length to indicate that the ad_data
//! has been finalized and the next write should be counted towards the scan
//! response payload. The maximum scan_resp_data_length is 31 bytes, so this
//! value lies outside of the valid range. This is basically a memory savings
//! optimization, saving another "finalized" bool.
#define BLE_AD_DATA_FINALIZED ((uint8_t) 0x80)

bool prv_ad_is_finalized(const BLEAdData *ad_data) {
  // Scan response data has already been added / started
  return (ad_data->scan_resp_data_length != 0);
}

void ble_ad_start_scan_response(BLEAdData *ad_data) {
  if (prv_ad_is_finalized(ad_data)) {
    // Already finalized
    return;
  }
  ad_data->scan_resp_data_length = BLE_AD_DATA_FINALIZED;
}

// -----------------------------------------------------------------------------
//! Helper to calculate whether a number of bytes will still fit when appended.
//! @param length Pointer to the length of the part for which to try to fit in
//! size_to_write number of bytes.
//! @param size_to_write The number of bytes to that need to be appended.
//! @return Pointer to length if it could still appends size_to_write bytes or
//! NULL if not.
static uint8_t *prv_length_ptr_if_fits_or_null(uint8_t *length,
                                               size_t size_to_write) {
  // Unset finalized bit:
  const uint8_t used = *length & (~BLE_AD_DATA_FINALIZED);
  const uint8_t left = GAP_LE_AD_REPORT_DATA_MAX_LENGTH - used;
  // Return pointer to the pointer if size_to_write will fit, or NULL otherwise:
  return (left >= size_to_write) ? length : NULL;
}

// -----------------------------------------------------------------------------
//! @return Pointer to the length that is incremented when writing size_to_write
//! number of bytes, or NULL if there is not enough space left.
static uint8_t* prv_length_to_increase(BLEAdData *ad_data,
                                size_t size_to_write) {
  if (ad_data->scan_resp_data_length) {
    // The scan response part is already being populated:
    return prv_length_ptr_if_fits_or_null(&ad_data->scan_resp_data_length,
                                          size_to_write);
  } else {
    // The advertisement is still being populated:
    uint8_t *length = prv_length_ptr_if_fits_or_null(&ad_data->ad_data_length,
                                                     size_to_write);
    if (length) {
      // Hurray, the size_to_write fits in the advertisement part:
      return length;
    }
    // Last resort, try fitting into scan response part:
    return prv_length_ptr_if_fits_or_null(&ad_data->scan_resp_data_length,
                                          size_to_write);
  }
}

// -----------------------------------------------------------------------------
bool prv_write_element_to_ad_data(BLEAdData *ad_data,
                                  const BLEAdElement *element) {
  if (!ad_data || !element) {
    return false;
  }
  const size_t size_to_write = element->header.length + 1 /* Length Byte */;
  uint8_t* length = prv_length_to_increase(ad_data, size_to_write);
  if (!length) {
    // Not enough space...
    return false;
  }

  // Undo the magic number trick:
  if (*length == BLE_AD_DATA_FINALIZED) {
    *length = 0;
  }

  // Append the element to the end:
  uint8_t * const end = ad_data->data +
                        ad_data->ad_data_length +
                        ad_data->scan_resp_data_length;
  memcpy(end, (const uint8_t *) element, size_to_write);

  // Length book-keeping:
  *length += size_to_write;

  return true;
}

// -----------------------------------------------------------------------------
BLEAdData * ble_ad_create(void) {
  const size_t max_ad_data_size = sizeof(BLEAdData) +
                                      (GAP_LE_AD_REPORT_DATA_MAX_LENGTH * 2);
  BLEAdData *ad_data = applib_malloc(max_ad_data_size);
  if (ad_data) {
    memset(ad_data, 0, sizeof(BLEAdData));
  }
  return ad_data;
}

// -----------------------------------------------------------------------------
void ble_ad_destroy(BLEAdData *ad) {
  applib_free(ad);
}

// -----------------------------------------------------------------------------
//! The smallest UUID width, by reducing the width when a UUID is based on the
//! Bluetooth base UUID. @see bt_uuid_expand_16bit @see bt_uuid_expand_32bit
static uint8_t prv_smallest_bt_uuid_width_in_bytes(const Uuid *uuid) {
  const Uuid bt_uuid_base = bt_uuid_expand_16bit(0);

  // The bytes after the first 4 contain the Bluetooth base.
  // Check if the uuid is based off of the Bluetooth base UUID:
  const bool is_bt_uuid_based = (memcmp(&bt_uuid_base.byte4, &uuid->byte4,
                                    sizeof(Uuid) - offsetof(Uuid, byte4)) == 0);
  if (!is_bt_uuid_based) {
    // Not based on the Bluetooth base UUID, so use 128-bits:
    return sizeof(Uuid);
  }
  if (uuid->byte0 || uuid->byte1) {
    // If byte0 and byte1 not zero: 32-bit UUID, Bluetooth base UUID based:
    return sizeof(uint32_t);
  }
  // If byte0 and byte1 are zero: 16-bit UUID, Bluetooth base UUID based:
  return sizeof(uint16_t);
}

// -----------------------------------------------------------------------------
//! Finds the largest common UUID width. For UUIDs that are based on the
//! Bluetooth base UUID, a reduced width will be taken of either 16-bits or
//! 32-bits.
static uint8_t prv_largest_common_bt_uuid_width(const Uuid uuids[],
                                         uint8_t num_uuids) {
  uint8_t max_width_bytes = sizeof(uint16_t);
  for (unsigned int i = 0; i < num_uuids; ++i) {
    const Uuid *uuid = &uuids[i];
    const uint8_t width_bytes = prv_smallest_bt_uuid_width_in_bytes(uuid);
    max_width_bytes = MAX(width_bytes, max_width_bytes);
  }
  return max_width_bytes;
}

// -----------------------------------------------------------------------------
//! Helper to reduces a 128-bit UUID to 16-bits. Note: this function does not
//! check whether the original UUID is based on the Bluetooth base.
static uint16_t prv_convert_to_16bit_uuid(const Uuid *uuid) {
  uint16_t uuid_16bits = 0;
  // Use bytes 2-3 of the Uuid:
  for (int i = 2; i < 4; ++i) {
    uuid_16bits <<= 8;
    uuid_16bits += ((const uint8_t *) uuid)[i];
  }
  return uuid_16bits;
}

// -----------------------------------------------------------------------------
//! Helper to reduces a 128-bit UUID to 32-bits. Note: this function does not
//! check whether the original UUID is based on the Bluetooth base.
static uint32_t prv_convert_to_32bit_uuid(const Uuid *uuid) {
  uint32_t uuid_32bits = 0;
  // Use bytes 0-3 of the Uuid:
  for (int i = 0; i < 4; ++i) {
    uuid_32bits <<= 8;
    uuid_32bits += ((const uint8_t *) uuid)[i];
  }
  return uuid_32bits;
}

// -----------------------------------------------------------------------------
bool ble_ad_set_service_uuids(BLEAdData *ad,
                              const Uuid uuids[], uint8_t num_uuids) {
  struct __attribute__((__packed__)) BLEAdElementService {
    BLEAdElementHeader header;
    union {
      Uuid uuid_128[0];
      uint32_t uuid_32[0];
      uint16_t uuid_16[0];
    };
  };

  const uint8_t max_width_bytes = prv_largest_common_bt_uuid_width(uuids,
                                                                   num_uuids);
  // Allocate buffer:
  const size_t buffer_size = sizeof(struct BLEAdElementService) +
                                (max_width_bytes * num_uuids);
  uint8_t element_buffer[buffer_size];
  struct BLEAdElementService *element = (struct BLEAdElementService *) element_buffer;

  // Set header fields (assume Complete):
  switch (max_width_bytes) {
    case 16: element->header.type = BLEAdTypeService128BitUUIDComplete; break;
    case 4: element->header.type = BLEAdTypeService32BitUUIDComplete; break;
    case 2: element->header.type = BLEAdTypeService16BitUUIDComplete; break;
    default:
      WTF;
  }

  element->header.length = buffer_size - 1 /* -1 Length byte */;

  // Copy UUIDs:
  for (unsigned int i = 0; i < num_uuids; ++i) {
    switch (max_width_bytes) {
      case 16: element->uuid_128[i] = uuids[i]; break;
      case 4: element->uuid_32[i] = prv_convert_to_32bit_uuid(&uuids[i]); break;
      case 2: element->uuid_16[i] = prv_convert_to_16bit_uuid(&uuids[i]); break;
      default:
        WTF;
    }
  }

  return prv_write_element_to_ad_data(ad, (const BLEAdElement *) element);
}

// -----------------------------------------------------------------------------
bool ble_ad_set_local_name(BLEAdData *ad,
                           const char *local_name) {
  if (!local_name) {
    return false;
  }
  const size_t length = strlen(local_name);
  uint8_t element_buffer[sizeof(BLEAdElement) + length];
  BLEAdElement *element = (BLEAdElement *) &element_buffer;
  element->header.length = length + 1 /* +1 Type byte */;
  element->header.type = BLEAdTypeLocalNameComplete; /* assume Complete */
  // Note: *not* zero terminated by design
  memcpy(element->data, local_name, length);
  return prv_write_element_to_ad_data(ad, element);
}

// -----------------------------------------------------------------------------
bool ble_ad_set_tx_power_level(BLEAdData *ad) {
  uint8_t element_buffer[sizeof(BLEAdElement) + sizeof(int8_t)];
  BLEAdElement *element = (BLEAdElement *) element_buffer;
  element->header.length = sizeof(int8_t) + 1 /* +1 Type byte */;
  element->header.type = BLEAdTypeTxPowerLevel;
  *((int8_t *) element->data) = sys_ble_get_advertising_tx_power();

  return prv_write_element_to_ad_data(ad, element);
}

// -----------------------------------------------------------------------------
bool ble_ad_set_manufacturer_specific_data(BLEAdData *ad, uint16_t company_id,
                                           const uint8_t *data, size_t size) {
  struct __attribute__((__packed__)) BLEAdElementManufacturerSpecific {
    BLEAdElementHeader header;
    uint16_t company_id;
    uint8_t data[];
  };

  uint8_t element_buffer[sizeof(struct BLEAdElementManufacturerSpecific) + size];
  struct BLEAdElementManufacturerSpecific *element =
      (struct BLEAdElementManufacturerSpecific *) element_buffer;
  element->header.length = sizeof(struct BLEAdElementManufacturerSpecific)
                                          - 1 /* -1 Length byte */ + size;
  element->header.type = BLEAdTypeManufacturerSpecific;
  element->company_id = ltohs(company_id);
  memcpy(element->data, data, size);
  return prv_write_element_to_ad_data(ad, (const BLEAdElement *) element);
}

// -----------------------------------------------------------------------------
bool ble_ad_set_flags(BLEAdData *ad, uint8_t flags) {
  struct __attribute__((__packed__)) BLEAdElementManufacturerSpecific {
    BLEAdElementHeader header;
    uint8_t flags;
  } element = {
    .header = {
      .length = sizeof(struct BLEAdElementManufacturerSpecific)
                - 1 /* -1 Length byte */,
      .type = BLEAdTypeFlags,
    },
    .flags = flags,
  };
  return prv_write_element_to_ad_data(ad, (const BLEAdElement *) &element);
}
