// bslim_fuzzutil.h                                                   -*-C++-*-
#ifndef INCLUDED_BSLIM_FUZZUTIL
#define INCLUDED_BSLIM_FUZZUTIL

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide fuzz test utilities for basic types.
//
//@CLASSES:
// bslim::FuzzUtil: functions to create basic types from fuzz data
//
//@SEE_ALSO: bslim_fuzzdataview
//
//@DESCRIPTION: This component provides a namespace, `bslim::FuzzUtil`,
// containing functions that create fundamental and standard library types from
// fuzz data provided by a fuzz harness (e.g., `libFuzzer`).
//
// See {http://bburl/BDEFuzzTesting} for details on how to build and run with
// fuzz testing enabled.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Consuming Integers in a Range to Pass to an Interface
/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Suppose we wish to fuzz test a function with preconditions.
//
// First, we define the `TradingInterfaceUnderTest` `struct`:
// ```
// /// This utility class provides sample functionality to demonstrate how fuzz
// /// data might be used.
// struct TradingInterfaceUnderTest {
//     // CLASS METHODS
//
//     /// Return a value containing the number of earnings announcements in
//     /// the specified `year` and `month`.  The behavior is undefined unless
//     /// `1950 < year < 2030` and `month` is in `[1 .. 12]`.  Note that the
//     /// values here are arbitrary, and in the real-world this data would be
//     /// obtained from a database or an API.
//     static int numEarningsAnnouncements(int year, int month)
//     {
//         BSLS_ASSERT(1950 <  year  && year  < 2030);
//         BSLS_ASSERT(   1 <= month && month <=  12);
//
//         if (2020 < year && 6 < month) {
//             return 11;                                             // RETURN
//         }
//         return 6;
//     }
// };
// ```
// Then, we need a block of raw bytes.  This would normally come from a fuzz
// harness (e.g., the `LLVMFuzzerTestOneInput` entry point function from
// `libFuzzer`).  Since `libFuzzer` is not available here, we initialize a
// `myFuzzData` array that we will use instead.
// ```
// const bsl::uint8_t  myFuzzData[] = {0x43, 0x19, 0x0D, 0x44, 0x37, 0x0D,
//                                     0x38, 0x5E, 0x9B, 0xAA, 0xF3, 0xDA};
// ```
// Next, we create a `FuzzDataView` to wrap the raw bytes.
// ```
// bslim::FuzzDataView fdv(myFuzzData, sizeof myFuzzData);
// ```
// Now, we pass this `FuzzDataView` to `FuzzUtil` to generate values within the
// permissible range of the function under test:
// ```
// int month = bslim::FuzzUtil::consumeNumberInRange<int>(&fdv,    1,   12);
// int year  = bslim::FuzzUtil::consumeNumberInRange<int>(&fdv, 1951, 2029);
// assert(   1 <= month && month <=   12);
// assert(1951 <= year  && year  <= 2029);
// ```
// Finally, we can use these `int` values to pass to a function that returns
// the number of earnings announcements scheduled in a given month.
// ```
// int numEarnings =
//     TradingInterfaceUnderTest::numEarningsAnnouncements(year, month);
// (void) numEarnings;
// ```

#include <bslscm_version.h>

#include <bslim_fuzzdataview.h>

#include <bslmf_assert.h>

#include <bsls_assert.h>
#include <bsls_libraryfeatures.h>
#include <bsls_types.h>           // `bsls::Types::Uint64`

#include <bsl_cmath.h>            // `bsl::isfinite`
#include <bsl_cstdint.h>          // `bsl::uint8_t`
#include <bsl_limits.h>           // `bsl::numeric_limits`
#include <bsl_string.h>
#include <bsl_type_traits.h>      // `bsl::is_same`
#include <bsl_vector.h>

#include <string>
#include <vector>

namespace BloombergLP {
namespace bslim {

                              // ===============
                              // struct FuzzUtil
                              // ===============

/// This utility `struct` provides a namespace for a suite of functions
/// operating on objects of type `FuzzDataView`and providing the consumption
/// of fuzz data bytes into fundamental and standard library types.
struct FuzzUtil {

    // CLASS METHODS

    /// Return a `bool` value based upon consuming a single byte from the
    /// specified `fuzzDataView`.  If `fuzzDataView->length()` is 0, return
    /// `false`.
    static bool consumeBool(FuzzDataView *fuzzDataView);

    template <class TYPE>
    static typename bsl::enable_if<bsl::is_integral<TYPE>::value, TYPE>::type
    consumeNumber(FuzzDataView *fuzzDataView);

    /// Return a value of (template parameter) `TYPE` in the range
    /// [min .. max] -- where `min` and `max` are the minimum and maximum
    /// values representable by the `TYPE` -- based on at most the next
    /// `sizeof(TYPE) + 1` bytes from the specified `fuzzDataView`, and
    /// update `fuzzDataView` to reflect the bytes consumed.  If
    /// `0 == fuzzDataView->length()`, return the minimum value of `TYPE`.
    /// This function does not participate in overload resolution unless
    /// either `bsl::is_integral<TYPE>::value` or
    /// `bsl::is_floating_point<TYPE>::value` is `true`.  The behavior is
    /// undefined if `bsl::is_same<TYPE, bool>::value` or
    /// `bsl::is_same<TYPE, long double>` is `true`.
    template <class TYPE>
    static typename
    bsl::enable_if<bsl::is_floating_point<TYPE>::value, TYPE>::type
    consumeNumber(FuzzDataView *fuzzDataView);

    /// Return a value of (template parameter) `TYPE` in the specified range
    /// [min .. max] based on at most the next `sizeof(TYPE) + 1` bytes from
    /// the specified `fuzzDataView`, and update `fuzzDataView` to reflect
    /// the bytes consumed.  If `0 == fuzzDataView->length()`, return the
    /// specified `min`.  This function does not participate in overload
    /// resolution unless either `bsl::is_integral<TYPE>::value` or
    /// `bsl::is_floating_point<TYPE>::value` is `true`.  The behavior is
    /// undefined if `min > max`, `min` or `max` is not finite, or either
    /// `bsl::is_same<TYPE, bool>::value` or
    /// `bsl::is_same<TYPE, long double>` is `true`.
    template <class TYPE>
    static typename
    bsl::enable_if<bsl::is_integral<TYPE>::value, TYPE>::type
    consumeNumberInRange(FuzzDataView                *fuzzDataView,
                         TYPE                         min,
                         TYPE                         max);
    template <class TYPE>
    static typename
    bsl::enable_if<bsl::is_floating_point<TYPE>::value, TYPE>::type
    consumeNumberInRange(FuzzDataView *fuzzDataView, TYPE min, TYPE max);

    /// Load into the specified `output` a string consisting of printable ASCII
    /// characters of length from 0 to the specified `maxLength`.  If the
    /// specified `fuzzDataView` has fewer bytes than `maxLength`, load at most
    /// `fuzzDataView->length()` bytes into `output`.  If the buffer in
    /// `fuzzDataView` contains two successive backslash characters, then in
    /// `output` they will be converted to a single backslash (`\\`) character;
    /// if a single backslash character is encountered, the consumption of
    /// bytes is terminated.  Note that because double backslashes are mapped
    /// to single backslashes, more than `maxLength` bytes may be consumed from
    /// the buffer to produce the `output`.  Also note that the generated
    /// string is also a valid UTF-8 string.
    static void consumeRandomLengthAsciiString(bsl::string      *output,
                                               FuzzDataView     *fuzzDataView,
                                               bsl::size_t       maxLength);
    static void consumeRandomLengthAsciiString(std::string      *output,
                                               FuzzDataView     *fuzzDataView,
                                               bsl::size_t       maxLength);
#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR_STRING
    static void consumeRandomLengthAsciiString(std::pmr::string *output,
                                               FuzzDataView     *fuzzDataView,
                                               bsl::size_t       maxLength);
#endif

    /// Load into the specified `output` a sequence of characters of length
    /// from 0 to the specified `maxLength`.  If the specified `fuzzDataView`
    /// has fewer bytes than `maxLength`, load at most `fuzzDataView->length()`
    /// bytes into `output`.  If the buffer in `fuzzDataView` contains two
    /// successive backslash characters, then in `output` they will be
    /// converted to a single backslash (`\\`) character; if a single backslash
    /// character is encountered, the consumption of bytes is terminated.  Note
    /// that because double backslashes are mapped to single backslashes, more
    /// than `maxLength` bytes may be consumed from the buffer to produce the
    /// `output`.  Also note that the purpose of this function is to enable the
    /// creation of a non-zero-terminated `string_view`, which is not possible
    /// with the `string` counterpart.
    static void consumeRandomLengthChars(bsl::vector<char> *output,
                                         FuzzDataView      *fuzzDataView,
                                         bsl::size_t        maxLength);
    static void consumeRandomLengthChars(std::vector<char> *output,
                                         FuzzDataView      *fuzzDataView,
                                         bsl::size_t        maxLength);
#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR
    static void consumeRandomLengthChars(std::pmr::vector<char> *output,
                                         FuzzDataView           *fuzzDataView,
                                         bsl::size_t             maxLength);
#endif

    /// Load into the specified `output` a string of length from 0 to the
    /// specified `maxLength`.  If the specified `fuzzDataView` has fewer bytes
    /// than `maxLength`, load at most `fuzzDataView->length()` bytes into
    /// `output`.  If the buffer in `fuzzDataView` contains two successive
    /// backslash characters, then in `output` they will be converted to a
    /// single backslash (`\\`) character; if a single backslash character is
    /// encountered, the consumption of bytes is terminated.  Note that because
    /// double backslashes are mapped to single backslashes, more than
    /// `maxLength` bytes may be consumed from the buffer to produce the
    /// `output`.
    static void consumeRandomLengthString(bsl::string      *output,
                                          FuzzDataView     *fuzzDataView,
                                          bsl::size_t       maxLength);
    static void consumeRandomLengthString(std::string      *output,
                                          FuzzDataView     *fuzzDataView,
                                          bsl::size_t       maxLength);
#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR_STRING
    static void consumeRandomLengthString(std::pmr::string *output,
                                          FuzzDataView     *fuzzDataView,
                                          bsl::size_t       maxLength);
#endif
};

// ============================================================================
//                            INLINE DEFINITIONS
// ============================================================================

                              // ---------------
                              // struct FuzzUtil
                              // ---------------

// CLASS METHODS
inline
bool FuzzUtil::consumeBool(FuzzDataView *fuzzDataView)
{
    return 1 & consumeNumber<bsl::uint8_t>(fuzzDataView);
}

template <class TYPE>
typename bsl::enable_if<bsl::is_integral<TYPE>::value, TYPE>::type
FuzzUtil::consumeNumber(FuzzDataView *fuzzDataView)
{
    return consumeNumberInRange(fuzzDataView,
                                bsl::numeric_limits<TYPE>::min(),
                                bsl::numeric_limits<TYPE>::max());
}

template <class TYPE>
typename
bsl::enable_if<bsl::is_floating_point<TYPE>::value, TYPE>::type
FuzzUtil::consumeNumber(FuzzDataView *fuzzDataView)
{
    return consumeNumberInRange(fuzzDataView,
                                -bsl::numeric_limits<TYPE>::max(),
                                bsl::numeric_limits<TYPE>::max());
}

template <class TYPE>
typename bsl::enable_if<bsl::is_integral<TYPE>::value, TYPE>::type
FuzzUtil::consumeNumberInRange(FuzzDataView *fuzzDataView, TYPE min, TYPE max)
{
    BSLMF_ASSERT(bsl::is_integral<TYPE>::value);
    BSLMF_ASSERT((!bsl::is_same<TYPE, bool>::value));
    BSLMF_ASSERT(sizeof(TYPE) <= sizeof(bsls::Types::Uint64));
    BSLS_ASSERT(min <= max);

    bsls::Types::Uint64 range = static_cast<bsls::Types::Uint64>(max) - min;

    int numBytes = 0;

    for (bsls::Types::Uint64 rangeCpy = range; 0 != rangeCpy;
         rangeCpy >>= 8, ++numBytes) {
    }

    bsls::Types::Uint64 addend = 0;

    FuzzDataView prefix = fuzzDataView->removePrefix(numBytes);

    for (const bsl::uint8_t *it = prefix.begin(); it != prefix.end(); it++) {
        addend = (addend << 8) | *it;
    }

    if (bsl::numeric_limits<bsls::Types::Uint64>::max() != range) {
        addend %= (range + 1);
    }

    return static_cast<TYPE>(min + addend);
}

template <class TYPE>
typename bsl::enable_if<bsl::is_floating_point<TYPE>::value, TYPE>::type
FuzzUtil::consumeNumberInRange(FuzzDataView *fuzzDataView, TYPE min, TYPE max)
{
    BSLMF_ASSERT((!bsl::is_same<TYPE, long double>::value));
    BSLMF_ASSERT(bsl::numeric_limits<TYPE>::has_infinity);

    BSLS_ASSERT(min <= max);

    BSLS_ASSERT(min == min && max == max);
    BSLS_ASSERT(bsl::numeric_limits<TYPE>::infinity() != max &&
                -bsl::numeric_limits<TYPE>::infinity() != min);

    TYPE       addend = min;
    TYPE       range  = 0;
    const TYPE k_HALF = 0.5;

    if (max > min + bsl::numeric_limits<TYPE>::max()) {
        range = max * k_HALF - min * k_HALF;
        if (consumeBool(fuzzDataView)) {
            addend = min + range;
        }
    }
    else {
        range = max - min;
    }

    typedef typename bsl::conditional<(sizeof(TYPE) <= sizeof(bsl::uint32_t)),
                                      bsl::uint32_t,
                                      bsls::Types::Uint64>::type IntegralType;

    TYPE factor =
        static_cast<TYPE>(consumeNumber<IntegralType>(fuzzDataView)) /
        static_cast<TYPE>(
            bsl::numeric_limits<IntegralType>::max());  // between 0-1

    return addend + range * factor;
}

}  // close package namespace
}  // close enterprise namespace

#endif

// ----------------------------------------------------------------------------
// Copyright 2021 Bloomberg Finance L.P.
//
// 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.
// ----------------------------- END-OF-FILE ----------------------------------
