/* -*- mode: c++; indent-tabs-mode: nil -*- */
/*
    Pseudo_QC_Binary.qpp

    Qore Programming Language

    Copyright (C) 2003 - 2023 Qore Technologies, s.r.o.

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.

    Note that the Qore library is released under a choice of three open-source
    licenses: MIT (as above), LGPL 2+, or GPL 2+; see README-LICENSE for more
    information.
*/

#include <qore/Qore.h>

//! Methods in this pseudo-class can be executed on @ref binary "binary values"
/**
 */
qclass <binary> [arg=const BinaryNode* b = v.get<const BinaryNode>();vparent=<value>];

//! Returns Qore::NT_BINARY
/**
    @return Qore::NT_BINARY

    @par Example:
    @code{.py}
switch (b.typeCode()) {
    case NT_BINARY:
        printf("%y: is a binary\n", b);
        break;
}
    @endcode
*/
int <binary>::typeCode() [flags=CONSTANT] {
   return NT_BINARY;
}

//! Returns the number of bytes in the binary object
/**
    @return the number of bytes in the binary object

    @par Example:
    @code{.py}
int len = b.size();
    @endcode

    @see <binary>::sizep()
*/
int <binary>::size() [flags=CONSTANT] {
   return b->size();
}

//! Retrieves the byte position of a sequence of bytes within a binary object
/** @par Example:
    @code{.py}
int i = bin.find(bin2);
if (i == -1) {
    printf("could not find %y in %y\n", bin2, bin);
}
    @endcode

    @param bin the byte sequence to find in the binary object
    @param byte_pos the starting byte position for the search; if negative, then specifies an offset from the end of
    the current binary object

    @return the byte position of a sequence of bytes within a binary object, -1 is returned if the byte sequence is
    not found or if \a byte_pos specifies a position beyond the end of the string

    @note an empty \a bin argument always matches

    @see
    - <binary>::rfind(data, softint)

    @since %Qore 0.9.1
*/
int <binary>::find(data bin, softint byte_pos = 0) [flags=RET_VALUE_ONLY] {
    const char* bin_ptr;
    size_t bin_len;
    q_get_data(bin, bin_ptr, bin_len);

    if (byte_pos < 0) {
        byte_pos = b->size() + byte_pos;
        if (byte_pos < 0) {
            return -1;
        }
    } else if ((size_t)byte_pos >= b->size()) {
        return -1;
    }

    size_t len = b->size() - byte_pos;
    if (!len) {
        return bin_len ? -1 : 0;
    } else if (len < 0) {
        return -1;
    }

    void* ptr = q_memmem(static_cast<const char*>(b->getPtr()) + byte_pos, len, bin_ptr, bin_len);
    if (!ptr) {
        return -1;
    }
    return static_cast<int64>(reinterpret_cast<const char*>(ptr) - reinterpret_cast<const char*>(b->getPtr()));
}

//! Retrieves the byte position of a sequence of bytes within a binary object searching from the end of the binary object
/** @par Example:
    @code{.py}
int i = bin.rfind(bin2);
if (i == -1) {
    printf("could not find %y in %y\n", bin2, bin);
}
    @endcode

    @param bin the byte sequence to find in the binary object
    @param byte_pos the starting byte position for the search; if negative, then specifies an offset from the end of
    the current binary object; if \a byte_pos specifies a position beyond the end of the string, then the search
    starts from the end of the string

    @return the byte position of a sequence of bytes within a binary object, -1 is returned if the byte sequence is
    not found

    @note an empty \a bin argument always matches

    @see
    - <binary>::find(data, softint)

    @since %Qore 0.9.1
*/
int <binary>::rfind(data bin, softint byte_pos = -1) [flags=RET_VALUE_ONLY] {
    const char* bin_ptr;
    size_t bin_len;
    q_get_data(bin, bin_ptr, bin_len);

    if (byte_pos < 0) {
        byte_pos = b->size() + byte_pos;
        if (byte_pos < 0) {
            return -1;
        }
    } else if ((size_t)byte_pos >= b->size()) {
        byte_pos = b->size() - 1;
    }

    assert((size_t)byte_pos < b->size());
    void* ptr = q_memrmem(b->getPtr(), byte_pos + 1, bin_ptr, bin_len);
    if (!ptr) {
        return -1;
    }
    return static_cast<int64>(reinterpret_cast<const char*>(ptr) - reinterpret_cast<const char*>(b->getPtr()));
}

//! Returns @ref True if the binary object is empty (size = 0), @ref False if not
/** The opposite of <binary>::val()

    @return @ref True if the binary object is empty (size = 0), @ref False if not

    @par Example:
    @code{.py}
bool b = bin.empty();
    @endcode
*/
bool <binary>::empty() [flags=CONSTANT] {
   return b->empty();
}

//! Returns @ref False if the binary object is empty (size = 0), @ref True if not
/** The opposite of <binary>::empty()

    @return @ref False if the binary object is empty (size = 0), @ref True if not

    @par Example:
    @code{.py}
bool b = bin.val();
    @endcode

    @see
    - @ref perl-bool-eval "%perl-bool-eval"
    - @ref strict-bool-eval "%strict-bool-eval"
*/
bool <binary>::val() [flags=CONSTANT] {
   return !b->empty();
}

//! Returns a list of binary objects representing each component of the binary object separated by the bytes identified by the separator argument, with the separator removed
/**
    @param sep the bytes that separate the fields

    @return a list of binary objects representing each component of the binary object separated by the bytes identified by the separator argument, with the separator removed

    @par Example:
    @code{.py}
list<binary> l = bin.split(sep);
    @endcode

    @note equivalent to split(data, binary)

    @since %Qore 0.8.5
 */
list<binary> <binary>::split(data sep) [flags=CONSTANT] {
    const char* bin_ptr;
    size_t bin_len;
    q_get_data(sep, bin_ptr, bin_len);

    return split_intern(bin_ptr, bin_len, (const char*)b->getPtr(), b->size(), nullptr);
}

//! Returns the <a href="http://en.wikipedia.org/wiki/MD5">MD5 message digest</a> of the binary data as a hex string
/** The trailing null character is not included in the digest returned.

    @return a hex string of the digest (ex: \c "5d41402abc4b2a76b9719d911017c592")

    @par Example:
    @code{.py}
string str = binary("hello").toMD5(); # returns "5d41402abc4b2a76b9719d911017c592"
    @endcode

    @throw MD5-DIGEST-ERROR error calculating digest (should not normally happen)

    @note
    - equivalent to MD5()
    - the MD5 algorithm is not collision-resistant; it's recommended to use another hash algorithm (like SHA-256) if cryptographic security is important

    @see
    - MD5_bin()
    - <string>::toMD5()

    @since %Qore 0.8.5
 */
string <binary>::toMD5() [flags=RET_VALUE_ONLY] {
   DigestHelper dh(*b);
   if (dh.doDigest(MD5_ERR, EVP_md5(), xsink))
      return QoreValue();

   return dh.getString();
}

//! Returns the <a href="http://en.wikipedia.org/wiki/SHA-1">SHA1</a> message digest of the binary data as a hex string
/** The trailing null character is not included in the digest returned.

    @return a hex string of the digest (ex: \c "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d")

    @par Example:
    @code{.py}
string str = binary("hello").toSHA1(); # returns "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d"
    @endcode

    @throw SHA1-DIGEST-ERROR error calculating digest (should not normally happen)

    @note equivalent to SHA1()

    @see
    - SHA1_bin()
    - <string>::toSHA1()

    @since %Qore 0.8.5
 */
string <binary>::toSHA1() [flags=RET_VALUE_ONLY] {
   DigestHelper dh(*b);
   if (dh.doDigest(SHA1_ERR, EVP_sha1(), xsink))
      return QoreValue();

   return dh.getString();
}

//! Returns the SHA-224 message digest (a variant of <a href="http://en.wikipedia.org/wiki/SHA-2">SHA-2</a>) of the binary data as a hex string
/** The trailing null character is not included in the digest returned.

    @par Platform Availability:
    @ref Qore::Option::HAVE_SHA224

    @return a hex string of the digest (ex: \c "ea09ae9cc6768c50fcee903ed054556e5bfc8347907f12598aa24193")

    @par Example:
    @code{.py}
string str = binary("hello").toSHA224("hello"); # returns "ea09ae9cc6768c50fcee903ed054556e5bfc8347907f12598aa24193"
    @endcode

    @throw SHA224-DIGEST-ERROR error calculating digest (should not normally happen)

    @note equivalent to SHA224()

    @see
    - SHA224_bin()
    - <string>::toSHA224()

    @since %Qore 0.8.5
 */
string <binary>::toSHA224() [flags=RET_VALUE_ONLY] {
#if !defined(OPENSSL_NO_SHA256) && defined(HAVE_OPENSSL_SHA512)
   DigestHelper dh(*b);
   if (dh.doDigest(SHA224_ERR, EVP_sha224(), xsink))
      return QoreValue();

   return dh.getString();
#else
   missing_openssl_feature("SHA224", xsink);
   return QoreValue();
#endif
}

//! Returns the SHA-256 message digest (a variant of <a href="http://en.wikipedia.org/wiki/SHA-2">SHA-2</a>) of the binary data as a hex string
/** The trailing null character is not included in the digest returned.

    @par Platform Availability:
    @ref Qore::Option::HAVE_SHA256

    @return a hex string of the digest (ex: \c "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824")

    @par Example:
    @code{.py}
string str = binary("hello").toSHA256(); # returns "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
    @endcode

    @throw SHA256-DIGEST-ERROR error calculating digest (should not normally happen)

    @note equivalent to SHA256()

    @see
    - SHA256_bin()
    - <string>::toSHA256()

    @since %Qore 0.8.5
 */
string <binary>::toSHA256() [flags=RET_VALUE_ONLY] {
#if !defined(OPENSSL_NO_SHA256) && defined(HAVE_OPENSSL_SHA512)
   DigestHelper dh(*b);
   if (dh.doDigest(SHA256_ERR, EVP_sha256(), xsink))
      return QoreValue();

   return dh.getString();
#else
   missing_openssl_feature("SHA256", xsink);
   return QoreValue();
#endif
}

//! Returns the SHA-384 message digest (a variant of <a href="http://en.wikipedia.org/wiki/SHA-2">SHA-2</a>) of the binary data as a hex string
/** The trailing null character is not included in the digest returned.

    @par Platform Availability:
    @ref Qore::Option::HAVE_SHA384

    @return a hex string of the digest (ex: \c "59e1748777448c69de6b800d7a33bbfb9ff1b463e44354c3553bcdb9c666fa90125a3c79f90397bdf5f6a13de828684f")

    @par Example:
    @code{.py}
string str = binary("hello").toSHA384(); # returns "59e1748777448c69de6b800d7a33bbfb9ff1b463e44354c3553bcdb9c666fa90125a3c79f90397bdf5f6a13de828684f"
    @endcode

    @throw SHA384-DIGEST-ERROR error calculating digest (should not normally happen)

    @note equivalent to SHA384()

    @see
    - SHA384_bin()
    - <string>::toSHA384()

    @since %Qore 0.8.5
 */
string <binary>::toSHA384() [flags=RET_VALUE_ONLY] {
#if !defined(OPENSSL_NO_SHA512) && defined(HAVE_OPENSSL_SHA512)
   DigestHelper dh(*b);
   if (dh.doDigest(SHA384_ERR, EVP_sha384(), xsink))
      return QoreValue();

   return dh.getString();
#else
   missing_openssl_feature("SHA384", xsink);
   return QoreValue();
#endif
}

//! Returns the SHA-512 message digest (a variant of <a href="http://en.wikipedia.org/wiki/SHA-2">SHA-2</a>) of the binary data as a hex string
/** The trailing null character is not included in the digest returned.

    @par Platform Availability:
    @ref Qore::Option::HAVE_SHA512

    @return a hex string of the digest (ex: \c "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043")

    @par Example:
    @code{.py}
string str = binary("hello").toSHA512(); # returns "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043"
    @endcode

    @throw SHA512-DIGEST-ERROR error calculating digest (should not normally happen)

    @note equivalent to SHA512()

    @see
    - SHA512_bin()
    - <string>::toSHA512()

    @since %Qore 0.8.5
 */
string <binary>::toSHA512() [flags=RET_VALUE_ONLY] {
#if !defined(OPENSSL_NO_SHA512) && defined(HAVE_OPENSSL_SHA512)
   DigestHelper dh(*b);
   if (dh.doDigest(SHA512_ERR, EVP_sha512(), xsink))
      return QoreValue();

   return dh.getString();
#else
   missing_openssl_feature("SHA512", xsink);
   return QoreValue();
#endif
}

//! returns a string of hexadecimal digits corresponding to the contents of the binary object; hex characters are always in lower case
/** @par Example:
    @code{.py}
string str = b.toHex();
    @endcode

    @return a string of hexadecimal digits corresponding to the contents of the binary object; hex characters are always in lower case

    @since %Qore 0.8.8

    @see
    - @ref <string>::toHex()
    - @ref Qore::make_hex_string(binary)
 */
string <binary>::toHex() [flags=CONSTANT] {
   QoreStringNode *rv = new QoreStringNode;
   rv->concatHex(b);
   return rv;
}

//! Returns the base64-encoded representation of the binary object
/** @par Example:
    @code{.py}
string base64 = bin.toBase64(64);
    @endcode

    Implementation based on <a href="http://www.ietf.org/rfc/rfc1421.txt">RFC-1421</a> and
    <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC-2045</a>

    @param maxlinelen the maximum length of a line in the resulting output string in bytes; if this value is > 0 then
    output lines will be separated by CRLF characters

    @return the base64-encoded string of the data passed

    @since %Qore 0.8.8

    @see
    - <string>::toBase64()
    - make_base64_string(binary, softint)
*/
string <binary>::toBase64(softint maxlinelen = -1) [flags=CONSTANT] {
    return new QoreStringNode(b, maxlinelen);
}

//! Returns the base64-url-encoded representation of the binary object
/** Base64-URL encoding is defined by <a href="https://datatracker.ietf.org/doc/html/rfc4648">RFC-4648</a>

    @par Example:
    @code{.py}
string base64 = bin.toBase64Url();
    @endcode

    @return the base64-url-encoded string of the data passed

    @since %Qore 1.19.0

    @see
    - <string>::toBase64Url()
    - make_base64_url_string(binary)
*/
string <binary>::toBase64Url(softint maxlinelen = -1) [flags=CONSTANT] {
    QoreStringNode* rv = new QoreStringNode;
    rv->concatBase64Url(*b);
    return rv;
}

//! Returns a portion of the binary data starting from an integer offset
/** Arguments can be negative, giving offsets from the end of the data.

    @param start The starting byte for the portion of the data where the first byte is at offset 0; if the offset is negative, it designates the number of bytes from the end of the data

    @return the portion of the data starting from an integer bte offset; the rest of the data is returned after this offset

    @par Example:
    @code{.py}
# get the last 10 bytes of a binary object
binary b1 = b0.substr(-10);
    @endcode

    @note equivalent to @ref Qore::substr(binary, softint)

    @since %Qore 0.8.8
 */
binary <binary>::substr(softint start) [flags=CONSTANT] {
   BinaryNode* b1 = new BinaryNode;
   b->substr(*b1, start);
   return b1;
}

//! Returns a portion of the binary data starting from an integer offset
/** Arguments can be negative, giving offsets from the end of the data.

    @param start The starting byte for the portion of the data where the first byte is at offset 0; if the offset is negative, it designates the number of bytes from the end of the data
    @param len The maximum number of characters to copy; if this value is negative, the rest of the string from \a start will be copied to the substring, except without - \a len characters from the end of the string

    @return the portion of the data starting from an integer bte offset; the rest of the data is returned after this offset

    @par Example:
    @code{.py}
# get the last 5 bytes of the last 10 bytes of a binary object
binary b1 = b0.substr(-10, 5);
    @endcode

    @note equivalent to @ref Qore::substr(binary, softint, softint)

    @since %Qore 0.8.8
 */
binary <binary>::substr(softint start, softint len) [flags=CONSTANT] {
   BinaryNode* b1 = new BinaryNode;
   b->substr(*b1, start, len);
   return b1;
}

//! Returns a string created from the binary data, taking an optional second argument giving the string encoding; if no second argument is passed then the @ref default_encoding "default character encoding" is assumed
/**
    @param encoding the character encoding tag for the string return value; if not present, the @ref default_encoding "default character encoding" is assumed

    @return a string created from the binary data passed

    @par Example:
    @code{.py}
string str = b.toString("iso-8859-1");
    @endcode

    @since %Qore 0.8.8
*/
string <binary>::toString(*string encoding) [flags=CONSTANT] {
   const QoreEncoding *qe = encoding ? QEM.findCreate(encoding) : QCS_DEFAULT;
   if (!b->size())
      return new QoreStringNode(qe);
   return new QoreStringNode((const char *)b->getPtr(), b->size(), qe);
}

//! Returns @ref True since binary objects can return a non-zero size
/** @return @ref True since binary objects can return a non-zero size

    @see <binary>::size()

    @since %Qore 0.8.9
*/
bool <binary>::sizep() [flags=CONSTANT] {
   return true;
}
