/*
 * Copyright 2020 The Android Open Source Project
 *
 * 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.
 */

#ifndef FUZZER_SDP_FUNCTIONS_H_
#define FUZZER_SDP_FUNCTIONS_H_

#include <base/functional/bind.h>
#include <fuzzer/FuzzedDataProvider.h>

#include <vector>

#include "fuzzers/common/commonFuzzHelpers.h"
#include "fuzzers/sdp/sdpFuzzHelpers.h"
#include "stack/include/sdp_api.h"
#include "types/bluetooth/uuid.h"
#include "types/raw_address.h"

#define SDP_MAX_DB_LEN 1024 * 1024  // 1 MB
#define MAX_NUM_DBS 64

/* This is a vector of lambda functions the fuzzer will pull from.
 *  This is done so new functions can be added to the fuzzer easily
 *  without requiring modifications to the main fuzzer file. This also
 *  allows multiple fuzzers to include this file, if functionality is needed.
 */
static const std::vector<std::function<void(FuzzedDataProvider*)>> sdp_operations = {
        // ::SDP_InitDiscoveryDb
        [](FuzzedDataProvider* fdp) -> void {
          if (sdp_db_vect.size() >= MAX_NUM_DBS) {
            return;
          }

          // build out uuid_list
          std::vector<bluetooth::Uuid> uuid_list;
          uint8_t num_uuids = fdp->ConsumeIntegral<uint8_t>();
          for (uint8_t i = 0; i < num_uuids; i++) {
            uuid_list.push_back(generateArbitraryUuid(fdp));
          }

          // build out attr_list
          std::vector<uint16_t> attr_list = generateArbitraryAttrList(fdp);

          uint32_t db_size = fdp->ConsumeIntegralInRange<uint32_t>(0, SDP_MAX_DB_LEN);
          std::shared_ptr<tSDP_DISCOVERY_DB> p_db(
                  reinterpret_cast<tSDP_DISCOVERY_DB*>(malloc(db_size)), free);
          if (p_db) {
            bool success = get_legacy_stack_sdp_api()->service.SDP_InitDiscoveryDb(
                    p_db.get(), db_size, uuid_list.size(), uuid_list.data(), attr_list.size(),
                    reinterpret_cast<uint16_t*>(attr_list.data()));
            if (success) {
              sdp_db_vect.push_back(p_db);
            }
          }
        },

        // ::SDP_CancelServiceSearch
        [](FuzzedDataProvider* fdp) -> void {
          [[maybe_unused]] bool rc = get_legacy_stack_sdp_api()->service.SDP_CancelServiceSearch(
                  getArbitraryVectorElement(fdp, sdp_db_vect, true).get());
        },

        // ::SDP_ServiceSearchRequest
        [](FuzzedDataProvider* fdp) -> void {
          const RawAddress bd_addr = generateRawAddress(fdp);
          tSDP_DISCOVERY_DB* db = getArbitraryVectorElement(fdp, sdp_db_vect, false).get();
          if (db) {
            [[maybe_unused]] bool rc = get_legacy_stack_sdp_api()->service.SDP_ServiceSearchRequest(
                    bd_addr, db, &sdp_disc_cmpl_cb);
          }
        },

        // ::SDP_ServiceSearchAttributeRequest
        [](FuzzedDataProvider* fdp) -> void {
          const RawAddress bd_addr = generateRawAddress(fdp);
          tSDP_DISCOVERY_DB* db = getArbitraryVectorElement(fdp, sdp_db_vect, false).get();
          if (db) {
            [[maybe_unused]] bool rc =
                    get_legacy_stack_sdp_api()->service.SDP_ServiceSearchAttributeRequest(
                            bd_addr, db, &sdp_disc_cmpl_cb);
          }
        },

        // ::SDP_ServiceSearchAttributeRequest2
        [](FuzzedDataProvider* fdp) -> void {
          const RawAddress bd_addr = generateRawAddress(fdp);
          std::vector<uint8_t> user_data =
                  fdp->ConsumeBytes<uint8_t>(fdp->ConsumeIntegralInRange<size_t>(0, 1024));
          tSDP_DISCOVERY_DB* db = getArbitraryVectorElement(fdp, sdp_db_vect, false).get();

          if (db) {
            [[maybe_unused]] bool rc =
                    get_legacy_stack_sdp_api()->service.SDP_ServiceSearchAttributeRequest2(
                            bd_addr, db, base::BindRepeating(&sdp_disc_cmpl_cb2, user_data));
          }
        },

        // ::SDP_FindAttributeInRec
        [](FuzzedDataProvider* fdp) -> void {
          tSDP_DISC_REC* p_rec = generateArbitrarySdpDiscRecord(fdp, false).get();
          [[maybe_unused]] bool rc = get_legacy_stack_sdp_api()->record.SDP_FindAttributeInRec(
                  p_rec, fdp->ConsumeIntegral<uint16_t>());
        },

        // ::SDP_FindServiceInDb
        [](FuzzedDataProvider* fdp) -> void {
          [[maybe_unused]] bool rc = get_legacy_stack_sdp_api()->db.SDP_FindServiceInDb(
                  getArbitraryVectorElement(fdp, sdp_db_vect, true).get(),
                  fdp->ConsumeIntegral<uint16_t>(),
                  generateArbitrarySdpDiscRecord(fdp, true).get());
        },

        // ::SDP_FindServiceUUIDInDb
        [](FuzzedDataProvider* fdp) -> void {
          const bluetooth::Uuid uuid = generateArbitraryUuid(fdp);
          [[maybe_unused]] bool rc = get_legacy_stack_sdp_api()->db.SDP_FindServiceUUIDInDb(
                  getArbitraryVectorElement(fdp, sdp_db_vect, true).get(), uuid,
                  generateArbitrarySdpDiscRecord(fdp, true).get());
        },

        // ::SDP_FindServiceUUIDInRec_128bit
        [](FuzzedDataProvider* fdp) -> void {
          bluetooth::Uuid uuid = generateArbitraryUuid(fdp);
          tSDP_DISC_REC* p_rec = generateArbitrarySdpDiscRecord(fdp, false).get();
          [[maybe_unused]] bool rc =
                  get_legacy_stack_sdp_api()->record.SDP_FindServiceUUIDInRec_128bit(p_rec, &uuid);
        },

        // ::SDP_FindServiceInDb_128bit
        [](FuzzedDataProvider* fdp) -> void {
          [[maybe_unused]] bool rc = get_legacy_stack_sdp_api()->db.SDP_FindServiceInDb_128bit(
                  getArbitraryVectorElement(fdp, sdp_db_vect, true).get(),
                  generateArbitrarySdpDiscRecord(fdp, true).get());
        },

        // ::SDP_FindProtocolListElemInRec
        [](FuzzedDataProvider* fdp) -> void {
          tSDP_PROTOCOL_ELEM elem = generateArbitrarySdpProtocolElements(fdp);
          tSDP_DISC_REC* p_rec = generateArbitrarySdpDiscRecord(fdp, false).get();
          [[maybe_unused]] bool rc =
                  get_legacy_stack_sdp_api()->record.SDP_FindProtocolListElemInRec(
                          p_rec, fdp->ConsumeIntegral<uint16_t>(), &elem);
        },

        // ::SDP_FindProfileVersionInRec
        [](FuzzedDataProvider* fdp) -> void {
          uint16_t p_version;
          tSDP_DISC_REC* p_rec = generateArbitrarySdpDiscRecord(fdp, false).get();

          [[maybe_unused]] bool rc = get_legacy_stack_sdp_api()->record.SDP_FindProfileVersionInRec(
                  p_rec, fdp->ConsumeIntegral<uint16_t>(), &p_version);
        },

        // ::SDP_CreateRecord
        [](FuzzedDataProvider* /*fdp*/) -> void {
          uint32_t handle = get_legacy_stack_sdp_api()->handle.SDP_CreateRecord();
          if (handle) {
            sdp_record_handles.push_back(handle);
          }
        },

        // ::SDP_DeleteRecord
        [](FuzzedDataProvider* fdp) -> void {
          [[maybe_unused]] bool rc = get_legacy_stack_sdp_api()->handle.SDP_DeleteRecord(
                  getArbitraryVectorElement(fdp, sdp_record_handles, true));
        },

        // ::SDP_AddAttribute
        [](FuzzedDataProvider* fdp) -> void {
          std::vector<uint8_t> val =
                  fdp->ConsumeBytes<uint8_t>(fdp->ConsumeIntegralInRange<size_t>(1, 1024));
          if (val.size() > 0) {
            [[maybe_unused]] bool rc = get_legacy_stack_sdp_api()->handle.SDP_AddAttribute(
                    getArbitraryVectorElement(fdp, sdp_record_handles, true),
                    fdp->ConsumeIntegral<uint16_t>(), fdp->ConsumeIntegral<uint8_t>(), val.size(),
                    val.data());
          }
        },

        // ::SDP_AddSequence
        [](FuzzedDataProvider* fdp) -> void {
          SDP_Sequence_Helper seq = generateArbitrarySdpElemSequence(fdp);

          [[maybe_unused]] bool rc = get_legacy_stack_sdp_api()->handle.SDP_AddSequence(
                  getArbitraryVectorElement(fdp, sdp_record_handles, true),
                  fdp->ConsumeIntegral<uint16_t>(), seq.num_elem, seq.type.get(), seq.len.get(),
                  seq.p_val.get());
        },

        // ::SDP_AddUuidSequence
        [](FuzzedDataProvider* fdp) -> void {
          uint16_t num_uuids = fdp->ConsumeIntegralInRange<uint16_t>(1, 64);
          uint16_t* uuids = new uint16_t[num_uuids];
          for (uint16_t i = 0; i < num_uuids; i++) {
            uuids[i] = fdp->ConsumeIntegral<uint16_t>();
          }

          [[maybe_unused]] bool rc = get_legacy_stack_sdp_api()->handle.SDP_AddUuidSequence(
                  getArbitraryVectorElement(fdp, sdp_record_handles, true),
                  fdp->ConsumeIntegral<uint16_t>(), num_uuids, uuids);
          delete[] uuids;
        },

        // ::SDP_AddProtocolList
        [](FuzzedDataProvider* fdp) -> void {
          std::shared_ptr<tSDP_PROTO_LIST_ELEM> p_proto_list =
                  generateArbitrarySdpProtocolElementList(fdp);
          if (p_proto_list) {
            [[maybe_unused]] bool rc = get_legacy_stack_sdp_api()->handle.SDP_AddProtocolList(
                    getArbitraryVectorElement(fdp, sdp_record_handles, true),
                    p_proto_list.get()->num_elems, p_proto_list.get()->list_elem);
          }
        },

        // ::SDP_AddAdditionProtoLists
        [](FuzzedDataProvider* fdp) -> void {
          uint16_t arr_size;
          tSDP_PROTO_LIST_ELEM** p_proto_list =
                  generateArbitrarySdpProtocolElementListArray(fdp, &arr_size);
          if (p_proto_list) {
            if (p_proto_list[0]) {
              [[maybe_unused]] bool rc =
                      get_legacy_stack_sdp_api()->handle.SDP_AddAdditionProtoLists(
                              getArbitraryVectorElement(fdp, sdp_record_handles, true), arr_size,
                              p_proto_list[0]);
              for (uint16_t i = 0; i < arr_size; i++) {
                delete p_proto_list[i];
              }
            }
            free(p_proto_list);
          }
        },

        // ::SDP_AddProfileDescriptorList
        [](FuzzedDataProvider* fdp) -> void {
          [[maybe_unused]] bool rc =
                  get_legacy_stack_sdp_api()->handle.SDP_AddProfileDescriptorList(
                          getArbitraryVectorElement(fdp, sdp_record_handles, true),
                          fdp->ConsumeIntegral<uint16_t>(), fdp->ConsumeIntegral<uint16_t>());
        },

        // ::SDP_AddLanguageBaseAttrIDList
        [](FuzzedDataProvider* fdp) -> void {
          [[maybe_unused]] bool rc =
                  get_legacy_stack_sdp_api()->handle.SDP_AddLanguageBaseAttrIDList(
                          getArbitraryVectorElement(fdp, sdp_record_handles, true),
                          fdp->ConsumeIntegral<uint16_t>(), fdp->ConsumeIntegral<uint16_t>(),
                          fdp->ConsumeIntegral<uint16_t>());
        },

        // ::SDP_AddServiceClassIdList
        [](FuzzedDataProvider* fdp) -> void {
          uint16_t num_services = fdp->ConsumeIntegralInRange<uint16_t>(0, 64);
          uint16_t* service_uuids = new uint16_t[num_services];
          for (uint16_t i = 0; i < num_services; i++) {
            service_uuids[i] = fdp->ConsumeIntegral<uint16_t>();
          }

          [[maybe_unused]] bool rc = get_legacy_stack_sdp_api()->handle.SDP_AddServiceClassIdList(
                  getArbitraryVectorElement(fdp, sdp_record_handles, true), num_services,
                  service_uuids);

          delete[] service_uuids;
        },

        // ::SDP_SetLocalDiRecord
        [](FuzzedDataProvider* fdp) -> void {
          uint32_t handle;  // Output var
          tSDP_DI_RECORD device_info = generateArbitrarySdpDiRecord(fdp);
          [[maybe_unused]] tSDP_STATUS rc =
                  get_legacy_stack_sdp_api()->device_id.SDP_SetLocalDiRecord(&device_info, &handle);
        },

        // ::SDP_DiDiscover
        [](FuzzedDataProvider* fdp) -> void {
          const RawAddress remote_device = generateRawAddress(fdp);

          // Create a new buffer for the discoveryDB init call
          uint32_t db_size = fdp->ConsumeIntegralInRange<uint32_t>(0, SDP_MAX_DB_LEN);
          std::shared_ptr<tSDP_DISCOVERY_DB> p_db(
                  reinterpret_cast<tSDP_DISCOVERY_DB*>(malloc(db_size)), free);
          if (p_db) {
            [[maybe_unused]] tSDP_STATUS rc = get_legacy_stack_sdp_api()->device_id.SDP_DiDiscover(
                    remote_device, p_db.get(), db_size, &sdp_disc_cmpl_cb);
          }
        },

        // ::SDP_GetNumDiRecords
        [](FuzzedDataProvider* fdp) -> void {
          [[maybe_unused]] bool rc = get_legacy_stack_sdp_api()->device_id.SDP_GetNumDiRecords(
                  getArbitraryVectorElement(fdp, sdp_db_vect, true).get());
        },

        // ::SDP_GetDiRecord
        [](FuzzedDataProvider* fdp) -> void {
          tSDP_DI_GET_RECORD device_info;  // Output var
          [[maybe_unused]] tSDP_STATUS rc = get_legacy_stack_sdp_api()->device_id.SDP_GetDiRecord(
                  fdp->ConsumeIntegral<uint8_t>(), &device_info,
                  getArbitraryVectorElement(fdp, sdp_db_vect, true).get());
        },

        // ::SDP_FindServiceUUIDInRec
        [](FuzzedDataProvider* fdp) -> void {
          tSDP_DISC_REC* p_rec = generateArbitrarySdpDiscRecord(fdp, false).get();
          bluetooth::Uuid uuid;  // Output var
          [[maybe_unused]] bool rc =
                  get_legacy_stack_sdp_api()->record.SDP_FindServiceUUIDInRec(p_rec, &uuid);
        }};

#endif  // FUZZER_SDP_FUNCTIONS_H_
