Mercurial > code
changeset 485:898d8b29a4f1
Switch to lowercase filenames
line wrap: on
line diff
--- a/C++/modules/Base64/Base64.cpp Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,140 +0,0 @@ -/* - * Base64.cpp -- base64 encoding and decoding - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <cassert> -#include <iterator> -#include <sstream> -#include <unordered_map> - -#include "Base64.h" - -namespace base64 { - -namespace { - -const std::string table{"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"}; - -const std::unordered_map<unsigned char, unsigned int> rtable{ - { 'A', 0 }, - { 'B', 1 }, - { 'C', 2 }, - { 'D', 3 }, - { 'E', 4 }, - { 'F', 5 }, - { 'G', 6 }, - { 'H', 7 }, - { 'I', 8 }, - { 'J', 9 }, - { 'K', 10 }, - { 'L', 11 }, - { 'M', 12 }, - { 'N', 13 }, - { 'O', 14 }, - { 'P', 15 }, - { 'Q', 16 }, - { 'R', 17 }, - { 'S', 18 }, - { 'T', 19 }, - { 'U', 20 }, - { 'V', 21 }, - { 'W', 22 }, - { 'X', 23 }, - { 'Y', 24 }, - { 'Z', 25 }, - { 'a', 26 }, - { 'b', 27 }, - { 'c', 28 }, - { 'd', 29 }, - { 'e', 30 }, - { 'f', 31 }, - { 'g', 32 }, - { 'h', 33 }, - { 'i', 34 }, - { 'j', 35 }, - { 'k', 36 }, - { 'l', 37 }, - { 'm', 38 }, - { 'n', 39 }, - { 'o', 40 }, - { 'p', 41 }, - { 'q', 42 }, - { 'r', 43 }, - { 's', 44 }, - { 't', 45 }, - { 'u', 46 }, - { 'v', 47 }, - { 'w', 48 }, - { 'x', 49 }, - { 'y', 50 }, - { 'z', 51 }, - { '0', 52 }, - { '1', 53 }, - { '2', 54 }, - { '3', 55 }, - { '4', 56 }, - { '5', 57 }, - { '6', 58 }, - { '7', 59 }, - { '8', 60 }, - { '9', 61 }, - { '+', 62 }, - { '/', 63 } -}; - -} // !namespace - -unsigned char lookup(unsigned int value) noexcept -{ - assert(value < 64); - - return table[value]; -} - -unsigned int rlookup(unsigned char ch) -{ - assert(rtable.count(ch) != 0 && ch != '='); - - return rtable.at(ch); -} - -bool isValid(unsigned char ch) noexcept -{ - return ch == '=' || rtable.count(ch); -} - -std::string encode(const std::string &input) -{ - std::string result; - std::istringstream iss(input, std::istringstream::in); - - encode(std::istreambuf_iterator<char>(iss), std::istreambuf_iterator<char>(), std::back_inserter(result)); - - return result; -} - -std::string decode(const std::string &input) -{ - std::string result; - std::istringstream iss(input, std::istringstream::in); - - decode(std::istreambuf_iterator<char>(iss), std::istreambuf_iterator<char>(), std::back_inserter(result)); - - return result; -} - -} // !base64
--- a/C++/modules/Base64/Base64.h Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,163 +0,0 @@ -/* - * Base64.h -- base64 encoding and decoding - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef _BASE_64_H_ -#define _BASE_64_H_ - -/** - * @file Base64.h - * @brief Base64 encoding and decoding - */ - -#include <stdexcept> -#include <string> - -namespace base64 { - -/** - * Get the base 64 character from the 6-bits value. - * - * @pre value must be valid - * @param value the value - */ -unsigned char lookup(unsigned int value) noexcept; - -/** - * Get the integer value from the base 64 character. - * - * @pre ch must be a valid base 64 character but not '=' - * @param ch the base64 character - */ -unsigned int rlookup(unsigned char ch); - -/** - * Check if the given character is a valid base 64 character. - * - * @param char the character - * @return true if the character is valid - */ -bool isValid(unsigned char) noexcept; - -/** - * Encode the input to the output. Requirements: - * InputIt must be InputIterator - * OutputIt must be OutputIterator - * - * @param input the beginning - * @param end the end of the data - * @param output the output destination - * @return output - */ -template <typename InputIt, typename OutputIt> -OutputIt encode(InputIt input, InputIt end, OutputIt output) -{ - while (input != end) { - char inputbuf[3] = { 0, 0, 0 }; - int count; - - for (count = 0; count < 3 && input != end; ++count) { - inputbuf[count] = *input++; - } - - *output++ = lookup(inputbuf[0] >> 2 & 0x3f); - *output++ = lookup((inputbuf[0] << 4 & 0x3f) | (inputbuf[1] >> 4 & 0x0f)); - *output++ = (count < 2) ? '=' : lookup((inputbuf[1] << 2 & 0x3c) | (inputbuf[2] >> 6 & 0x03)); - *output++ = (count < 3) ? '=' : lookup(inputbuf[2] & 0x3f); - } - - return output; -} - -/** - * Decode the input to the output. Requirements: - * InputIt must be InputIterator - * OutputIt must be OutputIterator - * - * @param input the beginning - * @param end the end of the data - * @param output the output destination - * @return output - * @throw std::invalid_argument on bad base64 string - */ -template <typename InputIt, typename OutputIt> -OutputIt decode(InputIt input, InputIt end, OutputIt output) -{ - while (input != end) { - char inputbuf[4] = { -1, -1, -1, -1 }; - int count; - - for (count = 0; count < 4 && input != end; ++count) { - if (*input != '=') { - /* Check if the character is valid and get its value */ - if (isValid(*input)) { - inputbuf[count] = rlookup(*input); - } else { - throw std::invalid_argument{"invalid base64 string"}; - } - } else { - /* A base 64 string cannot start with "=" or "==" */ - if (count == 0 || count == 1) { - throw std::invalid_argument{"invalid or truncated base64 string"}; - } - } - - input++; - } - - if (count != 4) { - throw std::invalid_argument("truncated string"); - } - - *output++ = static_cast<unsigned char>(((inputbuf[0] << 2) & 0xfc) | ((inputbuf[1] >> 4) & 0x03)); - - if (inputbuf[2] != -1) { - *output++ = static_cast<unsigned char>(((inputbuf[1] << 4) & 0xf0) | ((inputbuf[2] >> 2) & 0x0f)); - } - if (inputbuf[3] != -1) { - /* "XY=Z" is not allowed */ - if (inputbuf[2] == -1) { - throw std::invalid_argument{"invalid base64 string"}; - } - - *output++ = static_cast<unsigned char>(((inputbuf[2] << 6) & 0xc0) | (inputbuf[3] & 0x3f)); - } - } - - return output; -} - -/** - * Encode a string. - * - * @param input the input string - * @return the base64 formatted string - */ -std::string encode(const std::string &input); - -/** - * Decode a string. - * - * @param input the base64 formatted string - * @return the original string - * @throw std::invalid_argument on bad base64 string - */ -std::string decode(const std::string &input); - -} // !base64 - -#endif // !_BASE_64_H_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Base64/base64.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,140 @@ +/* + * Base64.cpp -- base64 encoding and decoding + * + * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <cassert> +#include <iterator> +#include <sstream> +#include <unordered_map> + +#include "base64.h" + +namespace base64 { + +namespace { + +const std::string table{"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"}; + +const std::unordered_map<unsigned char, unsigned int> rtable{ + { 'A', 0 }, + { 'B', 1 }, + { 'C', 2 }, + { 'D', 3 }, + { 'E', 4 }, + { 'F', 5 }, + { 'G', 6 }, + { 'H', 7 }, + { 'I', 8 }, + { 'J', 9 }, + { 'K', 10 }, + { 'L', 11 }, + { 'M', 12 }, + { 'N', 13 }, + { 'O', 14 }, + { 'P', 15 }, + { 'Q', 16 }, + { 'R', 17 }, + { 'S', 18 }, + { 'T', 19 }, + { 'U', 20 }, + { 'V', 21 }, + { 'W', 22 }, + { 'X', 23 }, + { 'Y', 24 }, + { 'Z', 25 }, + { 'a', 26 }, + { 'b', 27 }, + { 'c', 28 }, + { 'd', 29 }, + { 'e', 30 }, + { 'f', 31 }, + { 'g', 32 }, + { 'h', 33 }, + { 'i', 34 }, + { 'j', 35 }, + { 'k', 36 }, + { 'l', 37 }, + { 'm', 38 }, + { 'n', 39 }, + { 'o', 40 }, + { 'p', 41 }, + { 'q', 42 }, + { 'r', 43 }, + { 's', 44 }, + { 't', 45 }, + { 'u', 46 }, + { 'v', 47 }, + { 'w', 48 }, + { 'x', 49 }, + { 'y', 50 }, + { 'z', 51 }, + { '0', 52 }, + { '1', 53 }, + { '2', 54 }, + { '3', 55 }, + { '4', 56 }, + { '5', 57 }, + { '6', 58 }, + { '7', 59 }, + { '8', 60 }, + { '9', 61 }, + { '+', 62 }, + { '/', 63 } +}; + +} // !namespace + +unsigned char lookup(unsigned int value) noexcept +{ + assert(value < 64); + + return table[value]; +} + +unsigned int rlookup(unsigned char ch) +{ + assert(rtable.count(ch) != 0 && ch != '='); + + return rtable.at(ch); +} + +bool isValid(unsigned char ch) noexcept +{ + return ch == '=' || rtable.count(ch); +} + +std::string encode(const std::string &input) +{ + std::string result; + std::istringstream iss(input, std::istringstream::in); + + encode(std::istreambuf_iterator<char>(iss), std::istreambuf_iterator<char>(), std::back_inserter(result)); + + return result; +} + +std::string decode(const std::string &input) +{ + std::string result; + std::istringstream iss(input, std::istringstream::in); + + decode(std::istreambuf_iterator<char>(iss), std::istreambuf_iterator<char>(), std::back_inserter(result)); + + return result; +} + +} // !base64
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Base64/base64.h Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,163 @@ +/* + * Base64.h -- base64 encoding and decoding + * + * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BASE_64_H_ +#define _BASE_64_H_ + +/** + * @file Base64.h + * @brief Base64 encoding and decoding + */ + +#include <stdexcept> +#include <string> + +namespace base64 { + +/** + * Get the base 64 character from the 6-bits value. + * + * @pre value must be valid + * @param value the value + */ +unsigned char lookup(unsigned int value) noexcept; + +/** + * Get the integer value from the base 64 character. + * + * @pre ch must be a valid base 64 character but not '=' + * @param ch the base64 character + */ +unsigned int rlookup(unsigned char ch); + +/** + * Check if the given character is a valid base 64 character. + * + * @param char the character + * @return true if the character is valid + */ +bool isValid(unsigned char) noexcept; + +/** + * Encode the input to the output. Requirements: + * InputIt must be InputIterator + * OutputIt must be OutputIterator + * + * @param input the beginning + * @param end the end of the data + * @param output the output destination + * @return output + */ +template <typename InputIt, typename OutputIt> +OutputIt encode(InputIt input, InputIt end, OutputIt output) +{ + while (input != end) { + char inputbuf[3] = { 0, 0, 0 }; + int count; + + for (count = 0; count < 3 && input != end; ++count) { + inputbuf[count] = *input++; + } + + *output++ = lookup(inputbuf[0] >> 2 & 0x3f); + *output++ = lookup((inputbuf[0] << 4 & 0x3f) | (inputbuf[1] >> 4 & 0x0f)); + *output++ = (count < 2) ? '=' : lookup((inputbuf[1] << 2 & 0x3c) | (inputbuf[2] >> 6 & 0x03)); + *output++ = (count < 3) ? '=' : lookup(inputbuf[2] & 0x3f); + } + + return output; +} + +/** + * Decode the input to the output. Requirements: + * InputIt must be InputIterator + * OutputIt must be OutputIterator + * + * @param input the beginning + * @param end the end of the data + * @param output the output destination + * @return output + * @throw std::invalid_argument on bad base64 string + */ +template <typename InputIt, typename OutputIt> +OutputIt decode(InputIt input, InputIt end, OutputIt output) +{ + while (input != end) { + char inputbuf[4] = { -1, -1, -1, -1 }; + int count; + + for (count = 0; count < 4 && input != end; ++count) { + if (*input != '=') { + /* Check if the character is valid and get its value */ + if (isValid(*input)) { + inputbuf[count] = rlookup(*input); + } else { + throw std::invalid_argument{"invalid base64 string"}; + } + } else { + /* A base 64 string cannot start with "=" or "==" */ + if (count == 0 || count == 1) { + throw std::invalid_argument{"invalid or truncated base64 string"}; + } + } + + input++; + } + + if (count != 4) { + throw std::invalid_argument("truncated string"); + } + + *output++ = static_cast<unsigned char>(((inputbuf[0] << 2) & 0xfc) | ((inputbuf[1] >> 4) & 0x03)); + + if (inputbuf[2] != -1) { + *output++ = static_cast<unsigned char>(((inputbuf[1] << 4) & 0xf0) | ((inputbuf[2] >> 2) & 0x0f)); + } + if (inputbuf[3] != -1) { + /* "XY=Z" is not allowed */ + if (inputbuf[2] == -1) { + throw std::invalid_argument{"invalid base64 string"}; + } + + *output++ = static_cast<unsigned char>(((inputbuf[2] << 6) & 0xc0) | (inputbuf[3] & 0x3f)); + } + } + + return output; +} + +/** + * Encode a string. + * + * @param input the input string + * @return the base64 formatted string + */ +std::string encode(const std::string &input); + +/** + * Decode a string. + * + * @param input the base64 formatted string + * @return the original string + * @throw std::invalid_argument on bad base64 string + */ +std::string decode(const std::string &input); + +} // !base64 + +#endif // !_BASE_64_H_
--- a/C++/modules/Converter/Converter.cpp Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,92 +0,0 @@ -/* - * Converter.cpp -- iconv based converter - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <cerrno> -#include <cstring> -#include <string> -#include <iterator> -#include <memory> -#include <string> -#include <stdexcept> -#include <vector> - -#include <iconv.h> - -#include "Converter.h" - -struct Deleter { - void operator()(iconv_t desc) - { - iconv_close(desc); - } -}; - -using Iconv = std::unique_ptr<std::remove_pointer<iconv_t>::type, Deleter>; - -std::string Converter::convert(const char *from, - const char *to, - const std::string &input) -{ - // No conversion if from and to are identical - if (std::strcmp(from, to) == 0) - return input; - - // Try to open the conversion descriptor - auto cd = iconv_open(to, from); - - if (cd == (iconv_t)-1) - throw std::invalid_argument(std::strerror(errno)); - - Iconv cv(cd); - std::size_t insize(input.size()); - std::size_t outsize(insize); - std::vector<char> result(insize + 1); - - auto *b = &input[0]; - auto *p = &result[0]; - - while (insize > 0) { - /* Convert */ - auto r = iconv(cv.get(), &b, &insize, &p, &outsize); - - if (r == (size_t)-1) { - switch (errno) { - case EBADF: - case EILSEQ: - case EINVAL: - throw std::invalid_argument(std::strerror(errno)); - case E2BIG: - /* - * Here, we need to reallocate more data because the output - * string may need more space. - * - * We use 16 as an optimistic value. - */ - - result.reserve(result.size() + 16 + 1); - p = &result[result.size()]; - outsize += 16; - default: - break; - } - } - - } - - return std::string(&result[0], (p - &result[0])); -}
--- a/C++/modules/Converter/Converter.h Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -/* - * Converter.h -- iconv based converter - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef _CONVERTER_H_ -#define _CONVERTER_H_ - -/** - * @file Converter.h - * @brief Converter using libiconv - */ - -#include <string> - -/** - * @class Converter - * @brief Convert string between different encodings - */ -class Converter { -public: - /** - * Convert the string into a different encoding. - * - * @param from the from encoding - * @param to the destination encoding - * @param input the string to convert - * @return the converted string - * @throw std::invalid_argument on invalid sequence - */ - static std::string convert(const char *from, - const char *to, - const std::string &input); -}; - -#endif // !_CONVERTER_H_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Converter/converter.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,92 @@ +/* + * Converter.cpp -- iconv based converter + * + * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <cerrno> +#include <cstring> +#include <string> +#include <iterator> +#include <memory> +#include <string> +#include <stdexcept> +#include <vector> + +#include <iconv.h> + +#include "converter.h" + +struct Deleter { + void operator()(iconv_t desc) + { + iconv_close(desc); + } +}; + +using Iconv = std::unique_ptr<std::remove_pointer<iconv_t>::type, Deleter>; + +std::string Converter::convert(const char *from, + const char *to, + const std::string &input) +{ + // No conversion if from and to are identical + if (std::strcmp(from, to) == 0) + return input; + + // Try to open the conversion descriptor + auto cd = iconv_open(to, from); + + if (cd == (iconv_t)-1) + throw std::invalid_argument(std::strerror(errno)); + + Iconv cv(cd); + std::size_t insize(input.size()); + std::size_t outsize(insize); + std::vector<char> result(insize + 1); + + auto *b = &input[0]; + auto *p = &result[0]; + + while (insize > 0) { + /* Convert */ + auto r = iconv(cv.get(), &b, &insize, &p, &outsize); + + if (r == (size_t)-1) { + switch (errno) { + case EBADF: + case EILSEQ: + case EINVAL: + throw std::invalid_argument(std::strerror(errno)); + case E2BIG: + /* + * Here, we need to reallocate more data because the output + * string may need more space. + * + * We use 16 as an optimistic value. + */ + + result.reserve(result.size() + 16 + 1); + p = &result[result.size()]; + outsize += 16; + default: + break; + } + } + + } + + return std::string(&result[0], (p - &result[0])); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Converter/converter.h Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,49 @@ +/* + * Converter.h -- iconv based converter + * + * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _CONVERTER_H_ +#define _CONVERTER_H_ + +/** + * @file Converter.h + * @brief Converter using libiconv + */ + +#include <string> + +/** + * @class Converter + * @brief Convert string between different encodings + */ +class Converter { +public: + /** + * Convert the string into a different encoding. + * + * @param from the from encoding + * @param to the destination encoding + * @param input the string to convert + * @return the converted string + * @throw std::invalid_argument on invalid sequence + */ + static std::string convert(const char *from, + const char *to, + const std::string &input); +}; + +#endif // !_CONVERTER_H_
--- a/C++/modules/Date/Date.cpp Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -/* - * Date.cpp -- date and time manipulation - * - * Copyright (c) 2011-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "Date.h" - -Date::Date() -{ - m_timestamp = time(nullptr); -} - -Date::Date(time_t timestamp) -{ - m_timestamp = timestamp; -} - -std::string Date::format(const std::string &format) -{ - char buffer[512]; - struct tm *tm; - - tm = localtime(&m_timestamp); - strftime(buffer, sizeof (buffer), format.c_str(), tm); - - return std::string(buffer); -} - -bool operator==(const Date &d1, const Date &d2) -{ - return d1.timestamp() == d2.timestamp(); -} - -bool operator<=(const Date &d1, const Date &d2) -{ - return d1.timestamp() <= d2.timestamp(); -}
--- a/C++/modules/Date/Date.h Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -/* - * Date.h -- date and time manipulation - * - * Copyright (c) 2011-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef _DATE_H_ -#define _DATE_H_ - -/** - * @file Date.h - * @brief Basic date management. - */ - -#include <cstdint> -#include <ctime> -#include <string> - -/** - * @class Date - * @brief Basic date class and format. - */ -class Date { -private: - time_t m_timestamp; - -public: - /** - * Default constructor to the current date. - */ - Date(); - - /** - * Date with specific timestamp. - * - * @param timestamp the timestamp - */ - Date(time_t timestamp); - - /** - * Get the timestamp. - * - * @return the timestamp - */ - inline time_t timestamp() const noexcept - { - return m_timestamp; - } - - /** - * Format the current that in the specified format, - * see strftime(3) for patterns. - * - * @param format the format - * @return the date formated - */ - std::string format(const std::string &format); -}; - -/** - * Check is two dates are identical. - * - * @param d1 the first date - * @param d2 the second date - * @return true if same - */ -bool operator==(const Date &d1, const Date &d2); - -/** - * Check is a date is less or equal the second date. - * - * @param d1 the first date - * @param d2 the second date - * @return true if d1 <= d2 - */ -bool operator<=(const Date &d1, const Date &d2); - -#endif // !_DATE_H_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Date/date.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,50 @@ +/* + * date.cpp -- date and time manipulation + * + * Copyright (c) 2011-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "date.h" + +Date::Date() +{ + m_timestamp = time(nullptr); +} + +Date::Date(time_t timestamp) +{ + m_timestamp = timestamp; +} + +std::string Date::format(const std::string &format) +{ + char buffer[512]; + struct tm *tm; + + tm = localtime(&m_timestamp); + strftime(buffer, sizeof (buffer), format.c_str(), tm); + + return std::string(buffer); +} + +bool operator==(const Date &d1, const Date &d2) +{ + return d1.timestamp() == d2.timestamp(); +} + +bool operator<=(const Date &d1, const Date &d2) +{ + return d1.timestamp() <= d2.timestamp(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Date/date.h Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,90 @@ +/* + * date.h -- date and time manipulation + * + * Copyright (c) 2011-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _DATE_H_ +#define _DATE_H_ + +/** + * @file date.h + * @brief Basic date management. + */ + +#include <cstdint> +#include <ctime> +#include <string> + +/** + * @class Date + * @brief Basic date class and format. + */ +class Date { +private: + time_t m_timestamp; + +public: + /** + * Default constructor to the current date. + */ + Date(); + + /** + * Date with specific timestamp. + * + * @param timestamp the timestamp + */ + Date(time_t timestamp); + + /** + * Get the timestamp. + * + * @return the timestamp + */ + inline time_t timestamp() const noexcept + { + return m_timestamp; + } + + /** + * Format the current that in the specified format, + * see strftime(3) for patterns. + * + * @param format the format + * @return the date formated + */ + std::string format(const std::string &format); +}; + +/** + * Check is two dates are identical. + * + * @param d1 the first date + * @param d2 the second date + * @return true if same + */ +bool operator==(const Date &d1, const Date &d2); + +/** + * Check is a date is less or equal the second date. + * + * @param d1 the first date + * @param d2 the second date + * @return true if d1 <= d2 + */ +bool operator<=(const Date &d1, const Date &d2); + +#endif // !_DATE_H_
--- a/C++/modules/Directory/Directory.cpp Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,159 +0,0 @@ -/* - * Directory.cpp -- open and read directories - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <sstream> -#include <stdexcept> - -#include "Directory.h" - -#if defined(_WIN32) -# include <Windows.h> -#else -# include <cstring> -# include <cerrno> - -# include <sys/types.h> -# include <dirent.h> -#endif - -#if defined(_WIN32) - -namespace { - -std::string systemError() -{ - LPSTR error = nullptr; - std::string errmsg = "Unknown error"; - - FormatMessageA( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - NULL, - GetLastError(), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPSTR)&error, 0, NULL); - - if (error) { - errmsg = std::string(error); - LocalFree(error); - } - - return errmsg; -} - -} // !namespace - -void Directory::systemLoad(const std::string &path, int flags) -{ - std::ostringstream oss; - HANDLE handle; - WIN32_FIND_DATA fdata; - - oss << path << "\\*"; - handle = FindFirstFile(oss.str().c_str(), &fdata); - - if (handle == nullptr) - throw std::runtime_error(systemError()); - - do { - DirectoryEntry entry; - - entry.name = fdata.cFileName; - if (entry.name == "." && !(flags & Directory::Dot)) - continue; - if (entry.name == ".." && !(flags & Directory::DotDot)) - continue; - - switch (fdata.dwFileAttributes) { - case FILE_ATTRIBUTE_DIRECTORY: - entry.type = DirectoryEntry::Dir; - break; - case FILE_ATTRIBUTE_NORMAL: - entry.type = DirectoryEntry::File; - break; - case FILE_ATTRIBUTE_REPARSE_POINT: - entry.type = DirectoryEntry::Link; - break; - default: - break; - } - - push_back(entry); - } while (FindNextFile(handle, &fdata) != 0); - - FindClose(handle); -} - -#else - -void Directory::systemLoad(const std::string &path, int flags) -{ - DIR *dp; - struct dirent *ent; - - if ((dp = opendir(path.c_str())) == nullptr) - throw std::runtime_error(strerror(errno)); - - while ((ent = readdir(dp)) != nullptr) { - DirectoryEntry entry; - - entry.name = ent->d_name; - if (entry.name == "." && !(flags & Directory::Dot)) - continue; - if (entry.name == ".." && !(flags & Directory::DotDot)) - continue; - - switch (ent->d_type) { - case DT_DIR: - entry.type = DirectoryEntry::Dir; - break; - case DT_REG: - entry.type = DirectoryEntry::File; - break; - case DT_LNK: - entry.type = DirectoryEntry::Link; - break; - default: - break; - } - - push_back(entry); - } - - closedir(dp); -} - -#endif - -bool operator==(const DirectoryEntry &e1, const DirectoryEntry &e2) -{ - return e1.name == e2.name && e1.type == e2.type; -} - -Directory::Directory() -{ -} - -Directory::Directory(const std::string &path, int flags) -{ - systemLoad(path, flags); -} - -int Directory::count() const -{ - return size(); -}
--- a/C++/modules/Directory/Directory.h Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -/* - * Directory.h -- open and read directories - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef _DIRECTORY_H_ -#define _DIRECTORY_H_ - -#include <cstddef> -#include <string> -#include <vector> - -/** - * @class DirectoryEntry - * @brief Entry in the directory list - */ -class DirectoryEntry { -public: - /** - * @brief Describe the type of an entry - */ - enum { - Unknown = 0, - File, - Dir, - Link - }; - - std::string name; //! name of entry (base name) - int type{Unknown}; //! type of file - - friend bool operator==(const DirectoryEntry &e1, const DirectoryEntry &e2); -}; - -/** - * @class Directory - * @brief class to manipulate directories - * - * This class allow the user to iterate directories in a for range based - * loop using iterators. - */ -class Directory : public std::vector<DirectoryEntry> { -public: - /** - * @enum Flags - * @brief optional flags to read directories - */ - enum Flags { - Dot = (1 << 0), //!< If set, lists "." too - DotDot = (1 << 1) //!< If set, lists ".." too - }; - -private: - void systemLoad(const std::string &path, int flags); - -public: - /** - * Default constructor, does nothing. - */ - Directory(); - - /** - * Open a directory and read all its content. - * @param path the path - * @param flags the optional flags - */ - Directory(const std::string &path, int flags = 0); - - /** - * Get the number of entries in the directory. - * - * @return the number - */ - int count() const; -}; - -#endif // !_DIRECTORY_H_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Directory/directory.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,159 @@ +/* + * directory.cpp -- open and read directories + * + * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sstream> +#include <stdexcept> + +#include "directory.h" + +#if defined(_WIN32) +# include <Windows.h> +#else +# include <cstring> +# include <cerrno> + +# include <sys/types.h> +# include <dirent.h> +#endif + +#if defined(_WIN32) + +namespace { + +std::string systemError() +{ + LPSTR error = nullptr; + std::string errmsg = "Unknown error"; + + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&error, 0, NULL); + + if (error) { + errmsg = std::string(error); + LocalFree(error); + } + + return errmsg; +} + +} // !namespace + +void Directory::systemLoad(const std::string &path, int flags) +{ + std::ostringstream oss; + HANDLE handle; + WIN32_FIND_DATA fdata; + + oss << path << "\\*"; + handle = FindFirstFile(oss.str().c_str(), &fdata); + + if (handle == nullptr) + throw std::runtime_error(systemError()); + + do { + DirectoryEntry entry; + + entry.name = fdata.cFileName; + if (entry.name == "." && !(flags & Directory::Dot)) + continue; + if (entry.name == ".." && !(flags & Directory::DotDot)) + continue; + + switch (fdata.dwFileAttributes) { + case FILE_ATTRIBUTE_DIRECTORY: + entry.type = DirectoryEntry::Dir; + break; + case FILE_ATTRIBUTE_NORMAL: + entry.type = DirectoryEntry::File; + break; + case FILE_ATTRIBUTE_REPARSE_POINT: + entry.type = DirectoryEntry::Link; + break; + default: + break; + } + + push_back(entry); + } while (FindNextFile(handle, &fdata) != 0); + + FindClose(handle); +} + +#else + +void Directory::systemLoad(const std::string &path, int flags) +{ + DIR *dp; + struct dirent *ent; + + if ((dp = opendir(path.c_str())) == nullptr) + throw std::runtime_error(strerror(errno)); + + while ((ent = readdir(dp)) != nullptr) { + DirectoryEntry entry; + + entry.name = ent->d_name; + if (entry.name == "." && !(flags & Directory::Dot)) + continue; + if (entry.name == ".." && !(flags & Directory::DotDot)) + continue; + + switch (ent->d_type) { + case DT_DIR: + entry.type = DirectoryEntry::Dir; + break; + case DT_REG: + entry.type = DirectoryEntry::File; + break; + case DT_LNK: + entry.type = DirectoryEntry::Link; + break; + default: + break; + } + + push_back(entry); + } + + closedir(dp); +} + +#endif + +bool operator==(const DirectoryEntry &e1, const DirectoryEntry &e2) +{ + return e1.name == e2.name && e1.type == e2.type; +} + +Directory::Directory() +{ +} + +Directory::Directory(const std::string &path, int flags) +{ + systemLoad(path, flags); +} + +int Directory::count() const +{ + return size(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Directory/directory.h Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,90 @@ +/* + * directory.h -- open and read directories + * + * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _DIRECTORY_H_ +#define _DIRECTORY_H_ + +#include <cstddef> +#include <string> +#include <vector> + +/** + * @class DirectoryEntry + * @brief Entry in the directory list + */ +class DirectoryEntry { +public: + /** + * @brief Describe the type of an entry + */ + enum { + Unknown = 0, + File, + Dir, + Link + }; + + std::string name; //! name of entry (base name) + int type{Unknown}; //! type of file + + friend bool operator==(const DirectoryEntry &e1, const DirectoryEntry &e2); +}; + +/** + * @class Directory + * @brief class to manipulate directories + * + * This class allow the user to iterate directories in a for range based + * loop using iterators. + */ +class Directory : public std::vector<DirectoryEntry> { +public: + /** + * @enum Flags + * @brief optional flags to read directories + */ + enum Flags { + Dot = (1 << 0), //!< If set, lists "." too + DotDot = (1 << 1) //!< If set, lists ".." too + }; + +private: + void systemLoad(const std::string &path, int flags); + +public: + /** + * Default constructor, does nothing. + */ + Directory(); + + /** + * Open a directory and read all its content. + * @param path the path + * @param flags the optional flags + */ + Directory(const std::string &path, int flags = 0); + + /** + * Get the number of entries in the directory. + * + * @return the number + */ + int count() const; +}; + +#endif // !_DIRECTORY_H_
--- a/C++/modules/Dynlib/Dynlib.cpp Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,144 +0,0 @@ -/* - * DynLib.cpp -- portable shared library loader - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <stdexcept> - -#if defined(_WIN32) -# include <Windows.h> -#else -# include <dlfcn.h> -#endif - -#include "Dynlib.h" - -#if defined(_WIN32) - -namespace { - -std::string systemError() -{ - LPSTR error = nullptr; - std::string errmsg = "Unknown error"; - - FormatMessageA( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - NULL, - GetLastError(), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPSTR)&error, 0, NULL); - - if (error) { - errmsg = std::string(error); - LocalFree(error); - } - - return errmsg; -} - -} - -void Dynlib::systemInit() -{ - m_handle = nullptr; -} - -Dynlib::Handle Dynlib::systemLoad(const std::string &path, Policy) const -{ - Handle handle = LoadLibraryA(path.c_str()); - - if (handle == nullptr) - throw std::runtime_error(systemError()); - - return handle; -} - -Dynlib::Sym Dynlib::systemSym(const std::string &name) -{ - Sym sym; - - if (m_handle == nullptr) - throw std::runtime_error("library not loaded"); - - sym = GetProcAddress(m_handle, name.c_str()); - if (sym == nullptr) - throw std::out_of_range(systemError()); - - return sym; -} - -void Dynlib::systemClose() -{ - if (m_handle != nullptr) - FreeLibrary(m_handle); -} - -#else - -void Dynlib::systemInit() -{ - m_handle = nullptr; -} - -Dynlib::Handle Dynlib::systemLoad(const std::string &path, Policy policy) const -{ - int mode = (policy == Immediately) ? RTLD_NOW : RTLD_LAZY; - Handle handle; - - handle = dlopen(path.c_str(), mode); - if (handle == nullptr) - throw std::runtime_error(dlerror()); - - return handle; -} - -Dynlib::Sym Dynlib::systemSym(const std::string &name) -{ - Sym sym; - - if (m_handle == nullptr) - throw std::runtime_error("library not loaded"); - - sym = dlsym(m_handle, name.c_str()); - if (sym == nullptr) - throw std::out_of_range(dlerror()); - - return sym; -} - -void Dynlib::systemClose() -{ - if (m_handle != nullptr) - dlclose(m_handle); -} - -#endif - -Dynlib::Dynlib() -{ - systemInit(); -} - -Dynlib::Dynlib(const std::string &path, Policy policy) -{ - m_handle = systemLoad(path, policy); -} - -Dynlib::~Dynlib() -{ - systemClose(); -}
--- a/C++/modules/Dynlib/Dynlib.h Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,103 +0,0 @@ -/* - * DynLib.h -- portable shared library loader - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef _DYNLIB_H_ -#define _DYNLIB_H_ - -#include <string> - -#if defined(_WIN32) -# include <Windows.h> -# define DYNLIB_EXPORT __declspec(dllexport) -#else -# define DYNLIB_EXPORT -#endif - -/** - * @class Dynlib - * @brief Load a dynamic module - * - * This class is a portable wrapper to load shared libraries on - * supported systems. - */ -class Dynlib { -public: -#if defined(_WIN32) - using Handle = HMODULE; - using Sym = FARPROC; -#else - using Handle = void *; - using Sym = void *; -#endif - - enum Policy { - Immediately, //! load symbols immediately - Lazy //! load symbols when needed - }; - -private: - Handle m_handle; - - void systemInit(); - Handle systemLoad(const std::string &path, Policy policy) const; - Sym systemSym(const std::string &name); - void systemClose(); - -public: - /** - * Copy is forbidden. - */ - Dynlib(const Dynlib &) = delete; - Dynlib &operator=(const Dynlib &) = delete; - - /** - * Default constructor. - */ - Dynlib(); - - /** - * Constructor to load a shared module. The path must - * be absolute. - * - * @param path the absolute path - * @param policy the policy to load - * @throw std::runtime_error on error - */ - Dynlib(const std::string &path, Policy policy = Immediately); - - /** - * Close the library automatically. - */ - ~Dynlib(); - - /** - * Get a symbol from the library. - * - * @param name the symbol - * @return the symbol - * @throw std::runtime_error on error - * @throw std::out_of_range if not found - */ - template <typename T> - T sym(const std::string &name) - { - return reinterpret_cast<T>(systemSym(name)); - } -}; - -#endif // !_DYNLIB_H_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Dynlib/dynlib.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,144 @@ +/* + * dynlib.cpp -- portable shared library loader + * + * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdexcept> + +#if defined(_WIN32) +# include <Windows.h> +#else +# include <dlfcn.h> +#endif + +#include "dynlib.h" + +#if defined(_WIN32) + +namespace { + +std::string systemError() +{ + LPSTR error = nullptr; + std::string errmsg = "Unknown error"; + + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&error, 0, NULL); + + if (error) { + errmsg = std::string(error); + LocalFree(error); + } + + return errmsg; +} + +} + +void Dynlib::systemInit() +{ + m_handle = nullptr; +} + +Dynlib::Handle Dynlib::systemLoad(const std::string &path, Policy) const +{ + Handle handle = LoadLibraryA(path.c_str()); + + if (handle == nullptr) + throw std::runtime_error(systemError()); + + return handle; +} + +Dynlib::Sym Dynlib::systemSym(const std::string &name) +{ + Sym sym; + + if (m_handle == nullptr) + throw std::runtime_error("library not loaded"); + + sym = GetProcAddress(m_handle, name.c_str()); + if (sym == nullptr) + throw std::out_of_range(systemError()); + + return sym; +} + +void Dynlib::systemClose() +{ + if (m_handle != nullptr) + FreeLibrary(m_handle); +} + +#else + +void Dynlib::systemInit() +{ + m_handle = nullptr; +} + +Dynlib::Handle Dynlib::systemLoad(const std::string &path, Policy policy) const +{ + int mode = (policy == Immediately) ? RTLD_NOW : RTLD_LAZY; + Handle handle; + + handle = dlopen(path.c_str(), mode); + if (handle == nullptr) + throw std::runtime_error(dlerror()); + + return handle; +} + +Dynlib::Sym Dynlib::systemSym(const std::string &name) +{ + Sym sym; + + if (m_handle == nullptr) + throw std::runtime_error("library not loaded"); + + sym = dlsym(m_handle, name.c_str()); + if (sym == nullptr) + throw std::out_of_range(dlerror()); + + return sym; +} + +void Dynlib::systemClose() +{ + if (m_handle != nullptr) + dlclose(m_handle); +} + +#endif + +Dynlib::Dynlib() +{ + systemInit(); +} + +Dynlib::Dynlib(const std::string &path, Policy policy) +{ + m_handle = systemLoad(path, policy); +} + +Dynlib::~Dynlib() +{ + systemClose(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Dynlib/dynlib.h Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,103 @@ +/* + * dynlib.h -- portable shared library loader + * + * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _DYNLIB_H_ +#define _DYNLIB_H_ + +#include <string> + +#if defined(_WIN32) +# include <Windows.h> +# define DYNLIB_EXPORT __declspec(dllexport) +#else +# define DYNLIB_EXPORT +#endif + +/** + * @class Dynlib + * @brief Load a dynamic module + * + * This class is a portable wrapper to load shared libraries on + * supported systems. + */ +class Dynlib { +public: +#if defined(_WIN32) + using Handle = HMODULE; + using Sym = FARPROC; +#else + using Handle = void *; + using Sym = void *; +#endif + + enum Policy { + Immediately, //! load symbols immediately + Lazy //! load symbols when needed + }; + +private: + Handle m_handle; + + void systemInit(); + Handle systemLoad(const std::string &path, Policy policy) const; + Sym systemSym(const std::string &name); + void systemClose(); + +public: + /** + * Copy is forbidden. + */ + Dynlib(const Dynlib &) = delete; + Dynlib &operator=(const Dynlib &) = delete; + + /** + * Default constructor. + */ + Dynlib(); + + /** + * Constructor to load a shared module. The path must + * be absolute. + * + * @param path the absolute path + * @param policy the policy to load + * @throw std::runtime_error on error + */ + Dynlib(const std::string &path, Policy policy = Immediately); + + /** + * Close the library automatically. + */ + ~Dynlib(); + + /** + * Get a symbol from the library. + * + * @param name the symbol + * @return the symbol + * @throw std::runtime_error on error + * @throw std::out_of_range if not found + */ + template <typename T> + T sym(const std::string &name) + { + return reinterpret_cast<T>(systemSym(name)); + } +}; + +#endif // !_DYNLIB_H_
--- a/C++/modules/Format/Format.cpp Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -/* - * Format.cpp -- convenient function for formatting text with escape sequences - * - * Copyright (c) 2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <array> -#include <sstream> -#include <unordered_map> - -#include "Format.h" - -namespace fmt { - -namespace { - -const std::array<unsigned char, 17> colorsTable{ - 0, // Default - 30, // Black - 31, // Red - 32, // Green - 33, // Yellow - 34, // Blue - 35, // Magenta - 36, // Cyan - 37, // Light gray - 90, // Dark gray - 91, // Light red - 92, // Light green - 93, // Light yellow - 94, // Light blue - 95, // Light cyan - 96, // White - 97 -}; - -const std::unordered_map<int, int> attributesTable{ - { Bold, 1 }, - { Dim, 2 }, - { Underline, 4 }, - { Blink, 5 }, - { Reverse, 7 }, - { Hidden, 8 } -}; - -} // !namespace - -std::string convert(std::string input, unsigned short flags) -{ - std::ostringstream oss; - -#if !defined(_WIN32) - // Attributes - for (const auto &pair : attributesTable) { - if (flags & pair.first) { - oss << "\033[" << pair.second << "m"; - } - } - - // Background - if (((flags >> 11) & 0x3f) != Default) { - oss << "\033[" << std::to_string(colorsTable[static_cast<unsigned char>((flags >> 11) & 0x3f)] + 10) << "m"; - } - - // Foreground - if (((flags >> 6) & 0x3f) != Default) { - oss << "\033[" << std::to_string(colorsTable[static_cast<unsigned char>((flags >> 6) & 0x3f)]) << "m"; - } - - oss << std::move(input) << "\033[0m"; -#else - oss << std::move(input); -#endif - - return oss.str(); -} - -} // !fmt
--- a/C++/modules/Format/Format.h Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,111 +0,0 @@ -/* - * Format.h -- convenient function for formatting text with escape sequences - * - * Copyright (c) 2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef _FORMAT_H_ -#define _FORMAT_H_ - -#include <string> - -/** - * Format namespace. - */ -namespace fmt { - -/** - * Enumeration of supported colors. - */ -enum Color { - Default = 0, - Black, - Red, - Green, - Yellow, - Blue, - Magenta, - Cyan, - LightGray, - DarkGray, - LightRed, - LightGreen, - LightYellow, - LightBlue, - LightMagenta, - LightCyan, - White -}; - -/** - * Enumeration of supported attributes. - */ -enum Attributes { - None = 0, - Bold = (1 << 0), //! [1m - Dim = (1 << 1), //! [2m - Underline = (1 << 2), //! [4m - Blink = (1 << 3), //! [5m - Reverse = (1 << 4), //! [7m - Hidden = (1 << 5) //! [8m -}; - -/** - * Create a mask for the foreground color. - * - * @param color the foreground color - * @return the correct mask - */ -constexpr unsigned short fg(unsigned short color) noexcept -{ - return color << 6; -} - -/** - * Create a mask for the background color. - * - * @param color the background color - * @return the correct mask - */ -constexpr unsigned short bg(unsigned short color) noexcept -{ - return color << 11; -} - -/** - * Return a formatted string with escape sequences. The string is terminated with reset sequence. - * - * Flags is defined as following: - * - * b b b b b f f f | f f a a a a a a - * - * Where: - * - b is the background color - * - g is the foreground color - * - a are attributes - * - * The attributes and colors are OR'ed together and to specify the background or foreground - * you must use the dedicated fg() and bg() functions. - * - * @param input the input string - * @param flags the flags - * @return the formatted string - * @example convert(fg(Red) | bg(White) | Bold | Underline); - */ -std::string convert(std::string input, unsigned short flags); - -} // !fmt - -#endif // !_FORMAT_H_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Format/format.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,90 @@ +/* + * format.cpp -- convenient function for formatting text with escape sequences + * + * Copyright (c) 2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <array> +#include <sstream> +#include <unordered_map> + +#include "format.h" + +namespace fmt { + +namespace { + +const std::array<unsigned char, 17> colorsTable{ + 0, // Default + 30, // Black + 31, // Red + 32, // Green + 33, // Yellow + 34, // Blue + 35, // Magenta + 36, // Cyan + 37, // Light gray + 90, // Dark gray + 91, // Light red + 92, // Light green + 93, // Light yellow + 94, // Light blue + 95, // Light cyan + 96, // White + 97 +}; + +const std::unordered_map<int, int> attributesTable{ + { Bold, 1 }, + { Dim, 2 }, + { Underline, 4 }, + { Blink, 5 }, + { Reverse, 7 }, + { Hidden, 8 } +}; + +} // !namespace + +std::string convert(std::string input, unsigned short flags) +{ + std::ostringstream oss; + +#if !defined(_WIN32) + // Attributes + for (const auto &pair : attributesTable) { + if (flags & pair.first) { + oss << "\033[" << pair.second << "m"; + } + } + + // Background + if (((flags >> 11) & 0x3f) != Default) { + oss << "\033[" << std::to_string(colorsTable[static_cast<unsigned char>((flags >> 11) & 0x3f)] + 10) << "m"; + } + + // Foreground + if (((flags >> 6) & 0x3f) != Default) { + oss << "\033[" << std::to_string(colorsTable[static_cast<unsigned char>((flags >> 6) & 0x3f)]) << "m"; + } + + oss << std::move(input) << "\033[0m"; +#else + oss << std::move(input); +#endif + + return oss.str(); +} + +} // !fmt
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Format/format.h Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,111 @@ +/* + * format.h -- convenient function for formatting text with escape sequences + * + * Copyright (c) 2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _FORMAT_H_ +#define _FORMAT_H_ + +#include <string> + +/** + * Format namespace. + */ +namespace fmt { + +/** + * Enumeration of supported colors. + */ +enum Color { + Default = 0, + Black, + Red, + Green, + Yellow, + Blue, + Magenta, + Cyan, + LightGray, + DarkGray, + LightRed, + LightGreen, + LightYellow, + LightBlue, + LightMagenta, + LightCyan, + White +}; + +/** + * Enumeration of supported attributes. + */ +enum Attributes { + None = 0, + Bold = (1 << 0), //! [1m + Dim = (1 << 1), //! [2m + Underline = (1 << 2), //! [4m + Blink = (1 << 3), //! [5m + Reverse = (1 << 4), //! [7m + Hidden = (1 << 5) //! [8m +}; + +/** + * Create a mask for the foreground color. + * + * @param color the foreground color + * @return the correct mask + */ +constexpr unsigned short fg(unsigned short color) noexcept +{ + return color << 6; +} + +/** + * Create a mask for the background color. + * + * @param color the background color + * @return the correct mask + */ +constexpr unsigned short bg(unsigned short color) noexcept +{ + return color << 11; +} + +/** + * Return a formatted string with escape sequences. The string is terminated with reset sequence. + * + * Flags is defined as following: + * + * b b b b b f f f | f f a a a a a a + * + * Where: + * - b is the background color + * - g is the foreground color + * - a are attributes + * + * The attributes and colors are OR'ed together and to specify the background or foreground + * you must use the dedicated fg() and bg() functions. + * + * @param input the input string + * @param flags the flags + * @return the formatted string + * @example convert(fg(Red) | bg(White) | Bold | Underline); + */ +std::string convert(std::string input, unsigned short flags); + +} // !fmt + +#endif // !_FORMAT_H_
--- a/C++/modules/Hash/Hash.cpp Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ -/* - * Hash.cpp -- hash functions - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "Hash.h" - -#include <openssl/sha.h> -#include <openssl/md5.h> - -namespace hash { - -namespace { - -template <typename Context> -using Init = int (*)(Context *); - -template <typename Context> -using Update = int (*)(Context *, const void *, size_t); - -template <typename Context> -using Final = int (*)(unsigned char *, Context *); - -template <typename Context, size_t Length> -std::string convert(const std::string &input, Init<Context> init, Update<Context> update, Final<Context> finalize) -{ - unsigned char digest[Length]; - char hash[Length * 2 + 1]; - - Context ctx; - init(&ctx); - update(&ctx, input.c_str(), input.length()); - finalize(digest, &ctx); - - for (unsigned long i = 0; i < Length; i++) - sprintf(&hash[i * 2], "%02x", (unsigned int)digest[i]); - - return std::string(hash); -} - -} // !namespace - -std::string md5(const std::string &input) -{ - return convert<MD5_CTX, MD5_DIGEST_LENGTH>(input, MD5_Init, MD5_Update, MD5_Final); -} - -std::string sha1(const std::string &input) -{ - return convert<SHA_CTX, SHA_DIGEST_LENGTH>(input, SHA1_Init, SHA1_Update, SHA1_Final); -} - -std::string sha256(const std::string &input) -{ - return convert<SHA256_CTX, SHA256_DIGEST_LENGTH>(input, SHA256_Init, SHA256_Update, SHA256_Final); -} - -std::string sha512(const std::string &input) -{ - return convert<SHA512_CTX, SHA512_DIGEST_LENGTH>(input, SHA512_Init, SHA512_Update, SHA512_Final); -} - -} // !hash
--- a/C++/modules/Hash/Hash.h Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +0,0 @@ -/* - * Hash.h -- hash functions - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef _HASH_H_ -#define _HASH_H_ - -/** - * @file Hash.h - * @brief Hash functions - */ - -#include <string> - -namespace hash { - -/** - * Hash using MD5. - * - * @param input the input string - * @return the hashed string - */ -std::string md5(const std::string &input); - -/** - * Hash using SHA1. - * - * @param input the input string - * @return the hashed string - */ -std::string sha1(const std::string &input); - -/** - * Hash using SHA256. - * - * @param input the input string - * @return the hashed string - */ -std::string sha256(const std::string &input); - -/** - * Hash using SHA512. - * - * @param input the input string - * @return the hashed string - */ -std::string sha512(const std::string &input); - -} // !hash - -#endif // !_HASH_H_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Hash/hash.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,76 @@ +/* + * hash.cpp -- hash functions + * + * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "hash.h" + +#include <openssl/sha.h> +#include <openssl/md5.h> + +namespace hash { + +namespace { + +template <typename Context> +using Init = int (*)(Context *); + +template <typename Context> +using Update = int (*)(Context *, const void *, size_t); + +template <typename Context> +using Final = int (*)(unsigned char *, Context *); + +template <typename Context, size_t Length> +std::string convert(const std::string &input, Init<Context> init, Update<Context> update, Final<Context> finalize) +{ + unsigned char digest[Length]; + char hash[Length * 2 + 1]; + + Context ctx; + init(&ctx); + update(&ctx, input.c_str(), input.length()); + finalize(digest, &ctx); + + for (unsigned long i = 0; i < Length; i++) + sprintf(&hash[i * 2], "%02x", (unsigned int)digest[i]); + + return std::string(hash); +} + +} // !namespace + +std::string md5(const std::string &input) +{ + return convert<MD5_CTX, MD5_DIGEST_LENGTH>(input, MD5_Init, MD5_Update, MD5_Final); +} + +std::string sha1(const std::string &input) +{ + return convert<SHA_CTX, SHA_DIGEST_LENGTH>(input, SHA1_Init, SHA1_Update, SHA1_Final); +} + +std::string sha256(const std::string &input) +{ + return convert<SHA256_CTX, SHA256_DIGEST_LENGTH>(input, SHA256_Init, SHA256_Update, SHA256_Final); +} + +std::string sha512(const std::string &input) +{ + return convert<SHA512_CTX, SHA512_DIGEST_LENGTH>(input, SHA512_Init, SHA512_Update, SHA512_Final); +} + +} // !hash
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Hash/hash.h Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,65 @@ +/* + * hash.h -- hash functions + * + * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _HASH_H_ +#define _HASH_H_ + +/** + * @file hash.h + * @brief Hash functions + */ + +#include <string> + +namespace hash { + +/** + * Hash using MD5. + * + * @param input the input string + * @return the hashed string + */ +std::string md5(const std::string &input); + +/** + * Hash using SHA1. + * + * @param input the input string + * @return the hashed string + */ +std::string sha1(const std::string &input); + +/** + * Hash using SHA256. + * + * @param input the input string + * @return the hashed string + */ +std::string sha256(const std::string &input); + +/** + * Hash using SHA512. + * + * @param input the input string + * @return the hashed string + */ +std::string sha512(const std::string &input); + +} // !hash + +#endif // !_HASH_H_
--- a/C++/modules/Ini/Ini.cpp Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,445 +0,0 @@ -/* - * Ini.cpp -- .ini file parsing - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <cctype> -#include <cstring> -#include <iostream> -#include <iterator> -#include <fstream> -#include <sstream> -#include <stdexcept> - -#if defined(_WIN32) -# include <Shlwapi.h> // for PathIsRelative -#endif - -#include "Ini.h" - -namespace { - -using namespace ini; - -using StreamIterator = std::istreambuf_iterator<char>; -using TokenIterator = std::vector<Token>::const_iterator; - -inline bool isAbsolute(const std::string &path) noexcept -{ -#if defined(_WIN32) - return !PathIsRelative(path.c_str()); -#else - return path.size() > 0 && path[0] == '/'; -#endif -} - -inline bool isQuote(char c) noexcept -{ - return c == '\'' || c == '"'; -} - -inline bool isSpace(char c) noexcept -{ - /* Custom version because std::isspace includes \n as space */ - return c == ' ' || c == '\t'; -} - -inline bool isList(char c) noexcept -{ - return c == '(' || c == ')' || c == ','; -} - -inline bool isReserved(char c) noexcept -{ - return isList(c) || isQuote(c) || c == '[' || c == ']' || c == '@' || c == '#' || c == '='; -} - -void analyzeLine(int &line, int &column, StreamIterator &it) noexcept -{ - assert(*it == '\n'); - - ++ line; - ++ it; - column = 0; -} - -void analyzeComment(int &column, StreamIterator &it, StreamIterator end) noexcept -{ - assert(*it == '#'); - - while (it != end && *it != '\n') { - ++ column; - ++ it; - } -} - -void analyzeSpaces(int &column, StreamIterator &it, StreamIterator end) noexcept -{ - assert(isSpace(*it)); - - while (it != end && isSpace(*it)) { - ++ column; - ++ it; - } -} - -void analyzeList(Tokens &list, int line, int &column, StreamIterator &it) noexcept -{ - assert(isList(*it)); - - switch (*it++) { - case '(': - list.emplace_back(Token::ListBegin, line, column++); - break; - case ')': - list.emplace_back(Token::ListEnd, line, column++); - break; - case ',': - list.emplace_back(Token::Comma, line, column++); - break; - default: - break; - } -} - -void analyzeSection(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end) -{ - assert(*it == '['); - - std::string value; - int save = column; - - /* Read section name */ - ++ it; - while (it != end && *it != ']') { - if (*it == '\n') { - throw Error{line, column, "section not terminated, missing ']'"}; - } - if (isReserved(*it)) { - throw Error{line, column, "section name expected after '[', got '" + std::string(1, *it) + "'"}; - } - ++ column; - value += *it++; - } - - if (it == end) { - throw Error{line, column, "section name expected after '[', got <EOF>"}; - } - if (value.empty()) { - throw Error{line, column, "empty section name"}; - } - - /* Remove ']' */ - ++ it; - - list.emplace_back(Token::Section, line, save, std::move(value)); -} - -void analyzeAssign(Tokens &list, int &line, int &column, StreamIterator &it) -{ - assert(*it == '='); - - list.push_back({ Token::Assign, line, column++ }); - ++ it; -} - -void analyzeQuotedWord(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end) -{ - std::string value; - int save = column; - char quote = *it++; - - while (it != end && *it != quote) { - // TODO: escape sequence - ++ column; - value += *it++; - } - - if (it == end) { - throw Error{line, column, "undisclosed '" + std::string(1, quote) + "', got <EOF>"}; - } - - /* Remove quote */ - ++ it; - - list.push_back({ Token::QuotedWord, line, save, std::move(value) }); -} - -void analyzeWord(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end) -{ - assert(!isReserved(*it)); - - std::string value; - int save = column; - - while (it != end && !std::isspace(*it) && !isReserved(*it)) { - ++ column; - value += *it++; - } - - list.push_back({ Token::Word, line, save, std::move(value) }); -} - -void analyzeInclude(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end) -{ - assert(*it == '@'); - - std::string include; - int save = column; - - /* Read include */ - ++ it; - while (it != end && !isSpace(*it)) { - ++ column; - include += *it++; - } - - if (include != "include") { - throw Error{line, column, "expected include after '@' token"}; - } - - list.push_back({ Token::Include, line, save }); -} - -Tokens analyze(StreamIterator &it, StreamIterator end) -{ - Tokens list; - int line = 1; - int column = 0; - - while (it != end) { - if (*it == '\n') { - analyzeLine(line, column, it); - } else if (*it == '#') { - analyzeComment(column, it, end); - } else if (*it == '[') { - analyzeSection(list, line, column, it, end); - } else if (*it == '=') { - analyzeAssign(list, line, column, it); - } else if (isSpace(*it)) { - analyzeSpaces(column, it, end); - } else if (*it == '@') { - analyzeInclude(list, line, column, it, end); - } else if (isQuote(*it)) { - analyzeQuotedWord(list, line, column, it, end); - } else if (isList(*it)) { - analyzeList(list, line, column, it); - } else { - analyzeWord(list, line, column, it, end); - } - } - - return list; -} - -void parseOptionValueSimple(Option &option, TokenIterator &it) -{ - assert(it->type() == Token::Word || it->type() == Token::QuotedWord); - - option.push_back((it++)->value()); -} - -void parseOptionValueList(Option &option, TokenIterator &it, TokenIterator end) -{ - assert(it->type() == Token::ListBegin); - - TokenIterator save = it++; - - while (it != end && it->type() != Token::ListEnd) { - switch (it->type()) { - case Token::Comma: - /* Previous must be a word */ - if (it[-1].type() != Token::Word && it[-1].type() != Token::QuotedWord) { - throw Error{it->line(), it->column(), "unexpected comma after '" + it[-1].value() + "'"}; - } - - ++ it; - break; - case Token::Word: - case Token::QuotedWord: - option.push_back((it++)->value()); - break; - default: - throw Error{it->line(), it->column(), "unexpected '" + it[-1].value() + "' in list construct"}; - break; - } - } - - if (it == end) { - throw Error{save->line(), save->column(), "unterminated list construct"}; - } - - /* Remove ) */ - ++ it; -} - -void parseOption(Section &sc, TokenIterator &it, TokenIterator end) -{ - Option option{it->value()}; - - TokenIterator save = it; - - /* No '=' or something else? */ - if (++it == end) { - throw Error{save->line(), save->column(), "expected '=' assignment, got <EOF>"}; - } - if (it->type() != Token::Assign) { - throw Error{it->line(), it->column(), "expected '=' assignment, got " + it->value()}; - } - - /* Empty options are allowed so just test for words */ - if (++it != end) { - if (it->type() == Token::Word || it->type() == Token::QuotedWord) { - parseOptionValueSimple(option, it); - } else if (it->type() == Token::ListBegin) { - parseOptionValueList(option, it, end); - } - } - - sc.push_back(std::move(option)); -} - -void parseInclude(Document &doc, TokenIterator &it, TokenIterator end) -{ - TokenIterator save = it; - - if (++it == end) { - throw Error{save->line(), save->column(), "expected file name after '@include' statement, got <EOF>"}; - } - - if (it->type() != Token::Word && it->type() != Token::QuotedWord) { - throw Error{it->line(), it->column(), "expected file name after '@include' statement, got " + it->value()}; - } - - if (doc.path().empty()) { - throw Error{it->line(), it->column(), "'@include' statement invalid with buffer documents"}; - } - - std::string value = (it++)->value(); - std::string file; - - if (!isAbsolute(value)) { -#if defined(_WIN32) - file = doc.path() + "\\" + value; -#else - file = doc.path() + "/" + value; -#endif - } else { - file = value; - } - - Document child{File{file}}; - - for (const auto &sc : child) { - doc.push_back(sc); - } -} - -void parseSection(Document &doc, TokenIterator &it, TokenIterator end) -{ - Section sc{it->value()}; - - /* Skip [section] */ - ++ it; - - /* Read until next section */ - while (it != end && it->type() != Token::Section) { - if (it->type() != Token::Word) { - throw Error{it->line(), it->column(), "unexpected token '" + it->value() + "' in section definition"}; - } - - parseOption(sc, it, end); - } - - doc.push_back(std::move(sc)); -} - -void parse(Document &doc, const Tokens &tokens) -{ - TokenIterator it = tokens.cbegin(); - TokenIterator end = tokens.cend(); - - while (it != end) { - /* Just ignore this */ - switch (it->type()) { - case Token::Include: - parseInclude(doc, it, end); - break; - case Token::Section: - parseSection(doc, it, end); - break; - default: - throw Error{it->line(), it->column(), "unexpected '" + it->value() + "' on root document"}; - } - } -} - -} // !namespace - -namespace ini { - -Tokens Document::analyze(const File &file) -{ - std::fstream stream{file.path}; - - if (!stream) { - throw std::runtime_error{std::strerror(errno)}; - } - - std::istreambuf_iterator<char> it{stream}; - std::istreambuf_iterator<char> end{}; - - return ::analyze(it, end); -} - -Tokens Document::analyze(const Buffer &buffer) -{ - std::istringstream stream{buffer.text}; - std::istreambuf_iterator<char> it{stream}; - std::istreambuf_iterator<char> end{}; - - return ::analyze(it, end); -} - -Document::Document(const File &file) - : m_path{file.path} -{ - /* Update path */ - auto pos = m_path.find_last_of("/\\"); - - if (pos != std::string::npos) { - m_path.erase(pos); - } else { - m_path = "."; - } - - parse(*this, analyze(file)); -} - -Document::Document(const Buffer &buffer) -{ - parse(*this, analyze(buffer)); -} - -void Document::dump(const Tokens &tokens) -{ - for (const Token &token: tokens) { - // TODO: add better description - std::cout << token.line() << ":" << token.column() << ": " << token.value() << std::endl; - } -} - -} // !ini
--- a/C++/modules/Ini/Ini.h Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,546 +0,0 @@ -/* - * Ini.h -- .ini file parsing - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef _INI_H_ -#define _INI_H_ - -/** - * @file Ini.h - * @brief Configuration file parser. - */ - -#include <algorithm> -#include <cassert> -#include <exception> -#include <stdexcept> -#include <string> -#include <vector> - -/** - * Namespace for ini related classes. - */ -namespace ini { - -class Document; - -/** - * @class Error - * @brief Error in a file - */ -class Error : public std::exception { -private: - int m_line; //!< line number - int m_column; //!< line column - std::string m_message; //!< error message - -public: - /** - * Constructor. - * - * @param l the line - * @param c the column - * @param m the message - */ - inline Error(int l, int c, std::string m) noexcept - : m_line{l} - , m_column{c} - , m_message{std::move(m)} - { - } - - /** - * Get the line number. - * - * @return the line - */ - inline int line() const noexcept - { - return m_line; - } - - /** - * Get the column number. - * - * @return the column - */ - inline int column() const noexcept - { - return m_column; - } - - /** - * Return the raw error message (no line and column shown). - * - * @return the error message - */ - const char *what() const noexcept override - { - return m_message.c_str(); - } -}; - -/** - * @class Token - * @brief Describe a token read in the .ini source - * - * This class can be used when you want to parse a .ini file yourself. - * - * @see Document::analyze - */ -class Token { -public: - /** - * @brief Token type - */ - enum Type { - Include, //!< include statement - Section, //!< [section] - Word, //!< word without quotes - QuotedWord, //!< word with quotes - Assign, //!< = assignment - ListBegin, //!< begin of list ( - ListEnd, //!< end of list ) - Comma //!< list separation - }; - -private: - Type m_type; - int m_line; - int m_column; - std::string m_value; - -public: - /** - * Construct a token. - * - * @param type the type - * @param line the line - * @param column the column - * @param value the value - */ - Token(Type type, int line, int column, std::string value = "") noexcept - : m_type{type} - , m_line{line} - , m_column{column} - { - switch (type) { - case Include: - m_value = "@include"; - break; - case Section: - case Word: - case QuotedWord: - m_value = value; - break; - case Assign: - m_value = "="; - break; - case ListBegin: - m_value = "("; - break; - case ListEnd: - m_value = ")"; - break; - case Comma: - m_value = ","; - break; - default: - break; - } - } - - /** - * Get the type. - * - * @return the type - */ - inline Type type() const noexcept - { - return m_type; - } - - /** - * Get the line. - * - * @return the line - */ - inline int line() const noexcept - { - return m_line; - } - - /** - * Get the column. - * - * @return the column - */ - inline int column() const noexcept - { - return m_column; - } - - /** - * Get the value. For words, quoted words and section, the value is the content. Otherwise it's the - * characters parsed. - * - * @return the value - */ - inline const std::string &value() const noexcept - { - return m_value; - } -}; - -/** - * List of tokens in order they are analyzed. - */ -using Tokens = std::vector<Token>; - -/** - * @class Option - * @brief Option definition. - */ -class Option : public std::vector<std::string> { -private: - std::string m_key; - -public: - /** - * Construct an empty option. - * - * @param key the key - * @param value the value - */ - inline Option(std::string key) noexcept - : std::vector<std::string>{} - , m_key{std::move(key)} - { - } - - /** - * Construct a single option. - * - * @param key the key - * @param value the value - */ - inline Option(std::string key, std::string value) noexcept - : m_key{std::move(key)} - { - push_back(std::move(value)); - } - - /** - * Construct a list option. - * - * @param key the key - * @param values the values - */ - inline Option(std::string key, std::vector<std::string> values) noexcept - : std::vector<std::string>{std::move(values)} - , m_key{std::move(key)} - { - } - - /** - * Get the option key. - * - * @return the key - */ - inline const std::string &key() const noexcept - { - return m_key; - } - - /** - * Get the option value. - * - * @return the value - */ - inline const std::string &value() const noexcept - { - static std::string dummy; - - return empty() ? dummy : (*this)[0]; - } -}; - -/** - * @class Section - * @brief Section that contains one or more options. - */ -class Section : public std::vector<Option> { -private: - std::string m_key; - -public: - /** - * Construct a section with its name. - * - * @param key the key - * @pre key must not be empty - */ - inline Section(std::string key) noexcept - : m_key{std::move(key)} - { - assert(!m_key.empty()); - } - - /** - * Get the section key. - * - * @return the key - */ - inline const std::string &key() const noexcept - { - return m_key; - } - - /** - * Check if the section contains a specific option. - * - * @param key the option key - * @return true if the option exists - */ - inline bool contains(const std::string &key) const noexcept - { - return find(key) != end(); - } - - /** - * Access an option at the specified key. - * - * @param key the key - * @return the option - * @pre contains(key) must return true - */ - inline Option &operator[](const std::string &key) - { - assert(contains(key)); - - return *find(key); - } - - /** - * Overloaded function. - * - * @param key the key - * @return the option - * @pre contains(key) must return true - */ - inline const Option &operator[](const std::string &key) const - { - assert(contains(key)); - - return *find(key); - } - - /** - * Find an option by key and return an iterator. - * - * @param key the key - * @return the iterator or end() if not found - */ - inline iterator find(const std::string &key) noexcept - { - return std::find_if(begin(), end(), [&] (const auto &o) { - return o.key() == key; - }); - } - - /** - * Find an option by key and return an iterator. - * - * @param key the key - * @return the iterator or end() if not found - */ - inline const_iterator find(const std::string &key) const noexcept - { - return std::find_if(cbegin(), cend(), [&] (const auto &o) { - return o.key() == key; - }); - } - - /** - * Inherited operators. - */ - using std::vector<Option>::operator[]; -}; - -/** - * @class File - * @brief Source for reading .ini files. - */ -class File { -public: - /** - * Path to the file. - */ - std::string path; -}; - -/** - * @class Buffer - * @brief Source for reading ini from text. - * @note the include statement is not supported with buffers. - */ -class Buffer { -public: - /** - * The ini content. - */ - std::string text; -}; - -/** - * @class Document - * @brief Ini config file loader - */ -class Document : public std::vector<Section> { -private: - std::string m_path; - -public: - /** - * Analyze a file and extract tokens. If the function succeeds, that does not mean the content is valid, - * it just means that there are no syntax error. - * - * For example, this class does not allow adding options under no sections and this function will not - * detect that issue. - * - * @param file the file to read - * @return the list of tokens - * @throws Error on errors - */ - static Tokens analyze(const File &file); - - /** - * Overloaded function for buffers. - * - * @param buffer the buffer to read - * @return the list of tokens - * @throws Error on errors - */ - static Tokens analyze(const Buffer &buffer); - - /** - * Show all tokens and their description. - * - * @param tokens the tokens - */ - static void dump(const Tokens &tokens); - - /** - * Construct a document from a file. - * - * @param file the file to read - * @throws Error on errors - */ - Document(const File &file); - - /** - * Overloaded constructor for buffers. - * - * @param buffer the buffer to read - * @throws Error on errors - */ - Document(const Buffer &buffer); - - /** - * Get the current document path, only useful when constructed from File source. - * - * @return the path - */ - inline const std::string &path() const noexcept - { - return m_path; - } - - /** - * Check if a document has a specific section. - * - * @param key the key - * @return true if the document contains the section - */ - inline bool contains(const std::string &key) const noexcept - { - return std::find_if(begin(), end(), [&] (const auto &sc) { return sc.key() == key; }) != end(); - } - - /** - * Access a section at the specified key. - * - * @param key the key - * @return the section - * @pre contains(key) must return true - */ - inline Section &operator[](const std::string &key) - { - assert(contains(key)); - - return *find(key); - } - - /** - * Overloaded function. - * - * @param key the key - * @return the section - * @pre contains(key) must return true - */ - inline const Section &operator[](const std::string &key) const - { - assert(contains(key)); - - return *find(key); - } - - /** - * Find a section by key and return an iterator. - * - * @param key the key - * @return the iterator or end() if not found - */ - inline iterator find(const std::string &key) noexcept - { - return std::find_if(begin(), end(), [&] (const auto &o) { - return o.key() == key; - }); - } - - /** - * Find a section by key and return an iterator. - * - * @param key the key - * @return the iterator or end() if not found - */ - inline const_iterator find(const std::string &key) const noexcept - { - return std::find_if(cbegin(), cend(), [&] (const auto &o) { - return o.key() == key; - }); - } - - /** - * Inherited operators. - */ - using std::vector<Section>::operator[]; -}; - -} // !ini - -#endif // !_INI_H_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Ini/ini.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,445 @@ +/* + * ini.cpp -- .ini file parsing + * + * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <cctype> +#include <cstring> +#include <iostream> +#include <iterator> +#include <fstream> +#include <sstream> +#include <stdexcept> + +#if defined(_WIN32) +# include <Shlwapi.h> // for PathIsRelative +#endif + +#include "ini.h" + +namespace { + +using namespace ini; + +using StreamIterator = std::istreambuf_iterator<char>; +using TokenIterator = std::vector<Token>::const_iterator; + +inline bool isAbsolute(const std::string &path) noexcept +{ +#if defined(_WIN32) + return !PathIsRelative(path.c_str()); +#else + return path.size() > 0 && path[0] == '/'; +#endif +} + +inline bool isQuote(char c) noexcept +{ + return c == '\'' || c == '"'; +} + +inline bool isSpace(char c) noexcept +{ + /* Custom version because std::isspace includes \n as space */ + return c == ' ' || c == '\t'; +} + +inline bool isList(char c) noexcept +{ + return c == '(' || c == ')' || c == ','; +} + +inline bool isReserved(char c) noexcept +{ + return isList(c) || isQuote(c) || c == '[' || c == ']' || c == '@' || c == '#' || c == '='; +} + +void analyzeLine(int &line, int &column, StreamIterator &it) noexcept +{ + assert(*it == '\n'); + + ++ line; + ++ it; + column = 0; +} + +void analyzeComment(int &column, StreamIterator &it, StreamIterator end) noexcept +{ + assert(*it == '#'); + + while (it != end && *it != '\n') { + ++ column; + ++ it; + } +} + +void analyzeSpaces(int &column, StreamIterator &it, StreamIterator end) noexcept +{ + assert(isSpace(*it)); + + while (it != end && isSpace(*it)) { + ++ column; + ++ it; + } +} + +void analyzeList(Tokens &list, int line, int &column, StreamIterator &it) noexcept +{ + assert(isList(*it)); + + switch (*it++) { + case '(': + list.emplace_back(Token::ListBegin, line, column++); + break; + case ')': + list.emplace_back(Token::ListEnd, line, column++); + break; + case ',': + list.emplace_back(Token::Comma, line, column++); + break; + default: + break; + } +} + +void analyzeSection(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end) +{ + assert(*it == '['); + + std::string value; + int save = column; + + /* Read section name */ + ++ it; + while (it != end && *it != ']') { + if (*it == '\n') { + throw Error{line, column, "section not terminated, missing ']'"}; + } + if (isReserved(*it)) { + throw Error{line, column, "section name expected after '[', got '" + std::string(1, *it) + "'"}; + } + ++ column; + value += *it++; + } + + if (it == end) { + throw Error{line, column, "section name expected after '[', got <EOF>"}; + } + if (value.empty()) { + throw Error{line, column, "empty section name"}; + } + + /* Remove ']' */ + ++ it; + + list.emplace_back(Token::Section, line, save, std::move(value)); +} + +void analyzeAssign(Tokens &list, int &line, int &column, StreamIterator &it) +{ + assert(*it == '='); + + list.push_back({ Token::Assign, line, column++ }); + ++ it; +} + +void analyzeQuotedWord(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end) +{ + std::string value; + int save = column; + char quote = *it++; + + while (it != end && *it != quote) { + // TODO: escape sequence + ++ column; + value += *it++; + } + + if (it == end) { + throw Error{line, column, "undisclosed '" + std::string(1, quote) + "', got <EOF>"}; + } + + /* Remove quote */ + ++ it; + + list.push_back({ Token::QuotedWord, line, save, std::move(value) }); +} + +void analyzeWord(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end) +{ + assert(!isReserved(*it)); + + std::string value; + int save = column; + + while (it != end && !std::isspace(*it) && !isReserved(*it)) { + ++ column; + value += *it++; + } + + list.push_back({ Token::Word, line, save, std::move(value) }); +} + +void analyzeInclude(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end) +{ + assert(*it == '@'); + + std::string include; + int save = column; + + /* Read include */ + ++ it; + while (it != end && !isSpace(*it)) { + ++ column; + include += *it++; + } + + if (include != "include") { + throw Error{line, column, "expected include after '@' token"}; + } + + list.push_back({ Token::Include, line, save }); +} + +Tokens analyze(StreamIterator &it, StreamIterator end) +{ + Tokens list; + int line = 1; + int column = 0; + + while (it != end) { + if (*it == '\n') { + analyzeLine(line, column, it); + } else if (*it == '#') { + analyzeComment(column, it, end); + } else if (*it == '[') { + analyzeSection(list, line, column, it, end); + } else if (*it == '=') { + analyzeAssign(list, line, column, it); + } else if (isSpace(*it)) { + analyzeSpaces(column, it, end); + } else if (*it == '@') { + analyzeInclude(list, line, column, it, end); + } else if (isQuote(*it)) { + analyzeQuotedWord(list, line, column, it, end); + } else if (isList(*it)) { + analyzeList(list, line, column, it); + } else { + analyzeWord(list, line, column, it, end); + } + } + + return list; +} + +void parseOptionValueSimple(Option &option, TokenIterator &it) +{ + assert(it->type() == Token::Word || it->type() == Token::QuotedWord); + + option.push_back((it++)->value()); +} + +void parseOptionValueList(Option &option, TokenIterator &it, TokenIterator end) +{ + assert(it->type() == Token::ListBegin); + + TokenIterator save = it++; + + while (it != end && it->type() != Token::ListEnd) { + switch (it->type()) { + case Token::Comma: + /* Previous must be a word */ + if (it[-1].type() != Token::Word && it[-1].type() != Token::QuotedWord) { + throw Error{it->line(), it->column(), "unexpected comma after '" + it[-1].value() + "'"}; + } + + ++ it; + break; + case Token::Word: + case Token::QuotedWord: + option.push_back((it++)->value()); + break; + default: + throw Error{it->line(), it->column(), "unexpected '" + it[-1].value() + "' in list construct"}; + break; + } + } + + if (it == end) { + throw Error{save->line(), save->column(), "unterminated list construct"}; + } + + /* Remove ) */ + ++ it; +} + +void parseOption(Section &sc, TokenIterator &it, TokenIterator end) +{ + Option option{it->value()}; + + TokenIterator save = it; + + /* No '=' or something else? */ + if (++it == end) { + throw Error{save->line(), save->column(), "expected '=' assignment, got <EOF>"}; + } + if (it->type() != Token::Assign) { + throw Error{it->line(), it->column(), "expected '=' assignment, got " + it->value()}; + } + + /* Empty options are allowed so just test for words */ + if (++it != end) { + if (it->type() == Token::Word || it->type() == Token::QuotedWord) { + parseOptionValueSimple(option, it); + } else if (it->type() == Token::ListBegin) { + parseOptionValueList(option, it, end); + } + } + + sc.push_back(std::move(option)); +} + +void parseInclude(Document &doc, TokenIterator &it, TokenIterator end) +{ + TokenIterator save = it; + + if (++it == end) { + throw Error{save->line(), save->column(), "expected file name after '@include' statement, got <EOF>"}; + } + + if (it->type() != Token::Word && it->type() != Token::QuotedWord) { + throw Error{it->line(), it->column(), "expected file name after '@include' statement, got " + it->value()}; + } + + if (doc.path().empty()) { + throw Error{it->line(), it->column(), "'@include' statement invalid with buffer documents"}; + } + + std::string value = (it++)->value(); + std::string file; + + if (!isAbsolute(value)) { +#if defined(_WIN32) + file = doc.path() + "\\" + value; +#else + file = doc.path() + "/" + value; +#endif + } else { + file = value; + } + + Document child{File{file}}; + + for (const auto &sc : child) { + doc.push_back(sc); + } +} + +void parseSection(Document &doc, TokenIterator &it, TokenIterator end) +{ + Section sc{it->value()}; + + /* Skip [section] */ + ++ it; + + /* Read until next section */ + while (it != end && it->type() != Token::Section) { + if (it->type() != Token::Word) { + throw Error{it->line(), it->column(), "unexpected token '" + it->value() + "' in section definition"}; + } + + parseOption(sc, it, end); + } + + doc.push_back(std::move(sc)); +} + +void parse(Document &doc, const Tokens &tokens) +{ + TokenIterator it = tokens.cbegin(); + TokenIterator end = tokens.cend(); + + while (it != end) { + /* Just ignore this */ + switch (it->type()) { + case Token::Include: + parseInclude(doc, it, end); + break; + case Token::Section: + parseSection(doc, it, end); + break; + default: + throw Error{it->line(), it->column(), "unexpected '" + it->value() + "' on root document"}; + } + } +} + +} // !namespace + +namespace ini { + +Tokens Document::analyze(const File &file) +{ + std::fstream stream{file.path}; + + if (!stream) { + throw std::runtime_error{std::strerror(errno)}; + } + + std::istreambuf_iterator<char> it{stream}; + std::istreambuf_iterator<char> end{}; + + return ::analyze(it, end); +} + +Tokens Document::analyze(const Buffer &buffer) +{ + std::istringstream stream{buffer.text}; + std::istreambuf_iterator<char> it{stream}; + std::istreambuf_iterator<char> end{}; + + return ::analyze(it, end); +} + +Document::Document(const File &file) + : m_path{file.path} +{ + /* Update path */ + auto pos = m_path.find_last_of("/\\"); + + if (pos != std::string::npos) { + m_path.erase(pos); + } else { + m_path = "."; + } + + parse(*this, analyze(file)); +} + +Document::Document(const Buffer &buffer) +{ + parse(*this, analyze(buffer)); +} + +void Document::dump(const Tokens &tokens) +{ + for (const Token &token: tokens) { + // TODO: add better description + std::cout << token.line() << ":" << token.column() << ": " << token.value() << std::endl; + } +} + +} // !ini
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Ini/ini.h Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,546 @@ +/* + * ini.h -- .ini file parsing + * + * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _INI_H_ +#define _INI_H_ + +/** + * @file ini.h + * @brief Configuration file parser. + */ + +#include <algorithm> +#include <cassert> +#include <exception> +#include <stdexcept> +#include <string> +#include <vector> + +/** + * Namespace for ini related classes. + */ +namespace ini { + +class Document; + +/** + * @class Error + * @brief Error in a file + */ +class Error : public std::exception { +private: + int m_line; //!< line number + int m_column; //!< line column + std::string m_message; //!< error message + +public: + /** + * Constructor. + * + * @param l the line + * @param c the column + * @param m the message + */ + inline Error(int l, int c, std::string m) noexcept + : m_line{l} + , m_column{c} + , m_message{std::move(m)} + { + } + + /** + * Get the line number. + * + * @return the line + */ + inline int line() const noexcept + { + return m_line; + } + + /** + * Get the column number. + * + * @return the column + */ + inline int column() const noexcept + { + return m_column; + } + + /** + * Return the raw error message (no line and column shown). + * + * @return the error message + */ + const char *what() const noexcept override + { + return m_message.c_str(); + } +}; + +/** + * @class Token + * @brief Describe a token read in the .ini source + * + * This class can be used when you want to parse a .ini file yourself. + * + * @see Document::analyze + */ +class Token { +public: + /** + * @brief Token type + */ + enum Type { + Include, //!< include statement + Section, //!< [section] + Word, //!< word without quotes + QuotedWord, //!< word with quotes + Assign, //!< = assignment + ListBegin, //!< begin of list ( + ListEnd, //!< end of list ) + Comma //!< list separation + }; + +private: + Type m_type; + int m_line; + int m_column; + std::string m_value; + +public: + /** + * Construct a token. + * + * @param type the type + * @param line the line + * @param column the column + * @param value the value + */ + Token(Type type, int line, int column, std::string value = "") noexcept + : m_type{type} + , m_line{line} + , m_column{column} + { + switch (type) { + case Include: + m_value = "@include"; + break; + case Section: + case Word: + case QuotedWord: + m_value = value; + break; + case Assign: + m_value = "="; + break; + case ListBegin: + m_value = "("; + break; + case ListEnd: + m_value = ")"; + break; + case Comma: + m_value = ","; + break; + default: + break; + } + } + + /** + * Get the type. + * + * @return the type + */ + inline Type type() const noexcept + { + return m_type; + } + + /** + * Get the line. + * + * @return the line + */ + inline int line() const noexcept + { + return m_line; + } + + /** + * Get the column. + * + * @return the column + */ + inline int column() const noexcept + { + return m_column; + } + + /** + * Get the value. For words, quoted words and section, the value is the content. Otherwise it's the + * characters parsed. + * + * @return the value + */ + inline const std::string &value() const noexcept + { + return m_value; + } +}; + +/** + * List of tokens in order they are analyzed. + */ +using Tokens = std::vector<Token>; + +/** + * @class Option + * @brief Option definition. + */ +class Option : public std::vector<std::string> { +private: + std::string m_key; + +public: + /** + * Construct an empty option. + * + * @param key the key + * @param value the value + */ + inline Option(std::string key) noexcept + : std::vector<std::string>{} + , m_key{std::move(key)} + { + } + + /** + * Construct a single option. + * + * @param key the key + * @param value the value + */ + inline Option(std::string key, std::string value) noexcept + : m_key{std::move(key)} + { + push_back(std::move(value)); + } + + /** + * Construct a list option. + * + * @param key the key + * @param values the values + */ + inline Option(std::string key, std::vector<std::string> values) noexcept + : std::vector<std::string>{std::move(values)} + , m_key{std::move(key)} + { + } + + /** + * Get the option key. + * + * @return the key + */ + inline const std::string &key() const noexcept + { + return m_key; + } + + /** + * Get the option value. + * + * @return the value + */ + inline const std::string &value() const noexcept + { + static std::string dummy; + + return empty() ? dummy : (*this)[0]; + } +}; + +/** + * @class Section + * @brief Section that contains one or more options. + */ +class Section : public std::vector<Option> { +private: + std::string m_key; + +public: + /** + * Construct a section with its name. + * + * @param key the key + * @pre key must not be empty + */ + inline Section(std::string key) noexcept + : m_key{std::move(key)} + { + assert(!m_key.empty()); + } + + /** + * Get the section key. + * + * @return the key + */ + inline const std::string &key() const noexcept + { + return m_key; + } + + /** + * Check if the section contains a specific option. + * + * @param key the option key + * @return true if the option exists + */ + inline bool contains(const std::string &key) const noexcept + { + return find(key) != end(); + } + + /** + * Access an option at the specified key. + * + * @param key the key + * @return the option + * @pre contains(key) must return true + */ + inline Option &operator[](const std::string &key) + { + assert(contains(key)); + + return *find(key); + } + + /** + * Overloaded function. + * + * @param key the key + * @return the option + * @pre contains(key) must return true + */ + inline const Option &operator[](const std::string &key) const + { + assert(contains(key)); + + return *find(key); + } + + /** + * Find an option by key and return an iterator. + * + * @param key the key + * @return the iterator or end() if not found + */ + inline iterator find(const std::string &key) noexcept + { + return std::find_if(begin(), end(), [&] (const auto &o) { + return o.key() == key; + }); + } + + /** + * Find an option by key and return an iterator. + * + * @param key the key + * @return the iterator or end() if not found + */ + inline const_iterator find(const std::string &key) const noexcept + { + return std::find_if(cbegin(), cend(), [&] (const auto &o) { + return o.key() == key; + }); + } + + /** + * Inherited operators. + */ + using std::vector<Option>::operator[]; +}; + +/** + * @class File + * @brief Source for reading .ini files. + */ +class File { +public: + /** + * Path to the file. + */ + std::string path; +}; + +/** + * @class Buffer + * @brief Source for reading ini from text. + * @note the include statement is not supported with buffers. + */ +class Buffer { +public: + /** + * The ini content. + */ + std::string text; +}; + +/** + * @class Document + * @brief Ini config file loader + */ +class Document : public std::vector<Section> { +private: + std::string m_path; + +public: + /** + * Analyze a file and extract tokens. If the function succeeds, that does not mean the content is valid, + * it just means that there are no syntax error. + * + * For example, this class does not allow adding options under no sections and this function will not + * detect that issue. + * + * @param file the file to read + * @return the list of tokens + * @throws Error on errors + */ + static Tokens analyze(const File &file); + + /** + * Overloaded function for buffers. + * + * @param buffer the buffer to read + * @return the list of tokens + * @throws Error on errors + */ + static Tokens analyze(const Buffer &buffer); + + /** + * Show all tokens and their description. + * + * @param tokens the tokens + */ + static void dump(const Tokens &tokens); + + /** + * Construct a document from a file. + * + * @param file the file to read + * @throws Error on errors + */ + Document(const File &file); + + /** + * Overloaded constructor for buffers. + * + * @param buffer the buffer to read + * @throws Error on errors + */ + Document(const Buffer &buffer); + + /** + * Get the current document path, only useful when constructed from File source. + * + * @return the path + */ + inline const std::string &path() const noexcept + { + return m_path; + } + + /** + * Check if a document has a specific section. + * + * @param key the key + * @return true if the document contains the section + */ + inline bool contains(const std::string &key) const noexcept + { + return std::find_if(begin(), end(), [&] (const auto &sc) { return sc.key() == key; }) != end(); + } + + /** + * Access a section at the specified key. + * + * @param key the key + * @return the section + * @pre contains(key) must return true + */ + inline Section &operator[](const std::string &key) + { + assert(contains(key)); + + return *find(key); + } + + /** + * Overloaded function. + * + * @param key the key + * @return the section + * @pre contains(key) must return true + */ + inline const Section &operator[](const std::string &key) const + { + assert(contains(key)); + + return *find(key); + } + + /** + * Find a section by key and return an iterator. + * + * @param key the key + * @return the iterator or end() if not found + */ + inline iterator find(const std::string &key) noexcept + { + return std::find_if(begin(), end(), [&] (const auto &o) { + return o.key() == key; + }); + } + + /** + * Find a section by key and return an iterator. + * + * @param key the key + * @return the iterator or end() if not found + */ + inline const_iterator find(const std::string &key) const noexcept + { + return std::find_if(cbegin(), cend(), [&] (const auto &o) { + return o.key() == key; + }); + } + + /** + * Inherited operators. + */ + using std::vector<Section>::operator[]; +}; + +} // !ini + +#endif // !_INI_H_
--- a/C++/modules/Js/Js.cpp Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ -/* - * Js.cpp -- JavaScript C++14 wrapper for Duktape - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "Js.h" - -using namespace std::string_literals; - -namespace js { - -ErrorInfo Context::error(int index) -{ - ErrorInfo error; - - index = duk_normalize_index(m_handle.get(), index); - - duk_get_prop_string(m_handle.get(), index, "name"); - error.name = duk_to_string(m_handle.get(), -1); - duk_get_prop_string(m_handle.get(), index, "message"); - error.message = duk_to_string(m_handle.get(), -1); - duk_get_prop_string(m_handle.get(), index, "fileName"); - error.fileName = duk_to_string(m_handle.get(), -1); - duk_get_prop_string(m_handle.get(), index, "lineNumber"); - error.lineNumber = duk_to_int(m_handle.get(), -1); - duk_get_prop_string(m_handle.get(), index, "stack"); - error.stack = duk_to_string(m_handle.get(), -1); - duk_pop_n(m_handle.get(), 5); - - return error; -} - -void Context::pcall(unsigned nargs) -{ - if (duk_pcall(m_handle.get(), nargs) != 0) { - ErrorInfo info = error(-1); - duk_pop(m_handle.get()); - - throw info; - } -} - -void Context::peval() -{ - if (duk_peval(m_handle.get()) != 0) { - ErrorInfo info = error(-1); - duk_pop(m_handle.get()); - - throw info; - } -} - -void TypeInfo<Function>::push(Context &ctx, Function fn) -{ - /* 1. Push function wrapper */ - duk_push_c_function(ctx, [] (duk_context *ctx) -> duk_ret_t { - Context context{ctx}; - - duk_push_current_function(ctx); - duk_get_prop_string(ctx, -1, "\xff""\xff""js-func"); - Function *f = static_cast<Function *>(duk_to_pointer(ctx, -1)); - duk_pop_2(ctx); - - return static_cast<duk_ret_t>(f->function(context)); - }, fn.nargs); - - /* 2. Store the moved function */ - duk_push_pointer(ctx, new Function(std::move(fn))); - duk_put_prop_string(ctx, -2, "\xff""\xff""js-func"); - - /* 3. Store deletion flags */ - duk_push_boolean(ctx, false); - duk_put_prop_string(ctx, -2, "\xff""\xff""js-deleted"); - - /* 4. Push and set a finalizer */ - duk_push_c_function(ctx, [] (duk_context *ctx) { - duk_get_prop_string(ctx, 0, "\xff""\xff""js-deleted"); - - if (!duk_to_boolean(ctx, -1)) { - duk_push_boolean(ctx, true); - duk_put_prop_string(ctx, 0, "\xff""\xff""js-deleted"); - duk_get_prop_string(ctx, 0, "\xff""\xff""js-func"); - delete static_cast<Function *>(duk_to_pointer(ctx, -1)); - duk_pop(ctx); - } - - duk_pop(ctx); - - return 0; - }, 1); - duk_set_finalizer(ctx, -2); -} - -} // !js
--- a/C++/modules/Js/Js.h Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2105 +0,0 @@ -/* - * Js.h -- JavaScript C++14 wrapper for Duktape - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef _JS_H_ -#define _JS_H_ - -/** - * @file Js.h - * @brief Bring JavaScript using Duktape - * - * This file provides usual Duktape function renamed and placed into `js` namespace. It also replaces error - * code with exceptions when possible. - * - * For convenience, this file also provides templated functions, overloads and much more. - */ - -#include <functional> -#include <memory> -#include <string> -#include <type_traits> -#include <unordered_map> -#include <utility> -#include <vector> - -#include <duktape.h> - -/** - * Duktape C++ namespace wrapper. - */ -namespace js { - -class Context; - -/** - * Typedef for readability. - */ -using ContextPtr = duk_context *; - -/* - * Basic types to manipulate with the stack - * ------------------------------------------------------------------ - * - * The following types can be used in some of the operations like Context::push or Context::is, they are defined - * usually as empty classes to determine the appropriate action to execute. - * - * For example, `ctx.push(js::Object{})` will push an empty object into the stack. - */ - -/** - * @class Object - * @brief Empty class tag for push() function. - */ -class Object { -}; - -/** - * @class Array - * @brief Empty class tag for push() function. - */ -class Array { -}; - -/** - * @class Global - * @brief Empty class tag to push the global object. - */ -class Global { -}; - -/** - * @class Undefined - * @brief Empty class tag to push undefined to the stack. - */ -class Undefined { -}; - -/** - * @class Null - * @brief Empty class tag to push null to the stack. - */ -class Null { -}; - -/** - * @class This - * @brief Empty class tag to push this binding to the stack. - */ -class This { -}; - -/** - * @class RawPointer - * @brief Push a non-managed pointer to Duktape, the pointer will never be deleted. - * @note For a managed pointer with prototype, see Pointer - */ -template <typename T> -class RawPointer { -public: - /** - * The pointer to push. - */ - T *object; -}; - -/* - * Extended type manipulation - * ------------------------------------------------------------------ - * - * The following types are different as there are no equivalent in the native Duktape API, they are available for - * convenience. - */ - -/** - * @brief Manage shared_ptr from C++ and JavaScript - * - * This class allowed you to push and retrieve shared_ptr from C++ and JavaScript without taking care of ownership - * and deletion. - * - * The only requirement is to have the function `void prototype(Context &ctx)` in your class T. - */ -template <typename T> -class Shared { -public: - /** - * The shared object. - */ - std::shared_ptr<T> object; -}; - -/** - * @brief Manage pointers from C++ and JavaScript - * - * This class allowed you to push and retrieve C++ pointers from C++ and JavaScript. The object will be deleted when - * the JavaScript garbage collectors collect them so never store a pointer created with this. - * - * The only requirement is to have the function `void prototype(Context &ctx)` in your class T. - */ -template <typename T> -class Pointer { -public: - /** - * The object. - */ - T *object{nullptr}; -}; - -/** - * @class Function - * @brief Duktape/C function definition. - * - * This class wraps the std::function as a Duktape/C function by storing a copied pointer. - */ -class Function { -public: - /** - * The function pointer, must not be null. - */ - std::function<int (Context &)> function; - - /** - * Number of args that the function takes - */ - int nargs{0}; -}; - -/** - * Map of functions to set on an object. - */ -using FunctionMap = std::unordered_map<std::string, Function>; - -/** - * Map of string to type, ideal for setting constants like enums. - */ -template <typename Type> -using Map = std::unordered_map<std::string, Type>; - -/** - * @class ErrorInfo - * @brief Error description. - * - * This class fills the fields got in an Error object. - */ -class ErrorInfo : public std::exception { -public: - std::string name; //!< name of error - std::string message; //!< error message - std::string stack; //!< stack if available - std::string fileName; //!< filename if applicable - int lineNumber{0}; //!< line number if applicable - - /** - * Get the error message. This effectively returns message field. - * - * @return the message - */ - const char *what() const noexcept override - { - return message.c_str(); - } -}; - -/** - * @class TypeInfo - * @brief Type information to implement new types in JavaScript's context. - * - * This class depending on your needs may have the following functions: - * - * - `static void construct(Context &ctx, Type value)` - * - `static Type get(Context &ctx, int index)` - * - `static bool is(Context &ctx, int index)` - * - `static Type optional(Context &ctx, int index, Type defaultValue)` - * - `static void push(Context &ctx, Type value)` - * - `static Type require(Context &ctx, int index)` - * - * The `construct` function is used in Context::construct to build a new value as this (e.g. constructors). - * - * The `get` function is used in Context::get, Context::getProperty, Context::getGlobal to retrieve a value from the - * stack. - * - * The `is` function is used in Context::is to check if the value on the stack is of type `Type`. - * - * The `optional` function is used in Context::optional to get a value or a replacement if not applicable. - * - * The `push` function is used in Context::push to usually create a new value on the stack but some specializations - * may not (e.g. FunctionMap). - * - * The `require` function is used in Context::require to get a value from the stack or raise a JavaScript exception if - * not applicable. - * - * This class is fully specialized for: `bool`, `const char *`, `double`, `int`, `std::string`. - * - * It is also partially specialized for : `Global`, `Object`, `Array`, `Undefined`, `Null`, `std::vector<Type>`. - */ -template <typename Type> -class TypeInfo { -}; - -/** - * @class File - * @brief Evaluate script from file. - * @see Context::eval - * @see Context::peval - */ -class File { -public: - /** - * Path to the file. - */ - std::string path; - - /** - * Evaluate the file. - * - * @param ctx the context - */ - inline void eval(duk_context *ctx) - { - duk_eval_file(ctx, path.c_str()); - } - - /** - * Evaluate in protected mode the file. - * - * @param ctx the context - */ - inline int peval(duk_context *ctx) - { - return duk_peval_file(ctx, path.c_str()); - } -}; - -/** - * @class Script - * @brief Evaluate script from raw text. - * @see Context::eval - * @see Context::peval - */ -class Script { -public: - /** - * The script content. - */ - std::string text; - - /** - * Evaluate the script. - * - * @param ctx the context - */ - inline void eval(duk_context *ctx) - { - duk_eval_string(ctx, text.c_str()); - } - - /** - * Evaluate in protected mode the script. - * - * @param ctx the context - */ - inline int peval(duk_context *ctx) - { - return duk_peval_string(ctx, text.c_str()); - } -}; - -/** - * @class Context - * @brief RAII based Duktape handler. - * - * This class is implicitly convertible to duk_context for convenience. - */ -class Context { -private: - using Deleter = void (*)(duk_context *); - using Handle = std::unique_ptr<duk_context, Deleter>; - - Handle m_handle; - - /* Move and copy forbidden */ - Context(const Context &) = delete; - Context &operator=(const Context &) = delete; - Context(const Context &&) = delete; - Context &operator=(const Context &&) = delete; - -public: - /** - * Create default context. - */ - inline Context() - : m_handle{duk_create_heap_default(), duk_destroy_heap} - { - } - - /** - * Create borrowed context that will not be deleted. - * - * @param ctx the pointer to duk_context - */ - inline Context(ContextPtr ctx) noexcept - : m_handle{ctx, [] (ContextPtr) {}} - { - } - - /** - * Convert the context to the native Duktape/C type. - * - * @return the duk_context - */ - inline operator duk_context *() noexcept - { - return m_handle.get(); - } - - /** - * Convert the context to the native Duktape/C type. - * - * @return the duk_context - */ - inline operator duk_context *() const noexcept - { - return m_handle.get(); - } - - /* - * Basic functions - * ---------------------------------------------------------- - * - * The following functions are just standard wrappers around the native Duktape C functions, they are - * defined with the same signature except that for convenience some parameters have default sane values. - */ - - /** - * Call the object at the top of the stack. - * - * @param ctx the context - * @param nargs the number of arguments - * @note Non-protected - */ - inline void call(unsigned nargs = 0) - { - duk_call(m_handle.get(), nargs); - } - - /** - * Copy a value from from to to, overwriting the previous value. If either index is invalid, throws an error. - * - * @param from the from index - * @param to the destination - */ - inline void copy(int from, int to) - { - duk_copy(m_handle.get(), from, to); - } - - /** - * Define a property. - * - * @param index the object index - * @param flags the flags - * @note Wrapper of duk_def_prop - */ - inline void defineProperty(int index, int flags) - { - duk_def_prop(m_handle.get(), index, flags); - } - - /** - * Delete a property. - * - * @param index the object index - * @return true if deleted - * @note Wrapper of duk_del_prop - */ - inline bool deleteProperty(int index) - { - return duk_del_prop(m_handle.get(), index); - } - - /** - * Delete a property by index. - * - * @param index the object index - * @param position the property index - * @return true if deleted - * @note Wrapper of duk_del_prop_index - */ - inline bool deleteProperty(int index, int position) - { - return duk_del_prop_index(m_handle.get(), index, position); - } - - /** - * Delete a property by name. - * - * @param index the object index - * @param name the property name - * @return true if deleted - * @note Wrapper of duk_del_prop_string - */ - inline bool deleteProperty(int index, const std::string &name) - { - return duk_del_prop_string(m_handle.get(), index, name.c_str()); - } - - /** - * Push a duplicate of value at from_index to the stack. If from_index is invalid, throws an error. - * - * @param index the value to copy - * @note Wrapper of duk_dup - */ - inline void dup(int index = -1) - { - duk_dup(m_handle.get(), index); - } - - /** - * Evaluate a non-protected chunk that is at the top of the stack. - */ - inline void eval() - { - duk_eval(m_handle.get()); - } - - /** - * Check if the object as a property. - * - * @param index the object index - * @return true if has - * @note Wrapper of duk_has_prop - */ - inline bool hasProperty(int index) - { - return duk_has_prop(m_handle.get(), index); - } - - /** - * Check if the object as a property by index. - * - * @param index the object index - * @param position the property index - * @return true if has - * @note Wrapper of duk_has_prop_index - */ - inline bool hasProperty(int index, int position) - { - return duk_has_prop_index(m_handle.get(), index, position); - } - - /** - * Check if the object as a property by string - * - * @param index the object index - * @param name the property name - * @return true if has - * @note Wrapper of duk_has_prop_string - */ - inline bool hasProperty(int index, const std::string &name) - { - return duk_has_prop_string(m_handle.get(), index, name.c_str()); - } - - /** - * Check if idx1 is an instance of idx2. - * - * @param ctx the context - * @param idx1 the value to test - * @param idx2 the instance requested - * @return true if idx1 is instance of idx2 - * @note Wrapper of duk_instanceof - */ - inline bool instanceof(int idx1, int idx2) - { - return duk_instanceof(m_handle.get(), idx1, idx2); - } - - /** - * Insert a value at to with a value popped from the stack top. The previous value at to and any values above - * it are moved up the stack by a step. If to is an invalid index, throws an error. - * - * @note Negative indices are evaluated prior to popping the value at the stack top - * @param to the destination - * @note Wrapper of duk_insert - */ - inline void insert(int to) - { - duk_insert(m_handle.get(), to); - } - - /** - * Pop a certain number of values from the top of the stack. - * - * @param ctx the context - * @param count the number of values to pop - * @note Wrapper of duk_pop_n - */ - inline void pop(unsigned count = 1) - { - duk_pop_n(m_handle.get(), count); - } - - /** - * Remove value at index. Elements above index are shifted down the stack by a step. If to is an invalid index, - * throws an error. - * - * @param index the value to remove - * @note Wrapper of duk_remove - */ - inline void remove(int index) - { - duk_remove(m_handle.get(), index); - } - - /** - * Replace value at to_index with a value popped from the stack top. If to_index is an invalid index, - * throws an error. - * - * @param index the value to replace by the value at the top of the stack - * @note Negative indices are evaluated prior to popping the value at the stack top. - * @note Wrapper of duk_replace - */ - inline void replace(int index) - { - duk_replace(m_handle.get(), index); - } - - /** - * Swap values at indices index1 and index2. If the indices are the same, the call is a no-op. If either index - * is invalid, throws an error. - * - * @param index1 the first index - * @param index2 the second index - * @note Wrapper of duk_swap - */ - inline void swap(int index1, int index2) - { - duk_swap(m_handle.get(), index1, index2); - } - - /** - * Get the current stack size. - * - * @param ctx the context - * @return the stack size - * @note Wrapper of duk_get_top - */ - inline int top() noexcept - { - return duk_get_top(m_handle.get()); - } - - /** - * Get the type of the value at the specified index. - * - * @param ctx the context - * @param index the idnex - * @return the type - * @note Wrapper of duk_get_type - */ - inline int type(int index) noexcept - { - return duk_get_type(m_handle.get(), index); - } - - /* - * Extended native functions - * ---------------------------------------------------------- - * - * The following functions have different behaviour than the original native Duktape C functions, see their - * descriptions for more information - */ - - /** - * Call in protected mode the object at the top of the stack. - * - * @param nargs the number of arguments - * @throw ErrorInfo on errors - * @note Wrapper of duk_pcall - */ - void pcall(unsigned nargs = 0); - - /** - * Evaluate a non-protected source. - * - * @param source the source - * @see File - * @see Script - * @note Wrapper of duk_eval - */ - template <typename Source> - inline void eval(Source &&source) - { - source.eval(m_handle.get()); - } - - /** - * Evaluate a protected chunk that is at the top of the stack. - * - * @throw ErrorInfo the error - * @note Wrapper of duk_peval - */ - void peval(); - - /** - * Evaluate a protected source. - * - * @param source the source - * @see File - * @see Script - * @throw ErrorInfo on failure - * @note Wrapper of duk_peval - */ - template <typename Source> - inline void peval(Source &&source) - { - if (source.peval(m_handle.get()) != 0) { - ErrorInfo info = error(-1); - duk_pop(m_handle.get()); - - throw info; - } - } - - /* - * Push / Get / Require / Is / Optional - * ---------------------------------------------------------- - * - * The following functions are used to push, get or check values from the stack. They use specialization - * of TypeInfo class. - */ - - /** - * Push a value into the stack. Calls TypeInfo<T>::push(*this, value); - * - * @param value the value to forward - */ - template <typename Type> - inline void push(Type &&value) - { - TypeInfo<std::decay_t<Type>>::push(*this, std::forward<Type>(value)); - } - - /** - * Generic template function to get a value from the stack. - * - * @param index the index - * @return the value - */ - template <typename Type> - inline auto get(int index) -> decltype(TypeInfo<Type>::get(*this, 0)) - { - return TypeInfo<Type>::get(*this, index); - } - - /** - * Require a type at the specified index. - * - * @param index the index - * @return the value - */ - template <typename Type> - inline auto require(int index) -> decltype(TypeInfo<Type>::require(*this, 0)) - { - return TypeInfo<Type>::require(*this, index); - } - - /** - * Check if a value is a type of T. - * - * The TypeInfo<T> must have `static bool is(ContextPtr ptr, int index)`. - * - * @param index the value index - * @return true if is the type - */ - template <typename T> - inline bool is(int index) - { - return TypeInfo<T>::is(*this, index); - } - - /** - * Get an optional value from the stack, if the value is not available of not the correct type, - * return defaultValue instead. - * - * The TypeInfo<T> must have `static T optional(Context &, int index, T &&defaultValue)`. - * - * @param index the value index - * @param defaultValue the value replacement - * @return the value or defaultValue - */ - template <typename Type> - inline auto optional(int index, Type &&defaultValue) - { - return TypeInfo<std::decay_t<Type>>::optional(*this, index, std::forward<Type>(defaultValue)); - } - - /* - * Properties management - * ---------------------------------------------------------- - * - * The following functions are used to read or set properties on objects or globals also using TypeInfo. - */ - - /** - * Get the property `name' as value from the object at the specified index. - * - * @param index the object index - * @param name the property name - * @return the value - * @note The stack is unchanged - */ - template <typename Type, typename std::enable_if_t<!std::is_void<Type>::value> * = nullptr> - inline auto getProperty(int index, const std::string &name) -> decltype(get<Type>(0)) - { - duk_get_prop_string(m_handle.get(), index, name.c_str()); - decltype(get<Type>(0)) value = get<Type>(-1); - duk_pop(m_handle.get()); - - return value; - } - - /** - * Get a property by index, for arrays. - * - * @param index the object index - * @param position the position int the object - * @return the value - * @note The stack is unchanged - */ - template <typename Type, typename std::enable_if_t<!std::is_void<Type>::value> * = nullptr> - inline auto getProperty(int index, int position) -> decltype(get<Type>(0)) - { - duk_get_prop_index(m_handle.get(), index, position); - decltype(get<Type>(0)) value = get<Type>(-1); - duk_pop(m_handle.get()); - - return value; - } - - /** - * Get the property `name' and push it to the stack from the object at the specified index. - * - * @param index the object index - * @param name the property name - * @note The stack contains the property value - */ - template <typename Type, typename std::enable_if_t<std::is_void<Type>::value> * = nullptr> - inline void getProperty(int index, const std::string &name) - { - duk_get_prop_string(m_handle.get(), index, name.c_str()); - } - - /** - * Get the property by index and push it to the stack from the object at the specified index. - * - * @param index the object index - * @param position the position in the object - * @note The stack contains the property value - */ - template <typename Type, typename std::enable_if_t<std::is_void<Type>::value> * = nullptr> - inline void getProperty(int index, int position) - { - duk_get_prop_index(m_handle.get(), index, position); - } - - /** - * Get an optional property `name` from the object at the specified index. - * - * @param index the object index - * @param name the property name - * @param def the default value - * @return the value or def - * @note The stack is unchanged - */ - template <typename Type, typename DefaultValue> - inline auto optionalProperty(int index, const std::string &name, DefaultValue &&def) -> decltype(optional<Type>(0, std::forward<DefaultValue>(def))) - { - duk_get_prop_string(m_handle.get(), index, name.c_str()); - decltype(optional<Type>(0, std::forward<DefaultValue>(def))) value = optional<Type>(-1, std::forward<DefaultValue>(def)); - duk_pop(m_handle.get()); - - return value; - } - - /** - * Get an optional property by index, for arrays - * - * @param index the object index - * @param position the position int the object - * @param def the default value - * @return the value or def - * @note The stack is unchanged - */ - template <typename Type, typename DefaultValue> - inline auto optionalProperty(int index, int position, DefaultValue &&def) -> decltype(optional<Type>(0, std::forward<DefaultValue>(def))) - { - duk_get_prop_index(m_handle.get(), index, position); - decltype(optional<Type>(0, std::forward<DefaultValue>(def))) value = optional<Type>(-1, std::forward<DefaultValue>(def)); - duk_pop(m_handle.get()); - - return value; - } - - /** - * Set a property to the object at the specified index. - * - * @param index the object index - * @param name the property name - * @param value the value to forward - * @note The stack is unchanged - */ - template <typename Type> - void putProperty(int index, const std::string &name, Type &&value) - { - index = duk_normalize_index(m_handle.get(), index); - - push(std::forward<Type>(value)); - duk_put_prop_string(m_handle.get(), index, name.c_str()); - } - - /** - * Set a property by index, for arrays. - * - * @param index the object index - * @param position the position in the object - * @param value the value to forward - * @note The stack is unchanged - */ - template <typename Type> - void putProperty(int index, int position, Type &&value) - { - index = duk_normalize_index(m_handle.get(), index); - - push(std::forward<Type>(value)); - duk_put_prop_index(m_handle.get(), index, position); - } - - /** - * Put the value that is at the top of the stack as property to the object. - * - * @param index the object index - * @param name the property name - */ - inline void putProperty(int index, const std::string &name) - { - duk_put_prop_string(m_handle.get(), index, name.c_str()); - } - - /** - * Put the value that is at the top of the stack to the object as index. - * - * @param index the object index - * @param position the position in the object - */ - inline void putProperty(int index, int position) - { - duk_put_prop_index(m_handle.get(), index, position); - } - - /** - * Get a global value. - * - * @param name the name of the global variable - * @return the value - */ - template <typename Type> - inline auto getGlobal(const std::string &name, std::enable_if_t<!std::is_void<Type>::value> * = nullptr) -> decltype(get<Type>(0)) - { - duk_get_global_string(m_handle.get(), name.c_str()); - decltype(get<Type>(0)) value = get<Type>(-1); - duk_pop(m_handle.get()); - - return value; - } - - /** - * Overload that push the value at the top of the stack instead of returning it. - */ - template <typename Type> - inline void getGlobal(const std::string &name, std::enable_if_t<std::is_void<Type>::value> * = nullptr) noexcept - { - duk_get_global_string(m_handle.get(), name.c_str()); - } - - /** - * Set a global variable. - * - * @param name the name of the global variable - * @param type the value to set - */ - template <typename Type> - inline void putGlobal(const std::string &name, Type&& type) - { - push(std::forward<Type>(type)); - duk_put_global_string(m_handle.get(), name.c_str()); - } - - /** - * Put the value at the top of the stack as global property. - * - * @param name the property name - */ - inline void putGlobal(const std::string &name) - { - duk_put_global_string(m_handle.get(), name.c_str()); - } - - /* - * Extra functions - * ---------------------------------------------------------- - * - * The following functions are implemented for convenience and do not exists in the native Duktape API. - */ - - /** - * Get the error object when a JavaScript error has been thrown (e.g. eval failure). - * - * @param index the index - * @return the information - */ - ErrorInfo error(int index); - - /** - * Enumerate an object or an array at the specified index. - * - * @param index the object or array index - * @param flags the optional flags to pass to duk_enum - * @param getvalue set to true if you want to extract the value - * @param func the function to call for each properties - */ - template <typename Func> - void enumerate(int index, duk_uint_t flags, duk_bool_t getvalue, Func&& func) - { - duk_enum(m_handle.get(), index, flags); - - while (duk_next(m_handle.get(), -1, getvalue)) { - func(*this); - duk_pop_n(m_handle.get(), 1 + (getvalue ? 1 : 0)); - } - - duk_pop(m_handle.get()); - } - - /** - * Return the this binding of the current function. - * - * @return the this binding as the template given - */ - template <typename T> - inline auto self() -> decltype(TypeInfo<T>::get(*this, 0)) - { - duk_push_this(m_handle.get()); - decltype(TypeInfo<T>::get(*this, 0)) value = TypeInfo<T>::get(*this, -1); - duk_pop(m_handle.get()); - - return value; - } - - /** - * Throw an ECMAScript exception. - * - * @param ex the exception - */ - template <typename Exception> - void raise(const Exception &ex) - { - ex.create(m_handle.get()); - - duk_push_string(m_handle.get(), ex.name().c_str()); - duk_put_prop_string(m_handle.get(), -2, "name"); - duk_throw(m_handle.get()); - } - - /** - * Construct the object in place, setting value as this binding. - * - * The TypeInfo<T> must have the following requirements: - * - * - static void construct(Context &, T): must update this with the value and keep the stack unchanged - * - * @param value the value to forward - * @see self - */ - template <typename T> - inline void construct(T &&value) - { - TypeInfo<std::decay_t<T>>::construct(*this, std::forward<T>(value)); - } -}; - -/* ------------------------------------------------------------------ - * Exception handling - * ------------------------------------------------------------------ */ - -/** - * @class Error - * @brief Base ECMAScript error class. - * @warning Override the function create for your own exceptions - */ -class Error { -protected: - std::string m_name; //!< Name of exception (e.g RangeError) - std::string m_message; //!< The message - - /** - * Constructor with a type of error specified, specially designed for derived errors. - * - * @param name the error name (e.g RangeError) - * @param message the message - */ - inline Error(std::string name, std::string message) noexcept - : m_name{std::move(name)} - , m_message{std::move(message)} - { - } - -public: - /** - * Constructor with a message. - * - * @param message the message - */ - inline Error(std::string message) noexcept - : m_name{"Error"} - , m_message{std::move(message)} - { - } - - /** - * Get the error type (e.g RangeError). - * - * @return the name - */ - inline const std::string &name() const noexcept - { - return m_name; - } - - /** - * Create the exception on the stack. - * - * @note the default implementation search for the global variables - * @param ctx the context - */ - virtual void create(ContextPtr ctx) const noexcept - { - duk_get_global_string(ctx, m_name.c_str()); - duk_push_string(ctx, m_message.c_str()); - duk_new(ctx, 1); - } -}; - -/** - * @class EvalError - * @brief Error in eval() function. - */ -class EvalError : public Error { -public: - /** - * Construct an EvalError. - * - * @param message the message - */ - inline EvalError(std::string message) noexcept - : Error{"EvalError", std::move(message)} - { - } -}; - -/** - * @class RangeError - * @brief Value is out of range. - */ -class RangeError : public Error { -public: - /** - * Construct an RangeError. - * - * @param message the message - */ - inline RangeError(std::string message) noexcept - : Error{"RangeError", std::move(message)} - { - } -}; - -/** - * @class ReferenceError - * @brief Trying to use a variable that does not exist. - */ -class ReferenceError : public Error { -public: - /** - * Construct an ReferenceError. - * - * @param message the message - */ - inline ReferenceError(std::string message) noexcept - : Error{"ReferenceError", std::move(message)} - { - } -}; - -/** - * @class SyntaxError - * @brief Syntax error in the script. - */ -class SyntaxError : public Error { -public: - /** - * Construct an SyntaxError. - * - * @param message the message - */ - inline SyntaxError(std::string message) noexcept - : Error{"SyntaxError", std::move(message)} - { - } -}; - -/** - * @class TypeError - * @brief Invalid type given. - */ -class TypeError : public Error { -public: - /** - * Construct an TypeError. - * - * @param message the message - */ - inline TypeError(std::string message) noexcept - : Error{"TypeError", std::move(message)} - { - } -}; - -/** - * @class URIError - * @brief URI manipulation failure. - */ -class URIError : public Error { -public: - /** - * Construct an URIError. - * - * @param message the message - */ - inline URIError(std::string message) noexcept - : Error{"URIError", std::move(message)} - { - } -}; - -/* ------------------------------------------------------------------ - * Standard overloads for TypeInfo<T> - * ------------------------------------------------------------------ */ - -/** - * @class TypeInfo<int> - * @brief Default implementation for int. - * - * Provides: get, is, optional, push, require. - */ -template <> -class TypeInfo<int> { -public: - /** - * Get an integer, return 0 if not an integer. - * - * @param ctx the context - * @param index the index - * @return the integer - */ - static inline int get(Context &ctx, int index) - { - return duk_get_int(ctx, index); - } - - /** - * Check if value is an integer. - * - * @param ctx the context - * @param index the index - * @return true if integer - */ - static inline bool is(Context &ctx, int index) - { - return duk_is_number(ctx, index); - } - - /** - * Get an integer, return defaultValue if the value is not an integer. - * - * @param ctx the context - * @param index the index - * @param defaultValue the defaultValue - * @return the integer or defaultValue - */ - static inline int optional(Context &ctx, int index, int defaultValue) - { - if (!duk_is_number(ctx, index)) { - return defaultValue; - } - - return duk_get_int(ctx, index); - } - - /** - * Push an integer. - * - * @param ctx the context - * @param value the value - */ - static inline void push(Context &ctx, int value) - { - duk_push_int(ctx, value); - } - - /** - * Require an integer, throws a JavaScript exception if not an integer. - * - * @param ctx the context - * @param index the index - * @return the integer - */ - static inline int require(Context &ctx, int index) - { - return duk_require_int(ctx, index); - } -}; - -/** - * @class TypeInfo<bool> - * @brief Default implementation for bool. - * - * Provides: get, is, optional, push, require. - */ -template <> -class TypeInfo<bool> { -public: - /** - * Get a boolean, return 0 if not a boolean. - * - * @param ctx the context - * @param index the index - * @return the boolean - */ - static inline bool get(Context &ctx, int index) - { - return duk_get_boolean(ctx, index); - } - - /** - * Check if value is a boolean. - * - * @param ctx the context - * @param index the index - * @return true if boolean - */ - static inline bool is(Context &ctx, int index) - { - return duk_is_boolean(ctx, index); - } - - /** - * Get a bool, return defaultValue if the value is not a boolean. - * - * @param ctx the context - * @param index the index - * @param defaultValue the defaultValue - * @return the boolean or defaultValue - */ - static inline bool optional(Context &ctx, int index, bool defaultValue) - { - if (!duk_is_boolean(ctx, index)) { - return defaultValue; - } - - return duk_get_boolean(ctx, index); - } - - /** - * Push a boolean. - * - * @param ctx the context - * @param value the value - */ - static inline void push(Context &ctx, bool value) - { - duk_push_boolean(ctx, value); - } - - /** - * Require a boolean, throws a JavaScript exception if not a boolean. - * - * @param ctx the context - * @param index the index - * @return the boolean - */ - static inline bool require(Context &ctx, int index) - { - return duk_require_boolean(ctx, index); - } -}; - -/** - * @class TypeInfo<double> - * @brief Default implementation for double. - * - * Provides: get, is, optional, push, require. - */ -template <> -class TypeInfo<double> { -public: - /** - * Get a double, return 0 if not a double. - * - * @param ctx the context - * @param index the index - * @return the double - */ - static inline double get(Context &ctx, int index) - { - return duk_get_number(ctx, index); - } - - /** - * Check if value is a double. - * - * @param ctx the context - * @param index the index - * @return true if double - */ - static inline bool is(Context &ctx, int index) - { - return duk_is_number(ctx, index); - } - - /** - * Get a double, return defaultValue if the value is not a double. - * - * @param ctx the context - * @param index the index - * @param defaultValue the defaultValue - * @return the double or defaultValue - */ - static inline double optional(Context &ctx, int index, double defaultValue) - { - if (!duk_is_number(ctx, index)) { - return defaultValue; - } - - return duk_get_number(ctx, index); - } - - /** - * Push a double. - * - * @param ctx the context - * @param value the value - */ - static inline void push(Context &ctx, double value) - { - duk_push_number(ctx, value); - } - - /** - * Require a double, throws a JavaScript exception if not a double. - * - * @param ctx the context - * @param index the index - * @return the double - */ - static inline double require(Context &ctx, int index) - { - return duk_require_number(ctx, index); - } -}; - -/** - * @class TypeInfo<std::string> - * @brief Default implementation for std::string. - * - * Provides: get, is, optional, push, require. - * - * Note: the functions allows embedded '\0'. - */ -template <> -class TypeInfo<std::string> { -public: - /** - * Get a string, return 0 if not a string. - * - * @param ctx the context - * @param index the index - * @return the string - */ - static inline std::string get(Context &ctx, int index) - { - duk_size_t size; - const char *text = duk_get_lstring(ctx, index, &size); - - return std::string{text, size}; - } - - /** - * Check if value is a string. - * - * @param ctx the context - * @param index the index - * @return true if string - */ - static inline bool is(Context &ctx, int index) - { - return duk_is_string(ctx, index); - } - - /** - * Get a string, return defaultValue if the value is not an string. - * - * @param ctx the context - * @param index the index - * @param defaultValue the defaultValue - * @return the string or defaultValue - */ - static inline std::string optional(Context &ctx, int index, std::string defaultValue) - { - if (!duk_is_string(ctx, index)) { - return defaultValue; - } - - return get(ctx, index); - } - - /** - * Push a string. - * - * @param ctx the context - * @param value the value - */ - static inline void push(Context &ctx, const std::string &value) - { - duk_push_lstring(ctx, value.c_str(), value.length()); - } - - /** - * Require a string, throws a JavaScript exception if not a string. - * - * @param ctx the context - * @param index the index - * @return the string - */ - static inline std::string require(Context &ctx, int index) - { - duk_size_t size; - const char *text = duk_require_lstring(ctx, index, &size); - - return std::string{text, size}; - } -}; - -/** - * @class TypeInfo<const char *> - * @brief Default implementation for const char literals. - * - * Provides: get, is, optional, push, require. - */ -template <> -class TypeInfo<const char *> { -public: - /** - * Get a string, return 0 if not a string. - * - * @param ctx the context - * @param index the index - * @return the string - */ - static inline const char *get(Context &ctx, int index) - { - return duk_get_string(ctx, index); - } - - /** - * Check if value is a string. - * - * @param ctx the context - * @param index the index - * @return true if string - */ - static inline bool is(Context &ctx, int index) - { - return duk_is_string(ctx, index); - } - - /** - * Get an integer, return defaultValue if the value is not an integer. - * - * @param ctx the context - * @param index the index - * @param defaultValue the defaultValue - * @return the integer or defaultValue - */ - static inline const char *optional(Context &ctx, int index, const char *defaultValue) - { - if (!duk_is_string(ctx, index)) { - return defaultValue; - } - - return duk_get_string(ctx, index); - } - - /** - * Push a string. - * - * @param ctx the context - * @param value the value - */ - static inline void push(Context &ctx, const char *value) - { - duk_push_string(ctx, value); - } - - /** - * Require a string, throws a JavaScript exception if not a string. - * - * @param ctx the context - * @param index the index - * @return the string - */ - static inline const char *require(Context &ctx, int index) - { - return duk_require_string(ctx, index); - } -}; - -/** - * @brief Implementation for non-managed pointers. - * - * Provides: get, is, optional, push, require. - */ -template <typename T> -class TypeInfo<RawPointer<T>> { -public: - /** - * Get a pointer, return nullptr if not a pointer. - * - * @param ctx the context - * @param index the index - * @return the pointer - */ - static inline T *get(Context &ctx, int index) - { - return static_cast<T *>(duk_to_pointer(ctx, index)); - } - - /** - * Check if value is a pointer. - * - * @param ctx the context - * @param index the index - * @return true if pointer - */ - static inline bool is(Context &ctx, int index) - { - return duk_is_pointer(ctx, index); - } - - /** - * Get a pointer, return defaultValue if the value is not a pointer. - * - * @param ctx the context - * @param index the index - * @param defaultValue the defaultValue - * @return the pointer or defaultValue - */ - static inline T *optional(Context &ctx, int index, RawPointer<T> defaultValue) - { - if (!duk_is_pointer(ctx, index)) { - return defaultValue.object; - } - - return static_cast<T *>(duk_to_pointer(ctx, index)); - } - - /** - * Push a pointer. - * - * @param ctx the context - * @param value the value - */ - static inline void push(Context &ctx, const RawPointer<T> &value) - { - duk_push_pointer(ctx, value.object); - } - - /** - * Require a pointer, throws a JavaScript exception if not a pointer. - * - * @param ctx the context - * @param index the index - * @return the pointer - */ - static inline T *require(Context &ctx, int index) - { - return static_cast<T *>(duk_require_pointer(ctx, index)); - } -}; - -/** - * @class TypeInfo<Function> - * @brief Push C++ function to the stack. - * - * Provides: push. - * - * This implementation push a Duktape/C function that is wrapped as C++ for convenience. - */ -template <> -class TypeInfo<Function> { -public: - /** - * Push the C++ function, it is wrapped as Duktape/C function and allocated on the heap by moving the - * std::function. - * - * @param ctx the context - * @param fn the function - */ - static void push(Context &ctx, Function fn); -}; - -/** - * @class TypeInfo<FunctionMap> - * @brief Put the functions to the object at the top of the stack. - * - * Provides: push. - */ -template <> -class TypeInfo<FunctionMap> { -public: - /** - * Push a map of function to the object at the top of the stack. - * - * @param ctx the context - * @param map the map of function - */ - static inline void push(Context &ctx, const FunctionMap &map) - { - for (const auto &entry : map) { - ctx.putProperty(-1, entry.first, entry.second); - } - } -}; - -/** - * @class TypeInfo<Object> - * @brief Push empty object to the stack. - * - * Provides: is, push. - */ -template <> -class TypeInfo<Object> { -public: - /** - * Check if value is an object. - * - * @param ctx the context - * @param index the index - * @return true if object - */ - static inline bool is(Context &ctx, int index) - { - return duk_is_object(ctx, index); - } - - /** - * Create an empty object on the stack. - * - * @param ctx the context - */ - static inline void push(Context &ctx, const Object &) - { - duk_push_object(ctx); - } -}; - -/** - * @class TypeInfo<Array> - * @brief Push empty array to the stack. - * - * Provides: is, push. - */ -template <> -class TypeInfo<Array> { -public: - /** - * Check if value is a array. - * - * @param ctx the context - * @param index the index - * @return true if array - */ - static inline bool is(Context &ctx, int index) - { - return duk_is_array(ctx, index); - } - - /** - * Create an empty array on the stack. - * - * @param ctx the context - */ - static inline void push(Context &ctx, const Array &) - { - duk_push_array(ctx); - } -}; - -/** - * @class TypeInfo<Undefined> - * @brief Push undefined value to the stack. - * - * Provides: is, push. - */ -template <> -class TypeInfo<Undefined> { -public: - /** - * Check if value is undefined. - * - * @param ctx the context - * @param index the index - * @return true if undefined - */ - static inline bool is(Context &ctx, int index) - { - return duk_is_undefined(ctx, index); - } - - /** - * Push undefined value on the stack. - * - * @param ctx the context - */ - static inline void push(Context &ctx, const Undefined &) - { - duk_push_undefined(ctx); - } -}; - -/** - * @class TypeInfo<Null> - * @brief Push null value to the stack. - * - * Provides: is, push. - */ -template <> -class TypeInfo<Null> { -public: - /** - * Check if value is null. - * - * @param ctx the context - * @param index the index - * @return true if null - */ - static inline bool is(Context &ctx, int index) - { - return duk_is_null(ctx, index); - } - - /** - * Push null value on the stack. - * - * @param ctx the context - */ - static inline void push(Context &ctx, const Null &) - { - duk_push_null(ctx); - } -}; - -/** - * @brief Push this binding into the stack. - * - * Provides: push. - */ -template <> -class TypeInfo<This> { -public: - /** - * Push this function into the stack. - * - * @param ctx the context - */ - static inline void push(Context &ctx, const This &) - { - duk_push_this(ctx); - } -}; - -/** - * @class TypeInfo<Global> - * @brief Push the global object to the stack. - * - * Provides: push. - */ -template <> -class TypeInfo<Global> { -public: - /** - * Push the global object into the stack. - * - * @param ctx the context - */ - static inline void push(Context &ctx, const Global &) - { - duk_push_global_object(ctx); - } -}; - -/** - * @brief Push a map of key-value pair as objects. - * - * Provides: push. - * - * This class is convenient for settings constants such as enums, string and such. - */ -template <typename T> -class TypeInfo<std::unordered_map<std::string, T>> { -public: - /** - * Put all values from the map as properties to the object at the top of the stack. - * - * @param ctx the context - * @param map the values - * @note You need an object at the top of the stack before calling this function - */ - static void push(Context &ctx, const std::unordered_map<std::string, T> &map) - { - for (const auto &pair : map) { - TypeInfo<T>::push(ctx, pair.second); - duk_put_prop_string(ctx, -2, pair.first.c_str()); - } - } -}; - -/** - * @brief Push or get vectors as JavaScript arrays. - * - * Provides: get, push. - */ -template <typename T> -class TypeInfo<std::vector<T>> { -public: - /** - * Get an array from the stack. - * - * @param ctx the context - * @param index the array index - * @return the array or empty array if the value is not an array - */ - static std::vector<T> get(Context &ctx, int index) - { - std::vector<T> result; - - if (!duk_is_array(ctx, -1)) { - return result; - } - - int total = duk_get_length(ctx, index); - for (int i = 0; i < total; ++i) { - result.push_back(ctx.getProperty<T>(index, i)); - } - - return result; - } - - /** - * Create an array with the specified values. - * - * @param ctx the context - * @param array the values - */ - static void push(Context &ctx, const std::vector<T> &array) - { - duk_push_array(ctx); - - int i = 0; - for (const auto &v : array) { - TypeInfo<T>::push(ctx, v); - duk_put_prop_index(ctx, -2, i++); - } - } -}; - -/** - * @brief Implementation of managed shared_ptr - * @see Shared - */ -template <typename T> -class TypeInfo<Shared<T>> { -private: - static void apply(Context &ctx, std::shared_ptr<T> value) - { - duk_push_boolean(ctx, false); - duk_put_prop_string(ctx, -2, "\xff""\xff""js-deleted"); - duk_push_pointer(ctx, new std::shared_ptr<T>(value)); - duk_put_prop_string(ctx, -2, "\xff""\xff""js-shared-ptr"); - duk_push_c_function(ctx, [] (duk_context *ctx) -> duk_ret_t { - duk_get_prop_string(ctx, 0, "\xff""\xff""js-deleted"); - - if (!duk_to_boolean(ctx, -1)) { - duk_push_boolean(ctx, true); - duk_put_prop_string(ctx, 0, "\xff""\xff""js-deleted"); - duk_get_prop_string(ctx, 0, "\xff""\xff""js-shared-ptr"); - delete static_cast<std::shared_ptr<T> *>(duk_to_pointer(ctx, -1)); - duk_pop(ctx); - } - - duk_pop(ctx); - - return 0; - }, 1); - duk_set_finalizer(ctx, -2); - } - -public: - /** - * Construct the shared_ptr as this. - * - * @param ctx the context - * @param value the value - */ - static void construct(Context &ctx, Shared<T> value) - { - duk_push_this(ctx); - apply(ctx, std::move(value.object)); - duk_pop(ctx); - } - - /** - * Push a managed shared_ptr as object. - * - * @param ctx the context - * @param value the value - */ - static void push(Context &ctx, Shared<T> value) - { - duk_push_object(ctx); - apply(ctx, value.object); - value.object->prototype(ctx); - duk_set_prototype(ctx, -2); - } - - /** - * Get a managed shared_ptr from the stack. - * - * @param ctx the context - * @param index the object index - * @return the shared_ptr - */ - static std::shared_ptr<T> get(Context &ctx, int index) - { - duk_get_prop_string(ctx, index, "\xff""\xff""js-shared-ptr"); - std::shared_ptr<T> value = *static_cast<std::shared_ptr<T> *>(duk_to_pointer(ctx, -1)); - duk_pop(ctx); - - return value; - } -}; - -/** - * @brief Implementation of managed pointers - * @see Pointer - */ -template <typename T> -class TypeInfo<Pointer<T>> { -private: - static void apply(Context &ctx, T *value) - { - duk_push_boolean(ctx, false); - duk_put_prop_string(ctx, -2, "\xff""\xff""js-deleted"); - duk_push_pointer(ctx, value); - duk_put_prop_string(ctx, -2, "\xff""\xff""js-ptr"); - duk_push_c_function(ctx, [] (duk_context *ctx) -> duk_ret_t { - duk_get_prop_string(ctx, 0, "\xff""\xff""js-deleted"); - - if (!duk_to_boolean(ctx, -1)) { - duk_push_boolean(ctx, true); - duk_put_prop_string(ctx, 0, "\xff""\xff""js-deleted"); - duk_get_prop_string(ctx, 0, "\xff""\xff""js-ptr"); - delete static_cast<T *>(duk_to_pointer(ctx, -1)); - duk_pop(ctx); - } - - duk_pop(ctx); - - return 0; - }, 1); - duk_set_finalizer(ctx, -2); - } - -public: - /** - * Construct the pointer as this. - * - * @param ctx the context - * @param value the value - */ - static void construct(Context &ctx, Pointer<T> value) - { - duk_push_this(ctx); - apply(ctx, value.object); - duk_pop(ctx); - } - - /** - * Push a managed pointer as object. - * - * @param ctx the context - * @param value the value - */ - static void push(Context &ctx, Pointer<T> value) - { - duk_push_object(ctx); - apply(ctx, value.object); - value.object->prototype(ctx); - duk_set_prototype(ctx, -2); - } - - /** - * Get a managed pointer from the stack. - * - * @param ctx the context - * @param index the object index - * @return the pointer - * @warning Do not store the pointer into the C++ side, the object can be deleted at any time - */ - static T *get(Context &ctx, int index) - { - duk_get_prop_string(ctx, index, "\xff""\xff""js-ptr"); - T *value = static_cast<T *>(duk_to_pointer(ctx, -1)); - duk_pop(ctx); - - return value; - } -}; - -} // !js - -#endif // !_JS_H_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Js/js.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,107 @@ +/* + * js.cpp -- JavaScript C++14 wrapper for Duktape + * + * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "js.h" + +using namespace std::string_literals; + +namespace js { + +ErrorInfo Context::error(int index) +{ + ErrorInfo error; + + index = duk_normalize_index(m_handle.get(), index); + + duk_get_prop_string(m_handle.get(), index, "name"); + error.name = duk_to_string(m_handle.get(), -1); + duk_get_prop_string(m_handle.get(), index, "message"); + error.message = duk_to_string(m_handle.get(), -1); + duk_get_prop_string(m_handle.get(), index, "fileName"); + error.fileName = duk_to_string(m_handle.get(), -1); + duk_get_prop_string(m_handle.get(), index, "lineNumber"); + error.lineNumber = duk_to_int(m_handle.get(), -1); + duk_get_prop_string(m_handle.get(), index, "stack"); + error.stack = duk_to_string(m_handle.get(), -1); + duk_pop_n(m_handle.get(), 5); + + return error; +} + +void Context::pcall(unsigned nargs) +{ + if (duk_pcall(m_handle.get(), nargs) != 0) { + ErrorInfo info = error(-1); + duk_pop(m_handle.get()); + + throw info; + } +} + +void Context::peval() +{ + if (duk_peval(m_handle.get()) != 0) { + ErrorInfo info = error(-1); + duk_pop(m_handle.get()); + + throw info; + } +} + +void TypeInfo<Function>::push(Context &ctx, Function fn) +{ + /* 1. Push function wrapper */ + duk_push_c_function(ctx, [] (duk_context *ctx) -> duk_ret_t { + Context context{ctx}; + + duk_push_current_function(ctx); + duk_get_prop_string(ctx, -1, "\xff""\xff""js-func"); + Function *f = static_cast<Function *>(duk_to_pointer(ctx, -1)); + duk_pop_2(ctx); + + return static_cast<duk_ret_t>(f->function(context)); + }, fn.nargs); + + /* 2. Store the moved function */ + duk_push_pointer(ctx, new Function(std::move(fn))); + duk_put_prop_string(ctx, -2, "\xff""\xff""js-func"); + + /* 3. Store deletion flags */ + duk_push_boolean(ctx, false); + duk_put_prop_string(ctx, -2, "\xff""\xff""js-deleted"); + + /* 4. Push and set a finalizer */ + duk_push_c_function(ctx, [] (duk_context *ctx) { + duk_get_prop_string(ctx, 0, "\xff""\xff""js-deleted"); + + if (!duk_to_boolean(ctx, -1)) { + duk_push_boolean(ctx, true); + duk_put_prop_string(ctx, 0, "\xff""\xff""js-deleted"); + duk_get_prop_string(ctx, 0, "\xff""\xff""js-func"); + delete static_cast<Function *>(duk_to_pointer(ctx, -1)); + duk_pop(ctx); + } + + duk_pop(ctx); + + return 0; + }, 1); + duk_set_finalizer(ctx, -2); +} + +} // !js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Js/js.h Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,2105 @@ +/* + * js.h -- JavaScript C++14 wrapper for Duktape + * + * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _JS_H_ +#define _JS_H_ + +/** + * @file js.h + * @brief Bring JavaScript using Duktape + * + * This file provides usual Duktape function renamed and placed into `js` namespace. It also replaces error + * code with exceptions when possible. + * + * For convenience, this file also provides templated functions, overloads and much more. + */ + +#include <functional> +#include <memory> +#include <string> +#include <type_traits> +#include <unordered_map> +#include <utility> +#include <vector> + +#include <duktape.h> + +/** + * Duktape C++ namespace wrapper. + */ +namespace js { + +class Context; + +/** + * Typedef for readability. + */ +using ContextPtr = duk_context *; + +/* + * Basic types to manipulate with the stack + * ------------------------------------------------------------------ + * + * The following types can be used in some of the operations like Context::push or Context::is, they are defined + * usually as empty classes to determine the appropriate action to execute. + * + * For example, `ctx.push(js::Object{})` will push an empty object into the stack. + */ + +/** + * @class Object + * @brief Empty class tag for push() function. + */ +class Object { +}; + +/** + * @class Array + * @brief Empty class tag for push() function. + */ +class Array { +}; + +/** + * @class Global + * @brief Empty class tag to push the global object. + */ +class Global { +}; + +/** + * @class Undefined + * @brief Empty class tag to push undefined to the stack. + */ +class Undefined { +}; + +/** + * @class Null + * @brief Empty class tag to push null to the stack. + */ +class Null { +}; + +/** + * @class This + * @brief Empty class tag to push this binding to the stack. + */ +class This { +}; + +/** + * @class RawPointer + * @brief Push a non-managed pointer to Duktape, the pointer will never be deleted. + * @note For a managed pointer with prototype, see Pointer + */ +template <typename T> +class RawPointer { +public: + /** + * The pointer to push. + */ + T *object; +}; + +/* + * Extended type manipulation + * ------------------------------------------------------------------ + * + * The following types are different as there are no equivalent in the native Duktape API, they are available for + * convenience. + */ + +/** + * @brief Manage shared_ptr from C++ and JavaScript + * + * This class allowed you to push and retrieve shared_ptr from C++ and JavaScript without taking care of ownership + * and deletion. + * + * The only requirement is to have the function `void prototype(Context &ctx)` in your class T. + */ +template <typename T> +class Shared { +public: + /** + * The shared object. + */ + std::shared_ptr<T> object; +}; + +/** + * @brief Manage pointers from C++ and JavaScript + * + * This class allowed you to push and retrieve C++ pointers from C++ and JavaScript. The object will be deleted when + * the JavaScript garbage collectors collect them so never store a pointer created with this. + * + * The only requirement is to have the function `void prototype(Context &ctx)` in your class T. + */ +template <typename T> +class Pointer { +public: + /** + * The object. + */ + T *object{nullptr}; +}; + +/** + * @class Function + * @brief Duktape/C function definition. + * + * This class wraps the std::function as a Duktape/C function by storing a copied pointer. + */ +class Function { +public: + /** + * The function pointer, must not be null. + */ + std::function<int (Context &)> function; + + /** + * Number of args that the function takes + */ + int nargs{0}; +}; + +/** + * Map of functions to set on an object. + */ +using FunctionMap = std::unordered_map<std::string, Function>; + +/** + * Map of string to type, ideal for setting constants like enums. + */ +template <typename Type> +using Map = std::unordered_map<std::string, Type>; + +/** + * @class ErrorInfo + * @brief Error description. + * + * This class fills the fields got in an Error object. + */ +class ErrorInfo : public std::exception { +public: + std::string name; //!< name of error + std::string message; //!< error message + std::string stack; //!< stack if available + std::string fileName; //!< filename if applicable + int lineNumber{0}; //!< line number if applicable + + /** + * Get the error message. This effectively returns message field. + * + * @return the message + */ + const char *what() const noexcept override + { + return message.c_str(); + } +}; + +/** + * @class TypeInfo + * @brief Type information to implement new types in JavaScript's context. + * + * This class depending on your needs may have the following functions: + * + * - `static void construct(Context &ctx, Type value)` + * - `static Type get(Context &ctx, int index)` + * - `static bool is(Context &ctx, int index)` + * - `static Type optional(Context &ctx, int index, Type defaultValue)` + * - `static void push(Context &ctx, Type value)` + * - `static Type require(Context &ctx, int index)` + * + * The `construct` function is used in Context::construct to build a new value as this (e.g. constructors). + * + * The `get` function is used in Context::get, Context::getProperty, Context::getGlobal to retrieve a value from the + * stack. + * + * The `is` function is used in Context::is to check if the value on the stack is of type `Type`. + * + * The `optional` function is used in Context::optional to get a value or a replacement if not applicable. + * + * The `push` function is used in Context::push to usually create a new value on the stack but some specializations + * may not (e.g. FunctionMap). + * + * The `require` function is used in Context::require to get a value from the stack or raise a JavaScript exception if + * not applicable. + * + * This class is fully specialized for: `bool`, `const char *`, `double`, `int`, `std::string`. + * + * It is also partially specialized for : `Global`, `Object`, `Array`, `Undefined`, `Null`, `std::vector<Type>`. + */ +template <typename Type> +class TypeInfo { +}; + +/** + * @class File + * @brief Evaluate script from file. + * @see Context::eval + * @see Context::peval + */ +class File { +public: + /** + * Path to the file. + */ + std::string path; + + /** + * Evaluate the file. + * + * @param ctx the context + */ + inline void eval(duk_context *ctx) + { + duk_eval_file(ctx, path.c_str()); + } + + /** + * Evaluate in protected mode the file. + * + * @param ctx the context + */ + inline int peval(duk_context *ctx) + { + return duk_peval_file(ctx, path.c_str()); + } +}; + +/** + * @class Script + * @brief Evaluate script from raw text. + * @see Context::eval + * @see Context::peval + */ +class Script { +public: + /** + * The script content. + */ + std::string text; + + /** + * Evaluate the script. + * + * @param ctx the context + */ + inline void eval(duk_context *ctx) + { + duk_eval_string(ctx, text.c_str()); + } + + /** + * Evaluate in protected mode the script. + * + * @param ctx the context + */ + inline int peval(duk_context *ctx) + { + return duk_peval_string(ctx, text.c_str()); + } +}; + +/** + * @class Context + * @brief RAII based Duktape handler. + * + * This class is implicitly convertible to duk_context for convenience. + */ +class Context { +private: + using Deleter = void (*)(duk_context *); + using Handle = std::unique_ptr<duk_context, Deleter>; + + Handle m_handle; + + /* Move and copy forbidden */ + Context(const Context &) = delete; + Context &operator=(const Context &) = delete; + Context(const Context &&) = delete; + Context &operator=(const Context &&) = delete; + +public: + /** + * Create default context. + */ + inline Context() + : m_handle{duk_create_heap_default(), duk_destroy_heap} + { + } + + /** + * Create borrowed context that will not be deleted. + * + * @param ctx the pointer to duk_context + */ + inline Context(ContextPtr ctx) noexcept + : m_handle{ctx, [] (ContextPtr) {}} + { + } + + /** + * Convert the context to the native Duktape/C type. + * + * @return the duk_context + */ + inline operator duk_context *() noexcept + { + return m_handle.get(); + } + + /** + * Convert the context to the native Duktape/C type. + * + * @return the duk_context + */ + inline operator duk_context *() const noexcept + { + return m_handle.get(); + } + + /* + * Basic functions + * ---------------------------------------------------------- + * + * The following functions are just standard wrappers around the native Duktape C functions, they are + * defined with the same signature except that for convenience some parameters have default sane values. + */ + + /** + * Call the object at the top of the stack. + * + * @param ctx the context + * @param nargs the number of arguments + * @note Non-protected + */ + inline void call(unsigned nargs = 0) + { + duk_call(m_handle.get(), nargs); + } + + /** + * Copy a value from from to to, overwriting the previous value. If either index is invalid, throws an error. + * + * @param from the from index + * @param to the destination + */ + inline void copy(int from, int to) + { + duk_copy(m_handle.get(), from, to); + } + + /** + * Define a property. + * + * @param index the object index + * @param flags the flags + * @note Wrapper of duk_def_prop + */ + inline void defineProperty(int index, int flags) + { + duk_def_prop(m_handle.get(), index, flags); + } + + /** + * Delete a property. + * + * @param index the object index + * @return true if deleted + * @note Wrapper of duk_del_prop + */ + inline bool deleteProperty(int index) + { + return duk_del_prop(m_handle.get(), index); + } + + /** + * Delete a property by index. + * + * @param index the object index + * @param position the property index + * @return true if deleted + * @note Wrapper of duk_del_prop_index + */ + inline bool deleteProperty(int index, int position) + { + return duk_del_prop_index(m_handle.get(), index, position); + } + + /** + * Delete a property by name. + * + * @param index the object index + * @param name the property name + * @return true if deleted + * @note Wrapper of duk_del_prop_string + */ + inline bool deleteProperty(int index, const std::string &name) + { + return duk_del_prop_string(m_handle.get(), index, name.c_str()); + } + + /** + * Push a duplicate of value at from_index to the stack. If from_index is invalid, throws an error. + * + * @param index the value to copy + * @note Wrapper of duk_dup + */ + inline void dup(int index = -1) + { + duk_dup(m_handle.get(), index); + } + + /** + * Evaluate a non-protected chunk that is at the top of the stack. + */ + inline void eval() + { + duk_eval(m_handle.get()); + } + + /** + * Check if the object as a property. + * + * @param index the object index + * @return true if has + * @note Wrapper of duk_has_prop + */ + inline bool hasProperty(int index) + { + return duk_has_prop(m_handle.get(), index); + } + + /** + * Check if the object as a property by index. + * + * @param index the object index + * @param position the property index + * @return true if has + * @note Wrapper of duk_has_prop_index + */ + inline bool hasProperty(int index, int position) + { + return duk_has_prop_index(m_handle.get(), index, position); + } + + /** + * Check if the object as a property by string + * + * @param index the object index + * @param name the property name + * @return true if has + * @note Wrapper of duk_has_prop_string + */ + inline bool hasProperty(int index, const std::string &name) + { + return duk_has_prop_string(m_handle.get(), index, name.c_str()); + } + + /** + * Check if idx1 is an instance of idx2. + * + * @param ctx the context + * @param idx1 the value to test + * @param idx2 the instance requested + * @return true if idx1 is instance of idx2 + * @note Wrapper of duk_instanceof + */ + inline bool instanceof(int idx1, int idx2) + { + return duk_instanceof(m_handle.get(), idx1, idx2); + } + + /** + * Insert a value at to with a value popped from the stack top. The previous value at to and any values above + * it are moved up the stack by a step. If to is an invalid index, throws an error. + * + * @note Negative indices are evaluated prior to popping the value at the stack top + * @param to the destination + * @note Wrapper of duk_insert + */ + inline void insert(int to) + { + duk_insert(m_handle.get(), to); + } + + /** + * Pop a certain number of values from the top of the stack. + * + * @param ctx the context + * @param count the number of values to pop + * @note Wrapper of duk_pop_n + */ + inline void pop(unsigned count = 1) + { + duk_pop_n(m_handle.get(), count); + } + + /** + * Remove value at index. Elements above index are shifted down the stack by a step. If to is an invalid index, + * throws an error. + * + * @param index the value to remove + * @note Wrapper of duk_remove + */ + inline void remove(int index) + { + duk_remove(m_handle.get(), index); + } + + /** + * Replace value at to_index with a value popped from the stack top. If to_index is an invalid index, + * throws an error. + * + * @param index the value to replace by the value at the top of the stack + * @note Negative indices are evaluated prior to popping the value at the stack top. + * @note Wrapper of duk_replace + */ + inline void replace(int index) + { + duk_replace(m_handle.get(), index); + } + + /** + * Swap values at indices index1 and index2. If the indices are the same, the call is a no-op. If either index + * is invalid, throws an error. + * + * @param index1 the first index + * @param index2 the second index + * @note Wrapper of duk_swap + */ + inline void swap(int index1, int index2) + { + duk_swap(m_handle.get(), index1, index2); + } + + /** + * Get the current stack size. + * + * @param ctx the context + * @return the stack size + * @note Wrapper of duk_get_top + */ + inline int top() noexcept + { + return duk_get_top(m_handle.get()); + } + + /** + * Get the type of the value at the specified index. + * + * @param ctx the context + * @param index the idnex + * @return the type + * @note Wrapper of duk_get_type + */ + inline int type(int index) noexcept + { + return duk_get_type(m_handle.get(), index); + } + + /* + * Extended native functions + * ---------------------------------------------------------- + * + * The following functions have different behaviour than the original native Duktape C functions, see their + * descriptions for more information + */ + + /** + * Call in protected mode the object at the top of the stack. + * + * @param nargs the number of arguments + * @throw ErrorInfo on errors + * @note Wrapper of duk_pcall + */ + void pcall(unsigned nargs = 0); + + /** + * Evaluate a non-protected source. + * + * @param source the source + * @see File + * @see Script + * @note Wrapper of duk_eval + */ + template <typename Source> + inline void eval(Source &&source) + { + source.eval(m_handle.get()); + } + + /** + * Evaluate a protected chunk that is at the top of the stack. + * + * @throw ErrorInfo the error + * @note Wrapper of duk_peval + */ + void peval(); + + /** + * Evaluate a protected source. + * + * @param source the source + * @see File + * @see Script + * @throw ErrorInfo on failure + * @note Wrapper of duk_peval + */ + template <typename Source> + inline void peval(Source &&source) + { + if (source.peval(m_handle.get()) != 0) { + ErrorInfo info = error(-1); + duk_pop(m_handle.get()); + + throw info; + } + } + + /* + * Push / Get / Require / Is / Optional + * ---------------------------------------------------------- + * + * The following functions are used to push, get or check values from the stack. They use specialization + * of TypeInfo class. + */ + + /** + * Push a value into the stack. Calls TypeInfo<T>::push(*this, value); + * + * @param value the value to forward + */ + template <typename Type> + inline void push(Type &&value) + { + TypeInfo<std::decay_t<Type>>::push(*this, std::forward<Type>(value)); + } + + /** + * Generic template function to get a value from the stack. + * + * @param index the index + * @return the value + */ + template <typename Type> + inline auto get(int index) -> decltype(TypeInfo<Type>::get(*this, 0)) + { + return TypeInfo<Type>::get(*this, index); + } + + /** + * Require a type at the specified index. + * + * @param index the index + * @return the value + */ + template <typename Type> + inline auto require(int index) -> decltype(TypeInfo<Type>::require(*this, 0)) + { + return TypeInfo<Type>::require(*this, index); + } + + /** + * Check if a value is a type of T. + * + * The TypeInfo<T> must have `static bool is(ContextPtr ptr, int index)`. + * + * @param index the value index + * @return true if is the type + */ + template <typename T> + inline bool is(int index) + { + return TypeInfo<T>::is(*this, index); + } + + /** + * Get an optional value from the stack, if the value is not available of not the correct type, + * return defaultValue instead. + * + * The TypeInfo<T> must have `static T optional(Context &, int index, T &&defaultValue)`. + * + * @param index the value index + * @param defaultValue the value replacement + * @return the value or defaultValue + */ + template <typename Type> + inline auto optional(int index, Type &&defaultValue) + { + return TypeInfo<std::decay_t<Type>>::optional(*this, index, std::forward<Type>(defaultValue)); + } + + /* + * Properties management + * ---------------------------------------------------------- + * + * The following functions are used to read or set properties on objects or globals also using TypeInfo. + */ + + /** + * Get the property `name' as value from the object at the specified index. + * + * @param index the object index + * @param name the property name + * @return the value + * @note The stack is unchanged + */ + template <typename Type, typename std::enable_if_t<!std::is_void<Type>::value> * = nullptr> + inline auto getProperty(int index, const std::string &name) -> decltype(get<Type>(0)) + { + duk_get_prop_string(m_handle.get(), index, name.c_str()); + decltype(get<Type>(0)) value = get<Type>(-1); + duk_pop(m_handle.get()); + + return value; + } + + /** + * Get a property by index, for arrays. + * + * @param index the object index + * @param position the position int the object + * @return the value + * @note The stack is unchanged + */ + template <typename Type, typename std::enable_if_t<!std::is_void<Type>::value> * = nullptr> + inline auto getProperty(int index, int position) -> decltype(get<Type>(0)) + { + duk_get_prop_index(m_handle.get(), index, position); + decltype(get<Type>(0)) value = get<Type>(-1); + duk_pop(m_handle.get()); + + return value; + } + + /** + * Get the property `name' and push it to the stack from the object at the specified index. + * + * @param index the object index + * @param name the property name + * @note The stack contains the property value + */ + template <typename Type, typename std::enable_if_t<std::is_void<Type>::value> * = nullptr> + inline void getProperty(int index, const std::string &name) + { + duk_get_prop_string(m_handle.get(), index, name.c_str()); + } + + /** + * Get the property by index and push it to the stack from the object at the specified index. + * + * @param index the object index + * @param position the position in the object + * @note The stack contains the property value + */ + template <typename Type, typename std::enable_if_t<std::is_void<Type>::value> * = nullptr> + inline void getProperty(int index, int position) + { + duk_get_prop_index(m_handle.get(), index, position); + } + + /** + * Get an optional property `name` from the object at the specified index. + * + * @param index the object index + * @param name the property name + * @param def the default value + * @return the value or def + * @note The stack is unchanged + */ + template <typename Type, typename DefaultValue> + inline auto optionalProperty(int index, const std::string &name, DefaultValue &&def) -> decltype(optional<Type>(0, std::forward<DefaultValue>(def))) + { + duk_get_prop_string(m_handle.get(), index, name.c_str()); + decltype(optional<Type>(0, std::forward<DefaultValue>(def))) value = optional<Type>(-1, std::forward<DefaultValue>(def)); + duk_pop(m_handle.get()); + + return value; + } + + /** + * Get an optional property by index, for arrays + * + * @param index the object index + * @param position the position int the object + * @param def the default value + * @return the value or def + * @note The stack is unchanged + */ + template <typename Type, typename DefaultValue> + inline auto optionalProperty(int index, int position, DefaultValue &&def) -> decltype(optional<Type>(0, std::forward<DefaultValue>(def))) + { + duk_get_prop_index(m_handle.get(), index, position); + decltype(optional<Type>(0, std::forward<DefaultValue>(def))) value = optional<Type>(-1, std::forward<DefaultValue>(def)); + duk_pop(m_handle.get()); + + return value; + } + + /** + * Set a property to the object at the specified index. + * + * @param index the object index + * @param name the property name + * @param value the value to forward + * @note The stack is unchanged + */ + template <typename Type> + void putProperty(int index, const std::string &name, Type &&value) + { + index = duk_normalize_index(m_handle.get(), index); + + push(std::forward<Type>(value)); + duk_put_prop_string(m_handle.get(), index, name.c_str()); + } + + /** + * Set a property by index, for arrays. + * + * @param index the object index + * @param position the position in the object + * @param value the value to forward + * @note The stack is unchanged + */ + template <typename Type> + void putProperty(int index, int position, Type &&value) + { + index = duk_normalize_index(m_handle.get(), index); + + push(std::forward<Type>(value)); + duk_put_prop_index(m_handle.get(), index, position); + } + + /** + * Put the value that is at the top of the stack as property to the object. + * + * @param index the object index + * @param name the property name + */ + inline void putProperty(int index, const std::string &name) + { + duk_put_prop_string(m_handle.get(), index, name.c_str()); + } + + /** + * Put the value that is at the top of the stack to the object as index. + * + * @param index the object index + * @param position the position in the object + */ + inline void putProperty(int index, int position) + { + duk_put_prop_index(m_handle.get(), index, position); + } + + /** + * Get a global value. + * + * @param name the name of the global variable + * @return the value + */ + template <typename Type> + inline auto getGlobal(const std::string &name, std::enable_if_t<!std::is_void<Type>::value> * = nullptr) -> decltype(get<Type>(0)) + { + duk_get_global_string(m_handle.get(), name.c_str()); + decltype(get<Type>(0)) value = get<Type>(-1); + duk_pop(m_handle.get()); + + return value; + } + + /** + * Overload that push the value at the top of the stack instead of returning it. + */ + template <typename Type> + inline void getGlobal(const std::string &name, std::enable_if_t<std::is_void<Type>::value> * = nullptr) noexcept + { + duk_get_global_string(m_handle.get(), name.c_str()); + } + + /** + * Set a global variable. + * + * @param name the name of the global variable + * @param type the value to set + */ + template <typename Type> + inline void putGlobal(const std::string &name, Type&& type) + { + push(std::forward<Type>(type)); + duk_put_global_string(m_handle.get(), name.c_str()); + } + + /** + * Put the value at the top of the stack as global property. + * + * @param name the property name + */ + inline void putGlobal(const std::string &name) + { + duk_put_global_string(m_handle.get(), name.c_str()); + } + + /* + * Extra functions + * ---------------------------------------------------------- + * + * The following functions are implemented for convenience and do not exists in the native Duktape API. + */ + + /** + * Get the error object when a JavaScript error has been thrown (e.g. eval failure). + * + * @param index the index + * @return the information + */ + ErrorInfo error(int index); + + /** + * Enumerate an object or an array at the specified index. + * + * @param index the object or array index + * @param flags the optional flags to pass to duk_enum + * @param getvalue set to true if you want to extract the value + * @param func the function to call for each properties + */ + template <typename Func> + void enumerate(int index, duk_uint_t flags, duk_bool_t getvalue, Func&& func) + { + duk_enum(m_handle.get(), index, flags); + + while (duk_next(m_handle.get(), -1, getvalue)) { + func(*this); + duk_pop_n(m_handle.get(), 1 + (getvalue ? 1 : 0)); + } + + duk_pop(m_handle.get()); + } + + /** + * Return the this binding of the current function. + * + * @return the this binding as the template given + */ + template <typename T> + inline auto self() -> decltype(TypeInfo<T>::get(*this, 0)) + { + duk_push_this(m_handle.get()); + decltype(TypeInfo<T>::get(*this, 0)) value = TypeInfo<T>::get(*this, -1); + duk_pop(m_handle.get()); + + return value; + } + + /** + * Throw an ECMAScript exception. + * + * @param ex the exception + */ + template <typename Exception> + void raise(const Exception &ex) + { + ex.create(m_handle.get()); + + duk_push_string(m_handle.get(), ex.name().c_str()); + duk_put_prop_string(m_handle.get(), -2, "name"); + duk_throw(m_handle.get()); + } + + /** + * Construct the object in place, setting value as this binding. + * + * The TypeInfo<T> must have the following requirements: + * + * - static void construct(Context &, T): must update this with the value and keep the stack unchanged + * + * @param value the value to forward + * @see self + */ + template <typename T> + inline void construct(T &&value) + { + TypeInfo<std::decay_t<T>>::construct(*this, std::forward<T>(value)); + } +}; + +/* ------------------------------------------------------------------ + * Exception handling + * ------------------------------------------------------------------ */ + +/** + * @class Error + * @brief Base ECMAScript error class. + * @warning Override the function create for your own exceptions + */ +class Error { +protected: + std::string m_name; //!< Name of exception (e.g RangeError) + std::string m_message; //!< The message + + /** + * Constructor with a type of error specified, specially designed for derived errors. + * + * @param name the error name (e.g RangeError) + * @param message the message + */ + inline Error(std::string name, std::string message) noexcept + : m_name{std::move(name)} + , m_message{std::move(message)} + { + } + +public: + /** + * Constructor with a message. + * + * @param message the message + */ + inline Error(std::string message) noexcept + : m_name{"Error"} + , m_message{std::move(message)} + { + } + + /** + * Get the error type (e.g RangeError). + * + * @return the name + */ + inline const std::string &name() const noexcept + { + return m_name; + } + + /** + * Create the exception on the stack. + * + * @note the default implementation search for the global variables + * @param ctx the context + */ + virtual void create(ContextPtr ctx) const noexcept + { + duk_get_global_string(ctx, m_name.c_str()); + duk_push_string(ctx, m_message.c_str()); + duk_new(ctx, 1); + } +}; + +/** + * @class EvalError + * @brief Error in eval() function. + */ +class EvalError : public Error { +public: + /** + * Construct an EvalError. + * + * @param message the message + */ + inline EvalError(std::string message) noexcept + : Error{"EvalError", std::move(message)} + { + } +}; + +/** + * @class RangeError + * @brief Value is out of range. + */ +class RangeError : public Error { +public: + /** + * Construct an RangeError. + * + * @param message the message + */ + inline RangeError(std::string message) noexcept + : Error{"RangeError", std::move(message)} + { + } +}; + +/** + * @class ReferenceError + * @brief Trying to use a variable that does not exist. + */ +class ReferenceError : public Error { +public: + /** + * Construct an ReferenceError. + * + * @param message the message + */ + inline ReferenceError(std::string message) noexcept + : Error{"ReferenceError", std::move(message)} + { + } +}; + +/** + * @class SyntaxError + * @brief Syntax error in the script. + */ +class SyntaxError : public Error { +public: + /** + * Construct an SyntaxError. + * + * @param message the message + */ + inline SyntaxError(std::string message) noexcept + : Error{"SyntaxError", std::move(message)} + { + } +}; + +/** + * @class TypeError + * @brief Invalid type given. + */ +class TypeError : public Error { +public: + /** + * Construct an TypeError. + * + * @param message the message + */ + inline TypeError(std::string message) noexcept + : Error{"TypeError", std::move(message)} + { + } +}; + +/** + * @class URIError + * @brief URI manipulation failure. + */ +class URIError : public Error { +public: + /** + * Construct an URIError. + * + * @param message the message + */ + inline URIError(std::string message) noexcept + : Error{"URIError", std::move(message)} + { + } +}; + +/* ------------------------------------------------------------------ + * Standard overloads for TypeInfo<T> + * ------------------------------------------------------------------ */ + +/** + * @class TypeInfo<int> + * @brief Default implementation for int. + * + * Provides: get, is, optional, push, require. + */ +template <> +class TypeInfo<int> { +public: + /** + * Get an integer, return 0 if not an integer. + * + * @param ctx the context + * @param index the index + * @return the integer + */ + static inline int get(Context &ctx, int index) + { + return duk_get_int(ctx, index); + } + + /** + * Check if value is an integer. + * + * @param ctx the context + * @param index the index + * @return true if integer + */ + static inline bool is(Context &ctx, int index) + { + return duk_is_number(ctx, index); + } + + /** + * Get an integer, return defaultValue if the value is not an integer. + * + * @param ctx the context + * @param index the index + * @param defaultValue the defaultValue + * @return the integer or defaultValue + */ + static inline int optional(Context &ctx, int index, int defaultValue) + { + if (!duk_is_number(ctx, index)) { + return defaultValue; + } + + return duk_get_int(ctx, index); + } + + /** + * Push an integer. + * + * @param ctx the context + * @param value the value + */ + static inline void push(Context &ctx, int value) + { + duk_push_int(ctx, value); + } + + /** + * Require an integer, throws a JavaScript exception if not an integer. + * + * @param ctx the context + * @param index the index + * @return the integer + */ + static inline int require(Context &ctx, int index) + { + return duk_require_int(ctx, index); + } +}; + +/** + * @class TypeInfo<bool> + * @brief Default implementation for bool. + * + * Provides: get, is, optional, push, require. + */ +template <> +class TypeInfo<bool> { +public: + /** + * Get a boolean, return 0 if not a boolean. + * + * @param ctx the context + * @param index the index + * @return the boolean + */ + static inline bool get(Context &ctx, int index) + { + return duk_get_boolean(ctx, index); + } + + /** + * Check if value is a boolean. + * + * @param ctx the context + * @param index the index + * @return true if boolean + */ + static inline bool is(Context &ctx, int index) + { + return duk_is_boolean(ctx, index); + } + + /** + * Get a bool, return defaultValue if the value is not a boolean. + * + * @param ctx the context + * @param index the index + * @param defaultValue the defaultValue + * @return the boolean or defaultValue + */ + static inline bool optional(Context &ctx, int index, bool defaultValue) + { + if (!duk_is_boolean(ctx, index)) { + return defaultValue; + } + + return duk_get_boolean(ctx, index); + } + + /** + * Push a boolean. + * + * @param ctx the context + * @param value the value + */ + static inline void push(Context &ctx, bool value) + { + duk_push_boolean(ctx, value); + } + + /** + * Require a boolean, throws a JavaScript exception if not a boolean. + * + * @param ctx the context + * @param index the index + * @return the boolean + */ + static inline bool require(Context &ctx, int index) + { + return duk_require_boolean(ctx, index); + } +}; + +/** + * @class TypeInfo<double> + * @brief Default implementation for double. + * + * Provides: get, is, optional, push, require. + */ +template <> +class TypeInfo<double> { +public: + /** + * Get a double, return 0 if not a double. + * + * @param ctx the context + * @param index the index + * @return the double + */ + static inline double get(Context &ctx, int index) + { + return duk_get_number(ctx, index); + } + + /** + * Check if value is a double. + * + * @param ctx the context + * @param index the index + * @return true if double + */ + static inline bool is(Context &ctx, int index) + { + return duk_is_number(ctx, index); + } + + /** + * Get a double, return defaultValue if the value is not a double. + * + * @param ctx the context + * @param index the index + * @param defaultValue the defaultValue + * @return the double or defaultValue + */ + static inline double optional(Context &ctx, int index, double defaultValue) + { + if (!duk_is_number(ctx, index)) { + return defaultValue; + } + + return duk_get_number(ctx, index); + } + + /** + * Push a double. + * + * @param ctx the context + * @param value the value + */ + static inline void push(Context &ctx, double value) + { + duk_push_number(ctx, value); + } + + /** + * Require a double, throws a JavaScript exception if not a double. + * + * @param ctx the context + * @param index the index + * @return the double + */ + static inline double require(Context &ctx, int index) + { + return duk_require_number(ctx, index); + } +}; + +/** + * @class TypeInfo<std::string> + * @brief Default implementation for std::string. + * + * Provides: get, is, optional, push, require. + * + * Note: the functions allows embedded '\0'. + */ +template <> +class TypeInfo<std::string> { +public: + /** + * Get a string, return 0 if not a string. + * + * @param ctx the context + * @param index the index + * @return the string + */ + static inline std::string get(Context &ctx, int index) + { + duk_size_t size; + const char *text = duk_get_lstring(ctx, index, &size); + + return std::string{text, size}; + } + + /** + * Check if value is a string. + * + * @param ctx the context + * @param index the index + * @return true if string + */ + static inline bool is(Context &ctx, int index) + { + return duk_is_string(ctx, index); + } + + /** + * Get a string, return defaultValue if the value is not an string. + * + * @param ctx the context + * @param index the index + * @param defaultValue the defaultValue + * @return the string or defaultValue + */ + static inline std::string optional(Context &ctx, int index, std::string defaultValue) + { + if (!duk_is_string(ctx, index)) { + return defaultValue; + } + + return get(ctx, index); + } + + /** + * Push a string. + * + * @param ctx the context + * @param value the value + */ + static inline void push(Context &ctx, const std::string &value) + { + duk_push_lstring(ctx, value.c_str(), value.length()); + } + + /** + * Require a string, throws a JavaScript exception if not a string. + * + * @param ctx the context + * @param index the index + * @return the string + */ + static inline std::string require(Context &ctx, int index) + { + duk_size_t size; + const char *text = duk_require_lstring(ctx, index, &size); + + return std::string{text, size}; + } +}; + +/** + * @class TypeInfo<const char *> + * @brief Default implementation for const char literals. + * + * Provides: get, is, optional, push, require. + */ +template <> +class TypeInfo<const char *> { +public: + /** + * Get a string, return 0 if not a string. + * + * @param ctx the context + * @param index the index + * @return the string + */ + static inline const char *get(Context &ctx, int index) + { + return duk_get_string(ctx, index); + } + + /** + * Check if value is a string. + * + * @param ctx the context + * @param index the index + * @return true if string + */ + static inline bool is(Context &ctx, int index) + { + return duk_is_string(ctx, index); + } + + /** + * Get an integer, return defaultValue if the value is not an integer. + * + * @param ctx the context + * @param index the index + * @param defaultValue the defaultValue + * @return the integer or defaultValue + */ + static inline const char *optional(Context &ctx, int index, const char *defaultValue) + { + if (!duk_is_string(ctx, index)) { + return defaultValue; + } + + return duk_get_string(ctx, index); + } + + /** + * Push a string. + * + * @param ctx the context + * @param value the value + */ + static inline void push(Context &ctx, const char *value) + { + duk_push_string(ctx, value); + } + + /** + * Require a string, throws a JavaScript exception if not a string. + * + * @param ctx the context + * @param index the index + * @return the string + */ + static inline const char *require(Context &ctx, int index) + { + return duk_require_string(ctx, index); + } +}; + +/** + * @brief Implementation for non-managed pointers. + * + * Provides: get, is, optional, push, require. + */ +template <typename T> +class TypeInfo<RawPointer<T>> { +public: + /** + * Get a pointer, return nullptr if not a pointer. + * + * @param ctx the context + * @param index the index + * @return the pointer + */ + static inline T *get(Context &ctx, int index) + { + return static_cast<T *>(duk_to_pointer(ctx, index)); + } + + /** + * Check if value is a pointer. + * + * @param ctx the context + * @param index the index + * @return true if pointer + */ + static inline bool is(Context &ctx, int index) + { + return duk_is_pointer(ctx, index); + } + + /** + * Get a pointer, return defaultValue if the value is not a pointer. + * + * @param ctx the context + * @param index the index + * @param defaultValue the defaultValue + * @return the pointer or defaultValue + */ + static inline T *optional(Context &ctx, int index, RawPointer<T> defaultValue) + { + if (!duk_is_pointer(ctx, index)) { + return defaultValue.object; + } + + return static_cast<T *>(duk_to_pointer(ctx, index)); + } + + /** + * Push a pointer. + * + * @param ctx the context + * @param value the value + */ + static inline void push(Context &ctx, const RawPointer<T> &value) + { + duk_push_pointer(ctx, value.object); + } + + /** + * Require a pointer, throws a JavaScript exception if not a pointer. + * + * @param ctx the context + * @param index the index + * @return the pointer + */ + static inline T *require(Context &ctx, int index) + { + return static_cast<T *>(duk_require_pointer(ctx, index)); + } +}; + +/** + * @class TypeInfo<Function> + * @brief Push C++ function to the stack. + * + * Provides: push. + * + * This implementation push a Duktape/C function that is wrapped as C++ for convenience. + */ +template <> +class TypeInfo<Function> { +public: + /** + * Push the C++ function, it is wrapped as Duktape/C function and allocated on the heap by moving the + * std::function. + * + * @param ctx the context + * @param fn the function + */ + static void push(Context &ctx, Function fn); +}; + +/** + * @class TypeInfo<FunctionMap> + * @brief Put the functions to the object at the top of the stack. + * + * Provides: push. + */ +template <> +class TypeInfo<FunctionMap> { +public: + /** + * Push a map of function to the object at the top of the stack. + * + * @param ctx the context + * @param map the map of function + */ + static inline void push(Context &ctx, const FunctionMap &map) + { + for (const auto &entry : map) { + ctx.putProperty(-1, entry.first, entry.second); + } + } +}; + +/** + * @class TypeInfo<Object> + * @brief Push empty object to the stack. + * + * Provides: is, push. + */ +template <> +class TypeInfo<Object> { +public: + /** + * Check if value is an object. + * + * @param ctx the context + * @param index the index + * @return true if object + */ + static inline bool is(Context &ctx, int index) + { + return duk_is_object(ctx, index); + } + + /** + * Create an empty object on the stack. + * + * @param ctx the context + */ + static inline void push(Context &ctx, const Object &) + { + duk_push_object(ctx); + } +}; + +/** + * @class TypeInfo<Array> + * @brief Push empty array to the stack. + * + * Provides: is, push. + */ +template <> +class TypeInfo<Array> { +public: + /** + * Check if value is a array. + * + * @param ctx the context + * @param index the index + * @return true if array + */ + static inline bool is(Context &ctx, int index) + { + return duk_is_array(ctx, index); + } + + /** + * Create an empty array on the stack. + * + * @param ctx the context + */ + static inline void push(Context &ctx, const Array &) + { + duk_push_array(ctx); + } +}; + +/** + * @class TypeInfo<Undefined> + * @brief Push undefined value to the stack. + * + * Provides: is, push. + */ +template <> +class TypeInfo<Undefined> { +public: + /** + * Check if value is undefined. + * + * @param ctx the context + * @param index the index + * @return true if undefined + */ + static inline bool is(Context &ctx, int index) + { + return duk_is_undefined(ctx, index); + } + + /** + * Push undefined value on the stack. + * + * @param ctx the context + */ + static inline void push(Context &ctx, const Undefined &) + { + duk_push_undefined(ctx); + } +}; + +/** + * @class TypeInfo<Null> + * @brief Push null value to the stack. + * + * Provides: is, push. + */ +template <> +class TypeInfo<Null> { +public: + /** + * Check if value is null. + * + * @param ctx the context + * @param index the index + * @return true if null + */ + static inline bool is(Context &ctx, int index) + { + return duk_is_null(ctx, index); + } + + /** + * Push null value on the stack. + * + * @param ctx the context + */ + static inline void push(Context &ctx, const Null &) + { + duk_push_null(ctx); + } +}; + +/** + * @brief Push this binding into the stack. + * + * Provides: push. + */ +template <> +class TypeInfo<This> { +public: + /** + * Push this function into the stack. + * + * @param ctx the context + */ + static inline void push(Context &ctx, const This &) + { + duk_push_this(ctx); + } +}; + +/** + * @class TypeInfo<Global> + * @brief Push the global object to the stack. + * + * Provides: push. + */ +template <> +class TypeInfo<Global> { +public: + /** + * Push the global object into the stack. + * + * @param ctx the context + */ + static inline void push(Context &ctx, const Global &) + { + duk_push_global_object(ctx); + } +}; + +/** + * @brief Push a map of key-value pair as objects. + * + * Provides: push. + * + * This class is convenient for settings constants such as enums, string and such. + */ +template <typename T> +class TypeInfo<std::unordered_map<std::string, T>> { +public: + /** + * Put all values from the map as properties to the object at the top of the stack. + * + * @param ctx the context + * @param map the values + * @note You need an object at the top of the stack before calling this function + */ + static void push(Context &ctx, const std::unordered_map<std::string, T> &map) + { + for (const auto &pair : map) { + TypeInfo<T>::push(ctx, pair.second); + duk_put_prop_string(ctx, -2, pair.first.c_str()); + } + } +}; + +/** + * @brief Push or get vectors as JavaScript arrays. + * + * Provides: get, push. + */ +template <typename T> +class TypeInfo<std::vector<T>> { +public: + /** + * Get an array from the stack. + * + * @param ctx the context + * @param index the array index + * @return the array or empty array if the value is not an array + */ + static std::vector<T> get(Context &ctx, int index) + { + std::vector<T> result; + + if (!duk_is_array(ctx, -1)) { + return result; + } + + int total = duk_get_length(ctx, index); + for (int i = 0; i < total; ++i) { + result.push_back(ctx.getProperty<T>(index, i)); + } + + return result; + } + + /** + * Create an array with the specified values. + * + * @param ctx the context + * @param array the values + */ + static void push(Context &ctx, const std::vector<T> &array) + { + duk_push_array(ctx); + + int i = 0; + for (const auto &v : array) { + TypeInfo<T>::push(ctx, v); + duk_put_prop_index(ctx, -2, i++); + } + } +}; + +/** + * @brief Implementation of managed shared_ptr + * @see Shared + */ +template <typename T> +class TypeInfo<Shared<T>> { +private: + static void apply(Context &ctx, std::shared_ptr<T> value) + { + duk_push_boolean(ctx, false); + duk_put_prop_string(ctx, -2, "\xff""\xff""js-deleted"); + duk_push_pointer(ctx, new std::shared_ptr<T>(value)); + duk_put_prop_string(ctx, -2, "\xff""\xff""js-shared-ptr"); + duk_push_c_function(ctx, [] (duk_context *ctx) -> duk_ret_t { + duk_get_prop_string(ctx, 0, "\xff""\xff""js-deleted"); + + if (!duk_to_boolean(ctx, -1)) { + duk_push_boolean(ctx, true); + duk_put_prop_string(ctx, 0, "\xff""\xff""js-deleted"); + duk_get_prop_string(ctx, 0, "\xff""\xff""js-shared-ptr"); + delete static_cast<std::shared_ptr<T> *>(duk_to_pointer(ctx, -1)); + duk_pop(ctx); + } + + duk_pop(ctx); + + return 0; + }, 1); + duk_set_finalizer(ctx, -2); + } + +public: + /** + * Construct the shared_ptr as this. + * + * @param ctx the context + * @param value the value + */ + static void construct(Context &ctx, Shared<T> value) + { + duk_push_this(ctx); + apply(ctx, std::move(value.object)); + duk_pop(ctx); + } + + /** + * Push a managed shared_ptr as object. + * + * @param ctx the context + * @param value the value + */ + static void push(Context &ctx, Shared<T> value) + { + duk_push_object(ctx); + apply(ctx, value.object); + value.object->prototype(ctx); + duk_set_prototype(ctx, -2); + } + + /** + * Get a managed shared_ptr from the stack. + * + * @param ctx the context + * @param index the object index + * @return the shared_ptr + */ + static std::shared_ptr<T> get(Context &ctx, int index) + { + duk_get_prop_string(ctx, index, "\xff""\xff""js-shared-ptr"); + std::shared_ptr<T> value = *static_cast<std::shared_ptr<T> *>(duk_to_pointer(ctx, -1)); + duk_pop(ctx); + + return value; + } +}; + +/** + * @brief Implementation of managed pointers + * @see Pointer + */ +template <typename T> +class TypeInfo<Pointer<T>> { +private: + static void apply(Context &ctx, T *value) + { + duk_push_boolean(ctx, false); + duk_put_prop_string(ctx, -2, "\xff""\xff""js-deleted"); + duk_push_pointer(ctx, value); + duk_put_prop_string(ctx, -2, "\xff""\xff""js-ptr"); + duk_push_c_function(ctx, [] (duk_context *ctx) -> duk_ret_t { + duk_get_prop_string(ctx, 0, "\xff""\xff""js-deleted"); + + if (!duk_to_boolean(ctx, -1)) { + duk_push_boolean(ctx, true); + duk_put_prop_string(ctx, 0, "\xff""\xff""js-deleted"); + duk_get_prop_string(ctx, 0, "\xff""\xff""js-ptr"); + delete static_cast<T *>(duk_to_pointer(ctx, -1)); + duk_pop(ctx); + } + + duk_pop(ctx); + + return 0; + }, 1); + duk_set_finalizer(ctx, -2); + } + +public: + /** + * Construct the pointer as this. + * + * @param ctx the context + * @param value the value + */ + static void construct(Context &ctx, Pointer<T> value) + { + duk_push_this(ctx); + apply(ctx, value.object); + duk_pop(ctx); + } + + /** + * Push a managed pointer as object. + * + * @param ctx the context + * @param value the value + */ + static void push(Context &ctx, Pointer<T> value) + { + duk_push_object(ctx); + apply(ctx, value.object); + value.object->prototype(ctx); + duk_set_prototype(ctx, -2); + } + + /** + * Get a managed pointer from the stack. + * + * @param ctx the context + * @param index the object index + * @return the pointer + * @warning Do not store the pointer into the C++ side, the object can be deleted at any time + */ + static T *get(Context &ctx, int index) + { + duk_get_prop_string(ctx, index, "\xff""\xff""js-ptr"); + T *value = static_cast<T *>(duk_to_pointer(ctx, -1)); + duk_pop(ctx); + + return value; + } +}; + +} // !js + +#endif // !_JS_H_
--- a/C++/modules/Json/Json.cpp Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,372 +0,0 @@ -/* - * Json.cpp -- C++14 JSON manipulation using jansson parser - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - - -#include <jansson.h> - -#include <sstream> - -#include "Json.h" - -namespace json { - -namespace { - -void readObject(Value &parent, json_t *object); -void readArray(Value &parent, json_t *array); - -Value readValue(json_t *v) -{ - if (json_is_null(v)) { - return Value{nullptr}; - } - if (json_is_string(v)) { - return Value{json_string_value(v)}; - } - if (json_is_real(v)) { - return Value{json_number_value(v)}; - } - if (json_is_integer(v)) { - return Value{static_cast<int>(json_integer_value(v))}; - } - if (json_is_boolean(v)) { - return Value{json_boolean_value(v)}; - } - if (json_is_object(v)) { - Value object{Type::Object}; - - readObject(object, v); - - return object; - } - if (json_is_array(v)) { - Value array{Type::Array}; - - readArray(array, v); - - return array; - } - - return Value{}; -} - -void readObject(Value &parent, json_t *object) -{ - const char *key; - json_t *value; - - json_object_foreach(object, key, value) { - parent.insert(key, readValue(value)); - } -} - -void readArray(Value &parent, json_t *array) -{ - size_t index; - json_t *value; - - json_array_foreach(array, index, value) { - parent.append(readValue(value)); - } -} - -template <typename Func, typename... Args> -Value convert(Func fn, Args&&... args) -{ - json_error_t error; - json_t *json = fn(std::forward<Args>(args)..., &error); - - if (json == nullptr) { - throw Error{error.text, error.source, error.line, error.column, error.position}; - } - - Value value; - - if (json_is_object(json)) { - value = Value{Type::Object}; - readObject(value, json); - } else { - value = Value{Type::Array}; - readArray(value, json); - } - - json_decref(json); - - return value; -} - -std::string indent(int param, int level) -{ - std::string str; - - if (param < 0) { - str = std::string(level, '\t'); - } else if (param > 0) { - str = std::string(param * level, ' '); - } - - return str; -} - -} // !namespace - -void Value::copy(const Value &other) -{ - switch (other.m_type) { - case Type::Array: - new (&m_array) std::deque<Value>(other.m_array); - break; - case Type::Boolean: - m_boolean = other.m_boolean; - break; - case Type::Int: - m_integer = other.m_integer; - break; - case Type::Object: - new (&m_object) std::map<std::string, Value>(other.m_object); - break; - case Type::Real: - m_number = other.m_number; - break; - case Type::String: - new (&m_string) std::string(other.m_string); - break; - default: - break; - } - - m_type = other.m_type; -} - -void Value::move(Value &&other) -{ - switch (other.m_type) { - case Type::Array: - new (&m_array) std::deque<Value>(std::move(other.m_array)); - break; - case Type::Boolean: - m_boolean = other.m_boolean; - break; - case Type::Int: - m_integer = other.m_integer; - break; - case Type::Object: - new (&m_object) std::map<std::string, Value>(std::move(other.m_object)); - break; - case Type::Real: - m_number = other.m_number; - break; - case Type::String: - new (&m_string) std::string(std::move(other.m_string)); - break; - default: - break; - } - - m_type = other.m_type; -} - -Value::Value(Type type) - : m_type{type} -{ - switch (m_type) { - case Type::Array: - new (&m_array) std::deque<Value>(); - break; - case Type::Boolean: - m_boolean = false; - break; - case Type::Int: - m_integer = 0; - break; - case Type::Object: - new (&m_object) std::map<std::string, Value>(); - break; - case Type::Real: - m_number = 0; - break; - case Type::String: - new (&m_string) std::string(); - break; - default: - break; - } -} - -Value::~Value() -{ - switch (m_type) { - case Type::Array: - m_array.~deque<Value>(); - break; - case Type::Object: - m_object.~map<std::string, Value>(); - break; - case Type::String: - m_string.~basic_string(); - break; - default: - break; - } -} - -bool Value::toBool() const noexcept -{ - if (m_type != Type::Boolean) { - return false; - } - - return m_boolean; -} - -double Value::toReal() const noexcept -{ - if (m_type != Type::Real) { - return 0; - } - - return m_number; -} - -int Value::toInt() const noexcept -{ - if (m_type != Type::Int) { - return 0; - } - - return m_integer; -} - -std::string Value::toString() const noexcept -{ - if (m_type != Type::String) { - return ""; - } - - return m_string; -} - -Value::Value(const Buffer &buffer) -{ - *this = convert(json_loads, buffer.text.c_str(), 0); -} - -Value::Value(const File &file) -{ - *this = convert(json_load_file, file.path.c_str(), 0); -} - -std::string Value::toJson(int level, int current) const -{ - std::ostringstream oss; - - switch (m_type) { - case Type::Array: { - oss << '[' << (level != 0 ? "\n" : ""); - - unsigned total = m_array.size(); - unsigned i = 0; - for (const auto &v : m_array) { - oss << indent(level, current + 1) << v.toJson(level, current + 1); - oss << (++i < total ? "," : ""); - oss << (level != 0 ? "\n" : ""); - } - - oss << (level != 0 ? indent(level, current) : "") << ']'; - break; - } - case Type::Boolean: - oss << (m_boolean ? true : false); - break; - case Type::Int: - oss << m_integer; - break; - case Type::Object: { - oss << '{' << (level != 0 ? "\n" : ""); - - unsigned total = m_object.size(); - unsigned i = 0; - for (const auto &pair : m_object) { - oss << indent(level, current + 1); - - /* Key and : */ - oss << "\"" << pair.first << "\":" << (level != 0 ? " " : ""); - - /* Value */ - oss << pair.second.toJson(level, current + 1); - - /* Comma, new line if needed */ - oss << (++i < total ? "," : "") << (level != 0 ? "\n" : ""); - } - - oss << (level != 0 ? indent(level, current) : "") << '}'; - break; - } - case Type::Real: - oss << m_number; - break; - case Type::String: - oss << "\"" << escape(m_string) << "\""; - break; - default: - break; - } - - return oss.str(); -} - -std::string escape(const std::string &value) -{ - std::string result; - - for (auto it = value.begin(); it != value.end(); ++it) { - switch (*it) { - case '\\': - result += "\\\\"; - break; - case '/': - result += "\\/"; - break; - case '"': - result += "\\\""; - break; - case '\b': - result += "\\b"; - break; - case '\f': - result += "\\f"; - break; - case '\n': - result += "\\n"; - break; - case '\r': - result += "\\r"; - break; - case '\t': - result += "\\t"; - break; - default: - result += *it; - break; - } - } - - return result; -} - -} // !json
--- a/C++/modules/Json/Json.h Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1161 +0,0 @@ -/* - * Json.h -- C++14 JSON manipulation using jansson parser - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef _JSON_H_ -#define _JSON_H_ - -/** - * @file Json.h - * @brief Jansson C++14 wrapper - * - * These classes can be used to build or parse JSON documents using jansson library. It is designed to be safe - * and explicit. It does not implement implicit sharing like jansson so when you access (e.g. Value::toObject) values - * you get real copies, thus when you read big documents it can has a performance cost. - */ - -#include <cassert> -#include <exception> -#include <initializer_list> -#include <map> -#include <string> -#include <utility> -#include <deque> - -/** - * Json namespace. - */ -namespace json { - -/** - * @enum Type - * @brief Type of Value. - */ -enum class Type { - Array, //!< Value is an array [] - Boolean, //!< Value is boolean - Int, //!< Value is integer - Real, //!< Value is float - Object, //!< Value is object {} - String, //!< Value is unicode string - Null //!< Value is defined to null -}; - -/** - * @class Error - * @brief Error description. - */ -class Error : public std::exception { -private: - std::string m_text; - std::string m_source; - int m_line; - int m_column; - int m_position; - -public: - /** - * Create the error. - * - * @param text the text message - * @param source the source (e.g. file name) - * @param line the line number - * @param column the column number - * @param position the position - */ - inline Error(std::string text, std::string source, int line, int column, int position) noexcept - : m_text{std::move(text)} - , m_source{std::move(source)} - , m_line{line} - , m_column{column} - , m_position{position} - { - } - - /** - * Get the error message. - * - * @return the text - */ - inline const std::string &text() const noexcept - { - return m_text; - } - - /** - * Get the source (e.g. a file name). - * - * @return the source - */ - inline const std::string &source() const noexcept - { - return m_source; - } - - /** - * Get the line. - * - * @return the line - */ - inline int line() const noexcept - { - return m_line; - } - - /** - * Get the column. - * - * @return the column - */ - inline int column() const noexcept - { - return m_column; - } - - /** - * Get the position. - * - * @return the position - */ - inline int position() const noexcept - { - return m_position; - } - - /** - * Get the error message. - * - * @return the message - */ - const char *what() const noexcept override - { - return m_text.c_str(); - } -}; - -/** - * @class Buffer - * @brief Open JSON document from text. - */ -class Buffer { -public: - std::string text; //!< The JSON text -}; - -/** - * @class File - * @brief Open JSON document from a file. - */ -class File { -public: - std::string path; //!< The path to the file -}; - -/** - * @class Value - * @brief Generic JSON value wrapper. - */ -class Value { -private: - Type m_type{Type::Null}; - - union { - double m_number; - bool m_boolean; - int m_integer; - std::string m_string; - std::deque<Value> m_array; - std::map<std::string, Value> m_object; - }; - - void copy(const Value &); - void move(Value &&); - std::string toJson(int indent, int current) const; - - /** - * @class BaseIterator - * @brief This is the base class for iterator and const_iterator - * - * This iterator works for both arrays and objects. Because of that purpose, it is only available - * as forward iterator. - * - * When iterator comes from an object, you can use key() otherwise you can use index(). - */ - template <typename ValueType, typename ArrayIteratorType, typename ObjectIteratorType> - class BaseIterator : public std::iterator<std::forward_iterator_tag, ValueType> { - private: - friend class Value; - - ValueType &m_value; - ArrayIteratorType m_ita; - ObjectIteratorType m_itm; - - inline void increment() - { - if (m_value.isObject()) { - m_itm++; - } else { - m_ita++; - } - } - - BaseIterator(ValueType &value, ObjectIteratorType it) - : m_value(value) - , m_itm(it) - { - } - - BaseIterator(ValueType &value, ArrayIteratorType it) - : m_value(value) - , m_ita(it) - { - } - - public: - /** - * Get the iterator key (for objects). - * - * @pre iterator must be dereferenceable - * @pre iterator must come from object - * @return the key - */ - inline const std::string &key() const noexcept - { - assert(m_value.isObject()); - assert(m_itm != m_value.m_object.end()); - - return m_itm->first; - } - - /** - * Get the iterator position (for arrays). - * - * @pre iterator must be dereferenceable - * @pre iterator must come from arrays - * @return the index - */ - inline unsigned index() const noexcept - { - assert(m_value.isArray()); - assert(m_ita != m_value.m_array.end()); - - return std::distance(m_value.m_array.begin(), m_ita); - } - - /** - * Dereference the iterator. - * - * @pre iterator be dereferenceable - * @return the value - */ - inline ValueType &operator*() noexcept - { - assert((m_value.isArray() && m_ita != m_value.m_array.end()) || - (m_value.isObject() && m_itm != m_value.m_object.end())); - - return (m_value.m_type == Type::Object) ? m_itm->second : *m_ita; - } - - /** - * Dereference the iterator as a pointer. - * - * @pre iterator must be dereferenceable - * @return the value - */ - inline ValueType *operator->() noexcept - { - assert((m_value.isArray() && m_ita != m_value.m_array.end()) || - (m_value.isObject() && m_itm != m_value.m_object.end())); - - return (m_value.m_type == Type::Object) ? &m_itm->second : &(*m_ita); - } - - /** - * Increment the iterator. (Prefix version). - * - * @pre iterator must be dereferenceable - * @return *this; - */ - inline BaseIterator &operator++() noexcept - { - assert((m_value.isArray() && m_ita != m_value.m_array.end()) || - (m_value.isObject() && m_itm != m_value.m_object.end())); - - increment(); - - return *this; - } - - /** - * Increment the iterator. (Postfix version). - * - * @pre iterator must be dereferenceable - * @return *this; - */ - inline BaseIterator &operator++(int) noexcept - { - assert((m_value.isArray() && m_ita != m_value.m_array.end()) || - (m_value.isObject() && m_itm != m_value.m_object.end())); - - increment(); - - return *this; - } - - /** - * Compare two iterators. - * - * @param it1 the first iterator - * @param it2 the second iterator - * @return true if they are same - */ - bool operator==(const BaseIterator &it) const noexcept - { - if (m_value.isObject() && it.m_value.isObject()) - return m_itm == it.m_itm; - if (m_value.isArray() && it.m_value.isArray()) - return m_ita == it.m_ita; - - return false; - } - - /** - * Test if the iterator is different. - * - * @param it the iterator - * @return true if they are different - */ - inline bool operator!=(const BaseIterator &it) const noexcept - { - return !(*this == it); - } - }; - -public: - /** - * Forward iterator. - */ - using iterator = BaseIterator<Value, typename std::deque<Value>::iterator, typename std::map<std::string, Value>::iterator>; - - /** - * Const forward iterator. - */ - using const_iterator = BaseIterator<const Value, typename std::deque<Value>::const_iterator, typename std::map<std::string, Value>::const_iterator>; - - /** - * Construct a null value. - */ - inline Value() - { - } - - /** - * Create a value with a specified type, this is usually only needed when you want to create an object or - * an array. - * - * For any other types, initialize with sane default value. - * - * @param type the type - */ - Value(Type type); - - /** - * Construct a null value. - */ - inline Value(std::nullptr_t) noexcept - : m_type{Type::Null} - { - } - - /** - * Construct a boolean value. - * - * @param value the boolean value - */ - inline Value(bool value) noexcept - : m_type{Type::Boolean} - , m_boolean{value} - { - } - - /** - * Create value from integer. - * - * @param value the value - */ - inline Value(int value) noexcept - : m_type{Type::Int} - , m_integer{value} - { - } - - /** - * Construct a value from a C-string. - * - * @param value the C-string - */ - inline Value(const char *value) - : m_type{Type::String} - { - new (&m_string) std::string{value ? value : ""}; - } - - /** - * Construct a number value. - * - * @param value the real value - */ - inline Value(double value) noexcept - : m_type{Type::Real} - , m_number{value} - { - } - - /** - * Construct a string value. - * - * @param value the string - */ - inline Value(std::string value) noexcept - : m_type{Type::String} - { - new (&m_string) std::string{std::move(value)}; - } - - /** - * Create an object from a map. - * - * @param values the values - * @see fromObject - */ - inline Value(std::map<std::string, Value> values) - : Value{Type::Object} - { - for (const auto &pair : values) { - insert(pair.first, pair.second); - } - } - - /** - * Create an array from a deque. - * - * @param values the values - * @see fromArray - */ - inline Value(std::deque<Value> values) - : Value{Type::Array} - { - for (Value value : values) { - append(std::move(value)); - } - } - - /** - * Construct a value from a buffer. - * - * @param buffer the text - * @throw Error on errors - */ - Value(const Buffer &buffer); - - /** - * Construct a value from a file. - * - * @param file the file - * @throw Error on errors - */ - Value(const File &file); - - /** - * Move constructor. - * - * @param other the value to move from - */ - inline Value(Value &&other) - { - move(std::move(other)); - } - - /** - * Copy constructor. - * - * @param other the value to copy from - */ - inline Value(const Value &other) - { - copy(other); - } - - /** - * Copy operator. - * - * @param other the value to copy from - * @return *this - */ - inline Value &operator=(const Value &other) - { - copy(other); - - return *this; - } - - /** - * Move operator. - * - * @param other the value to move from - */ - inline Value &operator=(Value &&other) - { - move(std::move(other)); - - return *this; - } - - /** - * Destructor. - */ - ~Value(); - - /** - * Get an iterator to the beginning. - * - * @pre must be an array or object - * @return the iterator - */ - inline iterator begin() noexcept - { - assert(isArray() || isObject()); - - return m_type == Type::Object ? iterator(*this, m_object.begin()) : iterator(*this, m_array.begin()); - } - - /** - * Overloaded function. - * - * @pre must be an array or object - * @return the iterator - */ - inline const_iterator begin() const noexcept - { - assert(isArray() || isObject()); - - return m_type == Type::Object ? const_iterator(*this, m_object.begin()) : const_iterator(*this, m_array.begin()); - } - - /** - * Overloaded function. - * - * @pre must be an array or object - * @return the iterator - */ - inline const_iterator cbegin() const noexcept - { - assert(isArray() || isObject()); - - return m_type == Type::Object ? const_iterator(*this, m_object.cbegin()) : const_iterator(*this, m_array.cbegin()); - } - - /** - * Get an iterator to the end. - * - * @pre must be an array or object - * @return the iterator - */ - inline iterator end() noexcept - { - assert(isArray() || isObject()); - - return m_type == Type::Object ? iterator(*this, m_object.end()) : iterator(*this, m_array.end()); - } - - /** - * Get an iterator to the end. - * - * @pre must be an array or object - * @return the iterator - */ - inline const_iterator end() const noexcept - { - assert(isArray() || isObject()); - - return m_type == Type::Object ? const_iterator(*this, m_object.end()) : const_iterator(*this, m_array.end()); - } - - /** - * Get an iterator to the end. - * - * @pre must be an array or object - * @return the iterator - */ - inline const_iterator cend() const noexcept - { - assert(isArray() || isObject()); - - return m_type == Type::Object ? const_iterator(*this, m_object.cend()) : const_iterator(*this, m_array.cend()); - } - - /** - * Get the value type. - * - * @return the type - */ - inline Type typeOf() const noexcept - { - return m_type; - } - - /** - * Get the value as boolean. - * - * @return the value or false if not a boolean - */ - bool toBool() const noexcept; - - /** - * Get the value as integer. - * - * @return the value or 0 if not a integer - */ - int toInt() const noexcept; - - /** - * Get the value as real. - * - * @return the value or 0 if not a real - */ - double toReal() const noexcept; - - /** - * Get the value as string. - * - * @return the value or empty stirng if not a string - */ - std::string toString() const noexcept; - - /** - * Check if the value is boolean type. - * - * @return true if boolean - */ - inline bool isBool() const noexcept - { - return m_type == Type::Boolean; - } - - /** - * Check if the value is integer type. - * - * @return true if integer - */ - inline bool isInt() const noexcept - { - return m_type == Type::Int; - } - - /** - * Check if the value is object type. - * - * @return true if object - */ - inline bool isObject() const noexcept - { - return m_type == Type::Object; - } - - /** - * Check if the value is array type. - * - * @return true if array - */ - inline bool isArray() const noexcept - { - return m_type == Type::Array; - } - - /** - * Check if the value is integer or real type. - * - * @return true if integer or real - * @see toInt - * @see toReal - */ - inline bool isNumber() const noexcept - { - return m_type == Type::Real || m_type == Type::Int; - } - - /** - * Check if the value is real type. - * - * @return true if real - */ - inline bool isReal() const noexcept - { - return m_type == Type::Real; - } - - /** - * Check if the value is null type. - * - * @return true if null - */ - inline bool isNull() const noexcept - { - return m_type == Type::Null; - } - - /** - * Check if the value is string type. - * - * @return true if string - */ - inline bool isString() const noexcept - { - return m_type == Type::String; - } - - /** - * Get the array or object size. - * - * @pre must be an array or object - * @return the size - */ - inline unsigned size() const noexcept - { - assert(isArray() || isObject()); - - if (m_type == Type::Object) { - return m_object.size(); - } - - return m_array.size(); - } - - /** - * Remove all the values. - * - * @pre must be an array or an object - */ - inline void clear() noexcept - { - assert(isArray() || isObject()); - - if (m_type == Type::Array) { - m_array.clear(); - } else { - m_object.clear(); - } - } - - /* - * Array functions - * ---------------------------------------------------------- - */ - - /** - * Get the value at the specified position or the defaultValue if position is out of bounds. - * - * @param position the position - * @param defaultValue the value replacement - * @return the value or defaultValue - */ - template <typename DefaultValue> - inline Value valueOr(unsigned position, DefaultValue &&defaultValue) const - { - if (m_type != Type::Array || position >= m_array.size()) { - return defaultValue; - } - - return m_array[position]; - } - - /** - * Get a value at the specified index. - * - * @pre must be an array - * @param position the position - * @return the value - * @throw std::out_of_range if out of bounds - */ - inline const Value &at(unsigned position) const - { - assert(isArray()); - - return m_array.at(position); - } - - /** - * Overloaded function. - * - * @pre must be an array - * @param position the position - * @return the value - * @throw std::out_of_range if out of bounds - */ - inline Value &at(unsigned position) - { - assert(isArray()); - - return m_array.at(position); - } - - /** - * Get a value at the specified index. - * - * @pre must be an array - * @pre position must be valid - * @param position the position - * @return the value - */ - inline const Value &operator[](unsigned position) const - { - assert(isArray()); - assert(position < m_array.size()); - - return m_array[position]; - } - - /** - * Overloaded function. - * - * @pre must be an array - * @pre position must be valid - * @param position the position - * @return the value - */ - inline Value &operator[](unsigned position) - { - assert(isArray()); - assert(position < m_array.size()); - - return m_array[position]; - } - - /** - * Push a value to the beginning of the array. - * - * @pre must be an array - * @param value the value to push - */ - inline void push(const Value &value) - { - assert(isArray()); - - m_array.push_front(value); - } - - /** - * Overloaded function. - * - * @pre must be an array - * @param value the value to push - */ - inline void push(Value &&value) - { - assert(isArray()); - - m_array.push_front(std::move(value)); - } - - /** - * Insert a value at the specified position. - * - * @pre must be an array - * @pre position must be valid - * @param position the position - * @param value the value to push - */ - inline void insert(unsigned position, const Value &value) - { - assert(isArray()); - assert(position <= m_array.size()); - - m_array.insert(m_array.begin() + position, value); - } - - /** - * Overloaded function. - * - * @pre must be an array - * @pre position must be valid - * @param position the position - * @param value the value to push - */ - inline void insert(unsigned position, Value &&value) - { - assert(isArray()); - assert(position <= m_array.size()); - - m_array.insert(m_array.begin() + position, std::move(value)); - } - - /** - * Add a new value to the end. - * - * @pre must be an array - * @param value the value to append - */ - inline void append(const Value &value) - { - assert(isArray()); - - m_array.push_back(value); - } - - /** - * Overloaded function. - * - * @pre must be an array - * @param value the value to append - */ - inline void append(Value &&value) - { - assert(isArray()); - - m_array.push_back(std::move(value)); - } - - /** - * Remove a value at the specified position. - * - * @pre must be an array - * @pre position must be valid - * @param position the position - */ - inline void erase(unsigned position) - { - assert(isArray()); - assert(position < m_array.size()); - - m_array.erase(m_array.begin() + position); - } - - /* - * Object functions - * ---------------------------------------------------------- - */ - - /** - * Get the value at the specified key or the defaultValue if key is absent. - * - * @param name the name - * @param defaultValue the value replacement - * @return the value or defaultValue - */ - template <typename DefaultValue> - Value valueOr(const std::string &name, DefaultValue &&defaultValue) const - { - if (m_type != Type::Object) { - return defaultValue; - } - - auto it = m_object.find(name); - - if (it == m_object.end()) { - return defaultValue; - } - - return it->second; - } - - /** - * Get a value from the object. - * - * @pre must be an object - * @param name the value key - * @return the value - * @throw std::out_of_range if not found - */ - inline const Value &at(const std::string &name) const - { - assert(isObject()); - - return m_object.at(name); - } - - /** - * Overloaded function. - * - * @pre must be an object - * @param name the value key - * @return the value - * @throw std::out_of_range if not found - */ - inline Value &at(const std::string &name) - { - assert(isObject()); - - return m_object.at(name); - } - - /** - * Get a value from the object. - * - * @pre must be an object - * @param name the value key - * @return the value - */ - inline Value &operator[](const std::string &name) - { - assert(isObject()); - - return m_object[name]; - } - - /** - * Get a value from the object. - * - * @pre must be an object - * @param name the value key - * @return the value - */ - inline const Value &operator[](const std::string &name) const - { - assert(isObject()); - - return m_object.at(name); - } - - /** - * Find a value by key. - * - * @pre must be an object - * @param key the property key - * @return the iterator or past the end if not found - */ - inline iterator find(const std::string &key) - { - assert(isObject()); - - return iterator(*this, m_object.find(key)); - } - - /** - * Overloaded function. - * - * @pre must be an object - * @param key the property key - * @return the iterator or past the end if not found - */ - inline const_iterator find(const std::string &key) const - { - assert(isObject()); - - return const_iterator(*this, m_object.find(key)); - } - - /** - * Insert a new value. - * - * @pre must be an object - * @param name the key - * @param value the value - */ - inline void insert(std::string name, const Value &value) - { - assert(isObject()); - - m_object.insert({std::move(name), value}); - } - - /** - * Overloaded function. - * - * @pre must be an object - * @param name the key - * @param value the value - */ - inline void insert(std::string name, Value &&value) - { - assert(isObject()); - - m_object.insert({std::move(name), std::move(value)}); - } - - /** - * Check if a value exists. - * - * @pre must be an object - * @param key the key value - * @return true if exists - */ - inline bool contains(const std::string &key) const noexcept - { - assert(isObject()); - - return m_object.find(key) != m_object.end(); - } - - /** - * Remove a value of the specified key. - * - * @pre must be an object - * @param key the value key - */ - inline void erase(const std::string &key) - { - assert(isObject()); - - m_object.erase(key); - } - - /** - * Return this value as JSon representation. - * - * @param indent, the indentation to use (0 == compact, < 0 == tabs, > 0 == number of spaces) - * @param tabs, use tabs or not - * @return the string - */ - inline std::string toJson(int indent = 2) const - { - return toJson(indent, 0); - } -}; - -/** - * Escape the input. - * - * @param input the input - * @return the escaped string - */ -std::string escape(const std::string &input); - -/** - * Convenient function for creating array from initializer list. - * - * @param values the values - * @return the array - */ -inline Value array(std::initializer_list<Value> values) -{ - return Value(std::deque<Value>(values.begin(), values.end())); -} - -/** - * Convenient function for creating object from initializer list. - * - * @param values the values - * @return the object - */ -inline Value object(std::initializer_list<std::pair<std::string, Value>> values) -{ - return Value(std::map<std::string, Value>(values.begin(), values.end())); -} - -} // !json - -#endif // !_JSON_H_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Json/json.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,371 @@ +/* + * json.cpp -- C++14 JSON manipulation using jansson parser + * + * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <jansson.h> + +#include <sstream> + +#include "json.h" + +namespace json { + +namespace { + +void readObject(Value &parent, json_t *object); +void readArray(Value &parent, json_t *array); + +Value readValue(json_t *v) +{ + if (json_is_null(v)) { + return Value{nullptr}; + } + if (json_is_string(v)) { + return Value{json_string_value(v)}; + } + if (json_is_real(v)) { + return Value{json_number_value(v)}; + } + if (json_is_integer(v)) { + return Value{static_cast<int>(json_integer_value(v))}; + } + if (json_is_boolean(v)) { + return Value{json_boolean_value(v)}; + } + if (json_is_object(v)) { + Value object{Type::Object}; + + readObject(object, v); + + return object; + } + if (json_is_array(v)) { + Value array{Type::Array}; + + readArray(array, v); + + return array; + } + + return Value{}; +} + +void readObject(Value &parent, json_t *object) +{ + const char *key; + json_t *value; + + json_object_foreach(object, key, value) { + parent.insert(key, readValue(value)); + } +} + +void readArray(Value &parent, json_t *array) +{ + size_t index; + json_t *value; + + json_array_foreach(array, index, value) { + parent.append(readValue(value)); + } +} + +template <typename Func, typename... Args> +Value convert(Func fn, Args&&... args) +{ + json_error_t error; + json_t *json = fn(std::forward<Args>(args)..., &error); + + if (json == nullptr) { + throw Error{error.text, error.source, error.line, error.column, error.position}; + } + + Value value; + + if (json_is_object(json)) { + value = Value{Type::Object}; + readObject(value, json); + } else { + value = Value{Type::Array}; + readArray(value, json); + } + + json_decref(json); + + return value; +} + +std::string indent(int param, int level) +{ + std::string str; + + if (param < 0) { + str = std::string(level, '\t'); + } else if (param > 0) { + str = std::string(param * level, ' '); + } + + return str; +} + +} // !namespace + +void Value::copy(const Value &other) +{ + switch (other.m_type) { + case Type::Array: + new (&m_array) std::deque<Value>(other.m_array); + break; + case Type::Boolean: + m_boolean = other.m_boolean; + break; + case Type::Int: + m_integer = other.m_integer; + break; + case Type::Object: + new (&m_object) std::map<std::string, Value>(other.m_object); + break; + case Type::Real: + m_number = other.m_number; + break; + case Type::String: + new (&m_string) std::string(other.m_string); + break; + default: + break; + } + + m_type = other.m_type; +} + +void Value::move(Value &&other) +{ + switch (other.m_type) { + case Type::Array: + new (&m_array) std::deque<Value>(std::move(other.m_array)); + break; + case Type::Boolean: + m_boolean = other.m_boolean; + break; + case Type::Int: + m_integer = other.m_integer; + break; + case Type::Object: + new (&m_object) std::map<std::string, Value>(std::move(other.m_object)); + break; + case Type::Real: + m_number = other.m_number; + break; + case Type::String: + new (&m_string) std::string(std::move(other.m_string)); + break; + default: + break; + } + + m_type = other.m_type; +} + +Value::Value(Type type) + : m_type{type} +{ + switch (m_type) { + case Type::Array: + new (&m_array) std::deque<Value>(); + break; + case Type::Boolean: + m_boolean = false; + break; + case Type::Int: + m_integer = 0; + break; + case Type::Object: + new (&m_object) std::map<std::string, Value>(); + break; + case Type::Real: + m_number = 0; + break; + case Type::String: + new (&m_string) std::string(); + break; + default: + break; + } +} + +Value::~Value() +{ + switch (m_type) { + case Type::Array: + m_array.~deque<Value>(); + break; + case Type::Object: + m_object.~map<std::string, Value>(); + break; + case Type::String: + m_string.~basic_string(); + break; + default: + break; + } +} + +bool Value::toBool() const noexcept +{ + if (m_type != Type::Boolean) { + return false; + } + + return m_boolean; +} + +double Value::toReal() const noexcept +{ + if (m_type != Type::Real) { + return 0; + } + + return m_number; +} + +int Value::toInt() const noexcept +{ + if (m_type != Type::Int) { + return 0; + } + + return m_integer; +} + +std::string Value::toString() const noexcept +{ + if (m_type != Type::String) { + return ""; + } + + return m_string; +} + +Value::Value(const Buffer &buffer) +{ + *this = convert(json_loads, buffer.text.c_str(), 0); +} + +Value::Value(const File &file) +{ + *this = convert(json_load_file, file.path.c_str(), 0); +} + +std::string Value::toJson(int level, int current) const +{ + std::ostringstream oss; + + switch (m_type) { + case Type::Array: { + oss << '[' << (level != 0 ? "\n" : ""); + + unsigned total = m_array.size(); + unsigned i = 0; + for (const auto &v : m_array) { + oss << indent(level, current + 1) << v.toJson(level, current + 1); + oss << (++i < total ? "," : ""); + oss << (level != 0 ? "\n" : ""); + } + + oss << (level != 0 ? indent(level, current) : "") << ']'; + break; + } + case Type::Boolean: + oss << (m_boolean ? true : false); + break; + case Type::Int: + oss << m_integer; + break; + case Type::Object: { + oss << '{' << (level != 0 ? "\n" : ""); + + unsigned total = m_object.size(); + unsigned i = 0; + for (const auto &pair : m_object) { + oss << indent(level, current + 1); + + /* Key and : */ + oss << "\"" << pair.first << "\":" << (level != 0 ? " " : ""); + + /* Value */ + oss << pair.second.toJson(level, current + 1); + + /* Comma, new line if needed */ + oss << (++i < total ? "," : "") << (level != 0 ? "\n" : ""); + } + + oss << (level != 0 ? indent(level, current) : "") << '}'; + break; + } + case Type::Real: + oss << m_number; + break; + case Type::String: + oss << "\"" << escape(m_string) << "\""; + break; + default: + break; + } + + return oss.str(); +} + +std::string escape(const std::string &value) +{ + std::string result; + + for (auto it = value.begin(); it != value.end(); ++it) { + switch (*it) { + case '\\': + result += "\\\\"; + break; + case '/': + result += "\\/"; + break; + case '"': + result += "\\\""; + break; + case '\b': + result += "\\b"; + break; + case '\f': + result += "\\f"; + break; + case '\n': + result += "\\n"; + break; + case '\r': + result += "\\r"; + break; + case '\t': + result += "\\t"; + break; + default: + result += *it; + break; + } + } + + return result; +} + +} // !json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Json/json.h Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,1161 @@ +/* + * json.h -- C++14 JSON manipulation using jansson parser + * + * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _JSON_H_ +#define _JSON_H_ + +/** + * @file json.h + * @brief Jansson C++14 wrapper + * + * These classes can be used to build or parse JSON documents using jansson library. It is designed to be safe + * and explicit. It does not implement implicit sharing like jansson so when you access (e.g. Value::toObject) values + * you get real copies, thus when you read big documents it can has a performance cost. + */ + +#include <cassert> +#include <exception> +#include <initializer_list> +#include <map> +#include <string> +#include <utility> +#include <deque> + +/** + * Json namespace. + */ +namespace json { + +/** + * @enum Type + * @brief Type of Value. + */ +enum class Type { + Array, //!< Value is an array [] + Boolean, //!< Value is boolean + Int, //!< Value is integer + Real, //!< Value is float + Object, //!< Value is object {} + String, //!< Value is unicode string + Null //!< Value is defined to null +}; + +/** + * @class Error + * @brief Error description. + */ +class Error : public std::exception { +private: + std::string m_text; + std::string m_source; + int m_line; + int m_column; + int m_position; + +public: + /** + * Create the error. + * + * @param text the text message + * @param source the source (e.g. file name) + * @param line the line number + * @param column the column number + * @param position the position + */ + inline Error(std::string text, std::string source, int line, int column, int position) noexcept + : m_text{std::move(text)} + , m_source{std::move(source)} + , m_line{line} + , m_column{column} + , m_position{position} + { + } + + /** + * Get the error message. + * + * @return the text + */ + inline const std::string &text() const noexcept + { + return m_text; + } + + /** + * Get the source (e.g. a file name). + * + * @return the source + */ + inline const std::string &source() const noexcept + { + return m_source; + } + + /** + * Get the line. + * + * @return the line + */ + inline int line() const noexcept + { + return m_line; + } + + /** + * Get the column. + * + * @return the column + */ + inline int column() const noexcept + { + return m_column; + } + + /** + * Get the position. + * + * @return the position + */ + inline int position() const noexcept + { + return m_position; + } + + /** + * Get the error message. + * + * @return the message + */ + const char *what() const noexcept override + { + return m_text.c_str(); + } +}; + +/** + * @class Buffer + * @brief Open JSON document from text. + */ +class Buffer { +public: + std::string text; //!< The JSON text +}; + +/** + * @class File + * @brief Open JSON document from a file. + */ +class File { +public: + std::string path; //!< The path to the file +}; + +/** + * @class Value + * @brief Generic JSON value wrapper. + */ +class Value { +private: + Type m_type{Type::Null}; + + union { + double m_number; + bool m_boolean; + int m_integer; + std::string m_string; + std::deque<Value> m_array; + std::map<std::string, Value> m_object; + }; + + void copy(const Value &); + void move(Value &&); + std::string toJson(int indent, int current) const; + + /** + * @class BaseIterator + * @brief This is the base class for iterator and const_iterator + * + * This iterator works for both arrays and objects. Because of that purpose, it is only available + * as forward iterator. + * + * When iterator comes from an object, you can use key() otherwise you can use index(). + */ + template <typename ValueType, typename ArrayIteratorType, typename ObjectIteratorType> + class BaseIterator : public std::iterator<std::forward_iterator_tag, ValueType> { + private: + friend class Value; + + ValueType &m_value; + ArrayIteratorType m_ita; + ObjectIteratorType m_itm; + + inline void increment() + { + if (m_value.isObject()) { + m_itm++; + } else { + m_ita++; + } + } + + BaseIterator(ValueType &value, ObjectIteratorType it) + : m_value(value) + , m_itm(it) + { + } + + BaseIterator(ValueType &value, ArrayIteratorType it) + : m_value(value) + , m_ita(it) + { + } + + public: + /** + * Get the iterator key (for objects). + * + * @pre iterator must be dereferenceable + * @pre iterator must come from object + * @return the key + */ + inline const std::string &key() const noexcept + { + assert(m_value.isObject()); + assert(m_itm != m_value.m_object.end()); + + return m_itm->first; + } + + /** + * Get the iterator position (for arrays). + * + * @pre iterator must be dereferenceable + * @pre iterator must come from arrays + * @return the index + */ + inline unsigned index() const noexcept + { + assert(m_value.isArray()); + assert(m_ita != m_value.m_array.end()); + + return std::distance(m_value.m_array.begin(), m_ita); + } + + /** + * Dereference the iterator. + * + * @pre iterator be dereferenceable + * @return the value + */ + inline ValueType &operator*() noexcept + { + assert((m_value.isArray() && m_ita != m_value.m_array.end()) || + (m_value.isObject() && m_itm != m_value.m_object.end())); + + return (m_value.m_type == Type::Object) ? m_itm->second : *m_ita; + } + + /** + * Dereference the iterator as a pointer. + * + * @pre iterator must be dereferenceable + * @return the value + */ + inline ValueType *operator->() noexcept + { + assert((m_value.isArray() && m_ita != m_value.m_array.end()) || + (m_value.isObject() && m_itm != m_value.m_object.end())); + + return (m_value.m_type == Type::Object) ? &m_itm->second : &(*m_ita); + } + + /** + * Increment the iterator. (Prefix version). + * + * @pre iterator must be dereferenceable + * @return *this; + */ + inline BaseIterator &operator++() noexcept + { + assert((m_value.isArray() && m_ita != m_value.m_array.end()) || + (m_value.isObject() && m_itm != m_value.m_object.end())); + + increment(); + + return *this; + } + + /** + * Increment the iterator. (Postfix version). + * + * @pre iterator must be dereferenceable + * @return *this; + */ + inline BaseIterator &operator++(int) noexcept + { + assert((m_value.isArray() && m_ita != m_value.m_array.end()) || + (m_value.isObject() && m_itm != m_value.m_object.end())); + + increment(); + + return *this; + } + + /** + * Compare two iterators. + * + * @param it1 the first iterator + * @param it2 the second iterator + * @return true if they are same + */ + bool operator==(const BaseIterator &it) const noexcept + { + if (m_value.isObject() && it.m_value.isObject()) + return m_itm == it.m_itm; + if (m_value.isArray() && it.m_value.isArray()) + return m_ita == it.m_ita; + + return false; + } + + /** + * Test if the iterator is different. + * + * @param it the iterator + * @return true if they are different + */ + inline bool operator!=(const BaseIterator &it) const noexcept + { + return !(*this == it); + } + }; + +public: + /** + * Forward iterator. + */ + using iterator = BaseIterator<Value, typename std::deque<Value>::iterator, typename std::map<std::string, Value>::iterator>; + + /** + * Const forward iterator. + */ + using const_iterator = BaseIterator<const Value, typename std::deque<Value>::const_iterator, typename std::map<std::string, Value>::const_iterator>; + + /** + * Construct a null value. + */ + inline Value() + { + } + + /** + * Create a value with a specified type, this is usually only needed when you want to create an object or + * an array. + * + * For any other types, initialize with sane default value. + * + * @param type the type + */ + Value(Type type); + + /** + * Construct a null value. + */ + inline Value(std::nullptr_t) noexcept + : m_type{Type::Null} + { + } + + /** + * Construct a boolean value. + * + * @param value the boolean value + */ + inline Value(bool value) noexcept + : m_type{Type::Boolean} + , m_boolean{value} + { + } + + /** + * Create value from integer. + * + * @param value the value + */ + inline Value(int value) noexcept + : m_type{Type::Int} + , m_integer{value} + { + } + + /** + * Construct a value from a C-string. + * + * @param value the C-string + */ + inline Value(const char *value) + : m_type{Type::String} + { + new (&m_string) std::string{value ? value : ""}; + } + + /** + * Construct a number value. + * + * @param value the real value + */ + inline Value(double value) noexcept + : m_type{Type::Real} + , m_number{value} + { + } + + /** + * Construct a string value. + * + * @param value the string + */ + inline Value(std::string value) noexcept + : m_type{Type::String} + { + new (&m_string) std::string{std::move(value)}; + } + + /** + * Create an object from a map. + * + * @param values the values + * @see fromObject + */ + inline Value(std::map<std::string, Value> values) + : Value{Type::Object} + { + for (const auto &pair : values) { + insert(pair.first, pair.second); + } + } + + /** + * Create an array from a deque. + * + * @param values the values + * @see fromArray + */ + inline Value(std::deque<Value> values) + : Value{Type::Array} + { + for (Value value : values) { + append(std::move(value)); + } + } + + /** + * Construct a value from a buffer. + * + * @param buffer the text + * @throw Error on errors + */ + Value(const Buffer &buffer); + + /** + * Construct a value from a file. + * + * @param file the file + * @throw Error on errors + */ + Value(const File &file); + + /** + * Move constructor. + * + * @param other the value to move from + */ + inline Value(Value &&other) + { + move(std::move(other)); + } + + /** + * Copy constructor. + * + * @param other the value to copy from + */ + inline Value(const Value &other) + { + copy(other); + } + + /** + * Copy operator. + * + * @param other the value to copy from + * @return *this + */ + inline Value &operator=(const Value &other) + { + copy(other); + + return *this; + } + + /** + * Move operator. + * + * @param other the value to move from + */ + inline Value &operator=(Value &&other) + { + move(std::move(other)); + + return *this; + } + + /** + * Destructor. + */ + ~Value(); + + /** + * Get an iterator to the beginning. + * + * @pre must be an array or object + * @return the iterator + */ + inline iterator begin() noexcept + { + assert(isArray() || isObject()); + + return m_type == Type::Object ? iterator(*this, m_object.begin()) : iterator(*this, m_array.begin()); + } + + /** + * Overloaded function. + * + * @pre must be an array or object + * @return the iterator + */ + inline const_iterator begin() const noexcept + { + assert(isArray() || isObject()); + + return m_type == Type::Object ? const_iterator(*this, m_object.begin()) : const_iterator(*this, m_array.begin()); + } + + /** + * Overloaded function. + * + * @pre must be an array or object + * @return the iterator + */ + inline const_iterator cbegin() const noexcept + { + assert(isArray() || isObject()); + + return m_type == Type::Object ? const_iterator(*this, m_object.cbegin()) : const_iterator(*this, m_array.cbegin()); + } + + /** + * Get an iterator to the end. + * + * @pre must be an array or object + * @return the iterator + */ + inline iterator end() noexcept + { + assert(isArray() || isObject()); + + return m_type == Type::Object ? iterator(*this, m_object.end()) : iterator(*this, m_array.end()); + } + + /** + * Get an iterator to the end. + * + * @pre must be an array or object + * @return the iterator + */ + inline const_iterator end() const noexcept + { + assert(isArray() || isObject()); + + return m_type == Type::Object ? const_iterator(*this, m_object.end()) : const_iterator(*this, m_array.end()); + } + + /** + * Get an iterator to the end. + * + * @pre must be an array or object + * @return the iterator + */ + inline const_iterator cend() const noexcept + { + assert(isArray() || isObject()); + + return m_type == Type::Object ? const_iterator(*this, m_object.cend()) : const_iterator(*this, m_array.cend()); + } + + /** + * Get the value type. + * + * @return the type + */ + inline Type typeOf() const noexcept + { + return m_type; + } + + /** + * Get the value as boolean. + * + * @return the value or false if not a boolean + */ + bool toBool() const noexcept; + + /** + * Get the value as integer. + * + * @return the value or 0 if not a integer + */ + int toInt() const noexcept; + + /** + * Get the value as real. + * + * @return the value or 0 if not a real + */ + double toReal() const noexcept; + + /** + * Get the value as string. + * + * @return the value or empty stirng if not a string + */ + std::string toString() const noexcept; + + /** + * Check if the value is boolean type. + * + * @return true if boolean + */ + inline bool isBool() const noexcept + { + return m_type == Type::Boolean; + } + + /** + * Check if the value is integer type. + * + * @return true if integer + */ + inline bool isInt() const noexcept + { + return m_type == Type::Int; + } + + /** + * Check if the value is object type. + * + * @return true if object + */ + inline bool isObject() const noexcept + { + return m_type == Type::Object; + } + + /** + * Check if the value is array type. + * + * @return true if array + */ + inline bool isArray() const noexcept + { + return m_type == Type::Array; + } + + /** + * Check if the value is integer or real type. + * + * @return true if integer or real + * @see toInt + * @see toReal + */ + inline bool isNumber() const noexcept + { + return m_type == Type::Real || m_type == Type::Int; + } + + /** + * Check if the value is real type. + * + * @return true if real + */ + inline bool isReal() const noexcept + { + return m_type == Type::Real; + } + + /** + * Check if the value is null type. + * + * @return true if null + */ + inline bool isNull() const noexcept + { + return m_type == Type::Null; + } + + /** + * Check if the value is string type. + * + * @return true if string + */ + inline bool isString() const noexcept + { + return m_type == Type::String; + } + + /** + * Get the array or object size. + * + * @pre must be an array or object + * @return the size + */ + inline unsigned size() const noexcept + { + assert(isArray() || isObject()); + + if (m_type == Type::Object) { + return m_object.size(); + } + + return m_array.size(); + } + + /** + * Remove all the values. + * + * @pre must be an array or an object + */ + inline void clear() noexcept + { + assert(isArray() || isObject()); + + if (m_type == Type::Array) { + m_array.clear(); + } else { + m_object.clear(); + } + } + + /* + * Array functions + * ---------------------------------------------------------- + */ + + /** + * Get the value at the specified position or the defaultValue if position is out of bounds. + * + * @param position the position + * @param defaultValue the value replacement + * @return the value or defaultValue + */ + template <typename DefaultValue> + inline Value valueOr(unsigned position, DefaultValue &&defaultValue) const + { + if (m_type != Type::Array || position >= m_array.size()) { + return defaultValue; + } + + return m_array[position]; + } + + /** + * Get a value at the specified index. + * + * @pre must be an array + * @param position the position + * @return the value + * @throw std::out_of_range if out of bounds + */ + inline const Value &at(unsigned position) const + { + assert(isArray()); + + return m_array.at(position); + } + + /** + * Overloaded function. + * + * @pre must be an array + * @param position the position + * @return the value + * @throw std::out_of_range if out of bounds + */ + inline Value &at(unsigned position) + { + assert(isArray()); + + return m_array.at(position); + } + + /** + * Get a value at the specified index. + * + * @pre must be an array + * @pre position must be valid + * @param position the position + * @return the value + */ + inline const Value &operator[](unsigned position) const + { + assert(isArray()); + assert(position < m_array.size()); + + return m_array[position]; + } + + /** + * Overloaded function. + * + * @pre must be an array + * @pre position must be valid + * @param position the position + * @return the value + */ + inline Value &operator[](unsigned position) + { + assert(isArray()); + assert(position < m_array.size()); + + return m_array[position]; + } + + /** + * Push a value to the beginning of the array. + * + * @pre must be an array + * @param value the value to push + */ + inline void push(const Value &value) + { + assert(isArray()); + + m_array.push_front(value); + } + + /** + * Overloaded function. + * + * @pre must be an array + * @param value the value to push + */ + inline void push(Value &&value) + { + assert(isArray()); + + m_array.push_front(std::move(value)); + } + + /** + * Insert a value at the specified position. + * + * @pre must be an array + * @pre position must be valid + * @param position the position + * @param value the value to push + */ + inline void insert(unsigned position, const Value &value) + { + assert(isArray()); + assert(position <= m_array.size()); + + m_array.insert(m_array.begin() + position, value); + } + + /** + * Overloaded function. + * + * @pre must be an array + * @pre position must be valid + * @param position the position + * @param value the value to push + */ + inline void insert(unsigned position, Value &&value) + { + assert(isArray()); + assert(position <= m_array.size()); + + m_array.insert(m_array.begin() + position, std::move(value)); + } + + /** + * Add a new value to the end. + * + * @pre must be an array + * @param value the value to append + */ + inline void append(const Value &value) + { + assert(isArray()); + + m_array.push_back(value); + } + + /** + * Overloaded function. + * + * @pre must be an array + * @param value the value to append + */ + inline void append(Value &&value) + { + assert(isArray()); + + m_array.push_back(std::move(value)); + } + + /** + * Remove a value at the specified position. + * + * @pre must be an array + * @pre position must be valid + * @param position the position + */ + inline void erase(unsigned position) + { + assert(isArray()); + assert(position < m_array.size()); + + m_array.erase(m_array.begin() + position); + } + + /* + * Object functions + * ---------------------------------------------------------- + */ + + /** + * Get the value at the specified key or the defaultValue if key is absent. + * + * @param name the name + * @param defaultValue the value replacement + * @return the value or defaultValue + */ + template <typename DefaultValue> + Value valueOr(const std::string &name, DefaultValue &&defaultValue) const + { + if (m_type != Type::Object) { + return defaultValue; + } + + auto it = m_object.find(name); + + if (it == m_object.end()) { + return defaultValue; + } + + return it->second; + } + + /** + * Get a value from the object. + * + * @pre must be an object + * @param name the value key + * @return the value + * @throw std::out_of_range if not found + */ + inline const Value &at(const std::string &name) const + { + assert(isObject()); + + return m_object.at(name); + } + + /** + * Overloaded function. + * + * @pre must be an object + * @param name the value key + * @return the value + * @throw std::out_of_range if not found + */ + inline Value &at(const std::string &name) + { + assert(isObject()); + + return m_object.at(name); + } + + /** + * Get a value from the object. + * + * @pre must be an object + * @param name the value key + * @return the value + */ + inline Value &operator[](const std::string &name) + { + assert(isObject()); + + return m_object[name]; + } + + /** + * Get a value from the object. + * + * @pre must be an object + * @param name the value key + * @return the value + */ + inline const Value &operator[](const std::string &name) const + { + assert(isObject()); + + return m_object.at(name); + } + + /** + * Find a value by key. + * + * @pre must be an object + * @param key the property key + * @return the iterator or past the end if not found + */ + inline iterator find(const std::string &key) + { + assert(isObject()); + + return iterator(*this, m_object.find(key)); + } + + /** + * Overloaded function. + * + * @pre must be an object + * @param key the property key + * @return the iterator or past the end if not found + */ + inline const_iterator find(const std::string &key) const + { + assert(isObject()); + + return const_iterator(*this, m_object.find(key)); + } + + /** + * Insert a new value. + * + * @pre must be an object + * @param name the key + * @param value the value + */ + inline void insert(std::string name, const Value &value) + { + assert(isObject()); + + m_object.insert({std::move(name), value}); + } + + /** + * Overloaded function. + * + * @pre must be an object + * @param name the key + * @param value the value + */ + inline void insert(std::string name, Value &&value) + { + assert(isObject()); + + m_object.insert({std::move(name), std::move(value)}); + } + + /** + * Check if a value exists. + * + * @pre must be an object + * @param key the key value + * @return true if exists + */ + inline bool contains(const std::string &key) const noexcept + { + assert(isObject()); + + return m_object.find(key) != m_object.end(); + } + + /** + * Remove a value of the specified key. + * + * @pre must be an object + * @param key the value key + */ + inline void erase(const std::string &key) + { + assert(isObject()); + + m_object.erase(key); + } + + /** + * Return this value as JSon representation. + * + * @param indent, the indentation to use (0 == compact, < 0 == tabs, > 0 == number of spaces) + * @param tabs, use tabs or not + * @return the string + */ + inline std::string toJson(int indent = 2) const + { + return toJson(indent, 0); + } +}; + +/** + * Escape the input. + * + * @param input the input + * @return the escaped string + */ +std::string escape(const std::string &input); + +/** + * Convenient function for creating array from initializer list. + * + * @param values the values + * @return the array + */ +inline Value array(std::initializer_list<Value> values) +{ + return Value(std::deque<Value>(values.begin(), values.end())); +} + +/** + * Convenient function for creating object from initializer list. + * + * @param values the values + * @return the object + */ +inline Value object(std::initializer_list<std::pair<std::string, Value>> values) +{ + return Value(std::map<std::string, Value>(values.begin(), values.end())); +} + +} // !json + +#endif // !_JSON_H_
--- a/C++/modules/OptionParser/OptionParser.cpp Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,189 +0,0 @@ -/* - * OptionParser.cpp -- parse Unix command line options - * - * Copyright (c) 2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <cassert> - -#include "OptionParser.h" - -namespace parser { - -namespace { - -using Iterator = std::vector<std::string>::iterator; -using Args = std::vector<std::string>; - -inline bool isOption(const std::string &arg) noexcept -{ - return arg.size() >= 2 && arg[0] == '-'; -} - -inline bool isLongOption(const std::string &arg) noexcept -{ - assert(isOption(arg)); - - return arg.size() >= 3 && arg[1] == '-'; -} - -inline bool isShortSimple(const std::string &arg) noexcept -{ - assert(isOption(arg)); - assert(!isLongOption(arg)); - - return arg.size() == 2; -} - -void parseLongOption(Result &result, Args &args, Iterator &it, Iterator &end, const Options &definition) -{ - auto arg = *it++; - auto opt = definition.find(arg); - - if (opt == definition.end()) { - throw InvalidOption{arg}; - } - - /* Need argument? */ - if (opt->second) { - if (it == end || isOption(*it)) { - throw MissingValue{arg}; - } - - result.insert(std::make_pair(arg, *it++)); - it = args.erase(args.begin(), it); - end = args.end(); - } else { - result.insert(std::make_pair(arg, "")); - it = args.erase(args.begin()); - end = args.end(); - } -} - -void parseShortOption(Result &result, Args &args, Iterator &it, Iterator &end, const Options &definition) -{ - if (isShortSimple(*it)) { - /* - * Here two cases: - * - * -v (no option) - * -c value - */ - auto arg = *it++; - auto opt = definition.find(arg); - - if (opt == definition.end()) { - throw InvalidOption{arg}; - } - - /* Need argument? */ - if (opt->second) { - if (it == end || isOption(*it)) { - throw MissingValue{arg}; - } - - result.insert(std::make_pair(arg, *it++)); - it = args.erase(args.begin(), it); - end = args.end(); - } else { - result.insert(std::make_pair(arg, "")); - it = args.erase(args.begin()); - end = args.end(); - } - } else { - /* - * Here multiple scenarios: - * - * 1. -abc (-a -b -c if all are simple boolean arguments) - * 2. -vc foo.conf (-v -c foo.conf if -c is argument dependant) - * 3. -vcfoo.conf (-v -c foo.conf also) - */ - auto value = it->substr(1); - auto len = value.length(); - int toremove = 1; - - for (decltype(len) i = 0; i < len; ++i) { - auto arg = std::string{'-'} + value[i]; - auto opt = definition.find(arg); - - if (opt == definition.end()) { - throw InvalidOption{arg}; - } - - if (opt->second) { - if (i == (len - 1)) { - /* End of string, get the next argument (see 2.) */ - if (++it == end || isOption(*it)) { - throw MissingValue{arg}; - } - - result.insert(std::make_pair(arg, *it)); - toremove += 1; - } else { - result.insert(std::make_pair(arg, value.substr(i + 1))); - i = len; - } - } else { - result.insert(std::make_pair(arg, "")); - } - } - - it = args.erase(args.begin(), args.begin() + toremove); - end = args.end(); - } -} - -} // !namespace - -Result read(std::vector<std::string> &args, const Options &definition) -{ - Result result; - - auto it = args.begin(); - auto end = args.end(); - - while (it != end) { - if (!isOption(*it)) { - break; - } - - if (isLongOption(*it)) { - parseLongOption(result, args, it, end, definition); - } else { - parseShortOption(result, args, it, end, definition); - } - } - - return result; -} - -Result read(int &argc, char **&argv, const Options &definition) -{ - std::vector<std::string> args; - - for (int i = 0; i < argc; ++i) { - args.push_back(argv[i]); - } - - auto before = args.size(); - auto result = read(args, definition); - - argc -= before - args.size(); - argv += before - args.size(); - - return result; -} - -} // !parser \ No newline at end of file
--- a/C++/modules/OptionParser/OptionParser.h Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,141 +0,0 @@ -/* - * OptionParser.h -- parse Unix command line options - * - * Copyright (c) 2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef _OPTION_PARSER_H_ -#define _OPTION_PARSER_H_ - -#include <exception> -#include <map> -#include <string> -#include <utility> -#include <vector> - -/** - * Namespace for options parsing. - */ -namespace parser { - -/** - * @class InvalidOption - * @brief This exception is thrown when an invalid option has been found. - */ -class InvalidOption : public std::exception { -private: - std::string message; - -public: - /** - * The invalid option given. - */ - std::string argument; - - /** - * Construct the exception. - * - * @param arg the argument missing - */ - inline InvalidOption(std::string arg) - : argument{std::move(arg)} - { - message = std::string{"invalid option: "} + argument; - } - - /** - * Get the error message. - * - * @return the error message - */ - const char *what() const noexcept override - { - return message.c_str(); - } -}; - -/** - * @class MissingValue - * @brief This exception is thrown when an option requires a value and no value has been given - */ -class MissingValue : public std::exception { -private: - std::string message; - -public: - /** - * The argument that requires a value. - */ - std::string argument; - - /** - * Construct the exception. - * - * @param arg the argument that requires a value - */ - inline MissingValue(std::string arg) - : argument{std::move(arg)} - { - message = std::string{"missing argument for: "} + argument; - } - - /** - * Get the error message. - * - * @return the error message - */ - const char *what() const noexcept override - { - return message.c_str(); - } -}; - -/** - * Packed multimap of options. - */ -using Result = std::multimap<std::string, std::string>; - -/** - * Define the allowed options. - */ -using Options = std::map<std::string, bool>; - -/** - * Extract the command line options and return a result. - * - * @param args the arguments - * @param definition - * @warning the arguments vector is modified in place to remove parsed options - * @throw MissingValue - * @throw InvalidOption - */ -Result read(std::vector<std::string> &args, const Options &definition); - -/** - * Overloaded function for usage with main() arguments. - * - * @param argc the number of arguments - * @param argv the argument vector - * @param definition - * @note don't forget to remove the first argv[0] argument - * @warning the argc and argv are modified in place to remove parsed options - * @throw MissingValue - * @throw InvalidOption - */ -Result read(int &argc, char **&argv, const Options &definition); - -} // !parser - -#endif // !_OPTION_PARSER_H_ \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/OptionParser/options.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,189 @@ +/* + * options.cpp -- parse Unix command line options + * + * Copyright (c) 2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <cassert> + +#include "options.h" + +namespace option { + +namespace { + +using Iterator = std::vector<std::string>::iterator; +using Args = std::vector<std::string>; + +inline bool isOption(const std::string &arg) noexcept +{ + return arg.size() >= 2 && arg[0] == '-'; +} + +inline bool isLongOption(const std::string &arg) noexcept +{ + assert(isOption(arg)); + + return arg.size() >= 3 && arg[1] == '-'; +} + +inline bool isShortSimple(const std::string &arg) noexcept +{ + assert(isOption(arg)); + assert(!isLongOption(arg)); + + return arg.size() == 2; +} + +void parseLongOption(Result &result, Args &args, Iterator &it, Iterator &end, const Options &definition) +{ + auto arg = *it++; + auto opt = definition.find(arg); + + if (opt == definition.end()) { + throw InvalidOption{arg}; + } + + /* Need argument? */ + if (opt->second) { + if (it == end || isOption(*it)) { + throw MissingValue{arg}; + } + + result.insert(std::make_pair(arg, *it++)); + it = args.erase(args.begin(), it); + end = args.end(); + } else { + result.insert(std::make_pair(arg, "")); + it = args.erase(args.begin()); + end = args.end(); + } +} + +void parseShortOption(Result &result, Args &args, Iterator &it, Iterator &end, const Options &definition) +{ + if (isShortSimple(*it)) { + /* + * Here two cases: + * + * -v (no option) + * -c value + */ + auto arg = *it++; + auto opt = definition.find(arg); + + if (opt == definition.end()) { + throw InvalidOption{arg}; + } + + /* Need argument? */ + if (opt->second) { + if (it == end || isOption(*it)) { + throw MissingValue{arg}; + } + + result.insert(std::make_pair(arg, *it++)); + it = args.erase(args.begin(), it); + end = args.end(); + } else { + result.insert(std::make_pair(arg, "")); + it = args.erase(args.begin()); + end = args.end(); + } + } else { + /* + * Here multiple scenarios: + * + * 1. -abc (-a -b -c if all are simple boolean arguments) + * 2. -vc foo.conf (-v -c foo.conf if -c is argument dependant) + * 3. -vcfoo.conf (-v -c foo.conf also) + */ + auto value = it->substr(1); + auto len = value.length(); + int toremove = 1; + + for (decltype(len) i = 0; i < len; ++i) { + auto arg = std::string{'-'} + value[i]; + auto opt = definition.find(arg); + + if (opt == definition.end()) { + throw InvalidOption{arg}; + } + + if (opt->second) { + if (i == (len - 1)) { + /* End of string, get the next argument (see 2.) */ + if (++it == end || isOption(*it)) { + throw MissingValue{arg}; + } + + result.insert(std::make_pair(arg, *it)); + toremove += 1; + } else { + result.insert(std::make_pair(arg, value.substr(i + 1))); + i = len; + } + } else { + result.insert(std::make_pair(arg, "")); + } + } + + it = args.erase(args.begin(), args.begin() + toremove); + end = args.end(); + } +} + +} // !namespace + +Result read(std::vector<std::string> &args, const Options &definition) +{ + Result result; + + auto it = args.begin(); + auto end = args.end(); + + while (it != end) { + if (!isOption(*it)) { + break; + } + + if (isLongOption(*it)) { + parseLongOption(result, args, it, end, definition); + } else { + parseShortOption(result, args, it, end, definition); + } + } + + return result; +} + +Result read(int &argc, char **&argv, const Options &definition) +{ + std::vector<std::string> args; + + for (int i = 0; i < argc; ++i) { + args.push_back(argv[i]); + } + + auto before = args.size(); + auto result = read(args, definition); + + argc -= before - args.size(); + argv += before - args.size(); + + return result; +} + +} // !option
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/OptionParser/options.h Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,141 @@ +/* + * options.h -- parse Unix command line options + * + * Copyright (c) 2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _OPTIONS_H_ +#define _OPTIONS_H_ + +#include <exception> +#include <map> +#include <string> +#include <utility> +#include <vector> + +/** + * Namespace for options parsing. + */ +namespace option { + +/** + * @class InvalidOption + * @brief This exception is thrown when an invalid option has been found. + */ +class InvalidOption : public std::exception { +private: + std::string message; + +public: + /** + * The invalid option given. + */ + std::string argument; + + /** + * Construct the exception. + * + * @param arg the argument missing + */ + inline InvalidOption(std::string arg) + : argument{std::move(arg)} + { + message = std::string{"invalid option: "} + argument; + } + + /** + * Get the error message. + * + * @return the error message + */ + const char *what() const noexcept override + { + return message.c_str(); + } +}; + +/** + * @class MissingValue + * @brief This exception is thrown when an option requires a value and no value has been given + */ +class MissingValue : public std::exception { +private: + std::string message; + +public: + /** + * The argument that requires a value. + */ + std::string argument; + + /** + * Construct the exception. + * + * @param arg the argument that requires a value + */ + inline MissingValue(std::string arg) + : argument{std::move(arg)} + { + message = std::string{"missing argument for: "} + argument; + } + + /** + * Get the error message. + * + * @return the error message + */ + const char *what() const noexcept override + { + return message.c_str(); + } +}; + +/** + * Packed multimap of options. + */ +using Result = std::multimap<std::string, std::string>; + +/** + * Define the allowed options. + */ +using Options = std::map<std::string, bool>; + +/** + * Extract the command line options and return a result. + * + * @param args the arguments + * @param definition + * @warning the arguments vector is modified in place to remove parsed options + * @throw MissingValue + * @throw InvalidOption + */ +Result read(std::vector<std::string> &args, const Options &definition); + +/** + * Overloaded function for usage with main() arguments. + * + * @param argc the number of arguments + * @param argv the argument vector + * @param definition + * @note don't forget to remove the first argv[0] argument + * @warning the argc and argv are modified in place to remove parsed options + * @throw MissingValue + * @throw InvalidOption + */ +Result read(int &argc, char **&argv, const Options &definition); + +} // !option + +#endif // !_OPTIONS_H_
--- a/C++/modules/Socket/Sockets.cpp Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,709 +0,0 @@ -/* - * Sockets.cpp -- portable C++ socket wrappers - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#define TIMEOUT_MSG "operation timeout" - -#include <algorithm> -#include <atomic> -#include <cstring> -#include <mutex> - -#include "Sockets.h" - -namespace net { - -/* - * Portable constants - * ------------------------------------------------------------------ - */ - -/* {{{ Constants */ - -#if defined(_WIN32) - -const Handle Invalid{INVALID_SOCKET}; -const int Failure{SOCKET_ERROR}; - -#else - -const Handle Invalid{-1}; -const int Failure{-1}; - -#endif - -/* }}} */ - -/* - * Portable functions - * ------------------------------------------------------------------ - */ - -/* {{{ Functions */ - -#if defined(_WIN32) - -namespace { - -static std::mutex s_mutex; -static std::atomic<bool> s_initialized{false}; - -} // !namespace - -#endif // !_WIN32 - -void init() noexcept -{ -#if defined(_WIN32) - std::lock_guard<std::mutex> lock(s_mutex); - - if (!s_initialized) { - s_initialized = true; - - WSADATA wsa; - WSAStartup(MAKEWORD(2, 2), &wsa); - - /* - * If SOCKET_WSA_NO_INIT is not set then the user - * must also call finish himself. - */ -#if !defined(SOCKET_NO_AUTO_INIT) - atexit(finish); -#endif - } -#endif -} - -void finish() noexcept -{ -#if defined(_WIN32) - WSACleanup(); -#endif -} - -std::string error(int errn) -{ -#if defined(_WIN32) - LPSTR str = nullptr; - std::string errmsg = "Unknown error"; - - FormatMessageA( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - NULL, - errn, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPSTR)&str, 0, NULL); - - - if (str) { - errmsg = std::string(str); - LocalFree(str); - } - - return errmsg; -#else - return strerror(errn); -#endif -} - -std::string error() -{ -#if defined(_WIN32) - return error(WSAGetLastError()); -#else - return error(errno); -#endif -} - -/* }}} */ - -/* - * SSL stuff - * ------------------------------------------------------------------ - */ - -/* {{{ SSL initialization */ - -#if !defined(SOCKET_NO_SSL) - -namespace ssl { - -namespace { - -std::mutex mutex; -std::atomic<bool> initialized{false}; - -} // !namespace - -void finish() noexcept -{ - ERR_free_strings(); -} - -void init() noexcept -{ - std::lock_guard<std::mutex> lock{mutex}; - - if (!initialized) { - initialized = true; - - SSL_library_init(); - SSL_load_error_strings(); - OpenSSL_add_all_algorithms(); - -#if !defined(SOCKET_NO_AUTO_SSL_INIT) - atexit(finish); -#endif // SOCKET_NO_AUTO_SSL_INIT - } -} - -} // !ssl - -#endif // SOCKET_NO_SSL - -/* }}} */ - -/* - * Error class - * ------------------------------------------------------------------ - */ - -/* {{{ Error */ - -Error::Error(Code code, std::string function) - : m_code{code} - , m_function{std::move(function)} - , m_error{error()} -{ -} - -Error::Error(Code code, std::string function, int n) - : m_code{code} - , m_function{std::move(function)} - , m_error{error(n)} -{ -} - -Error::Error(Code code, std::string function, std::string error) - : m_code{code} - , m_function{std::move(function)} - , m_error{std::move(error)} -{ -} - -/* }}} */ - -/* - * Predefine addressed to be used - * ------------------------------------------------------------------ - */ - -/* {{{ Addresses */ - -namespace address { - -/* Default domain */ -int Ip::m_default{AF_INET}; - -Ip::Ip(Type domain) noexcept - : m_domain(static_cast<int>(domain)) -{ - assert(m_domain == AF_INET6 || m_domain == AF_INET); - - if (m_domain == AF_INET6) { - std::memset(&m_sin6, 0, sizeof (sockaddr_in6)); - } else { - std::memset(&m_sin, 0, sizeof (sockaddr_in)); - } -} - -Ip::Ip(const std::string &host, int port, Type domain) - : m_domain(static_cast<int>(domain)) -{ - assert(m_domain == AF_INET6 || m_domain == AF_INET); - - if (host == "*") { - if (m_domain == AF_INET6) { - std::memset(&m_sin6, 0, sizeof (sockaddr_in6)); - - m_length = sizeof (sockaddr_in6); - m_sin6.sin6_addr = in6addr_any; - m_sin6.sin6_family = AF_INET6; - m_sin6.sin6_port = htons(port); - } else { - std::memset(&m_sin, 0, sizeof (sockaddr_in)); - - m_length = sizeof (sockaddr_in); - m_sin.sin_addr.s_addr = INADDR_ANY; - m_sin.sin_family = AF_INET; - m_sin.sin_port = htons(port); - } - } else { - addrinfo hints, *res; - - std::memset(&hints, 0, sizeof (addrinfo)); - hints.ai_family = domain; - - auto error = getaddrinfo(host.c_str(), std::to_string(port).c_str(), &hints, &res); - if (error != 0) { - throw Error{Error::System, "getaddrinfo", gai_strerror(error)}; - } - - if (m_domain == AF_INET6) { - std::memcpy(&m_sin6, res->ai_addr, res->ai_addrlen); - } else { - std::memcpy(&m_sin, res->ai_addr, res->ai_addrlen); - } - - m_length = res->ai_addrlen; - freeaddrinfo(res); - } -} - -Ip::Ip(const sockaddr_storage *ss, socklen_t length) noexcept - : m_length{length} - , m_domain{ss->ss_family} -{ - assert(ss->ss_family == AF_INET6 || ss->ss_family == AF_INET); - - if (ss->ss_family == AF_INET6) { - std::memcpy(&m_sin6, ss, length); - } else if (ss->ss_family == AF_INET) { - std::memcpy(&m_sin, ss, length); - } -} - -#if !defined(_WIN32) - -Local::Local() noexcept -{ - std::memset(&m_sun, 0, sizeof (sockaddr_un)); -} - -Local::Local(std::string path, bool rm) noexcept - : m_path{std::move(path)} -{ - /* Silently remove the file even if it fails */ - if (rm) { - ::remove(m_path.c_str()); - } - - /* Copy the path */ - std::memset(m_sun.sun_path, 0, sizeof (m_sun.sun_path)); - std::strncpy(m_sun.sun_path, m_path.c_str(), sizeof (m_sun.sun_path) - 1); - - /* Set the parameters */ - m_sun.sun_family = AF_LOCAL; -} - -Local::Local(const sockaddr_storage *ss, socklen_t length) noexcept -{ - assert(ss->ss_family == AF_LOCAL); - - if (ss->ss_family == AF_LOCAL) { - std::memcpy(&m_sun, ss, length); - m_path = reinterpret_cast<const sockaddr_un &>(m_sun).sun_path; - } -} - -#endif // !_WIN32 - -} // !address - -/* }}} */ - -/* - * Select - * ------------------------------------------------------------------ - */ - -/* {{{ Select */ - -std::vector<ListenerStatus> Select::wait(const ListenerTable &table, int ms) -{ - timeval maxwait, *towait; - fd_set readset; - fd_set writeset; - - FD_ZERO(&readset); - FD_ZERO(&writeset); - - Handle max = 0; - - for (const auto &pair : table) { - if ((pair.second & Condition::Readable) == Condition::Readable) { - FD_SET(pair.first, &readset); - } - if ((pair.second & Condition::Writable) == Condition::Writable) { - FD_SET(pair.first, &writeset); - } - - if (pair.first > max) { - max = pair.first; - } - } - - maxwait.tv_sec = 0; - maxwait.tv_usec = ms * 1000; - - // Set to nullptr for infinite timeout. - towait = (ms < 0) ? nullptr : &maxwait; - - auto error = ::select(max + 1, &readset, &writeset, nullptr, towait); - if (error == Failure) { - throw Error{Error::System, "select"}; - } - if (error == 0) { - throw Error{Error::Timeout, "select", TIMEOUT_MSG}; - } - - std::vector<ListenerStatus> sockets; - - for (const auto &pair : table) { - if (FD_ISSET(pair.first, &readset)) { - sockets.push_back(ListenerStatus{pair.first, Condition::Readable}); - } - if (FD_ISSET(pair.first, &writeset)) { - sockets.push_back(ListenerStatus{pair.first, Condition::Writable}); - } - } - - return sockets; -} - -/* }}} */ - -/* - * Poll - * ------------------------------------------------------------------ - */ - -/* {{{ Poll */ - -/* - * Poll implementation - * ------------------------------------------------------------------ - */ - -#if defined(SOCKET_HAVE_POLL) - -#if defined(_WIN32) -# define poll WSAPoll -#endif - -short Poll::toPoll(Condition condition) const noexcept -{ - short result(0); - - if ((condition & Condition::Readable) == Condition::Readable) { - result |= POLLIN; - } - if ((condition & Condition::Writable) == Condition::Writable) { - result |= POLLOUT; - } - - return result; -} - -Condition Poll::toCondition(short &event) const noexcept -{ - Condition condition{Condition::None}; - - /* - * Poll implementations mark the socket differently regarding - * the disconnection of a socket. - * - * At least, even if POLLHUP or POLLIN is set, recv() always - * return 0 so we mark the socket as readable. - */ - if ((event & POLLIN) || (event & POLLHUP)) { - condition |= Condition::Readable; - } - if (event & POLLOUT) { - condition |= Condition::Writable; - } - - /* Reset event for safety */ - event = 0; - - return condition; -} - -void Poll::set(const ListenerTable &, Handle h, Condition condition, bool add) -{ - if (add) { - m_fds.push_back(pollfd{h, toPoll(condition), 0}); - } else { - auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const pollfd &pfd) { - return pfd.fd == h; - }); - - it->events |= toPoll(condition); - } -} - -void Poll::unset(const ListenerTable &, Handle h, Condition condition, bool remove) -{ - auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const pollfd &pfd) { - return pfd.fd == h; - }); - - if (remove) { - m_fds.erase(it); - } else { - it->events &= ~(toPoll(condition)); - } -} - -std::vector<ListenerStatus> Poll::wait(const ListenerTable &, int ms) -{ - auto result = poll(m_fds.data(), m_fds.size(), ms); - if (result == 0) { - throw Error{Error::Timeout, "select", TIMEOUT_MSG}; - } - if (result < 0) { - throw Error{Error::System, "poll"}; - } - - std::vector<ListenerStatus> sockets; - for (auto &fd : m_fds) { - if (fd.revents != 0) { - sockets.push_back(ListenerStatus{fd.fd, toCondition(fd.revents)}); - } - } - - return sockets; -} - -#endif // !SOCKET_HAVE_POLL - -/* }}} */ - -/* - * Epoll implementation - * ------------------------------------------------------------------ - */ - -/* {{{ Epoll */ - -#if defined(SOCKET_HAVE_EPOLL) - -uint32_t Epoll::toEpoll(Condition condition) const noexcept -{ - uint32_t events = 0; - - if ((condition & Condition::Readable) == Condition::Readable) { - events |= EPOLLIN; - } - if ((condition & Condition::Writable) == Condition::Writable) { - events |= EPOLLOUT; - } - - return events; -} - -Condition Epoll::toCondition(uint32_t events) const noexcept -{ - Condition condition{Condition::None}; - - if ((events & EPOLLIN) || (events & EPOLLHUP)) { - condition |= Condition::Readable; - } - if (events & EPOLLOUT) { - condition |= Condition::Writable; - } - - return condition; -} - -void Epoll::update(Handle h, int op, int eflags) -{ - epoll_event ev; - - std::memset(&ev, 0, sizeof (epoll_event)); - - ev.events = eflags; - ev.data.fd = h; - - if (epoll_ctl(m_handle, op, h, &ev) < 0) { - throw Error{Error::System, "epoll_ctl"}; - } -} - -Epoll::Epoll() - : m_handle{epoll_create1(0)} -{ - if (m_handle < 0) { - throw Error{Error::System, "epoll_create"}; - } -} - -Epoll::~Epoll() -{ - close(m_handle); -} - -/* - * For set and unset, we need to apply the whole flags required, so if the socket - * was set to Connection::Readable and user add Connection::Writable, we must - * place both. - */ -void Epoll::set(const ListenerTable &table, Handle sc, Condition condition, bool add) -{ - if (add) { - update(sc, EPOLL_CTL_ADD, toEpoll(condition)); - m_events.resize(m_events.size() + 1); - } else { - update(sc, EPOLL_CTL_MOD, toEpoll(table.at(sc) | condition)); - } -} - -/* - * Unset is a bit complicated case because Listener tells us which - * flag to remove but to update epoll descriptor we need to pass - * the effective flags that we want to be applied. - * - * So we put the same flags that are currently effective and remove the - * requested one. - */ -void Epoll::unset(const ListenerTable &table, Handle sc, Condition condition, bool remove) -{ - if (remove) { - update(sc, EPOLL_CTL_DEL, 0); - m_events.resize(m_events.size() - 1); - } else { - update(sc, EPOLL_CTL_MOD, toEpoll(table.at(sc) & ~(condition))); - } -} - -std::vector<ListenerStatus> Epoll::wait(const ListenerTable &, int ms) -{ - int ret = epoll_wait(m_handle, m_events.data(), m_events.size(), ms); - std::vector<ListenerStatus> result; - - if (ret == 0) { - throw Error{Error::Timeout, "epoll_wait", TIMEOUT_MSG}; - } - if (ret < 0) { - throw Error{Error::System, "epoll_wait"}; - } - - for (int i = 0; i < ret; ++i) { - result.push_back(ListenerStatus{m_events[i].data.fd, toCondition(m_events[i].events)}); - } - - return result; -} - -#endif // !SOCKET_HAVE_EPOLL - -/* }}} */ - -/* - * Kqueue implementation - * ------------------------------------------------------------------ - */ - -/* {{{ Kqueue */ - -#if defined(SOCKET_HAVE_KQUEUE) - -Kqueue::Kqueue() - : m_handle(kqueue()) -{ - if (m_handle < 0) { - throw Error{Error::System, "kqueue"}; - } -} - -Kqueue::~Kqueue() -{ - close(m_handle); -} - -void Kqueue::update(Handle h, int filter, int kflags) -{ - struct kevent ev; - - EV_SET(&ev, h, filter, kflags, 0, 0, nullptr); - - if (kevent(m_handle, &ev, 1, nullptr, 0, nullptr) < 0) { - throw Error{Error::System, "kevent"}; - } -} - -void Kqueue::set(const ListenerTable &, Handle h, Condition condition, bool add) -{ - if ((condition & Condition::Readable) == Condition::Readable) { - update(h, EVFILT_READ, EV_ADD | EV_ENABLE); - } - if ((condition & Condition::Writable) == Condition::Writable) { - update(h, EVFILT_WRITE, EV_ADD | EV_ENABLE); - } - - if (add) { - m_result.resize(m_result.size() + 1); - } -} - -void Kqueue::unset(const ListenerTable &, Handle h, Condition condition, bool remove) -{ - if ((condition & Condition::Readable) == Condition::Readable) { - update(h, EVFILT_READ, EV_DELETE); - } - if ((condition & Condition::Writable) == Condition::Writable) { - update(h, EVFILT_WRITE, EV_DELETE); - } - - if (remove) { - m_result.resize(m_result.size() - 1); - } -} - -std::vector<ListenerStatus> Kqueue::wait(const ListenerTable &, int ms) -{ - std::vector<ListenerStatus> sockets; - timespec ts = { 0, 0 }; - timespec *pts = (ms <= 0) ? nullptr : &ts; - - ts.tv_sec = ms / 1000; - ts.tv_nsec = (ms % 1000) * 1000000; - - int nevents = kevent(m_handle, nullptr, 0, &m_result[0], m_result.capacity(), pts); - - if (nevents == 0) { - throw Error{Error::Timeout, "kevent", TIMEOUT_MSG}; - } - if (nevents < 0) { - throw Error{Error::System, "kevent"}; - } - - for (int i = 0; i < nevents; ++i) { - sockets.push_back(ListenerStatus{ - static_cast<Handle>(m_result[i].ident), - m_result[i].filter == EVFILT_READ ? Condition::Readable : Condition::Writable - }); - } - - return sockets; -} - -#endif // !SOCKET_HAVE_KQUEUE - -/* }}} */ - -} // !net
--- a/C++/modules/Socket/Sockets.h Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4063 +0,0 @@ -/* - * Sockets.h -- portable C++ socket wrappers - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef _SOCKETS_H_ -#define _SOCKETS_H_ - -/** - * @file Sockets.h - * @brief Portable socket abstraction - * - * # Introduction - * - * This file is a portable networking library. - * - * ## Options - * - * The user may set the following variables before compiling these files: - * - * ### General options - * - * - **SOCKET_NO_AUTO_INIT**: (bool) Set to 0 if you don't want Socket class to - * automatically calls net::init function and net::finish functions. - * - **SOCKET_NO_SSL**: (bool) Set to 0 if you don't have access to OpenSSL library. - * - **SOCKET_NO_AUTO_SSL_INIT**: (bool) Set to 0 if you don't want Socket class with Tls to automatically init - * the OpenSSL library. You will need to call net::ssl::init and net::ssl::finish. - * - * ### Options for Listener class - * - * Feature detection, multiple implementations may be avaible, for example, Linux has poll, select and epoll. - * - * We assume that `select(2)` is always available. - * - * Of course, you can set the variables yourself if you test it with your build system. - * - * - **SOCKET_HAVE_POLL**: Defined on all BSD, Linux. Also defined on Windows - * if _WIN32_WINNT is set to 0x0600 or greater. - * - **SOCKET_HAVE_KQUEUE**: Defined on all BSD and Apple. - * - **SOCKET_HAVE_EPOLL**: Defined on Linux only. - * - **SOCKET_DEFAULT_BACKEND**: Which backend to use (e.g. `Select`). - * - * The preference priority is ordered from left to right. - * - * | System | Backend | Class name | - * |---------------|-------------------------|--------------| - * | Linux | epoll(7) | Epoll | - * | *BSD | kqueue(2) | Kqueue | - * | Windows | poll(2), select(2) | Poll, Select | - * | Mac OS X | kqueue(2) | Kqueue | - */ - -#if defined(_WIN32) -# if _WIN32_WINNT >= 0x0600 && !defined(SOCKET_HAVE_POLL) -# define SOCKET_HAVE_POLL -# endif -#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) -# if !defined(SOCKET_HAVE_KQUEUE) -# define SOCKET_HAVE_KQUEUE -# endif -# if !defined(SOCKET_HAVE_POLL) -# define SOCKET_HAVE_POLL -# endif -#elif defined(__linux__) -# if !defined(SOCKET_HAVE_EPOLL) -# define SOCKET_HAVE_EPOLL -# endif -# if !defined(SOCKET_HAVE_POLL) -# define SOCKET_HAVE_POLL -# endif -#endif - -/* - * Define SOCKET_DEFAULT_BACKEND - * ------------------------------------------------------------------ - */ - -/** - * Defines the default Listener backend to use. - * - * @note Do not rely on the value shown in doxygen. - */ -#if defined(_WIN32) -# if !defined(SOCKET_DEFAULT_BACKEND) -# if defined(SOCKET_HAVE_POLL) -# define SOCKET_DEFAULT_BACKEND Poll -# else -# define SOCKET_DEFAULT_BACKEND Select -# endif -# endif -#elif defined(__linux__) -# include <sys/epoll.h> - -# if !defined(SOCKET_DEFAULT_BACKEND) -# define SOCKET_DEFAULT_BACKEND Epoll -# endif -#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__) -# include <sys/types.h> -# include <sys/event.h> -# include <sys/time.h> - -# if !defined(SOCKET_DEFAULT_BACKEND) -# define SOCKET_DEFAULT_BACKEND Kqueue -# endif -#else -# if !defined(SOCKET_DEFAULT_BACKEND) -# define SOCKET_DEFAULT_BACKEND Select -# endif -#endif - -#if defined(SOCKET_HAVE_POLL) && !defined(_WIN32) -# include <poll.h> -#endif - -/* - * Headers to include - * ------------------------------------------------------------------ - */ - -#if defined(_WIN32) -# include <cstdlib> - -# include <WinSock2.h> -# include <WS2tcpip.h> -#else -# include <cerrno> - -# include <sys/ioctl.h> -# include <sys/types.h> -# include <sys/socket.h> -# include <sys/un.h> - -# include <arpa/inet.h> - -# include <netinet/in.h> -# include <netinet/tcp.h> - -# include <fcntl.h> -# include <netdb.h> -# include <unistd.h> -#endif - -#if !defined(SOCKET_NO_SSL) -# include <openssl/err.h> -# include <openssl/evp.h> -# include <openssl/ssl.h> -#endif - -#include <cassert> -#include <chrono> -#include <cstdlib> -#include <cstring> -#include <exception> -#include <functional> -#include <map> -#include <memory> -#include <string> -#include <vector> - -/** - * General network namespace. - */ -namespace net { - -/* - * Portables types - * ------------------------------------------------------------------ - * - * The following types are defined differently between Unix and Windows. - */ - -/* {{{ Protocols */ - -#if defined(_WIN32) - -/** - * Socket type, SOCKET. - */ -using Handle = SOCKET; - -/** - * Argument to pass to set. - */ -using ConstArg = const char *; - -/** - * Argument to pass to get. - */ -using Arg = char *; - -#else - -/** - * Socket type, int. - */ -using Handle = int; - -/** - * Argument to pass to set. - */ -using ConstArg = const void *; - -/** - * Argument to pass to get. - */ -using Arg = void *; - -#endif - -/* }}} */ - -/* - * Portable constants - * ------------------------------------------------------------------ - * - * These constants are needed to check functions return codes, they are rarely needed in end user code. - */ - -/* {{{ Constants */ - -/* - * The following constants are defined differently from Unix - * to Windows. - */ -#if defined(_WIN32) - -/** - * Socket creation failure or invalidation. - */ -extern const Handle Invalid; - -/** - * Socket operation failure. - */ -extern const int Failure; - -#else - -/** - * Socket creation failure or invalidation. - */ -extern const int Invalid; - -/** - * Socket operation failure. - */ -extern const int Failure; - -#endif - -#if !defined(SOCKET_NO_SSL) - -namespace ssl { - -/** - * @enum Method - * @brief Which OpenSSL method to use. - */ -enum Method { - Tlsv1, //!< TLS v1.2 (recommended) - Sslv3 //!< SSLv3 -}; - -} // !ssl - -#endif - -/* }}} */ - -/* - * Portable functions - * ------------------------------------------------------------------ - * - * The following free functions can be used to initialize the library or to get the last system error. - */ - -/* {{{ Functions */ - -/** - * Initialize the socket library. Except if you defined SOCKET_NO_AUTO_INIT, you don't need to call this - * function manually. - */ -void init() noexcept; - -/** - * Close the socket library. - */ -void finish() noexcept; - -#if !defined(SOCKET_NO_SSL) - -/** - * OpenSSL namespace. - */ -namespace ssl { - -/** - * Initialize the OpenSSL library. Except if you defined SOCKET_NO_AUTO_SSL_INIT, you don't need to call this function - * manually. - */ -void init() noexcept; - -/** - * Close the OpenSSL library. - */ -void finish() noexcept; - -} // !ssl - -#endif // SOCKET_NO_SSL - -/** - * Get the last socket system error. The error is set from errno or from - * WSAGetLastError on Windows. - * - * @return a string message - */ -std::string error(); - -/** - * Get the last system error. - * - * @param errn the error number (errno or WSAGetLastError) - * @return the error - */ -std::string error(int errn); - -/* }}} */ - -/* - * Error class - * ------------------------------------------------------------------ - * - * This is the main exception thrown on socket operations. - */ - -/* {{{ Error */ - -/** - * @class Error - * @brief Base class for sockets error - */ -class Error : public std::exception { -public: - /** - * @enum Code - * @brief Which kind of error - */ - enum Code { - Timeout, ///!< The action did timeout - System, ///!< There is a system error - Other ///!< Other custom error - }; - -private: - Code m_code; - std::string m_function; - std::string m_error; - -public: - /** - * Constructor that use the last system error. - * - * @param code which kind of error - * @param function the function name - */ - Error(Code code, std::string function); - - /** - * Constructor that use the system error set by the user. - * - * @param code which kind of error - * @param function the function name - * @param error the error - */ - Error(Code code, std::string function, int error); - - /** - * Constructor that set the error specified by the user. - * - * @param code which kind of error - * @param function the function name - * @param error the error - */ - Error(Code code, std::string function, std::string error); - - /** - * Get which function has triggered the error. - * - * @return the function name (e.g connect) - */ - inline const std::string &function() const noexcept - { - return m_function; - } - - /** - * The error code. - * - * @return the code - */ - inline Code code() const noexcept - { - return m_code; - } - - /** - * Get the error (only the error content). - * - * @return the error - */ - const char *what() const noexcept - { - return m_error.c_str(); - } -}; - -/* }}} */ - -/* - * State class - * ------------------------------------------------------------------ - * - * To facilitate higher-level stuff, the socket has a state. - */ - -/* {{{ State */ - -/** - * @enum State - * @brief Current socket state. - */ -enum class State { - Open, //!< Socket is open - Bound, //!< Socket is bound to an address - Connecting, //!< The connection is in progress - Connected, //!< Connection is complete - Accepted, //!< Socket has been accepted (client) - Accepting, //!< The client acceptation is in progress - Closed, //!< The socket has been closed - Disconnected, //!< The connection was lost -}; - -/* }}} */ - -/* - * Action enum - * ------------------------------------------------------------------ - * - * Defines the pending operation. - */ - -/* {{{ Action */ - -/** - * @enum Action - * @brief Define the current operation that must complete. - * - * Some operations like accept, connect, recv or send must sometimes do several round-trips to complete and the socket - * action is set with that enumeration. The user is responsible of calling accept, connect send or recv until the - * operation is complete. - * - * Note: the user must wait for the appropriate condition in Socket::condition to check if the required condition is - * met. - * - * It is important to complete the operation in the correct order because protocols like Tls may require to continue - * re-negociating when calling Socket::send or Socket::Recv. - */ -enum class Action { - None, //!< No action is required, socket is ready - Accept, //!< The socket is not yet accepted, caller must call accept() again - Connect, //!< The socket is not yet connected, caller must call connect() again - Receive, //!< The received operation has not succeeded yet, caller must call recv() or recvfrom() again - Send //!< The send operation has not succeded yet, caller must call send() or sendto() again -}; - -/* }}} */ - -/* - * Condition enum - * ------------------------------------------------------------------ - * - * Defines if we must wait for reading or writing. - */ - -/* {{{ Condition */ - -/** - * @enum Condition - * @brief Define the required condition for the socket. - * - * As explained in Action enumeration, some operations required to be called several times, before calling these - * operations, the user must wait the socket to be readable or writable. This can be checked with Socket::condition. - */ -enum class Condition { - None, //!< No condition is required - Readable = (1 << 0), //!< The socket must be readable - Writable = (1 << 1) //!< The socket must be writable -}; - -/** - * Apply bitwise XOR. - * - * @param v1 the first value - * @param v2 the second value - * @return the new value - */ -constexpr Condition operator^(Condition v1, Condition v2) noexcept -{ - return static_cast<Condition>(static_cast<int>(v1) ^ static_cast<int>(v2)); -} - -/** - * Apply bitwise AND. - * - * @param v1 the first value - * @param v2 the second value - * @return the new value - */ -constexpr Condition operator&(Condition v1, Condition v2) noexcept -{ - return static_cast<Condition>(static_cast<int>(v1) & static_cast<int>(v2)); -} - -/** - * Apply bitwise OR. - * - * @param v1 the first value - * @param v2 the second value - * @return the new value - */ -constexpr Condition operator|(Condition v1, Condition v2) noexcept -{ - return static_cast<Condition>(static_cast<int>(v1) | static_cast<int>(v2)); -} - -/** - * Apply bitwise NOT. - * - * @param v the value - * @return the complement - */ -constexpr Condition operator~(Condition v) noexcept -{ - return static_cast<Condition>(~static_cast<int>(v)); -} - -/** - * Assign bitwise OR. - * - * @param v1 the first value - * @param v2 the second value - * @return the new value - */ -inline Condition &operator|=(Condition &v1, Condition v2) noexcept -{ - v1 = static_cast<Condition>(static_cast<int>(v1) | static_cast<int>(v2)); - - return v1; -} - -/** - * Assign bitwise AND. - * - * @param v1 the first value - * @param v2 the second value - * @return the new value - */ -inline Condition &operator&=(Condition &v1, Condition v2) noexcept -{ - v1 = static_cast<Condition>(static_cast<int>(v1) & static_cast<int>(v2)); - - return v1; -} - -/** - * Assign bitwise XOR. - * - * @param v1 the first value - * @param v2 the second value - * @return the new value - */ -inline Condition &operator^=(Condition &v1, Condition v2) noexcept -{ - v1 = static_cast<Condition>(static_cast<int>(v1) ^ static_cast<int>(v2)); - - return v1; -} - -/* }}} */ - -/* - * Base Socket class - * ------------------------------------------------------------------ - * - * This base class has operations that are common to all types of sockets but you usually instanciate - * a SocketTcp or SocketUdp - */ - -/* {{{ Socket */ - -/** - * @class Socket - * @brief Base socket class for socket operations. - * - * **Important:** When using non-blocking sockets, some considerations must be taken. See the implementation of the - * underlying protocol for more details. - * - * @see protocol::Tls - * @see protocol::Tcp - * @see protocol::Udp - */ -template <typename Address, typename Protocol> -class Socket { -private: - Protocol m_proto; - State m_state{State::Closed}; - Action m_action{Action::None}; - Condition m_condition{Condition::None}; - -protected: - /** - * The native handle. - */ - Handle m_handle{Invalid}; - -public: - /** - * Create a socket handle. - * - * This is the primary function and the only one that creates the socket handle, all other constructors - * are just overloaded functions. - * - * @param domain the domain AF_* - * @param type the type SOCK_* - * @param protocol the protocol - * @param iface the implementation - * @throw net::Error on errors - * @post state is set to Open - * @post handle is not set to Invalid - */ - Socket(int domain, int type, int protocol, Protocol iface = {}) - : m_proto(std::move(iface)) - { -#if !defined(SOCKET_NO_AUTO_INIT) - init(); -#endif - m_handle = ::socket(domain, type, protocol); - - if (m_handle == Invalid) { - throw Error{Error::System, "socket"}; - } - - m_proto.create(*this); - m_state = State::Open; - - assert(m_handle != Invalid); - } - - /** - * This tries to create a socket. - * - * Domain and type are determined by the Address and Protocol object. - * - * @param protocol the protocol - * @param address which type of address - * @throw net::Error on errors - */ - explicit inline Socket(Protocol protocol = {}, const Address &address = {}) - : Socket{address.domain(), protocol.type(), 0, std::move(protocol)} - { - } - - /** - * Construct a socket with an already created descriptor. - * - * @param handle the native descriptor - * @param state specify the socket state - * @param protocol the type of socket implementation - * @post action is set to None - * @post condition is set to None - */ - explicit inline Socket(Handle handle, State state = State::Closed, Protocol protocol = {}) noexcept - : m_proto(std::move(protocol)) - , m_state{state} - , m_handle{handle} - { - assert(m_action == Action::None); - assert(m_condition == Condition::None); - } - - /** - * Create an invalid socket. Can be used when you cannot instanciate the socket immediately. - */ - explicit inline Socket(std::nullptr_t) noexcept - : m_handle{Invalid} - { - } - - /** - * Copy constructor deleted. - */ - Socket(const Socket &) = delete; - - /** - * Transfer ownership from other to this. - * - * @param other the other socket - */ - inline Socket(Socket &&other) noexcept - : m_proto(std::move(other.m_proto)) - , m_state{other.m_state} - , m_action{other.m_action} - , m_condition{other.m_condition} - , m_handle{other.m_handle} - { - /* Invalidate other */ - other.m_handle = Invalid; - other.m_state = State::Closed; - other.m_action = Action::None; - other.m_condition = Condition::None; - } - - /** - * Default destructor. - */ - virtual ~Socket() - { - close(); - } - - /** - * Access the implementation. - * - * @return the implementation - * @warning use this function with care - */ - inline const Protocol &protocol() const noexcept - { - return m_proto; - } - - /** - * Overloaded function. - * - * @return the implementation - */ - inline Protocol &protocol() noexcept - { - return m_proto; - } - - /** - * Get the current socket state. - * - * @return the state - */ - inline State state() const noexcept - { - return m_state; - } - - /** - * Change the current socket state. - * - * @param state the new state - * @warning only implementations should call this function - */ - inline void setState(State state) noexcept - { - m_state = state; - } - - /** - * Get the pending operation. - * - * @return the action to complete before continuing - * @note usually only needed in non-blocking sockets - */ - inline Action action() const noexcept - { - return m_action; - } - - /** - * Change the pending operation. - * - * @param action the action - * @warning you should not call this function yourself - */ - inline void setAction(Action action) noexcept - { - m_action = action; - } - - /** - * Get the condition to wait for. - * - * @return the condition - */ - inline Condition condition() const noexcept - { - return m_condition; - } - - /** - * Change the condition required. - * - * @param condition the condition - * @warning you should not call this function yourself - */ - inline void setCondition(Condition condition) noexcept - { - m_condition = condition; - } - - /** - * Set an option for the socket. Wrapper of setsockopt(2). - * - * @param level the setting level - * @param name the name - * @param arg the value - * @throw net::Error on errors - */ - template <typename Argument> - void set(int level, int name, const Argument &arg) - { - if (setsockopt(m_handle, level, name, (ConstArg)&arg, sizeof (arg)) == Failure) { - throw Error{Error::System, "set"}; - } - } - - /** - * Object-oriented option setter. - * - * The object must have `set(Socket<Address, Protocol> &) const`. - * - * @param option the option - * @throw net::Error on errors - */ - template <typename Option> - inline void set(const Option &option) - { - option.set(*this); - } - - /** - * Get an option for the socket. Wrapper of getsockopt(2). - * - * @param level the setting level - * @param name the name - * @throw net::Error on errors - */ - template <typename Argument> - Argument get(int level, int name) - { - Argument desired, result{}; - socklen_t size = sizeof (result); - - if (getsockopt(m_handle, level, name, (Arg)&desired, &size) == Failure) { - throw Error{Error::System, "get"}; - } - - std::memcpy(&result, &desired, size); - - return result; - } - - /** - * Object-oriented option getter. - * - * The object must have `T get(Socket<Address, Protocol> &) const`, T can be any type and it is the value - * returned from this function. - * - * @return the same value as get() in the option - * @throw net::Error on errors - */ - template <typename Option> - inline auto get() -> decltype(std::declval<Option>().get(*this)) - { - return Option{}.get(*this); - } - - /** - * Get the native handle. - * - * @return the handle - * @warning Not portable - */ - inline Handle handle() const noexcept - { - return m_handle; - } - - /** - * Bind using a native address. - * - * @param address the address - * @param length the size - * @pre state must not be Bound - * @throw net::Error on errors - */ - void bind(const sockaddr *address, socklen_t length) - { - assert(m_state != State::Bound); - - if (::bind(m_handle, address, length) == Failure) { - throw Error{Error::System, "bind"}; - } - - m_state = State::Bound; - } - - /** - * Overload that takes an address. - * - * @param address the address - * @throw net::Error on errors - */ - inline void bind(const Address &address) - { - bind(address.address(), address.length()); - } - - /** - * Listen for pending connection. - * - * @param max the maximum number - * @pre state must be Bound - * @throw net::Error on errors - */ - inline void listen(int max = 128) - { - assert(m_state == State::Bound); - - if (::listen(this->m_handle, max) == Failure) { - throw Error{Error::System, "listen"}; - } - } - - /** - * Connect to the address. - * - * If connection cannot be established immediately, connect with no argument must be called again. See - * the underlying protocol for more information. - * - * @pre state must be State::Open - * @param address the address - * @param length the the address length - * @throw net::Error on errors - * @post state is set to State::Connecting or State::Connected - * @note For non-blocking sockets, see the underlying protocol function for more details - */ - void connect(const sockaddr *address, socklen_t length) - { - assert(m_state == State::Open); - - m_action = Action::None; - m_condition = Condition::None; - - m_proto.connect(*this, address, length); - - assert((m_state == State::Connected && m_action == Action::None && m_condition == Condition::None) || - (m_state == State::Connecting && m_action == Action::Connect && m_condition != Condition::None)); - } - - /** - * Overloaded function. - * - * Effectively call connect(address.address(), address.length()); - * - * @param address the address - */ - inline void connect(const Address &address) - { - connect(address.address(), address.length()); - } - - /** - * Continue the connection, only required with non-blocking sockets. - * - * @pre state must be State::Connecting - * @throw net::Error on errors - */ - void connect() - { - assert(m_state == State::Connecting); - - m_action = Action::None; - m_condition = Condition::None; - - m_proto.connect(*this); - - assert((m_state == State::Connected && m_action == Action::None && m_condition == Condition::None) || - (m_state == State::Connecting && m_action == Action::Connect && m_condition != Condition::None)); - } - - /** - * Accept a new client. If there are no pending connection, throws an error. - * - * If the client cannot be accepted immediately, the client is returned and accept with no arguments - * must be called on it. See the underlying protocol for more information. - * - * @pre state must be State::Bound - * @param info the address where to store client's information (optional) - * @return the new socket - * @throw Error on errors - * @post returned client's state is set to State::Accepting or State::Accepted - * @note For non-blocking sockets, see the underlying protocol function for more details - */ - Socket<Address, Protocol> accept(Address *info) - { - assert(m_state == State::Bound); - - m_action = Action::None; - m_condition = Condition::None; - - sockaddr_storage storage; - socklen_t length = sizeof (storage); - - Socket<Address, Protocol> sc = m_proto.accept(*this, reinterpret_cast<sockaddr *>(&storage), &length); - - if (info) { - *info = Address{&storage, length}; - } - - /* Master do not change */ - assert(m_state == State::Bound); - assert(m_action == Action::None); - assert(m_condition == Condition::None); - - /* Client */ - assert( - (sc.state() == State::Accepting && sc.action() == Action::Accept && sc.condition() != Condition::None) || - (sc.state() == State::Accepted && sc.action() == Action::None && sc.condition() == Condition::None) - ); - - return sc; - } - - /** - * Continue the accept process on this client. This function must be called only when the socket is - * ready to be readable or writable! (see condition). - * - * @pre state must be State::Accepting - * @throw Error on errors - * @post if connection is complete, state is changed to State::Accepted, action and condition are unset - * @post if connection is still in progress, condition is set - */ - void accept() - { - assert(m_state == State::Accepting); - - m_action = Action::None; - m_condition = Condition::None; - - m_proto.accept(*this); - - assert( - (m_state == State::Accepting && m_action == Action::Accept && m_condition != Condition::None) || - (m_state == State::Accepted && m_action == Action::None && m_condition == Condition::None) - ); - } - - /** - * Get the local name. This is a wrapper of getsockname(). - * - * @return the address - * @throw Error on failures - * @pre state() must not be State::Closed - */ - Address address() const - { - assert(m_state != State::Closed); - - sockaddr_storage ss; - socklen_t length = sizeof (sockaddr_storage); - - if (::getsockname(m_handle, (sockaddr *)&ss, &length) == Failure) { - throw Error{Error::System, "getsockname"}; - } - - return Address(&ss, length); - } - - /** - * Receive some data. - * - * If the operation cannot be complete immediately, 0 is returned and user must call the function - * again when ready. See the underlying protocol for more information. - * - * If action is set to Action::None and result is set to 0, disconnection occured. - * - * @param data the destination buffer - * @param length the buffer length - * @pre action must not be Action::Send - * @return the number of bytes received or 0 - * @throw Error on error - * @note For non-blocking sockets, see the underlying protocol function for more details - */ - unsigned recv(void *data, unsigned length) - { - assert(m_action != Action::Send); - - m_action = Action::None; - m_condition = Condition::None; - - return m_proto.recv(*this, data, length); - } - - /** - * Overloaded function. - * - * @param count the number of bytes to receive - * @return the string - * @throw Error on error - */ - inline std::string recv(unsigned count) - { - std::string result; - - result.resize(count); - auto n = recv(const_cast<char *>(result.data()), count); - result.resize(n); - - return result; - } - - /** - * Send some data. - * - * If the operation cannot be complete immediately, 0 is returned and user must call the function - * again when ready. See the underlying protocol for more information. - * - * @param data the data buffer - * @param length the buffer length - * @return the number of bytes sent or 0 - * @pre action() must not be Flag::Receive - * @throw Error on error - * @note For non-blocking sockets, see the underlying protocol function for more details - */ - unsigned send(const void *data, unsigned length) - { - assert(m_action != Action::Receive); - - m_action = Action::None; - m_condition = Condition::None; - - return m_proto.send(*this, data, length); - } - - /** - * Overloaded function. - * - * @param data the string to send - * @return the number of bytes sent - * @throw Error on error - */ - inline unsigned send(const std::string &data) - { - return send(data.c_str(), data.size()); - } - - /** - * Send data to an end point. - * - * If the operation cannot be complete immediately, 0 is returned and user must call the function - * again when ready. See the underlying protocol for more information. - * - * @param data the buffer - * @param length the buffer length - * @param address the client address - * @param addrlen the address length - * @return the number of bytes sent - * @throw net::Error on errors - * @note For non-blocking sockets, see the underlying protocol function for more details - */ - inline unsigned sendto(const void *data, unsigned length, const sockaddr *address, socklen_t addrlen) - { - return m_proto.sendto(*this, data, length, address, addrlen); - } - - /** - * Overloaded function. - * - * @param data the buffer - * @param length the buffer length - * @param address the destination - * @return the number of bytes sent - * @throw net::Error on errors - */ - inline unsigned sendto(const void *data, unsigned length, const Address &address) - { - return sendto(data, length, address.address(), address.length()); - } - - /** - * Overloaded function. - * - * @param data the data - * @param address the address - * @return the number of bytes sent - * @throw net:;Error on errors - */ - inline unsigned sendto(const std::string &data, const Address &address) - { - return sendto(data.c_str(), data.length(), address); - } - - /** - * Receive data from an end point. - * - * If the operation cannot be complete immediately, 0 is returned and user must call the function - * again when ready. See the underlying protocol for more information. - * - * @param data the destination buffer - * @param length the buffer length - * @param address the address destination - * @param addrlen the address length (in/out) - * @return the number of bytes received - * @throw net::Error on errors - * @note For non-blocking sockets, see the underlying protocol function for more details - */ - inline unsigned recvfrom(void *data, unsigned length, sockaddr *address, socklen_t *addrlen) - { - return m_proto.recvfrom(*this, data, length, address, addrlen); - } - - /** - * Overloaded function. - * - * @param data the destination buffer - * @param length the buffer length - * @param info the address destination - * @return the number of bytes received - * @throw net::Error on errors - */ - inline unsigned recvfrom(void *data, unsigned length, Address *info = nullptr) - { - sockaddr_storage storage; - socklen_t addrlen = sizeof (sockaddr_storage); - - auto n = recvfrom(data, length, reinterpret_cast<sockaddr *>(&storage), &addrlen); - - if (info && n != 0) { - *info = Address{&storage, addrlen}; - } - - return n; - } - - /** - * Overloaded function. - * - * @param count the maximum number of bytes to receive - * @param info the client information - * @return the string - * @throw net::Error on errors - */ - std::string recvfrom(unsigned count, Address *info = nullptr) - { - std::string result; - - result.resize(count); - auto n = recvfrom(const_cast<char *>(result.data()), count, info); - result.resize(n); - - return result; - } - - /** - * Close the socket. - * - * Automatically called from the destructor. - */ - void close() - { - if (m_handle != Invalid) { -#if defined(_WIN32) - ::closesocket(m_handle); -#else - ::close(m_handle); -#endif - m_handle = Invalid; - } - - m_state = State::Closed; - m_action = Action::None; - m_condition = Condition::None; - } - - /** - * Assignment operator forbidden. - * - * @return *this - */ - Socket &operator=(const Socket &) = delete; - - /** - * Transfer ownership from other to this. The other socket is left - * invalid and will not be closed. - * - * @param other the other socket - * @return this - */ - Socket &operator=(Socket &&other) noexcept - { - m_handle = other.m_handle; - m_proto = std::move(other.m_proto); - m_state = other.m_state; - m_action = other.m_action; - m_condition = other.m_condition; - - /* Invalidate other */ - other.m_handle = Invalid; - other.m_state = State::Closed; - other.m_action = Action::None; - other.m_condition = Condition::None; - - return *this; - } -}; - -/** - * Compare two sockets. - * - * @param s1 the first socket - * @param s2 the second socket - * @return true if they equals - */ -template <typename Address, typename Protocol> -bool operator==(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() == s2.handle(); -} - -/** - * Compare two sockets. - * - * @param s1 the first socket - * @param s2 the second socket - * @return true if they are different - */ -template <typename Address, typename Protocol> -bool operator!=(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() != s2.handle(); -} - -/** - * Compare two sockets. - * - * @param s1 the first socket - * @param s2 the second socket - * @return true if s1 < s2 - */ -template <typename Address, typename Protocol> -bool operator<(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() < s2.handle(); -} - -/** - * Compare two sockets. - * - * @param s1 the first socket - * @param s2 the second socket - * @return true if s1 > s2 - */ -template <typename Address, typename Protocol> -bool operator>(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() > s2.handle(); -} - -/** - * Compare two sockets. - * - * @param s1 the first socket - * @param s2 the second socket - * @return true if s1 <= s2 - */ -template <typename Address, typename Protocol> -bool operator<=(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() <= s2.handle(); -} - -/** - * Compare two sockets. - * - * @param s1 the first socket - * @param s2 the second socket - * @return true if s1 >= s2 - */ -template <typename Address, typename Protocol> -bool operator>=(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) -{ - return s1.handle() >= s2.handle(); -} - -/* }}} */ - -/* - * Predefined options - * ------------------------------------------------------------------ - */ - -/* {{{ Options */ - -/** - * Namespace of predefined options. - */ -namespace option { - -/* - * Options for socket - * ------------------------------------------------------------------ - */ - -/* {{{ Options for socket */ - -/** - * @class SockBlockMode - * @brief Set or get the blocking-mode for a socket. - * @warning On Windows, it's not possible to check if the socket is blocking or not. - */ -class SockBlockMode { -public: - /** - * Set to false if you want non-blocking socket. - */ - bool value{false}; - - /** - * Set the option. - * - * @param sc the socket - * @throw Error on errors - */ - template <typename Address, typename Protocol> - void set(Socket<Address, Protocol> &sc) const - { -#if defined(O_NONBLOCK) && !defined(_WIN32) - int flags; - - if ((flags = fcntl(sc.handle(), F_GETFL, 0)) < 0) { - flags = 0; - } - - if (value) { - flags &= ~(O_NONBLOCK); - } else { - flags |= O_NONBLOCK; - } - - if (fcntl(sc.handle(), F_SETFL, flags) < 0) { - throw Error{Error::System, "fcntl"}; - } -#else - unsigned long flags = (value) ? 0 : 1; - - if (ioctlsocket(sc.handle(), FIONBIO, &flags) == Failure) { - throw Error{Error::System, "fcntl"}; - } -#endif - } - - /** - * Get the option. - * - * @return the value - * @throw Error on errors - */ - template <typename Address, typename Protocol> - bool get(Socket<Address, Protocol> &sc) const - { -#if defined(O_NONBLOCK) && !defined(_WIN32) - int flags = fcntl(sc.handle(), F_GETFL, 0); - - if (flags < 0) { - throw Error{Error::System, "fcntl"}; - } - - return !(flags & O_NONBLOCK); -#else - throw Error{Error::Other, "get", "Windows API cannot let you get the blocking status of a socket"}; -#endif - } -}; - -/** - * @class SockReuseAddress - * @brief Reuse address, must be used before calling Socket::bind - */ -class SockReuseAddress { -public: - /** - * Set to true if you want to set the SOL_SOCKET/SO_REUSEADDR option. - */ - bool value{true}; - - /** - * Set the option. - * - * @param sc the socket - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const - { - sc.set(SOL_SOCKET, SO_REUSEADDR, value ? 1 : 0); - } - - /** - * Get the option. - * - * @return the value - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline bool get(Socket<Address, Protocol> &sc) const - { - return static_cast<bool>(sc.template get<int>(SOL_SOCKET, SO_REUSEADDR)); - } -}; - -/** - * @class SockSendBuffer - * @brief Set or get the output buffer. - */ -class SockSendBuffer { -public: - /** - * Set to the buffer size. - */ - int value{2048}; - - /** - * Set the option. - * - * @param sc the socket - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const - { - sc.set(SOL_SOCKET, SO_SNDBUF, value); - } - - /** - * Get the option. - * - * @return the value - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline int get(Socket<Address, Protocol> &sc) const - { - return sc.template get<int>(SOL_SOCKET, SO_SNDBUF); - } -}; - -/** - * @class SockReceiveBuffer - * @brief Set or get the input buffer. - */ -class SockReceiveBuffer { -public: - /** - * Set to the buffer size. - */ - int value{2048}; - - /** - * Set the option. - * - * @param sc the socket - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const - { - sc.set(SOL_SOCKET, SO_RCVBUF, value); - } - - /** - * Get the option. - * - * @return the value - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline int get(Socket<Address, Protocol> &sc) const - { - return sc.template get<int>(SOL_SOCKET, SO_RCVBUF); - } -}; - -/* }}} */ - -/** - * @class TcpNoDelay - * @brief Set this option if you want to disable nagle's algorithm. - */ -class TcpNoDelay { -public: - /** - * Set to true to set TCP_NODELAY option. - */ - bool value{true}; - - /** - * Set the option. - * - * @param sc the socket - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const - { - sc.set(IPPROTO_TCP, TCP_NODELAY, value ? 1 : 0); - } - - /** - * Get the option. - * - * @return the value - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline bool get(Socket<Address, Protocol> &sc) const - { - return static_cast<bool>(sc.template get<int>(IPPROTO_TCP, TCP_NODELAY)); - } -}; - -/** - * @class Ipv6Only - * @brief Control IPPROTO_IPV6/IPV6_V6ONLY - * - * Note: some systems may or not set this option by default so it's a good idea to set it in any case to either - * false or true if portability is a concern. - */ -class Ipv6Only { -public: - /** - * Set this to use only IPv6. - */ - bool value{true}; - - /** - * Set the option. - * - * @param sc the socket - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline void set(Socket<Address, Protocol> &sc) const - { - sc.set(IPPROTO_IPV6, IPV6_V6ONLY, value ? 1 : 0); - } - - /** - * Get the option. - * - * @return the value - * @throw Error on errors - */ - template <typename Address, typename Protocol> - inline bool get(Socket<Address, Protocol> &sc) const - { - return static_cast<bool>(sc.template get<int>(IPPROTO_IPV6, IPV6_V6ONLY)); - } -}; - -} // !option - -/* }}} */ - -/* - * Predefined addressed to be used - * ------------------------------------------------------------------ - * - * - Ip, - * - Local. - */ - -/* {{{ Addresses */ - -/** - * Set of predefined addresses. - */ -namespace address { - -/** - * @class Ip - * @brief Base class for IPv6 and IPv4, you can use it if you don't know in advance if you'll use IPv6 or IPv4. - */ -class Ip { -public: - /** - * @enum Type - * @brief Type of ip address. - */ - enum Type { - v4 = AF_INET, //!< AF_INET - v6 = AF_INET6 //!< AF_INET6 - }; - -private: - /* - * Default domain when using default constructors. - * - * Note: AF_INET or AF_INET6, not - */ - static int m_default; - - union { - sockaddr_in m_sin; - sockaddr_in6 m_sin6; - }; - - socklen_t m_length{0}; - int m_domain{AF_INET}; - -public: - /** - * Set the default domain to use when using default Ip constructor. By default, AF_INET is used. - * - * @pre domain must be Type::v4 or Type::v6 - */ - static inline void setDefault(Type domain) noexcept - { - assert(domain == Type::v4 || domain == Type::v6); - - m_default = static_cast<int>(domain); - } - - /** - * Construct using the default domain. - */ - inline Ip() noexcept - : Ip(static_cast<Type>(m_default)) - { - } - - /** - * Default initialize the Ip domain. - * - * @pre domain must be AF_INET or AF_INET6 only - * @param domain the domain (AF_INET or AF_INET6) - */ - Ip(Type domain) noexcept; - - /** - * Construct an address suitable for bind() or connect(). - * - * @pre domain must be Type::v4 or Type::v6 - * @param domain the domain (AF_INET or AF_INET6) - * @param host the host (* for any) - * @param port the port number - * @throw Error on errors - */ - Ip(const std::string &host, int port, Type domain = v4); - - /** - * Construct an address from a storage. - * - * @pre storage's domain must be AF_INET or AF_INET6 only - * @param ss the storage - * @param length the length - */ - Ip(const sockaddr_storage *ss, socklen_t length) noexcept; - - /** - * Get the domain (AF_INET or AF_INET6). - * - * @return the domain - */ - inline int domain() const noexcept - { - return m_domain; - } - - /** - * Return the underlying address, either sockaddr_in6 or sockaddr_in. - * - * @return the address - */ - inline const sockaddr *address() const noexcept - { - if (m_domain == AF_INET6) { - return reinterpret_cast<const sockaddr *>(&m_sin6); - } - - return reinterpret_cast<const sockaddr *>(&m_sin); - } - - /** - * Return the underlying address length. - * - * @return the length - */ - inline socklen_t length() const noexcept - { - return m_length; - } - - /** - * Get the port. - * - * @return the port - */ - inline int port() const noexcept - { - if (m_domain == AF_INET6) { - return ntohs(m_sin6.sin6_port); - } - - return ntohs(m_sin.sin_port); - } -}; - -#if !defined(_WIN32) - -/** - * @class Local - * @brief unix family sockets - * - * Create an address to a specific path. Only available on Unix. - */ -class Local { -private: - sockaddr_un m_sun; - std::string m_path; - -public: - /** - * Get the domain AF_LOCAL. - * - * @return AF_LOCAL - */ - inline int domain() const noexcept - { - return AF_LOCAL; - } - - /** - * Default constructor. - */ - Local() noexcept; - - /** - * Construct an address to a path. - * - * @param path the path - * @param rm remove the file before (default: false) - */ - Local(std::string path, bool rm = false) noexcept; - - /** - * Construct an unix address from a storage address. - * - * @pre storage's domain must be AF_LOCAL - * @param ss the storage - * @param length the length - */ - Local(const sockaddr_storage *ss, socklen_t length) noexcept; - - /** - * Get the sockaddr_un. - * - * @return the address - */ - inline const sockaddr *address() const noexcept - { - return reinterpret_cast<const sockaddr *>(&m_sun); - } - - /** - * Get the address length. - * - * @return the length - */ - inline socklen_t length() const noexcept - { -#if defined(SOCKET_HAVE_SUN_LEN) - return SUN_LEN(&m_sun); -#else - return sizeof (m_sun); -#endif - } -}; - -#endif // !_WIN32 - -} // !address - -/* }}} */ - -/* - * Predefined protocols - * ------------------------------------------------------------------ - * - * - Tcp, for standard stream connections, - * - Udp, for standard datagram connections, - * - Tls, for secure stream connections. - */ - -/* {{{ Protocols */ - -/** - * Set of predefined protocols. - */ -namespace protocol { - -/* {{{ Tcp */ - -/** - * @class Tcp - * @brief Clear TCP implementation. - * - * This is the basic TCP protocol that implements recv, send, connect and accept as wrappers of the usual - * C functions. - */ -class Tcp { -public: - /** - * Socket type. - * - * @return SOCK_STREAM - */ - inline int type() const noexcept - { - return SOCK_STREAM; - } - - /** - * Do nothing. - * - * This function is just present for compatibility, it should never be called. - */ - template <typename Address> - inline void create(Socket<Address, Tcp> &) const noexcept - { - /* No-op */ - } - - /** - * Standard connect. - * - * If the socket is marked non-blocking and the connection cannot be established immediately, then the - * following is true: - * - * - state is set to State::Connecting, - * - action is set to Action::Connect, - * - condition is set to Condition::Writable. - * - * Then the user must wait until the socket is writable and call connect() with 0 arguments. - * - * If the socket is blocking, this function blocks until the connection is complete or an error occurs, in - * that case state is either set to State::Connected or State::Disconnected but action and condition are - * not set. - * - * @param sc the socket - * @param address the address - * @param length the length - * @throw net::Error on errors - * @note Wrapper of connect(2) - */ - template <typename Address, typename Protocol> - void connect(Socket<Address, Protocol> &sc, const sockaddr *address, socklen_t length) - { - if (::connect(sc.handle(), address, length) == Failure) { - /* - * Determine if the error comes from a non-blocking connect that cannot be - * accomplished yet. - */ -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - sc.setState(State::Connecting); - sc.setAction(Action::Connect); - sc.setCondition(Condition::Writable); - } else { - sc.setState(State::Disconnected); - throw Error{Error::System, "connect", error}; - } -#else - if (errno == EINPROGRESS) { - sc.setState(State::Connecting); - sc.setAction(Action::Connect); - sc.setCondition(Condition::Writable); - } else { - sc.setState(State::Disconnected); - throw Error{Error::System, "connect"}; - } -#endif - } else { - sc.setState(State::Connected); - } - } - - /** - * Continue the connection. This function must only be called when the socket is ready for writing, - * the user is responsible of waiting for that condition. - * - * This function check for SOL_SOCKET/SO_ERROR status. - * - * If the connection is complete, status is set to State::Connected, otherwise it is set to - * State::Disconnected. In both cases, action and condition are not set. - * - * @param sc the socket - * @throw net::Error on errors - */ - template <typename Address, typename Protocol> - void connect(Socket<Address, Protocol> &sc) - { - int error = sc.template get<int>(SOL_SOCKET, SO_ERROR); - - if (error == Failure) { - sc.setState(State::Disconnected); - throw Error{Error::System, "connect", error}; - } - - sc.setState(State::Connected); - } - - /** - * Accept a clear client. - * - * If the socket is marked non-blocking and there are no pending connection, this function throws an - * error. The user must wait that the socket is readable before calling this function. - * - * If the socket is blocking, this function blocks until a new client is connected or throws an error on - * errors. - * - * If the socket is correctly returned, its state is set to State::Accepted and its action and condition - * are not set. - * - * In any case, action and condition of this socket are not set. - * - * @param sc the socket - * @param address the address destination - * @param length the address length - * @return the socket - * @throw net::Error on errors - * @note Wrapper of accept(2) - */ - template <typename Address, typename Protocol> - Socket<Address, Protocol> accept(Socket<Address, Protocol> &sc, sockaddr *address, socklen_t *length) - { - Handle handle = ::accept(sc.handle(), address, length); - - if (handle == Invalid) { - throw Error{Error::System, "accept"}; - } - - return Socket<Address, Protocol>{handle, State::Accepted}; - } - - /** - * Continue accept. - * - * This function is just present for compatibility, it should never be called. - */ - template <typename Address, typename Protocol> - inline void accept(Socket<Address, Protocol> &) const noexcept - { - /* no-op */ - } - - /** - * Receive data. - * - * If the socket is marked non-blocking and no data is available, 0 is returned and condition is set to - * Condition::Readable. If 0 is returned and condition is not set, then the state is set to - * State::Disconnected. - * - * If the socket is blocking, this function blocks until some data is available or if an error occurs. - * - * In any case, action is never set. - * - * @param sc the socket - * @param data the destination - * @param length the destination length - * @return the number of bytes read - * @throw Error on errors - * @note Wrapper of recv(2) - */ - template <typename Address> - unsigned recv(Socket<Address, Tcp> &sc, void *data, unsigned length) - { - int nbread = ::recv(sc.handle(), (Arg)data, length, 0); - - if (nbread == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - nbread = 0; - sc.setCondition(Condition::Readable); - } else { - sc.setState(State::Disconnected); - throw Error{Error::System, "recv", error}; - } -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - sc.setCondition(Condition::Readable); - } else { - sc.setState(State::Disconnected); - throw Error{Error::System, "recv"}; - } -#endif - } else if (nbread == 0) { - sc.setState(State::Disconnected); - } - - return static_cast<unsigned>(nbread); - } - - /** - * Send some data. - * - * If the socket is marked non-blocking and the operation would block, then 0 is returned and condition is set to - * Condition::Writable. - * - * If the socket is blocking, this function blocks until the data has been sent. - * - * On any other errors, this function throw net::Error. - * - * @param sc the socket - * @param data the buffer to send - * @param length the buffer length - * @return the number of bytes sent - * @throw net::Error on errors - * @note Wrapper of send(2) - */ - template <typename Address> - unsigned send(Socket<Address, Tcp> &sc, const void *data, unsigned length) - { - int nbsent = ::send(sc.handle(), (ConstArg)data, length, 0); - - if (nbsent == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - nbsent = 0; - sc.setCondition(Condition::Writable); - } else { - sc.setState(State::Disconnected); - throw Error{Error::System, "send", error}; - } -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - nbsent = 0; - sc.setCondition(Condition::Writable); - } else { - sc.setState(State::Disconnected); - throw Error{Error::System, "send"}; - } -#endif - } - - return static_cast<unsigned>(nbsent); - } -}; - -/* }}} */ - -/* {{{ Udp */ - -/** - * @class Udp - * @brief Clear UDP type. - * - * This class is the basic implementation of UDP sockets. - */ -class Udp { -public: - /** - * Socket type. - * - * @return SOCK_DGRAM - */ - inline int type() const noexcept - { - return SOCK_DGRAM; - } - - /** - * Do nothing. - */ - template <typename Address> - inline void create(Socket<Address, Udp> &) noexcept - { - /* No-op */ - } - - /** - * Receive data from an end point. - * - * If the socket is marked non-blocking and no data is available, 0 is returned and condition is set to - * Condition::Readable. - * - * If the socket is blocking, this functions blocks until some data is available or if an error occurs. - * - * @param sc the socket - * @param data the destination buffer - * @param length the buffer length - * @param address the address - * @param addrlen the initial address length - * @return the number of bytes received - * @throw Error on error - */ - template <typename Address> - unsigned recvfrom(Socket<Address, Udp> &sc, void *data, unsigned length, sockaddr *address, socklen_t *addrlen) - { - int nbread; - - nbread = ::recvfrom(sc.handle(), (Arg)data, length, 0, address, addrlen); - - if (nbread == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - nbread = 0; - sc.setCondition(Condition::Readable); - } else { - throw Error{Error::System, "recvfrom"}; - } -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - nbread = 0; - sc.setCondition(Condition::Readable); - } else { - throw Error{Error::System, "recvfrom"}; - } -#endif - } - - return static_cast<unsigned>(nbread); - } - - /** - * Send data to an end point. - * - * If the socket is marked non-blocking and the operation would block, then 0 is returned and condition is set to - * Condition::Writable. - * - * If the socket is blocking, this functions blocks until the data has been sent. - * - * @param sc the socket - * @param data the buffer - * @param length the buffer length - * @param address the client address - * @param addrlen the adderss length - * @return the number of bytes sent - * @throw Error on error - */ - template <typename Address> - unsigned sendto(Socket<Address, Udp> &sc, const void *data, unsigned length, const sockaddr *address, socklen_t addrlen) - { - int nbsent; - - nbsent = ::sendto(sc.handle(), (ConstArg)data, length, 0, address, addrlen); - if (nbsent == Failure) { -#if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - nbsent = 0; - sc.setCondition(Condition::Writable); - } else { - throw Error{Error::System, "sendto", error}; - } -#else - if (errno == EAGAIN || errno == EWOULDBLOCK) { - nbsent = 0; - sc.setCondition(Condition::Writable); - } else { - throw Error{Error::System, "sendto"}; - } -#endif - } - - return static_cast<unsigned>(nbsent); - } -}; - -/* }}} */ - -/* {{{ Tls */ - -#if !defined(SOCKET_NO_SSL) - -/** - * @class Tls - * @brief OpenSSL secure layer for TCP. - * - * **Note:** This protocol is much more difficult to use with non-blocking sockets, if some operations would block, the - * user is responsible of calling the function again by waiting for the appropriate condition. See the functions for - * more details. - * - * @see Tls::accept - * @see Tls::connect - * @see Tls::recv - * @see Tls::send - */ -class Tls : private Tcp { -private: - using Context = std::shared_ptr<SSL_CTX>; - using Ssl = std::unique_ptr<SSL, void (*)(SSL *)>; - - /* OpenSSL objects */ - Context m_context; - Ssl m_ssl{nullptr, nullptr}; - - /* Status */ - bool m_tcpconnected{false}; - - /* - * User definable parameters - */ - ssl::Method m_method{ssl::Tlsv1}; - std::string m_key; - std::string m_certificate; - bool m_verify{false}; - - /* - * Construct with a context and ssl, for Tls::accept. - */ - Tls(Context context, Ssl ssl) - : m_context{std::move(context)} - , m_ssl{std::move(ssl)} - { - } - - /* - * Get the OpenSSL error message. - */ - inline std::string error(int error) - { - auto msg = ERR_reason_error_string(error); - - return msg == nullptr ? "" : msg; - } - - /* - * Update the states after an uncompleted operation. - */ - template <typename Address, typename Protocol> - inline void updateStates(Socket<Address, Protocol> &sc, State state, Action action, int code) - { - assert(code == SSL_ERROR_WANT_READ || code == SSL_ERROR_WANT_WRITE); - - sc.setState(state); - sc.setAction(action); - - if (code == SSL_ERROR_WANT_READ) { - sc.setCondition(Condition::Readable); - } else { - sc.setCondition(Condition::Writable); - } - } - - /* - * Continue the connect operation. - */ - template <typename Address, typename Protocol> - void processConnect(Socket<Address, Protocol> &sc) - { - int ret = SSL_connect(m_ssl.get()); - - if (ret <= 0) { - int no = SSL_get_error(m_ssl.get(), ret); - - if (no == SSL_ERROR_WANT_READ || no == SSL_ERROR_WANT_WRITE) { - updateStates(sc, State::Connecting, Action::Connect, no); - } else { - sc.setState(State::Disconnected); - throw Error{Error::System, "connect", error(no)}; - } - } else { - sc.setState(State::Connected); - } - } - - /* - * Continue accept. - */ - template <typename Address, typename Protocol> - void processAccept(Socket<Address, Protocol> &sc) - { - int ret = SSL_accept(m_ssl.get()); - - if (ret <= 0) { - int no = SSL_get_error(m_ssl.get(), ret); - - if (no == SSL_ERROR_WANT_READ || no == SSL_ERROR_WANT_WRITE) { - updateStates(sc, State::Accepting, Action::Accept, no); - } else { - sc.setState(State::Disconnected); - throw Error(Error::System, "accept", error(no)); - } - } else { - sc.setState(State::Accepted); - } - } - -public: - /** - * @copydoc Tcp::type - */ - inline int type() const noexcept - { - return SOCK_STREAM; - } - - /** - * Empty TLS constructor. - */ - Tls() - { -#if !defined(SOCKET_NO_SSL_AUTO_INIT) - ::net::ssl::init(); -#endif - } - - /** - * Set the method. - * - * @param method the method - * @pre the socket must not be already created - */ - inline void setMethod(ssl::Method method) noexcept - { - assert(!m_context); - assert(!m_ssl); - - m_method = method; - } - - /** - * Use the specified private key file. - * - * @param file the path to the private key - */ - inline void setPrivateKey(std::string file) noexcept - { - m_key = std::move(file); - } - - /** - * Use the specified certificate file. - * - * @param file the path to the file - */ - inline void setCertificate(std::string file) noexcept - { - m_certificate = std::move(file); - } - - /** - * Set to true if we must verify the certificate and private key. - * - * @param verify the mode - */ - inline void setVerify(bool verify = true) noexcept - { - m_verify = verify; - } - - /** - * Initialize the SSL objects after have created. - * - * @param sc the socket - * @throw net::Error on errors - */ - template <typename Address> - inline void create(Socket<Address, Tls> &sc) - { - auto method = (m_method == ssl::Tlsv1) ? TLSv1_method() : SSLv3_method(); - - m_context = {SSL_CTX_new(method), SSL_CTX_free}; - m_ssl = {SSL_new(m_context.get()), SSL_free}; - - SSL_set_fd(m_ssl.get(), sc.handle()); - - /* Load certificates */ - if (m_certificate.size() > 0) { - SSL_CTX_use_certificate_file(m_context.get(), m_certificate.c_str(), SSL_FILETYPE_PEM); - } - if (m_key.size() > 0) { - SSL_CTX_use_PrivateKey_file(m_context.get(), m_key.c_str(), SSL_FILETYPE_PEM); - } - if (m_verify && !SSL_CTX_check_private_key(m_context.get())) { - throw Error{Error::System, "(openssl)", "unable to verify key"}; - } - } - - /** - * Connect to a secure host. - * - * If the socket is marked non-blocking and the connection cannot be established yet, then the state is set - * to State::Connecting, the condition is set to Condition::Readable or Condition::Writable, the user must - * wait for the appropriate condition before calling the overload connect which takes 0 argument. - * - * If the socket is blocking, this functions blocks until the connection is complete. - * - * If the connection was completed correctly the state is set to State::Connected. - * - * @param sc the socket - * @param address the address - * @param length the address length - * @throw net::Error on errors - */ - template <typename Address, typename Protocol> - void connect(Socket<Address, Protocol> &sc, const sockaddr *address, socklen_t length) - { - /* 1. Connect using raw TCP */ - Tcp::connect(sc, address, length); - - /* 2. If the connection is complete (e.g. non-blocking), try handshake */ - if (sc.state() == State::Connected) { - m_tcpconnected = true; - processConnect(sc); - } - } - - /** - * Continue the connection. - * - * This function must be called when the socket is ready for reading or writing (check with Socket::condition), - * the state may change exactly like the initial connect call. - * - * @param sc the socket - * @throw net::Error on errors - */ - template <typename Address, typename Protocol> - void connect(Socket<Address, Protocol> &sc) - { - /* 1. Be sure to complete standard connect before */ - if (!m_tcpconnected) { - Tcp::connect(sc); - m_tcpconnected = sc.state() == State::Connected; - } - - if (m_tcpconnected) { - processConnect(sc); - } - } - - /** - * Accept a secure client. - * - * Because SSL needs several round-trips, if the socket is marked non-blocking and the connection is not - * completed yet, a new socket is returned but with the State::Accepting state. Its condition is set to - * Condition::Readable or Condition::Writable, the user is responsible of calling accept overload which takes - * 0 arguments on the returned socket when the condition is met. - * - * If the socket is blocking, this function blocks until the client is accepted and returned. - * - * If the client is accepted correctly, its state is set to State::Accepted. This instance does not change. - * - * @param sc the socket - * @param address the address destination - * @param length the address length - * @return the client - * @throw net::Error on errors - */ - template <typename Address> - Socket<Address, Tls> accept(Socket<Address, Tls> &sc, sockaddr *address, socklen_t *length) - { - Socket<Address, Tls> client = Tcp::accept(sc, address, length); - Tls &proto = client.protocol(); - - /* 1. Share the context */ - proto.m_context = m_context; - - /* 2. Create new SSL instance */ - proto.m_ssl = Ssl{SSL_new(m_context.get()), SSL_free}; - SSL_set_fd(proto.m_ssl.get(), client.handle()); - - /* 3. Try accept process on the **new** client */ - proto.processAccept(client); - - return client; - } - - /** - * Continue accept. - * - * This function must be called on the client that is being accepted. - * - * Like accept or connect, user is responsible of calling this function until the connection is complete. - * - * @param sc the socket - * @throw net::Error on errors - */ - template <typename Address, typename Protocol> - inline void accept(Socket<Address, Protocol> &sc) - { - processAccept(sc); - } - - /** - * Receive some secure data. - * - * If the socket is marked non-blocking, 0 is returned if no data is available yet or if the connection - * needs renegociation. If renegociation is required case, the action is set to Action::Receive and condition - * is set to Condition::Readable or Condition::Writable. The user must wait that the condition is met and - * call this function again. - * - * @param sc the socket - * @param data the destination - * @param len the buffer length - * @return the number of bytes read - * @throw net::Error on errors - */ - template <typename Address> - unsigned recv(Socket<Address, Tls> &sc, void *data, unsigned len) - { - auto nbread = SSL_read(m_ssl.get(), data, len); - - if (nbread <= 0) { - auto no = SSL_get_error(m_ssl.get(), nbread); - - if (no == SSL_ERROR_WANT_READ || no == SSL_ERROR_WANT_WRITE) { - nbread = 0; - updateStates(sc, sc.state(), Action::Receive, no); - } else { - throw Error{Error::System, "recv", error(no)}; - } - } - - return nbread; - } - - /** - * Send some data. - * - * Like recv, if the socket is marked non-blocking and no data can be sent or a negociation is required, - * condition and action are set. See receive for more details - * - * @param sc the socket - * @param data the data to send - * @param len the buffer length - * @return the number of bytes sent - * @throw net::Error on errors - */ - template <typename Address> - unsigned send(Socket<Address, Tls> &sc, const void *data, unsigned len) - { - auto nbsent = SSL_write(m_ssl.get(), data, len); - - if (nbsent <= 0) { - auto no = SSL_get_error(m_ssl.get(), nbsent); - - if (no == SSL_ERROR_WANT_READ || no == SSL_ERROR_WANT_WRITE) { - nbsent = 0; - updateStates(sc, sc.state(), Action::Send, no); - } else { - throw Error{Error::System, "send", error(no)}; - } - } - - return nbsent; - } -}; - -#endif // !SOCKET_NO_SSL - -/* }}} */ - -} // !protocol - -/* }}} */ - -/* - * Convenient helpers - * ------------------------------------------------------------------ - * - * - SocketTcp<Address>, for TCP sockets, - * - SocketUdp<Address>, for UDP sockets, - * - SocketTls<Address>, for secure TCP sockets. - */ - -/* {{{ Helpers */ - -/** - * Helper to create TCP sockets. - */ -template <typename Address> -using SocketTcp = Socket<Address, protocol::Tcp>; - -/** - * Helper to create TCP/IP sockets. - */ -using SocketTcpIp = Socket<address::Ip, protocol::Tcp>; - -#if !defined(_WIN32) - -/** - * Helper to create TCP/Local sockets. - */ -using SocketTcpLocal = Socket<address::Local, protocol::Tcp>; - -#endif - -/** - * Helper to create UDP sockets. - */ -template <typename Address> -using SocketUdp = Socket<Address, protocol::Udp>; - -/** - * Helper to create UDP/IP sockets. - */ -using SocketUdpIp = Socket<address::Ip, protocol::Udp>; - -#if !defined(SOCKET_NO_SSL) - -/** - * Helper to create OpenSSL TCP sockets. - */ -template <typename Address> -using SocketTls = Socket<Address, protocol::Tls>; - -/** - * Helper to create OpenSSL TCP/Ip sockets. - */ -using SocketTlsIp = Socket<address::Ip, protocol::Tls>; - -#endif // !SOCKET_NO_SSL - -/* }}} */ - -/* - * Select wrapper - * ------------------------------------------------------------------ - * - * Wrapper for select(2) and other various implementations. - */ - -/* {{{ Listener */ - -/** - * @class ListenerStatus - * @brief Result of polling - * - * Result of a select call, returns the first ready socket found with its - * flags. - */ -class ListenerStatus { -public: - Handle socket; //!< which socket is ready - Condition flags; //!< the flags -}; - -/** - * Table used in the socket listener to store which sockets have been - * set in which directions. - */ -using ListenerTable = std::map<Handle, Condition>; - -/** - * @class Select - * @brief Implements select(2) - * - * This class is the fallback of any other method, it is not preferred at all for many reasons. - */ -class Select { -public: - /** - * No-op, uses the ListenerTable directly. - */ - inline void set(const ListenerTable &, Handle, Condition, bool) noexcept {} - - /** - * No-op, uses the ListenerTable directly. - */ - inline void unset(const ListenerTable &, Handle, Condition, bool) noexcept {} - - /** - * Return the sockets - */ - std::vector<ListenerStatus> wait(const ListenerTable &table, int ms); - - /** - * Backend identifier - */ - inline const char *name() const noexcept - { - return "select"; - } -}; - -#if defined(SOCKET_HAVE_POLL) - -/** - * @class Poll - * @brief Implements poll(2). - * - * Poll is widely supported and is better than select(2). It is still not the - * best option as selecting the sockets is O(n). - */ -class Poll { -private: - std::vector<pollfd> m_fds; - - short toPoll(Condition flags) const noexcept; - Condition toCondition(short &event) const noexcept; - -public: - /** - * Set the handle. - */ - void set(const ListenerTable &, Handle, Condition, bool); - - /** - * Unset the handle. - */ - void unset(const ListenerTable &, Handle, Condition, bool); - - /** - * Wait for events. - */ - std::vector<ListenerStatus> wait(const ListenerTable &, int ms); - - /** - * Backend identifier - */ - inline const char *name() const noexcept - { - return "poll"; - } -}; - -#endif - -#if defined(SOCKET_HAVE_EPOLL) - -/** - * @class Epoll - * @brief Linux's epoll. - */ -class Epoll { -private: - int m_handle; - std::vector<epoll_event> m_events; - - Epoll(const Epoll &) = delete; - Epoll &operator=(const Epoll &) = delete; - Epoll(const Epoll &&) = delete; - Epoll &operator=(const Epoll &&) = delete; - - uint32_t toEpoll(Condition flags) const noexcept; - Condition toCondition(uint32_t events) const noexcept; - void update(Handle sc, int op, int eflags); - -public: - /** - * Construct the epoll instance. - */ - Epoll(); - - /** - * Close the epoll instance. - */ - ~Epoll(); - - /** - * Set the handle. - */ - void set(const ListenerTable &, Handle, Condition, bool); - - /** - * Unset the handle. - */ - void unset(const ListenerTable &, Handle, Condition, bool); - - /** - * Wait for events. - */ - std::vector<ListenerStatus> wait(const ListenerTable &, int); - - /** - * Backend identifier - */ - inline const char *name() const noexcept - { - return "epoll"; - } -}; - -#endif - -#if defined(SOCKET_HAVE_KQUEUE) - -/** - * @class Kqueue - * @brief Implements kqueue(2). - * - * This implementation is available on all BSD and Mac OS X. It is better than - * poll(2) because it's O(1), however it's a bit more memory consuming. - */ -class Kqueue { -private: - std::vector<struct kevent> m_result; - int m_handle; - - Kqueue(const Kqueue &) = delete; - Kqueue &operator=(const Kqueue &) = delete; - Kqueue(Kqueue &&) = delete; - Kqueue &operator=(Kqueue &&) = delete; - - void update(Handle sc, int filter, int kflags); - -public: - /** - * Construct the kqueue instance. - */ - Kqueue(); - - /** - * Destroy the kqueue instance. - */ - ~Kqueue(); - - /** - * Set the handle. - */ - void set(const ListenerTable &, Handle, Condition, bool); - - /** - * Unset the handle. - */ - void unset(const ListenerTable &, Handle, Condition, bool); - - /** - * Wait for events. - */ - std::vector<ListenerStatus> wait(const ListenerTable &, int); - - /** - * Backend identifier - */ - inline const char *name() const noexcept - { - return "kqueue"; - } -}; - -#endif - -/** - * @class Listener - * @brief Synchronous multiplexing - * - * Convenient wrapper around the select() system call. - * - * This class is implemented using a bridge pattern to allow different uses - * of listener implementation. - * - * You should not reinstanciate a new Listener at each iteartion of your - * main loop as it can be extremely costly. Instead use the same listener that - * you can safely modify on the fly. - * - * Currently, poll, epoll, select and kqueue are available. - * - * To implement the backend, the following functions must be available: - * - * ### Set - * - * @code - * void set(const ListenerTable &, Handle sc, Condition condition, bool add); - * @endcode - * - * This function, takes the socket to be added and the flags. The condition is - * always guaranteed to be correct and the function will never be called twice - * even if the user tries to set the same flag again. - * - * An optional add argument is added for backends which needs to do different - * operation depending if the socket was already set before or if it is the - * first time (e.g EPOLL_CTL_ADD vs EPOLL_CTL_MOD for epoll(7). - * - * ### Unset - * - * @code - * void unset(const ListenerTable &, Handle sc, Condition condition, bool remove); - * @endcode - * - * Like set, this function is only called if the condition is actually set and will - * not be called multiple times. - * - * Also like set, an optional remove argument is set if the socket is being - * completely removed (e.g no more flags are set for this socket). - * - * ### Wait - * - * @code - * std::vector<ListenerStatus> wait(const ListenerTable &, int ms); - * @endcode - * - * Wait for the sockets to be ready with the specified milliseconds. Must return a list of ListenerStatus, - * may throw any exceptions. - * - * ### Name - * - * @code - * inline const char *name() const noexcept - * @endcode - * - * Returns the backend name. Usually the class in lower case. - */ -template <typename Backend = SOCKET_DEFAULT_BACKEND> -class Listener { -private: - Backend m_backend; - ListenerTable m_table; - -public: - /** - * Construct an empty listener. - */ - Listener() = default; - - /** - * Get the backend. - * - * @return the backend - */ - inline const Backend &backend() const noexcept - { - return m_backend; - } - - /** - * Get the non-modifiable table. - * - * @return the table - */ - inline const ListenerTable &table() const noexcept - { - return m_table; - } - - /** - * Overloaded function. - * - * @return the iterator - */ - inline ListenerTable::const_iterator begin() const noexcept - { - return m_table.begin(); - } - - /** - * Overloaded function. - * - * @return the iterator - */ - inline ListenerTable::const_iterator cbegin() const noexcept - { - return m_table.cbegin(); - } - - /** - * Overloaded function. - * - * @return the iterator - */ - inline ListenerTable::const_iterator end() const noexcept - { - return m_table.end(); - } - - /** - * Overloaded function. - * - * @return the iterator - */ - inline ListenerTable::const_iterator cend() const noexcept - { - return m_table.cend(); - } - - /** - * Add or update a socket to the listener. - * - * If the socket is already placed with the appropriate flags, the - * function is a no-op. - * - * If incorrect flags are passed, the function does nothing. - * - * @param sc the socket - * @param condition the condition (may be OR'ed) - * @throw Error if the backend failed to set - */ - void set(Handle sc, Condition condition) - { - /* Invalid or useless flags */ - if (condition == Condition::None || static_cast<int>(condition) > 0x3) - return; - - auto it = m_table.find(sc); - - /* - * Do not update the table if the backend failed to add - * or update. - */ - if (it == m_table.end()) { - m_backend.set(m_table, sc, condition, true); - m_table.emplace(sc, condition); - } else { - /* Remove flag if already present */ - if ((condition & Condition::Readable) == Condition::Readable && - (it->second & Condition::Readable) == Condition::Readable) { - condition &= ~(Condition::Readable); - } - if ((condition & Condition::Writable) == Condition::Writable && - (it->second & Condition::Writable) == Condition::Writable) { - condition &= ~(Condition::Writable); - } - - /* Still need a call? */ - if (condition != Condition::None) { - m_backend.set(m_table, sc, condition, false); - it->second |= condition; - } - } - } - - /** - * Unset a socket from the listener, only the flags is removed - * unless the two flagss are requested. - * - * For example, if you added a socket for both reading and writing, - * unsetting the write flags will keep the socket for reading. - * - * @param sc the socket - * @param condition the condition (may be OR'ed) - * @see remove - */ - void unset(Handle sc, Condition condition) - { - auto it = m_table.find(sc); - - /* Invalid or useless flags */ - if (condition == Condition::None || static_cast<int>(condition) > 0x3 || it == m_table.end()) - return; - - /* - * Like set, do not update if the socket is already at the appropriate - * state. - */ - if ((condition & Condition::Readable) == Condition::Readable && - (it->second & Condition::Readable) != Condition::Readable) { - condition &= ~(Condition::Readable); - } - if ((condition & Condition::Writable) == Condition::Writable && - (it->second & Condition::Writable) != Condition::Writable) { - condition &= ~(Condition::Writable); - } - - if (condition != Condition::None) { - /* Determine if it's a complete removal */ - bool removal = ((it->second) & ~(condition)) == Condition::None; - - m_backend.unset(m_table, sc, condition, removal); - - if (removal) { - m_table.erase(it); - } else { - it->second &= ~(condition); - } - } - } - - /** - * Remove completely the socket from the listener. - * - * It is a shorthand for unset(sc, Condition::Readable | Condition::Writable); - * - * @param sc the socket - */ - inline void remove(Handle sc) - { - unset(sc, Condition::Readable | Condition::Writable); - } - - /** - * Remove all sockets. - */ - inline void clear() - { - while (!m_table.empty()) { - remove(m_table.begin()->first); - } - } - - /** - * Get the number of sockets in the listener. - */ - inline ListenerTable::size_type size() const noexcept - { - return m_table.size(); - } - - /** - * Select a socket. Waits for a specific amount of time specified as the duration. - * - * @param duration the duration - * @return the socket ready - */ - template <typename Rep, typename Ratio> - inline ListenerStatus wait(const std::chrono::duration<Rep, Ratio> &duration) - { - auto cvt = std::chrono::duration_cast<std::chrono::milliseconds>(duration); - - return m_backend.wait(m_table, cvt.count())[0]; - } - - /** - * Overload with milliseconds. - * - * @param timeout the optional timeout in milliseconds - * @return the socket ready - */ - inline ListenerStatus wait(int timeout = -1) - { - return wait(std::chrono::milliseconds(timeout)); - } - - /** - * Select multiple sockets. - * - * @param duration the duration - * @return the socket ready - */ - template <typename Rep, typename Ratio> - inline std::vector<ListenerStatus> waitMultiple(const std::chrono::duration<Rep, Ratio> &duration) - { - auto cvt = std::chrono::duration_cast<std::chrono::milliseconds>(duration); - - return m_backend.wait(m_table, cvt.count()); - } - - /** - * Overload with milliseconds. - * - * @return the socket ready - */ - inline std::vector<ListenerStatus> waitMultiple(int timeout = -1) - { - return waitMultiple(std::chrono::milliseconds(timeout)); - } -}; - -/* }}} */ - -/* - * Callback - * ------------------------------------------------------------------ - * - * Function owner with tests. - */ - -/* {{{ Callback */ - -/** - * @class Callback - * @brief Convenient signal owner that checks if the target is valid. - * - * This class also catch all errors thrown from signals to avoid interfering with our process. - */ -template <typename... Args> -class Callback : public std::function<void (Args...)> { -public: - /** - * Inherited constructors. - */ - using std::function<void (Args...)>::function; - - /** - * Execute the callback only if a target is set. - */ - void operator()(Args... args) const - { - if (*this) { - try { - std::function<void (Args...)>::operator()(args...); - } catch (...) { - } - } - } -}; - -/* }}} */ - -/* - * StreamConnection - * ------------------------------------------------------------------ - * - * Client connected on the server side. - */ - -/* {{{ StreamConnection */ - -/** - * @class StreamConnection - * @brief Connected client on the server side. - * - * This object is created from StreamServer when a new client is connected, it is the higher - * level object of sockets and completely asynchronous. - */ -template <typename Address, typename Protocol> -class StreamConnection { -public: - /** - * Called when the output has changed. - */ - using WriteHandler = Callback<>; - -private: - /* Signals */ - WriteHandler m_onWrite; - - /* Sockets and output buffer */ - Socket<Address, Protocol> m_socket; - std::string m_output; - -public: - /** - * Create the connection. - * - * @param s the socket - */ - StreamConnection(Socket<Address, Protocol> s) - : m_socket{std::move(s)} - { - m_socket.set(net::option::SockBlockMode{false}); - } - - /** - * Access the underlying socket. - * - * @return the socket - * @warning use with care - */ - inline Socket<Address, Protocol> &socket() noexcept - { - return m_socket; - } - - /** - * Access the current output. - * - * @return the output - */ - inline const std::string &output() const noexcept - { - return m_output; - } - - /** - * Overloaded function - * - * @return the output - * @warning use with care, avoid modifying the output if you don't know what you're doing - */ - inline std::string &output() noexcept - { - return m_output; - } - - /** - * Post some data to be sent asynchronously. - * - * @param str the data to append - */ - inline void send(std::string str) - { - m_output += str; - m_onWrite(); - } - - /** - * Kill the client. - */ - inline void close() - { - m_socket.close(); - } - - /** - * Set the write handler, the signal is emitted when the output has changed so that the StreamServer owner - * knows that there are some data to send. - * - * @param handler the handler - * @warning you usually never need to set this yourself - */ - inline void setWriteHandler(WriteHandler handler) - { - m_onWrite = std::move(handler); - } -}; - -/* }}} */ - -/* - * StreamServer - * ------------------------------------------------------------------ - * - * Convenient stream oriented server. - */ - -/* {{{ StreamServer */ - -/** - * @class StreamServer - * @brief Convenient stream server for TCP and TLS. - * - * This class does all the things for you as accepting new clients, listening for it and sending data. It works - * asynchronously without blocking to let you control your process workflow. - * - * This class is not thread safe and you must not call any of the functions from different threads. - */ -template <typename Address, typename Protocol> -class StreamServer { -public: - /** - * Handler when a new client is connected. - */ - using ConnectionHandler = Callback<const std::shared_ptr<StreamConnection<Address, Protocol>> &>; - - /** - * Handler when a client is disconnected. - */ - using DisconnectionHandler = Callback<const std::shared_ptr<StreamConnection<Address, Protocol>> &>; - - /** - * Handler when data has been received from a client. - */ - using ReadHandler = Callback<const std::shared_ptr<StreamConnection<Address, Protocol>> &, const std::string &>; - - /** - * Handler when data has been correctly sent to a client. - */ - using WriteHandler = Callback<const std::shared_ptr<StreamConnection<Address, Protocol>> &, const std::string &>; - - /** - * Handler when an error occured. - */ - using ErrorHandler = Callback<const Error &>; - - /** - * Handler when there was a timeout. - */ - using TimeoutHandler = Callback<>; - -private: - using ClientMap = std::map<Handle, std::shared_ptr<StreamConnection<Address, Protocol>>>; - - /* Signals */ - ConnectionHandler m_onConnection; - DisconnectionHandler m_onDisconnection; - ReadHandler m_onRead; - WriteHandler m_onWrite; - ErrorHandler m_onError; - TimeoutHandler m_onTimeout; - - /* Sockets */ - Socket<Address, Protocol> m_master; - Listener<> m_listener; - ClientMap m_clients; - - /* - * Update flags depending on the required condition. - */ - void updateFlags(std::shared_ptr<StreamConnection<Address, Protocol>> &client) - { - assert(client->socket().action() != Action::None); - - m_listener.remove(client->socket().handle()); - m_listener.set(client->socket().handle(), client->socket().condition()); - } - - /* - * Continue accept process. - */ - template <typename AcceptCall> - void processAccept(std::shared_ptr<StreamConnection<Address, Protocol>> &client, const AcceptCall &acceptFunc) - { - try { - /* Do the accept */ - acceptFunc(); - - /* 1. First remove completely the client */ - m_listener.remove(client->socket().handle()); - - /* 2. If accept is not finished, wait for the appropriate condition */ - if (client->socket().state() == State::Accepted) { - /* 3. Client is accepted, notify the user */ - m_listener.set(client->socket().handle(), Condition::Readable); - m_onConnection(client); - } else { - /* Operation still in progress */ - updateFlags(client); - } - } catch (const Error &error) { - m_clients.erase(client->socket().handle()); - m_listener.remove(client->socket().handle()); - m_onError(error); - } - } - - /* - * Process initial accept of master socket, this is the initial accepting process. Except on errors, the - * socket is stored but the user will be notified only once the socket is completely accepted. - */ - void processInitialAccept() - { - // TODO: store address too. - std::shared_ptr<StreamConnection<Address, Protocol>> client = std::make_shared<StreamConnection<Address, Protocol>>(m_master.accept(nullptr)); - std::weak_ptr<StreamConnection<Address, Protocol>> ptr{client}; - - /* 1. Register output changed to update listener */ - client->setWriteHandler([this, ptr] () { - auto client = ptr.lock(); - - /* Do not update the listener immediately if an action is pending */ - if (client && client->socket().action() == Action::None && !client->output().empty()) { - m_listener.set(client->socket().handle(), Condition::Writable); - } - }); - - /* 2. Add the client */ - m_clients.insert(std::make_pair(client->socket().handle(), client)); - - /* - * 2. Do an initial check to set the listener flags, at this moment the socket may or not be - * completely accepted. - */ - processAccept(client, [&] () {}); - } - - /* - * Read or complete the read operation. - */ - void processRead(std::shared_ptr<StreamConnection<Address, Protocol>> &client) - { - /* - * Read because there is something to read or because the pending operation is - * read and must complete. - */ - auto buffer = client->socket().recv(512); - - /* - * Now the receive operation may be completed, in that case, two possibilities: - * - * 1. The action is set to None (completed) - * 2. The action is still not complete, update the flags - */ - if (client->socket().action() == Action::None) { - /* Empty mean normal disconnection */ - if (buffer.empty()) { - m_listener.remove(client->socket().handle()); - m_clients.erase(client->socket().handle()); - m_onDisconnection(client); - } else { - /* - * At this step, it is possible that we were completing a receive operation, in this - * case the write flag may be removed, add it if required. - */ - if (!client->output().empty()) { - m_listener.set(client->socket().handle(), Condition::Writable); - } - - m_onRead(client, buffer); - } - } else { - /* Operation in progress */ - updateFlags(client); - } - } - - /* - * Flush the output buffer. - */ - void processWrite(std::shared_ptr<StreamConnection<Address, Protocol>> &client) - { - auto &output = client->output(); - auto nsent = client->socket().send(output); - - if (client->socket().action() == Action::None) { - /* 1. Create a copy of content that has been sent */ - auto sent = output.substr(0, nsent); - - /* 2. Erase the content sent */ - output.erase(0, nsent); - - /* 3. Update listener */ - if (output.empty()) { - m_listener.unset(client->socket().handle(), Condition::Writable); - } - - /* 4. Notify user */ - m_onWrite(client, sent); - } else { - updateFlags(client); - } - } - - void processSync(std::shared_ptr<StreamConnection<Address, Protocol>> &client, Condition flags) - { - try { - auto action = client->socket().action(); - - if (action == Action::Receive || - (action == Action::None && (flags & Condition::Readable) == Condition::Readable)) { - processRead(client); - } else if ((flags & Condition::Writable) == Condition::Writable) { - processWrite(client); - } - } catch (const Error &error) { - m_onDisconnection(client); - m_listener.remove(client->socket().handle()); - m_clients.erase(client->socket().handle()); - } - } - -public: - /** - * Create a stream server with the specified address to bind. - * - * @param protocol the protocol (Tcp or Tls) - * @param address the address to bind - * @param max the max number to listen - * @throw Error on errors - */ - StreamServer(Protocol protocol, const Address &address, int max = 128) - : m_master{std::move(protocol), address} - { - // TODO: m_onError - m_master.set(SOL_SOCKET, SO_REUSEADDR, 1); - m_master.bind(address); - m_master.listen(max); - m_listener.set(m_master.handle(), Condition::Readable); - } - - /** - * Set the connection handler, called when a new client is connected. - * - * @param handler the handler - */ - inline void setConnectionHandler(ConnectionHandler handler) - { - m_onConnection = std::move(handler); - } - - /** - * Set the disconnection handler, called when a client died. - * - * @param handler the handler - */ - inline void setDisconnectionHandler(DisconnectionHandler handler) - { - m_onDisconnection = std::move(handler); - } - - /** - * Set the receive handler, called when a client has sent something. - * - * @param handler the handler - */ - inline void setReadHandler(ReadHandler handler) - { - m_onRead = std::move(handler); - } - - /** - * Set the writing handler, called when some data has been sent to a client. - * - * @param handler the handler - */ - inline void setWriteHandler(WriteHandler handler) - { - m_onWrite = std::move(handler); - } - - /** - * Set the error handler, called when unrecoverable error has occured. - * - * @param handler the handler - */ - inline void setErrorHandler(ErrorHandler handler) - { - m_onError = std::move(handler); - } - - /** - * Set the timeout handler, called when the selection has timeout. - * - * @param handler the handler - */ - inline void setTimeoutHandler(TimeoutHandler handler) - { - m_onTimeout = std::move(handler); - } - - /** - * Poll for the next event. - * - * @param timeout the timeout (-1 for indefinitely) - * @throw Error on errors - */ - void poll(int timeout = -1) - { - try { - auto st = m_listener.wait(timeout); - - if (st.socket == m_master.handle()) { - /* New client */ - processInitialAccept(); - } else { - /* Recv / Send / Accept on a client */ - auto client = m_clients[st.socket]; - - if (client->socket().state() == State::Accepted) { - processSync(client, st.flags); - } else { - processAccept(client, [&] () { client->socket().accept(); }); - } - } - } catch (const Error &error) { - if (error.code() == Error::Timeout) { - m_onTimeout(); - } else { - m_onError(error); - } - } - } -}; - -/* }}} */ - -/* - * StreamClient - * ------------------------------------------------------------------ - */ - -/* {{{ StreamClient */ - -/** - * @class StreamClient - * @brief Client side connection to a server. - * - * This class is not thread safe and you must not call any of the functions from different threads. - */ -template <typename Address, typename Protocol> -class StreamClient { -public: - /** - * Handler when connection is complete. - */ - using ConnectionHandler = Callback<>; - - /** - * Handler when data has been received. - */ - using ReadHandler = Callback<const std::string &>; - - /** - * Handler when data has been sent correctly. - */ - using WriteHandler = Callback<const std::string &>; - - /** - * Handler when disconnected. - */ - using DisconnectionHandler = Callback<>; - - /** - * Handler on unrecoverable error. - */ - using ErrorHandler = Callback<const Error &>; - - /** - * Handler when timeout occured. - */ - using TimeoutHandler = Callback<>; - -private: - /* Signals */ - ConnectionHandler m_onConnection; - ReadHandler m_onRead; - WriteHandler m_onWrite; - DisconnectionHandler m_onDisconnection; - ErrorHandler m_onError; - TimeoutHandler m_onTimeout; - - /* Socket */ - Socket<Address, Protocol> m_socket; - Listener<> m_listener; - - /* Output buffer */ - std::string m_output; - - /* - * Update the flags after an uncompleted operation. This function must only be called when the operation - * has not complete (e.g. connect, recv, send). - */ - void updateFlags() - { - assert(m_socket.action() != Action::None); - - m_listener.remove(m_socket.handle()); - m_listener.set(m_socket.handle(), m_socket.condition()); - } - - /* - * This is the generic connect helper, it will be used to both initiate the connection or to continue the - * connection process if needed. - * - * Thus the template parameter is the appropriate function to call either, m_socket.connect(address) or - * m_socket.connect(). - * - * See poll() and connect() to understand. - */ - template <typename ConnectCall> - void processConnect(const ConnectCall &connectFunc) - { - /* Call m_socket.connect() or m_socket.connect(address) */ - connectFunc(); - - /* Remove entirely */ - m_listener.remove(m_socket.handle()); - - if (m_socket.state() == State::Connected) { - m_onConnection(); - m_listener.set(m_socket.handle(), Condition::Readable); - } else { - /* Connection still in progress */ - updateFlags(); - } - } - - /* - * Receive or complete the receive command, if the command is not complete, the listener is updated - * accordingly. - */ - void processRead() - { - auto received = m_socket.recv(512); - - if (m_socket.action() == Action::None) { - /* 0 means disconnection */ - if (received.empty()) { - m_onDisconnection(); - } else { - /* - * At this step, it is possible that we were completing a receive operation, in this - * case the write flag may be removed, add it if required. - */ - if (m_output.empty()) { - m_listener.unset(m_socket.handle(), Condition::Writable); - } - - m_onRead(received); - } - } else { - /* Receive operation in progress */ - updateFlags(); - } - } - - /* - * Send or complete the send command, if the command is not complete, the listener is updated - * accordingly. - */ - void processWrite() - { - auto nsent = m_socket.send(m_output); - - if (m_socket.action() == Action::None) { - /* 1. Make a copy of what has been sent */ - auto sent = m_output.substr(0, nsent); - - /* 2. Erase sent content */ - m_output.erase(0, nsent); - - /* 3. Update flags if needed */ - if (m_output.empty()) { - m_listener.unset(m_socket.handle(), Condition::Writable); - } - - /* 4. Notify user */ - m_onWrite(sent); - } else { - /* Send operation in progress */ - updateFlags(); - } - } - - /* - * Receive or send. - */ - void processSync(Condition condition) - { - if ((m_socket.action() == Action::Receive) || - (m_socket.action() == Action::None && (condition & Condition::Readable) == Condition::Readable)) { - processRead(); - } else { - processWrite(); - } - } - -public: - /** - * Create a client. The client is automatically marked as non-blocking. - * - * @param protocol the protocol (Tcp or Tls) - * @param address the optional address - * @throw net::Error on failures - */ - StreamClient(Protocol protocol = {}, const Address &address = {}) - : m_socket{std::move(protocol), address} - { - m_socket.set(net::option::SockBlockMode{false}); - m_listener.set(m_socket.handle(), Condition::Readable); - } - - /** - * Set the connection handler, called when the connection succeed. - * - * @param handler the handler - */ - inline void setConnectionHandler(ConnectionHandler handler) - { - m_onConnection = std::move(handler); - } - - /** - * Set the disconnection handler, called when the server closed the connection. - * - * @param handler the handler - */ - inline void setDisconnectionHandler(DisconnectionHandler handler) - { - m_onDisconnection = std::move(handler); - } - - /** - * Set the read handler, called when we received something. - * - * @param handler the handler - */ - inline void setReadHandler(ReadHandler handler) - { - m_onRead = std::move(handler); - } - - /** - * Set the write handler, called when we successfully sent data. - * - * @param handler the handler - */ - inline void setWriteHandler(WriteHandler handler) - { - m_onWrite = std::move(handler); - } - - /** - * Set the error handler, called when unexpected error occurs. - * - * @param handler the handler - */ - inline void setErrorHandler(ErrorHandler handler) - { - m_onError = std::move(handler); - } - - /** - * Connect to a server, this function may connect immediately or not in any case the connection handler - * will be called when the connection completed. - * - * @param address the address to connect to - */ - void connect(const Address &address) noexcept - { - assert(m_socket.state() == State::Open); - - processConnect([&] () { m_socket.connect(address); }); - } - - /** - * Asynchronously send data to the server. - * - * @param str the data to append - */ - void send(std::string str) - { - m_output += str; - - /* Don't update the listener if there is a pending operation */ - if (m_socket.state() == State::Connected && m_socket.action() == Action::None && !m_output.empty()) { - m_listener.set(m_socket.handle(), Condition::Writable); - } - } - - /** - * Wait for the next event. - * - * @param timeout the time to wait in milliseconds - * @throw Error on errors - */ - void poll(int timeout = -1) noexcept - { - try { - auto st = m_listener.wait(timeout); - - if (m_socket.state() != State::Connected) { - /* Continue the connection */ - processConnect([&] () { m_socket.connect(); }); - } else { - /* Read / Write */ - processSync(st.flags); - } - } catch (const Error &error) { - if (error.code() == Error::Timeout) { - m_onTimeout(); - } else { - m_listener.remove(m_socket.handle()); - m_onError(error); - } - } - } -}; - -/* }}} */ - -} // !net - -#endif // !_SOCKETS_H_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Socket/sockets.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,709 @@ +/* + * sockets.cpp -- portable C++ socket wrappers + * + * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define TIMEOUT_MSG "operation timeout" + +#include <algorithm> +#include <atomic> +#include <cstring> +#include <mutex> + +#include "sockets.h" + +namespace net { + +/* + * Portable constants + * ------------------------------------------------------------------ + */ + +/* {{{ Constants */ + +#if defined(_WIN32) + +const Handle Invalid{INVALID_SOCKET}; +const int Failure{SOCKET_ERROR}; + +#else + +const Handle Invalid{-1}; +const int Failure{-1}; + +#endif + +/* }}} */ + +/* + * Portable functions + * ------------------------------------------------------------------ + */ + +/* {{{ Functions */ + +#if defined(_WIN32) + +namespace { + +static std::mutex s_mutex; +static std::atomic<bool> s_initialized{false}; + +} // !namespace + +#endif // !_WIN32 + +void init() noexcept +{ +#if defined(_WIN32) + std::lock_guard<std::mutex> lock(s_mutex); + + if (!s_initialized) { + s_initialized = true; + + WSADATA wsa; + WSAStartup(MAKEWORD(2, 2), &wsa); + + /* + * If SOCKET_WSA_NO_INIT is not set then the user + * must also call finish himself. + */ +#if !defined(SOCKET_NO_AUTO_INIT) + atexit(finish); +#endif + } +#endif +} + +void finish() noexcept +{ +#if defined(_WIN32) + WSACleanup(); +#endif +} + +std::string error(int errn) +{ +#if defined(_WIN32) + LPSTR str = nullptr; + std::string errmsg = "Unknown error"; + + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + errn, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&str, 0, NULL); + + + if (str) { + errmsg = std::string(str); + LocalFree(str); + } + + return errmsg; +#else + return strerror(errn); +#endif +} + +std::string error() +{ +#if defined(_WIN32) + return error(WSAGetLastError()); +#else + return error(errno); +#endif +} + +/* }}} */ + +/* + * SSL stuff + * ------------------------------------------------------------------ + */ + +/* {{{ SSL initialization */ + +#if !defined(SOCKET_NO_SSL) + +namespace ssl { + +namespace { + +std::mutex mutex; +std::atomic<bool> initialized{false}; + +} // !namespace + +void finish() noexcept +{ + ERR_free_strings(); +} + +void init() noexcept +{ + std::lock_guard<std::mutex> lock{mutex}; + + if (!initialized) { + initialized = true; + + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + +#if !defined(SOCKET_NO_AUTO_SSL_INIT) + atexit(finish); +#endif // SOCKET_NO_AUTO_SSL_INIT + } +} + +} // !ssl + +#endif // SOCKET_NO_SSL + +/* }}} */ + +/* + * Error class + * ------------------------------------------------------------------ + */ + +/* {{{ Error */ + +Error::Error(Code code, std::string function) + : m_code{code} + , m_function{std::move(function)} + , m_error{error()} +{ +} + +Error::Error(Code code, std::string function, int n) + : m_code{code} + , m_function{std::move(function)} + , m_error{error(n)} +{ +} + +Error::Error(Code code, std::string function, std::string error) + : m_code{code} + , m_function{std::move(function)} + , m_error{std::move(error)} +{ +} + +/* }}} */ + +/* + * Predefine addressed to be used + * ------------------------------------------------------------------ + */ + +/* {{{ Addresses */ + +namespace address { + +/* Default domain */ +int Ip::m_default{AF_INET}; + +Ip::Ip(Type domain) noexcept + : m_domain(static_cast<int>(domain)) +{ + assert(m_domain == AF_INET6 || m_domain == AF_INET); + + if (m_domain == AF_INET6) { + std::memset(&m_sin6, 0, sizeof (sockaddr_in6)); + } else { + std::memset(&m_sin, 0, sizeof (sockaddr_in)); + } +} + +Ip::Ip(const std::string &host, int port, Type domain) + : m_domain(static_cast<int>(domain)) +{ + assert(m_domain == AF_INET6 || m_domain == AF_INET); + + if (host == "*") { + if (m_domain == AF_INET6) { + std::memset(&m_sin6, 0, sizeof (sockaddr_in6)); + + m_length = sizeof (sockaddr_in6); + m_sin6.sin6_addr = in6addr_any; + m_sin6.sin6_family = AF_INET6; + m_sin6.sin6_port = htons(port); + } else { + std::memset(&m_sin, 0, sizeof (sockaddr_in)); + + m_length = sizeof (sockaddr_in); + m_sin.sin_addr.s_addr = INADDR_ANY; + m_sin.sin_family = AF_INET; + m_sin.sin_port = htons(port); + } + } else { + addrinfo hints, *res; + + std::memset(&hints, 0, sizeof (addrinfo)); + hints.ai_family = domain; + + auto error = getaddrinfo(host.c_str(), std::to_string(port).c_str(), &hints, &res); + if (error != 0) { + throw Error{Error::System, "getaddrinfo", gai_strerror(error)}; + } + + if (m_domain == AF_INET6) { + std::memcpy(&m_sin6, res->ai_addr, res->ai_addrlen); + } else { + std::memcpy(&m_sin, res->ai_addr, res->ai_addrlen); + } + + m_length = res->ai_addrlen; + freeaddrinfo(res); + } +} + +Ip::Ip(const sockaddr_storage *ss, socklen_t length) noexcept + : m_length{length} + , m_domain{ss->ss_family} +{ + assert(ss->ss_family == AF_INET6 || ss->ss_family == AF_INET); + + if (ss->ss_family == AF_INET6) { + std::memcpy(&m_sin6, ss, length); + } else if (ss->ss_family == AF_INET) { + std::memcpy(&m_sin, ss, length); + } +} + +#if !defined(_WIN32) + +Local::Local() noexcept +{ + std::memset(&m_sun, 0, sizeof (sockaddr_un)); +} + +Local::Local(std::string path, bool rm) noexcept + : m_path{std::move(path)} +{ + /* Silently remove the file even if it fails */ + if (rm) { + ::remove(m_path.c_str()); + } + + /* Copy the path */ + std::memset(m_sun.sun_path, 0, sizeof (m_sun.sun_path)); + std::strncpy(m_sun.sun_path, m_path.c_str(), sizeof (m_sun.sun_path) - 1); + + /* Set the parameters */ + m_sun.sun_family = AF_LOCAL; +} + +Local::Local(const sockaddr_storage *ss, socklen_t length) noexcept +{ + assert(ss->ss_family == AF_LOCAL); + + if (ss->ss_family == AF_LOCAL) { + std::memcpy(&m_sun, ss, length); + m_path = reinterpret_cast<const sockaddr_un &>(m_sun).sun_path; + } +} + +#endif // !_WIN32 + +} // !address + +/* }}} */ + +/* + * Select + * ------------------------------------------------------------------ + */ + +/* {{{ Select */ + +std::vector<ListenerStatus> Select::wait(const ListenerTable &table, int ms) +{ + timeval maxwait, *towait; + fd_set readset; + fd_set writeset; + + FD_ZERO(&readset); + FD_ZERO(&writeset); + + Handle max = 0; + + for (const auto &pair : table) { + if ((pair.second & Condition::Readable) == Condition::Readable) { + FD_SET(pair.first, &readset); + } + if ((pair.second & Condition::Writable) == Condition::Writable) { + FD_SET(pair.first, &writeset); + } + + if (pair.first > max) { + max = pair.first; + } + } + + maxwait.tv_sec = 0; + maxwait.tv_usec = ms * 1000; + + // Set to nullptr for infinite timeout. + towait = (ms < 0) ? nullptr : &maxwait; + + auto error = ::select(max + 1, &readset, &writeset, nullptr, towait); + if (error == Failure) { + throw Error{Error::System, "select"}; + } + if (error == 0) { + throw Error{Error::Timeout, "select", TIMEOUT_MSG}; + } + + std::vector<ListenerStatus> sockets; + + for (const auto &pair : table) { + if (FD_ISSET(pair.first, &readset)) { + sockets.push_back(ListenerStatus{pair.first, Condition::Readable}); + } + if (FD_ISSET(pair.first, &writeset)) { + sockets.push_back(ListenerStatus{pair.first, Condition::Writable}); + } + } + + return sockets; +} + +/* }}} */ + +/* + * Poll + * ------------------------------------------------------------------ + */ + +/* {{{ Poll */ + +/* + * Poll implementation + * ------------------------------------------------------------------ + */ + +#if defined(SOCKET_HAVE_POLL) + +#if defined(_WIN32) +# define poll WSAPoll +#endif + +short Poll::toPoll(Condition condition) const noexcept +{ + short result(0); + + if ((condition & Condition::Readable) == Condition::Readable) { + result |= POLLIN; + } + if ((condition & Condition::Writable) == Condition::Writable) { + result |= POLLOUT; + } + + return result; +} + +Condition Poll::toCondition(short &event) const noexcept +{ + Condition condition{Condition::None}; + + /* + * Poll implementations mark the socket differently regarding + * the disconnection of a socket. + * + * At least, even if POLLHUP or POLLIN is set, recv() always + * return 0 so we mark the socket as readable. + */ + if ((event & POLLIN) || (event & POLLHUP)) { + condition |= Condition::Readable; + } + if (event & POLLOUT) { + condition |= Condition::Writable; + } + + /* Reset event for safety */ + event = 0; + + return condition; +} + +void Poll::set(const ListenerTable &, Handle h, Condition condition, bool add) +{ + if (add) { + m_fds.push_back(pollfd{h, toPoll(condition), 0}); + } else { + auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const pollfd &pfd) { + return pfd.fd == h; + }); + + it->events |= toPoll(condition); + } +} + +void Poll::unset(const ListenerTable &, Handle h, Condition condition, bool remove) +{ + auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const pollfd &pfd) { + return pfd.fd == h; + }); + + if (remove) { + m_fds.erase(it); + } else { + it->events &= ~(toPoll(condition)); + } +} + +std::vector<ListenerStatus> Poll::wait(const ListenerTable &, int ms) +{ + auto result = poll(m_fds.data(), m_fds.size(), ms); + if (result == 0) { + throw Error{Error::Timeout, "select", TIMEOUT_MSG}; + } + if (result < 0) { + throw Error{Error::System, "poll"}; + } + + std::vector<ListenerStatus> sockets; + for (auto &fd : m_fds) { + if (fd.revents != 0) { + sockets.push_back(ListenerStatus{fd.fd, toCondition(fd.revents)}); + } + } + + return sockets; +} + +#endif // !SOCKET_HAVE_POLL + +/* }}} */ + +/* + * Epoll implementation + * ------------------------------------------------------------------ + */ + +/* {{{ Epoll */ + +#if defined(SOCKET_HAVE_EPOLL) + +uint32_t Epoll::toEpoll(Condition condition) const noexcept +{ + uint32_t events = 0; + + if ((condition & Condition::Readable) == Condition::Readable) { + events |= EPOLLIN; + } + if ((condition & Condition::Writable) == Condition::Writable) { + events |= EPOLLOUT; + } + + return events; +} + +Condition Epoll::toCondition(uint32_t events) const noexcept +{ + Condition condition{Condition::None}; + + if ((events & EPOLLIN) || (events & EPOLLHUP)) { + condition |= Condition::Readable; + } + if (events & EPOLLOUT) { + condition |= Condition::Writable; + } + + return condition; +} + +void Epoll::update(Handle h, int op, int eflags) +{ + epoll_event ev; + + std::memset(&ev, 0, sizeof (epoll_event)); + + ev.events = eflags; + ev.data.fd = h; + + if (epoll_ctl(m_handle, op, h, &ev) < 0) { + throw Error{Error::System, "epoll_ctl"}; + } +} + +Epoll::Epoll() + : m_handle{epoll_create1(0)} +{ + if (m_handle < 0) { + throw Error{Error::System, "epoll_create"}; + } +} + +Epoll::~Epoll() +{ + close(m_handle); +} + +/* + * For set and unset, we need to apply the whole flags required, so if the socket + * was set to Connection::Readable and user add Connection::Writable, we must + * place both. + */ +void Epoll::set(const ListenerTable &table, Handle sc, Condition condition, bool add) +{ + if (add) { + update(sc, EPOLL_CTL_ADD, toEpoll(condition)); + m_events.resize(m_events.size() + 1); + } else { + update(sc, EPOLL_CTL_MOD, toEpoll(table.at(sc) | condition)); + } +} + +/* + * Unset is a bit complicated case because Listener tells us which + * flag to remove but to update epoll descriptor we need to pass + * the effective flags that we want to be applied. + * + * So we put the same flags that are currently effective and remove the + * requested one. + */ +void Epoll::unset(const ListenerTable &table, Handle sc, Condition condition, bool remove) +{ + if (remove) { + update(sc, EPOLL_CTL_DEL, 0); + m_events.resize(m_events.size() - 1); + } else { + update(sc, EPOLL_CTL_MOD, toEpoll(table.at(sc) & ~(condition))); + } +} + +std::vector<ListenerStatus> Epoll::wait(const ListenerTable &, int ms) +{ + int ret = epoll_wait(m_handle, m_events.data(), m_events.size(), ms); + std::vector<ListenerStatus> result; + + if (ret == 0) { + throw Error{Error::Timeout, "epoll_wait", TIMEOUT_MSG}; + } + if (ret < 0) { + throw Error{Error::System, "epoll_wait"}; + } + + for (int i = 0; i < ret; ++i) { + result.push_back(ListenerStatus{m_events[i].data.fd, toCondition(m_events[i].events)}); + } + + return result; +} + +#endif // !SOCKET_HAVE_EPOLL + +/* }}} */ + +/* + * Kqueue implementation + * ------------------------------------------------------------------ + */ + +/* {{{ Kqueue */ + +#if defined(SOCKET_HAVE_KQUEUE) + +Kqueue::Kqueue() + : m_handle(kqueue()) +{ + if (m_handle < 0) { + throw Error{Error::System, "kqueue"}; + } +} + +Kqueue::~Kqueue() +{ + close(m_handle); +} + +void Kqueue::update(Handle h, int filter, int kflags) +{ + struct kevent ev; + + EV_SET(&ev, h, filter, kflags, 0, 0, nullptr); + + if (kevent(m_handle, &ev, 1, nullptr, 0, nullptr) < 0) { + throw Error{Error::System, "kevent"}; + } +} + +void Kqueue::set(const ListenerTable &, Handle h, Condition condition, bool add) +{ + if ((condition & Condition::Readable) == Condition::Readable) { + update(h, EVFILT_READ, EV_ADD | EV_ENABLE); + } + if ((condition & Condition::Writable) == Condition::Writable) { + update(h, EVFILT_WRITE, EV_ADD | EV_ENABLE); + } + + if (add) { + m_result.resize(m_result.size() + 1); + } +} + +void Kqueue::unset(const ListenerTable &, Handle h, Condition condition, bool remove) +{ + if ((condition & Condition::Readable) == Condition::Readable) { + update(h, EVFILT_READ, EV_DELETE); + } + if ((condition & Condition::Writable) == Condition::Writable) { + update(h, EVFILT_WRITE, EV_DELETE); + } + + if (remove) { + m_result.resize(m_result.size() - 1); + } +} + +std::vector<ListenerStatus> Kqueue::wait(const ListenerTable &, int ms) +{ + std::vector<ListenerStatus> sockets; + timespec ts = { 0, 0 }; + timespec *pts = (ms <= 0) ? nullptr : &ts; + + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms % 1000) * 1000000; + + int nevents = kevent(m_handle, nullptr, 0, &m_result[0], m_result.capacity(), pts); + + if (nevents == 0) { + throw Error{Error::Timeout, "kevent", TIMEOUT_MSG}; + } + if (nevents < 0) { + throw Error{Error::System, "kevent"}; + } + + for (int i = 0; i < nevents; ++i) { + sockets.push_back(ListenerStatus{ + static_cast<Handle>(m_result[i].ident), + m_result[i].filter == EVFILT_READ ? Condition::Readable : Condition::Writable + }); + } + + return sockets; +} + +#endif // !SOCKET_HAVE_KQUEUE + +/* }}} */ + +} // !net
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Socket/sockets.h Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,4063 @@ +/* + * sockets.h -- portable C++ socket wrappers + * + * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _SOCKETS_H_ +#define _SOCKETS_H_ + +/** + * @file sockets.h + * @brief Portable socket abstraction + * + * # Introduction + * + * This file is a portable networking library. + * + * ## Options + * + * The user may set the following variables before compiling these files: + * + * ### General options + * + * - **SOCKET_NO_AUTO_INIT**: (bool) Set to 0 if you don't want Socket class to + * automatically calls net::init function and net::finish functions. + * - **SOCKET_NO_SSL**: (bool) Set to 0 if you don't have access to OpenSSL library. + * - **SOCKET_NO_AUTO_SSL_INIT**: (bool) Set to 0 if you don't want Socket class with Tls to automatically init + * the OpenSSL library. You will need to call net::ssl::init and net::ssl::finish. + * + * ### Options for Listener class + * + * Feature detection, multiple implementations may be avaible, for example, Linux has poll, select and epoll. + * + * We assume that `select(2)` is always available. + * + * Of course, you can set the variables yourself if you test it with your build system. + * + * - **SOCKET_HAVE_POLL**: Defined on all BSD, Linux. Also defined on Windows + * if _WIN32_WINNT is set to 0x0600 or greater. + * - **SOCKET_HAVE_KQUEUE**: Defined on all BSD and Apple. + * - **SOCKET_HAVE_EPOLL**: Defined on Linux only. + * - **SOCKET_DEFAULT_BACKEND**: Which backend to use (e.g. `Select`). + * + * The preference priority is ordered from left to right. + * + * | System | Backend | Class name | + * |---------------|-------------------------|--------------| + * | Linux | epoll(7) | Epoll | + * | *BSD | kqueue(2) | Kqueue | + * | Windows | poll(2), select(2) | Poll, Select | + * | Mac OS X | kqueue(2) | Kqueue | + */ + +#if defined(_WIN32) +# if _WIN32_WINNT >= 0x0600 && !defined(SOCKET_HAVE_POLL) +# define SOCKET_HAVE_POLL +# endif +#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) +# if !defined(SOCKET_HAVE_KQUEUE) +# define SOCKET_HAVE_KQUEUE +# endif +# if !defined(SOCKET_HAVE_POLL) +# define SOCKET_HAVE_POLL +# endif +#elif defined(__linux__) +# if !defined(SOCKET_HAVE_EPOLL) +# define SOCKET_HAVE_EPOLL +# endif +# if !defined(SOCKET_HAVE_POLL) +# define SOCKET_HAVE_POLL +# endif +#endif + +/* + * Define SOCKET_DEFAULT_BACKEND + * ------------------------------------------------------------------ + */ + +/** + * Defines the default Listener backend to use. + * + * @note Do not rely on the value shown in doxygen. + */ +#if defined(_WIN32) +# if !defined(SOCKET_DEFAULT_BACKEND) +# if defined(SOCKET_HAVE_POLL) +# define SOCKET_DEFAULT_BACKEND Poll +# else +# define SOCKET_DEFAULT_BACKEND Select +# endif +# endif +#elif defined(__linux__) +# include <sys/epoll.h> + +# if !defined(SOCKET_DEFAULT_BACKEND) +# define SOCKET_DEFAULT_BACKEND Epoll +# endif +#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__) +# include <sys/types.h> +# include <sys/event.h> +# include <sys/time.h> + +# if !defined(SOCKET_DEFAULT_BACKEND) +# define SOCKET_DEFAULT_BACKEND Kqueue +# endif +#else +# if !defined(SOCKET_DEFAULT_BACKEND) +# define SOCKET_DEFAULT_BACKEND Select +# endif +#endif + +#if defined(SOCKET_HAVE_POLL) && !defined(_WIN32) +# include <poll.h> +#endif + +/* + * Headers to include + * ------------------------------------------------------------------ + */ + +#if defined(_WIN32) +# include <cstdlib> + +# include <WinSock2.h> +# include <WS2tcpip.h> +#else +# include <cerrno> + +# include <sys/ioctl.h> +# include <sys/types.h> +# include <sys/socket.h> +# include <sys/un.h> + +# include <arpa/inet.h> + +# include <netinet/in.h> +# include <netinet/tcp.h> + +# include <fcntl.h> +# include <netdb.h> +# include <unistd.h> +#endif + +#if !defined(SOCKET_NO_SSL) +# include <openssl/err.h> +# include <openssl/evp.h> +# include <openssl/ssl.h> +#endif + +#include <cassert> +#include <chrono> +#include <cstdlib> +#include <cstring> +#include <exception> +#include <functional> +#include <map> +#include <memory> +#include <string> +#include <vector> + +/** + * General network namespace. + */ +namespace net { + +/* + * Portables types + * ------------------------------------------------------------------ + * + * The following types are defined differently between Unix and Windows. + */ + +/* {{{ Protocols */ + +#if defined(_WIN32) + +/** + * Socket type, SOCKET. + */ +using Handle = SOCKET; + +/** + * Argument to pass to set. + */ +using ConstArg = const char *; + +/** + * Argument to pass to get. + */ +using Arg = char *; + +#else + +/** + * Socket type, int. + */ +using Handle = int; + +/** + * Argument to pass to set. + */ +using ConstArg = const void *; + +/** + * Argument to pass to get. + */ +using Arg = void *; + +#endif + +/* }}} */ + +/* + * Portable constants + * ------------------------------------------------------------------ + * + * These constants are needed to check functions return codes, they are rarely needed in end user code. + */ + +/* {{{ Constants */ + +/* + * The following constants are defined differently from Unix + * to Windows. + */ +#if defined(_WIN32) + +/** + * Socket creation failure or invalidation. + */ +extern const Handle Invalid; + +/** + * Socket operation failure. + */ +extern const int Failure; + +#else + +/** + * Socket creation failure or invalidation. + */ +extern const int Invalid; + +/** + * Socket operation failure. + */ +extern const int Failure; + +#endif + +#if !defined(SOCKET_NO_SSL) + +namespace ssl { + +/** + * @enum Method + * @brief Which OpenSSL method to use. + */ +enum Method { + Tlsv1, //!< TLS v1.2 (recommended) + Sslv3 //!< SSLv3 +}; + +} // !ssl + +#endif + +/* }}} */ + +/* + * Portable functions + * ------------------------------------------------------------------ + * + * The following free functions can be used to initialize the library or to get the last system error. + */ + +/* {{{ Functions */ + +/** + * Initialize the socket library. Except if you defined SOCKET_NO_AUTO_INIT, you don't need to call this + * function manually. + */ +void init() noexcept; + +/** + * Close the socket library. + */ +void finish() noexcept; + +#if !defined(SOCKET_NO_SSL) + +/** + * OpenSSL namespace. + */ +namespace ssl { + +/** + * Initialize the OpenSSL library. Except if you defined SOCKET_NO_AUTO_SSL_INIT, you don't need to call this function + * manually. + */ +void init() noexcept; + +/** + * Close the OpenSSL library. + */ +void finish() noexcept; + +} // !ssl + +#endif // SOCKET_NO_SSL + +/** + * Get the last socket system error. The error is set from errno or from + * WSAGetLastError on Windows. + * + * @return a string message + */ +std::string error(); + +/** + * Get the last system error. + * + * @param errn the error number (errno or WSAGetLastError) + * @return the error + */ +std::string error(int errn); + +/* }}} */ + +/* + * Error class + * ------------------------------------------------------------------ + * + * This is the main exception thrown on socket operations. + */ + +/* {{{ Error */ + +/** + * @class Error + * @brief Base class for sockets error + */ +class Error : public std::exception { +public: + /** + * @enum Code + * @brief Which kind of error + */ + enum Code { + Timeout, ///!< The action did timeout + System, ///!< There is a system error + Other ///!< Other custom error + }; + +private: + Code m_code; + std::string m_function; + std::string m_error; + +public: + /** + * Constructor that use the last system error. + * + * @param code which kind of error + * @param function the function name + */ + Error(Code code, std::string function); + + /** + * Constructor that use the system error set by the user. + * + * @param code which kind of error + * @param function the function name + * @param error the error + */ + Error(Code code, std::string function, int error); + + /** + * Constructor that set the error specified by the user. + * + * @param code which kind of error + * @param function the function name + * @param error the error + */ + Error(Code code, std::string function, std::string error); + + /** + * Get which function has triggered the error. + * + * @return the function name (e.g connect) + */ + inline const std::string &function() const noexcept + { + return m_function; + } + + /** + * The error code. + * + * @return the code + */ + inline Code code() const noexcept + { + return m_code; + } + + /** + * Get the error (only the error content). + * + * @return the error + */ + const char *what() const noexcept + { + return m_error.c_str(); + } +}; + +/* }}} */ + +/* + * State class + * ------------------------------------------------------------------ + * + * To facilitate higher-level stuff, the socket has a state. + */ + +/* {{{ State */ + +/** + * @enum State + * @brief Current socket state. + */ +enum class State { + Open, //!< Socket is open + Bound, //!< Socket is bound to an address + Connecting, //!< The connection is in progress + Connected, //!< Connection is complete + Accepted, //!< Socket has been accepted (client) + Accepting, //!< The client acceptation is in progress + Closed, //!< The socket has been closed + Disconnected, //!< The connection was lost +}; + +/* }}} */ + +/* + * Action enum + * ------------------------------------------------------------------ + * + * Defines the pending operation. + */ + +/* {{{ Action */ + +/** + * @enum Action + * @brief Define the current operation that must complete. + * + * Some operations like accept, connect, recv or send must sometimes do several round-trips to complete and the socket + * action is set with that enumeration. The user is responsible of calling accept, connect send or recv until the + * operation is complete. + * + * Note: the user must wait for the appropriate condition in Socket::condition to check if the required condition is + * met. + * + * It is important to complete the operation in the correct order because protocols like Tls may require to continue + * re-negociating when calling Socket::send or Socket::Recv. + */ +enum class Action { + None, //!< No action is required, socket is ready + Accept, //!< The socket is not yet accepted, caller must call accept() again + Connect, //!< The socket is not yet connected, caller must call connect() again + Receive, //!< The received operation has not succeeded yet, caller must call recv() or recvfrom() again + Send //!< The send operation has not succeded yet, caller must call send() or sendto() again +}; + +/* }}} */ + +/* + * Condition enum + * ------------------------------------------------------------------ + * + * Defines if we must wait for reading or writing. + */ + +/* {{{ Condition */ + +/** + * @enum Condition + * @brief Define the required condition for the socket. + * + * As explained in Action enumeration, some operations required to be called several times, before calling these + * operations, the user must wait the socket to be readable or writable. This can be checked with Socket::condition. + */ +enum class Condition { + None, //!< No condition is required + Readable = (1 << 0), //!< The socket must be readable + Writable = (1 << 1) //!< The socket must be writable +}; + +/** + * Apply bitwise XOR. + * + * @param v1 the first value + * @param v2 the second value + * @return the new value + */ +constexpr Condition operator^(Condition v1, Condition v2) noexcept +{ + return static_cast<Condition>(static_cast<int>(v1) ^ static_cast<int>(v2)); +} + +/** + * Apply bitwise AND. + * + * @param v1 the first value + * @param v2 the second value + * @return the new value + */ +constexpr Condition operator&(Condition v1, Condition v2) noexcept +{ + return static_cast<Condition>(static_cast<int>(v1) & static_cast<int>(v2)); +} + +/** + * Apply bitwise OR. + * + * @param v1 the first value + * @param v2 the second value + * @return the new value + */ +constexpr Condition operator|(Condition v1, Condition v2) noexcept +{ + return static_cast<Condition>(static_cast<int>(v1) | static_cast<int>(v2)); +} + +/** + * Apply bitwise NOT. + * + * @param v the value + * @return the complement + */ +constexpr Condition operator~(Condition v) noexcept +{ + return static_cast<Condition>(~static_cast<int>(v)); +} + +/** + * Assign bitwise OR. + * + * @param v1 the first value + * @param v2 the second value + * @return the new value + */ +inline Condition &operator|=(Condition &v1, Condition v2) noexcept +{ + v1 = static_cast<Condition>(static_cast<int>(v1) | static_cast<int>(v2)); + + return v1; +} + +/** + * Assign bitwise AND. + * + * @param v1 the first value + * @param v2 the second value + * @return the new value + */ +inline Condition &operator&=(Condition &v1, Condition v2) noexcept +{ + v1 = static_cast<Condition>(static_cast<int>(v1) & static_cast<int>(v2)); + + return v1; +} + +/** + * Assign bitwise XOR. + * + * @param v1 the first value + * @param v2 the second value + * @return the new value + */ +inline Condition &operator^=(Condition &v1, Condition v2) noexcept +{ + v1 = static_cast<Condition>(static_cast<int>(v1) ^ static_cast<int>(v2)); + + return v1; +} + +/* }}} */ + +/* + * Base Socket class + * ------------------------------------------------------------------ + * + * This base class has operations that are common to all types of sockets but you usually instanciate + * a SocketTcp or SocketUdp + */ + +/* {{{ Socket */ + +/** + * @class Socket + * @brief Base socket class for socket operations. + * + * **Important:** When using non-blocking sockets, some considerations must be taken. See the implementation of the + * underlying protocol for more details. + * + * @see protocol::Tls + * @see protocol::Tcp + * @see protocol::Udp + */ +template <typename Address, typename Protocol> +class Socket { +private: + Protocol m_proto; + State m_state{State::Closed}; + Action m_action{Action::None}; + Condition m_condition{Condition::None}; + +protected: + /** + * The native handle. + */ + Handle m_handle{Invalid}; + +public: + /** + * Create a socket handle. + * + * This is the primary function and the only one that creates the socket handle, all other constructors + * are just overloaded functions. + * + * @param domain the domain AF_* + * @param type the type SOCK_* + * @param protocol the protocol + * @param iface the implementation + * @throw net::Error on errors + * @post state is set to Open + * @post handle is not set to Invalid + */ + Socket(int domain, int type, int protocol, Protocol iface = {}) + : m_proto(std::move(iface)) + { +#if !defined(SOCKET_NO_AUTO_INIT) + init(); +#endif + m_handle = ::socket(domain, type, protocol); + + if (m_handle == Invalid) { + throw Error{Error::System, "socket"}; + } + + m_proto.create(*this); + m_state = State::Open; + + assert(m_handle != Invalid); + } + + /** + * This tries to create a socket. + * + * Domain and type are determined by the Address and Protocol object. + * + * @param protocol the protocol + * @param address which type of address + * @throw net::Error on errors + */ + explicit inline Socket(Protocol protocol = {}, const Address &address = {}) + : Socket{address.domain(), protocol.type(), 0, std::move(protocol)} + { + } + + /** + * Construct a socket with an already created descriptor. + * + * @param handle the native descriptor + * @param state specify the socket state + * @param protocol the type of socket implementation + * @post action is set to None + * @post condition is set to None + */ + explicit inline Socket(Handle handle, State state = State::Closed, Protocol protocol = {}) noexcept + : m_proto(std::move(protocol)) + , m_state{state} + , m_handle{handle} + { + assert(m_action == Action::None); + assert(m_condition == Condition::None); + } + + /** + * Create an invalid socket. Can be used when you cannot instanciate the socket immediately. + */ + explicit inline Socket(std::nullptr_t) noexcept + : m_handle{Invalid} + { + } + + /** + * Copy constructor deleted. + */ + Socket(const Socket &) = delete; + + /** + * Transfer ownership from other to this. + * + * @param other the other socket + */ + inline Socket(Socket &&other) noexcept + : m_proto(std::move(other.m_proto)) + , m_state{other.m_state} + , m_action{other.m_action} + , m_condition{other.m_condition} + , m_handle{other.m_handle} + { + /* Invalidate other */ + other.m_handle = Invalid; + other.m_state = State::Closed; + other.m_action = Action::None; + other.m_condition = Condition::None; + } + + /** + * Default destructor. + */ + virtual ~Socket() + { + close(); + } + + /** + * Access the implementation. + * + * @return the implementation + * @warning use this function with care + */ + inline const Protocol &protocol() const noexcept + { + return m_proto; + } + + /** + * Overloaded function. + * + * @return the implementation + */ + inline Protocol &protocol() noexcept + { + return m_proto; + } + + /** + * Get the current socket state. + * + * @return the state + */ + inline State state() const noexcept + { + return m_state; + } + + /** + * Change the current socket state. + * + * @param state the new state + * @warning only implementations should call this function + */ + inline void setState(State state) noexcept + { + m_state = state; + } + + /** + * Get the pending operation. + * + * @return the action to complete before continuing + * @note usually only needed in non-blocking sockets + */ + inline Action action() const noexcept + { + return m_action; + } + + /** + * Change the pending operation. + * + * @param action the action + * @warning you should not call this function yourself + */ + inline void setAction(Action action) noexcept + { + m_action = action; + } + + /** + * Get the condition to wait for. + * + * @return the condition + */ + inline Condition condition() const noexcept + { + return m_condition; + } + + /** + * Change the condition required. + * + * @param condition the condition + * @warning you should not call this function yourself + */ + inline void setCondition(Condition condition) noexcept + { + m_condition = condition; + } + + /** + * Set an option for the socket. Wrapper of setsockopt(2). + * + * @param level the setting level + * @param name the name + * @param arg the value + * @throw net::Error on errors + */ + template <typename Argument> + void set(int level, int name, const Argument &arg) + { + if (setsockopt(m_handle, level, name, (ConstArg)&arg, sizeof (arg)) == Failure) { + throw Error{Error::System, "set"}; + } + } + + /** + * Object-oriented option setter. + * + * The object must have `set(Socket<Address, Protocol> &) const`. + * + * @param option the option + * @throw net::Error on errors + */ + template <typename Option> + inline void set(const Option &option) + { + option.set(*this); + } + + /** + * Get an option for the socket. Wrapper of getsockopt(2). + * + * @param level the setting level + * @param name the name + * @throw net::Error on errors + */ + template <typename Argument> + Argument get(int level, int name) + { + Argument desired, result{}; + socklen_t size = sizeof (result); + + if (getsockopt(m_handle, level, name, (Arg)&desired, &size) == Failure) { + throw Error{Error::System, "get"}; + } + + std::memcpy(&result, &desired, size); + + return result; + } + + /** + * Object-oriented option getter. + * + * The object must have `T get(Socket<Address, Protocol> &) const`, T can be any type and it is the value + * returned from this function. + * + * @return the same value as get() in the option + * @throw net::Error on errors + */ + template <typename Option> + inline auto get() -> decltype(std::declval<Option>().get(*this)) + { + return Option{}.get(*this); + } + + /** + * Get the native handle. + * + * @return the handle + * @warning Not portable + */ + inline Handle handle() const noexcept + { + return m_handle; + } + + /** + * Bind using a native address. + * + * @param address the address + * @param length the size + * @pre state must not be Bound + * @throw net::Error on errors + */ + void bind(const sockaddr *address, socklen_t length) + { + assert(m_state != State::Bound); + + if (::bind(m_handle, address, length) == Failure) { + throw Error{Error::System, "bind"}; + } + + m_state = State::Bound; + } + + /** + * Overload that takes an address. + * + * @param address the address + * @throw net::Error on errors + */ + inline void bind(const Address &address) + { + bind(address.address(), address.length()); + } + + /** + * Listen for pending connection. + * + * @param max the maximum number + * @pre state must be Bound + * @throw net::Error on errors + */ + inline void listen(int max = 128) + { + assert(m_state == State::Bound); + + if (::listen(this->m_handle, max) == Failure) { + throw Error{Error::System, "listen"}; + } + } + + /** + * Connect to the address. + * + * If connection cannot be established immediately, connect with no argument must be called again. See + * the underlying protocol for more information. + * + * @pre state must be State::Open + * @param address the address + * @param length the the address length + * @throw net::Error on errors + * @post state is set to State::Connecting or State::Connected + * @note For non-blocking sockets, see the underlying protocol function for more details + */ + void connect(const sockaddr *address, socklen_t length) + { + assert(m_state == State::Open); + + m_action = Action::None; + m_condition = Condition::None; + + m_proto.connect(*this, address, length); + + assert((m_state == State::Connected && m_action == Action::None && m_condition == Condition::None) || + (m_state == State::Connecting && m_action == Action::Connect && m_condition != Condition::None)); + } + + /** + * Overloaded function. + * + * Effectively call connect(address.address(), address.length()); + * + * @param address the address + */ + inline void connect(const Address &address) + { + connect(address.address(), address.length()); + } + + /** + * Continue the connection, only required with non-blocking sockets. + * + * @pre state must be State::Connecting + * @throw net::Error on errors + */ + void connect() + { + assert(m_state == State::Connecting); + + m_action = Action::None; + m_condition = Condition::None; + + m_proto.connect(*this); + + assert((m_state == State::Connected && m_action == Action::None && m_condition == Condition::None) || + (m_state == State::Connecting && m_action == Action::Connect && m_condition != Condition::None)); + } + + /** + * Accept a new client. If there are no pending connection, throws an error. + * + * If the client cannot be accepted immediately, the client is returned and accept with no arguments + * must be called on it. See the underlying protocol for more information. + * + * @pre state must be State::Bound + * @param info the address where to store client's information (optional) + * @return the new socket + * @throw Error on errors + * @post returned client's state is set to State::Accepting or State::Accepted + * @note For non-blocking sockets, see the underlying protocol function for more details + */ + Socket<Address, Protocol> accept(Address *info) + { + assert(m_state == State::Bound); + + m_action = Action::None; + m_condition = Condition::None; + + sockaddr_storage storage; + socklen_t length = sizeof (storage); + + Socket<Address, Protocol> sc = m_proto.accept(*this, reinterpret_cast<sockaddr *>(&storage), &length); + + if (info) { + *info = Address{&storage, length}; + } + + /* Master do not change */ + assert(m_state == State::Bound); + assert(m_action == Action::None); + assert(m_condition == Condition::None); + + /* Client */ + assert( + (sc.state() == State::Accepting && sc.action() == Action::Accept && sc.condition() != Condition::None) || + (sc.state() == State::Accepted && sc.action() == Action::None && sc.condition() == Condition::None) + ); + + return sc; + } + + /** + * Continue the accept process on this client. This function must be called only when the socket is + * ready to be readable or writable! (see condition). + * + * @pre state must be State::Accepting + * @throw Error on errors + * @post if connection is complete, state is changed to State::Accepted, action and condition are unset + * @post if connection is still in progress, condition is set + */ + void accept() + { + assert(m_state == State::Accepting); + + m_action = Action::None; + m_condition = Condition::None; + + m_proto.accept(*this); + + assert( + (m_state == State::Accepting && m_action == Action::Accept && m_condition != Condition::None) || + (m_state == State::Accepted && m_action == Action::None && m_condition == Condition::None) + ); + } + + /** + * Get the local name. This is a wrapper of getsockname(). + * + * @return the address + * @throw Error on failures + * @pre state() must not be State::Closed + */ + Address address() const + { + assert(m_state != State::Closed); + + sockaddr_storage ss; + socklen_t length = sizeof (sockaddr_storage); + + if (::getsockname(m_handle, (sockaddr *)&ss, &length) == Failure) { + throw Error{Error::System, "getsockname"}; + } + + return Address(&ss, length); + } + + /** + * Receive some data. + * + * If the operation cannot be complete immediately, 0 is returned and user must call the function + * again when ready. See the underlying protocol for more information. + * + * If action is set to Action::None and result is set to 0, disconnection occured. + * + * @param data the destination buffer + * @param length the buffer length + * @pre action must not be Action::Send + * @return the number of bytes received or 0 + * @throw Error on error + * @note For non-blocking sockets, see the underlying protocol function for more details + */ + unsigned recv(void *data, unsigned length) + { + assert(m_action != Action::Send); + + m_action = Action::None; + m_condition = Condition::None; + + return m_proto.recv(*this, data, length); + } + + /** + * Overloaded function. + * + * @param count the number of bytes to receive + * @return the string + * @throw Error on error + */ + inline std::string recv(unsigned count) + { + std::string result; + + result.resize(count); + auto n = recv(const_cast<char *>(result.data()), count); + result.resize(n); + + return result; + } + + /** + * Send some data. + * + * If the operation cannot be complete immediately, 0 is returned and user must call the function + * again when ready. See the underlying protocol for more information. + * + * @param data the data buffer + * @param length the buffer length + * @return the number of bytes sent or 0 + * @pre action() must not be Flag::Receive + * @throw Error on error + * @note For non-blocking sockets, see the underlying protocol function for more details + */ + unsigned send(const void *data, unsigned length) + { + assert(m_action != Action::Receive); + + m_action = Action::None; + m_condition = Condition::None; + + return m_proto.send(*this, data, length); + } + + /** + * Overloaded function. + * + * @param data the string to send + * @return the number of bytes sent + * @throw Error on error + */ + inline unsigned send(const std::string &data) + { + return send(data.c_str(), data.size()); + } + + /** + * Send data to an end point. + * + * If the operation cannot be complete immediately, 0 is returned and user must call the function + * again when ready. See the underlying protocol for more information. + * + * @param data the buffer + * @param length the buffer length + * @param address the client address + * @param addrlen the address length + * @return the number of bytes sent + * @throw net::Error on errors + * @note For non-blocking sockets, see the underlying protocol function for more details + */ + inline unsigned sendto(const void *data, unsigned length, const sockaddr *address, socklen_t addrlen) + { + return m_proto.sendto(*this, data, length, address, addrlen); + } + + /** + * Overloaded function. + * + * @param data the buffer + * @param length the buffer length + * @param address the destination + * @return the number of bytes sent + * @throw net::Error on errors + */ + inline unsigned sendto(const void *data, unsigned length, const Address &address) + { + return sendto(data, length, address.address(), address.length()); + } + + /** + * Overloaded function. + * + * @param data the data + * @param address the address + * @return the number of bytes sent + * @throw net:;Error on errors + */ + inline unsigned sendto(const std::string &data, const Address &address) + { + return sendto(data.c_str(), data.length(), address); + } + + /** + * Receive data from an end point. + * + * If the operation cannot be complete immediately, 0 is returned and user must call the function + * again when ready. See the underlying protocol for more information. + * + * @param data the destination buffer + * @param length the buffer length + * @param address the address destination + * @param addrlen the address length (in/out) + * @return the number of bytes received + * @throw net::Error on errors + * @note For non-blocking sockets, see the underlying protocol function for more details + */ + inline unsigned recvfrom(void *data, unsigned length, sockaddr *address, socklen_t *addrlen) + { + return m_proto.recvfrom(*this, data, length, address, addrlen); + } + + /** + * Overloaded function. + * + * @param data the destination buffer + * @param length the buffer length + * @param info the address destination + * @return the number of bytes received + * @throw net::Error on errors + */ + inline unsigned recvfrom(void *data, unsigned length, Address *info = nullptr) + { + sockaddr_storage storage; + socklen_t addrlen = sizeof (sockaddr_storage); + + auto n = recvfrom(data, length, reinterpret_cast<sockaddr *>(&storage), &addrlen); + + if (info && n != 0) { + *info = Address{&storage, addrlen}; + } + + return n; + } + + /** + * Overloaded function. + * + * @param count the maximum number of bytes to receive + * @param info the client information + * @return the string + * @throw net::Error on errors + */ + std::string recvfrom(unsigned count, Address *info = nullptr) + { + std::string result; + + result.resize(count); + auto n = recvfrom(const_cast<char *>(result.data()), count, info); + result.resize(n); + + return result; + } + + /** + * Close the socket. + * + * Automatically called from the destructor. + */ + void close() + { + if (m_handle != Invalid) { +#if defined(_WIN32) + ::closesocket(m_handle); +#else + ::close(m_handle); +#endif + m_handle = Invalid; + } + + m_state = State::Closed; + m_action = Action::None; + m_condition = Condition::None; + } + + /** + * Assignment operator forbidden. + * + * @return *this + */ + Socket &operator=(const Socket &) = delete; + + /** + * Transfer ownership from other to this. The other socket is left + * invalid and will not be closed. + * + * @param other the other socket + * @return this + */ + Socket &operator=(Socket &&other) noexcept + { + m_handle = other.m_handle; + m_proto = std::move(other.m_proto); + m_state = other.m_state; + m_action = other.m_action; + m_condition = other.m_condition; + + /* Invalidate other */ + other.m_handle = Invalid; + other.m_state = State::Closed; + other.m_action = Action::None; + other.m_condition = Condition::None; + + return *this; + } +}; + +/** + * Compare two sockets. + * + * @param s1 the first socket + * @param s2 the second socket + * @return true if they equals + */ +template <typename Address, typename Protocol> +bool operator==(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) +{ + return s1.handle() == s2.handle(); +} + +/** + * Compare two sockets. + * + * @param s1 the first socket + * @param s2 the second socket + * @return true if they are different + */ +template <typename Address, typename Protocol> +bool operator!=(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) +{ + return s1.handle() != s2.handle(); +} + +/** + * Compare two sockets. + * + * @param s1 the first socket + * @param s2 the second socket + * @return true if s1 < s2 + */ +template <typename Address, typename Protocol> +bool operator<(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) +{ + return s1.handle() < s2.handle(); +} + +/** + * Compare two sockets. + * + * @param s1 the first socket + * @param s2 the second socket + * @return true if s1 > s2 + */ +template <typename Address, typename Protocol> +bool operator>(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) +{ + return s1.handle() > s2.handle(); +} + +/** + * Compare two sockets. + * + * @param s1 the first socket + * @param s2 the second socket + * @return true if s1 <= s2 + */ +template <typename Address, typename Protocol> +bool operator<=(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) +{ + return s1.handle() <= s2.handle(); +} + +/** + * Compare two sockets. + * + * @param s1 the first socket + * @param s2 the second socket + * @return true if s1 >= s2 + */ +template <typename Address, typename Protocol> +bool operator>=(const Socket<Address, Protocol> &s1, const Socket<Address, Protocol> &s2) +{ + return s1.handle() >= s2.handle(); +} + +/* }}} */ + +/* + * Predefined options + * ------------------------------------------------------------------ + */ + +/* {{{ Options */ + +/** + * Namespace of predefined options. + */ +namespace option { + +/* + * Options for socket + * ------------------------------------------------------------------ + */ + +/* {{{ Options for socket */ + +/** + * @class SockBlockMode + * @brief Set or get the blocking-mode for a socket. + * @warning On Windows, it's not possible to check if the socket is blocking or not. + */ +class SockBlockMode { +public: + /** + * Set to false if you want non-blocking socket. + */ + bool value{false}; + + /** + * Set the option. + * + * @param sc the socket + * @throw Error on errors + */ + template <typename Address, typename Protocol> + void set(Socket<Address, Protocol> &sc) const + { +#if defined(O_NONBLOCK) && !defined(_WIN32) + int flags; + + if ((flags = fcntl(sc.handle(), F_GETFL, 0)) < 0) { + flags = 0; + } + + if (value) { + flags &= ~(O_NONBLOCK); + } else { + flags |= O_NONBLOCK; + } + + if (fcntl(sc.handle(), F_SETFL, flags) < 0) { + throw Error{Error::System, "fcntl"}; + } +#else + unsigned long flags = (value) ? 0 : 1; + + if (ioctlsocket(sc.handle(), FIONBIO, &flags) == Failure) { + throw Error{Error::System, "fcntl"}; + } +#endif + } + + /** + * Get the option. + * + * @return the value + * @throw Error on errors + */ + template <typename Address, typename Protocol> + bool get(Socket<Address, Protocol> &sc) const + { +#if defined(O_NONBLOCK) && !defined(_WIN32) + int flags = fcntl(sc.handle(), F_GETFL, 0); + + if (flags < 0) { + throw Error{Error::System, "fcntl"}; + } + + return !(flags & O_NONBLOCK); +#else + throw Error{Error::Other, "get", "Windows API cannot let you get the blocking status of a socket"}; +#endif + } +}; + +/** + * @class SockReuseAddress + * @brief Reuse address, must be used before calling Socket::bind + */ +class SockReuseAddress { +public: + /** + * Set to true if you want to set the SOL_SOCKET/SO_REUSEADDR option. + */ + bool value{true}; + + /** + * Set the option. + * + * @param sc the socket + * @throw Error on errors + */ + template <typename Address, typename Protocol> + inline void set(Socket<Address, Protocol> &sc) const + { + sc.set(SOL_SOCKET, SO_REUSEADDR, value ? 1 : 0); + } + + /** + * Get the option. + * + * @return the value + * @throw Error on errors + */ + template <typename Address, typename Protocol> + inline bool get(Socket<Address, Protocol> &sc) const + { + return static_cast<bool>(sc.template get<int>(SOL_SOCKET, SO_REUSEADDR)); + } +}; + +/** + * @class SockSendBuffer + * @brief Set or get the output buffer. + */ +class SockSendBuffer { +public: + /** + * Set to the buffer size. + */ + int value{2048}; + + /** + * Set the option. + * + * @param sc the socket + * @throw Error on errors + */ + template <typename Address, typename Protocol> + inline void set(Socket<Address, Protocol> &sc) const + { + sc.set(SOL_SOCKET, SO_SNDBUF, value); + } + + /** + * Get the option. + * + * @return the value + * @throw Error on errors + */ + template <typename Address, typename Protocol> + inline int get(Socket<Address, Protocol> &sc) const + { + return sc.template get<int>(SOL_SOCKET, SO_SNDBUF); + } +}; + +/** + * @class SockReceiveBuffer + * @brief Set or get the input buffer. + */ +class SockReceiveBuffer { +public: + /** + * Set to the buffer size. + */ + int value{2048}; + + /** + * Set the option. + * + * @param sc the socket + * @throw Error on errors + */ + template <typename Address, typename Protocol> + inline void set(Socket<Address, Protocol> &sc) const + { + sc.set(SOL_SOCKET, SO_RCVBUF, value); + } + + /** + * Get the option. + * + * @return the value + * @throw Error on errors + */ + template <typename Address, typename Protocol> + inline int get(Socket<Address, Protocol> &sc) const + { + return sc.template get<int>(SOL_SOCKET, SO_RCVBUF); + } +}; + +/* }}} */ + +/** + * @class TcpNoDelay + * @brief Set this option if you want to disable nagle's algorithm. + */ +class TcpNoDelay { +public: + /** + * Set to true to set TCP_NODELAY option. + */ + bool value{true}; + + /** + * Set the option. + * + * @param sc the socket + * @throw Error on errors + */ + template <typename Address, typename Protocol> + inline void set(Socket<Address, Protocol> &sc) const + { + sc.set(IPPROTO_TCP, TCP_NODELAY, value ? 1 : 0); + } + + /** + * Get the option. + * + * @return the value + * @throw Error on errors + */ + template <typename Address, typename Protocol> + inline bool get(Socket<Address, Protocol> &sc) const + { + return static_cast<bool>(sc.template get<int>(IPPROTO_TCP, TCP_NODELAY)); + } +}; + +/** + * @class Ipv6Only + * @brief Control IPPROTO_IPV6/IPV6_V6ONLY + * + * Note: some systems may or not set this option by default so it's a good idea to set it in any case to either + * false or true if portability is a concern. + */ +class Ipv6Only { +public: + /** + * Set this to use only IPv6. + */ + bool value{true}; + + /** + * Set the option. + * + * @param sc the socket + * @throw Error on errors + */ + template <typename Address, typename Protocol> + inline void set(Socket<Address, Protocol> &sc) const + { + sc.set(IPPROTO_IPV6, IPV6_V6ONLY, value ? 1 : 0); + } + + /** + * Get the option. + * + * @return the value + * @throw Error on errors + */ + template <typename Address, typename Protocol> + inline bool get(Socket<Address, Protocol> &sc) const + { + return static_cast<bool>(sc.template get<int>(IPPROTO_IPV6, IPV6_V6ONLY)); + } +}; + +} // !option + +/* }}} */ + +/* + * Predefined addressed to be used + * ------------------------------------------------------------------ + * + * - Ip, + * - Local. + */ + +/* {{{ Addresses */ + +/** + * Set of predefined addresses. + */ +namespace address { + +/** + * @class Ip + * @brief Base class for IPv6 and IPv4, you can use it if you don't know in advance if you'll use IPv6 or IPv4. + */ +class Ip { +public: + /** + * @enum Type + * @brief Type of ip address. + */ + enum Type { + v4 = AF_INET, //!< AF_INET + v6 = AF_INET6 //!< AF_INET6 + }; + +private: + /* + * Default domain when using default constructors. + * + * Note: AF_INET or AF_INET6, not + */ + static int m_default; + + union { + sockaddr_in m_sin; + sockaddr_in6 m_sin6; + }; + + socklen_t m_length{0}; + int m_domain{AF_INET}; + +public: + /** + * Set the default domain to use when using default Ip constructor. By default, AF_INET is used. + * + * @pre domain must be Type::v4 or Type::v6 + */ + static inline void setDefault(Type domain) noexcept + { + assert(domain == Type::v4 || domain == Type::v6); + + m_default = static_cast<int>(domain); + } + + /** + * Construct using the default domain. + */ + inline Ip() noexcept + : Ip(static_cast<Type>(m_default)) + { + } + + /** + * Default initialize the Ip domain. + * + * @pre domain must be AF_INET or AF_INET6 only + * @param domain the domain (AF_INET or AF_INET6) + */ + Ip(Type domain) noexcept; + + /** + * Construct an address suitable for bind() or connect(). + * + * @pre domain must be Type::v4 or Type::v6 + * @param domain the domain (AF_INET or AF_INET6) + * @param host the host (* for any) + * @param port the port number + * @throw Error on errors + */ + Ip(const std::string &host, int port, Type domain = v4); + + /** + * Construct an address from a storage. + * + * @pre storage's domain must be AF_INET or AF_INET6 only + * @param ss the storage + * @param length the length + */ + Ip(const sockaddr_storage *ss, socklen_t length) noexcept; + + /** + * Get the domain (AF_INET or AF_INET6). + * + * @return the domain + */ + inline int domain() const noexcept + { + return m_domain; + } + + /** + * Return the underlying address, either sockaddr_in6 or sockaddr_in. + * + * @return the address + */ + inline const sockaddr *address() const noexcept + { + if (m_domain == AF_INET6) { + return reinterpret_cast<const sockaddr *>(&m_sin6); + } + + return reinterpret_cast<const sockaddr *>(&m_sin); + } + + /** + * Return the underlying address length. + * + * @return the length + */ + inline socklen_t length() const noexcept + { + return m_length; + } + + /** + * Get the port. + * + * @return the port + */ + inline int port() const noexcept + { + if (m_domain == AF_INET6) { + return ntohs(m_sin6.sin6_port); + } + + return ntohs(m_sin.sin_port); + } +}; + +#if !defined(_WIN32) + +/** + * @class Local + * @brief unix family sockets + * + * Create an address to a specific path. Only available on Unix. + */ +class Local { +private: + sockaddr_un m_sun; + std::string m_path; + +public: + /** + * Get the domain AF_LOCAL. + * + * @return AF_LOCAL + */ + inline int domain() const noexcept + { + return AF_LOCAL; + } + + /** + * Default constructor. + */ + Local() noexcept; + + /** + * Construct an address to a path. + * + * @param path the path + * @param rm remove the file before (default: false) + */ + Local(std::string path, bool rm = false) noexcept; + + /** + * Construct an unix address from a storage address. + * + * @pre storage's domain must be AF_LOCAL + * @param ss the storage + * @param length the length + */ + Local(const sockaddr_storage *ss, socklen_t length) noexcept; + + /** + * Get the sockaddr_un. + * + * @return the address + */ + inline const sockaddr *address() const noexcept + { + return reinterpret_cast<const sockaddr *>(&m_sun); + } + + /** + * Get the address length. + * + * @return the length + */ + inline socklen_t length() const noexcept + { +#if defined(SOCKET_HAVE_SUN_LEN) + return SUN_LEN(&m_sun); +#else + return sizeof (m_sun); +#endif + } +}; + +#endif // !_WIN32 + +} // !address + +/* }}} */ + +/* + * Predefined protocols + * ------------------------------------------------------------------ + * + * - Tcp, for standard stream connections, + * - Udp, for standard datagram connections, + * - Tls, for secure stream connections. + */ + +/* {{{ Protocols */ + +/** + * Set of predefined protocols. + */ +namespace protocol { + +/* {{{ Tcp */ + +/** + * @class Tcp + * @brief Clear TCP implementation. + * + * This is the basic TCP protocol that implements recv, send, connect and accept as wrappers of the usual + * C functions. + */ +class Tcp { +public: + /** + * Socket type. + * + * @return SOCK_STREAM + */ + inline int type() const noexcept + { + return SOCK_STREAM; + } + + /** + * Do nothing. + * + * This function is just present for compatibility, it should never be called. + */ + template <typename Address> + inline void create(Socket<Address, Tcp> &) const noexcept + { + /* No-op */ + } + + /** + * Standard connect. + * + * If the socket is marked non-blocking and the connection cannot be established immediately, then the + * following is true: + * + * - state is set to State::Connecting, + * - action is set to Action::Connect, + * - condition is set to Condition::Writable. + * + * Then the user must wait until the socket is writable and call connect() with 0 arguments. + * + * If the socket is blocking, this function blocks until the connection is complete or an error occurs, in + * that case state is either set to State::Connected or State::Disconnected but action and condition are + * not set. + * + * @param sc the socket + * @param address the address + * @param length the length + * @throw net::Error on errors + * @note Wrapper of connect(2) + */ + template <typename Address, typename Protocol> + void connect(Socket<Address, Protocol> &sc, const sockaddr *address, socklen_t length) + { + if (::connect(sc.handle(), address, length) == Failure) { + /* + * Determine if the error comes from a non-blocking connect that cannot be + * accomplished yet. + */ +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) { + sc.setState(State::Connecting); + sc.setAction(Action::Connect); + sc.setCondition(Condition::Writable); + } else { + sc.setState(State::Disconnected); + throw Error{Error::System, "connect", error}; + } +#else + if (errno == EINPROGRESS) { + sc.setState(State::Connecting); + sc.setAction(Action::Connect); + sc.setCondition(Condition::Writable); + } else { + sc.setState(State::Disconnected); + throw Error{Error::System, "connect"}; + } +#endif + } else { + sc.setState(State::Connected); + } + } + + /** + * Continue the connection. This function must only be called when the socket is ready for writing, + * the user is responsible of waiting for that condition. + * + * This function check for SOL_SOCKET/SO_ERROR status. + * + * If the connection is complete, status is set to State::Connected, otherwise it is set to + * State::Disconnected. In both cases, action and condition are not set. + * + * @param sc the socket + * @throw net::Error on errors + */ + template <typename Address, typename Protocol> + void connect(Socket<Address, Protocol> &sc) + { + int error = sc.template get<int>(SOL_SOCKET, SO_ERROR); + + if (error == Failure) { + sc.setState(State::Disconnected); + throw Error{Error::System, "connect", error}; + } + + sc.setState(State::Connected); + } + + /** + * Accept a clear client. + * + * If the socket is marked non-blocking and there are no pending connection, this function throws an + * error. The user must wait that the socket is readable before calling this function. + * + * If the socket is blocking, this function blocks until a new client is connected or throws an error on + * errors. + * + * If the socket is correctly returned, its state is set to State::Accepted and its action and condition + * are not set. + * + * In any case, action and condition of this socket are not set. + * + * @param sc the socket + * @param address the address destination + * @param length the address length + * @return the socket + * @throw net::Error on errors + * @note Wrapper of accept(2) + */ + template <typename Address, typename Protocol> + Socket<Address, Protocol> accept(Socket<Address, Protocol> &sc, sockaddr *address, socklen_t *length) + { + Handle handle = ::accept(sc.handle(), address, length); + + if (handle == Invalid) { + throw Error{Error::System, "accept"}; + } + + return Socket<Address, Protocol>{handle, State::Accepted}; + } + + /** + * Continue accept. + * + * This function is just present for compatibility, it should never be called. + */ + template <typename Address, typename Protocol> + inline void accept(Socket<Address, Protocol> &) const noexcept + { + /* no-op */ + } + + /** + * Receive data. + * + * If the socket is marked non-blocking and no data is available, 0 is returned and condition is set to + * Condition::Readable. If 0 is returned and condition is not set, then the state is set to + * State::Disconnected. + * + * If the socket is blocking, this function blocks until some data is available or if an error occurs. + * + * In any case, action is never set. + * + * @param sc the socket + * @param data the destination + * @param length the destination length + * @return the number of bytes read + * @throw Error on errors + * @note Wrapper of recv(2) + */ + template <typename Address> + unsigned recv(Socket<Address, Tcp> &sc, void *data, unsigned length) + { + int nbread = ::recv(sc.handle(), (Arg)data, length, 0); + + if (nbread == Failure) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) { + nbread = 0; + sc.setCondition(Condition::Readable); + } else { + sc.setState(State::Disconnected); + throw Error{Error::System, "recv", error}; + } +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) { + sc.setCondition(Condition::Readable); + } else { + sc.setState(State::Disconnected); + throw Error{Error::System, "recv"}; + } +#endif + } else if (nbread == 0) { + sc.setState(State::Disconnected); + } + + return static_cast<unsigned>(nbread); + } + + /** + * Send some data. + * + * If the socket is marked non-blocking and the operation would block, then 0 is returned and condition is set to + * Condition::Writable. + * + * If the socket is blocking, this function blocks until the data has been sent. + * + * On any other errors, this function throw net::Error. + * + * @param sc the socket + * @param data the buffer to send + * @param length the buffer length + * @return the number of bytes sent + * @throw net::Error on errors + * @note Wrapper of send(2) + */ + template <typename Address> + unsigned send(Socket<Address, Tcp> &sc, const void *data, unsigned length) + { + int nbsent = ::send(sc.handle(), (ConstArg)data, length, 0); + + if (nbsent == Failure) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) { + nbsent = 0; + sc.setCondition(Condition::Writable); + } else { + sc.setState(State::Disconnected); + throw Error{Error::System, "send", error}; + } +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) { + nbsent = 0; + sc.setCondition(Condition::Writable); + } else { + sc.setState(State::Disconnected); + throw Error{Error::System, "send"}; + } +#endif + } + + return static_cast<unsigned>(nbsent); + } +}; + +/* }}} */ + +/* {{{ Udp */ + +/** + * @class Udp + * @brief Clear UDP type. + * + * This class is the basic implementation of UDP sockets. + */ +class Udp { +public: + /** + * Socket type. + * + * @return SOCK_DGRAM + */ + inline int type() const noexcept + { + return SOCK_DGRAM; + } + + /** + * Do nothing. + */ + template <typename Address> + inline void create(Socket<Address, Udp> &) noexcept + { + /* No-op */ + } + + /** + * Receive data from an end point. + * + * If the socket is marked non-blocking and no data is available, 0 is returned and condition is set to + * Condition::Readable. + * + * If the socket is blocking, this functions blocks until some data is available or if an error occurs. + * + * @param sc the socket + * @param data the destination buffer + * @param length the buffer length + * @param address the address + * @param addrlen the initial address length + * @return the number of bytes received + * @throw Error on error + */ + template <typename Address> + unsigned recvfrom(Socket<Address, Udp> &sc, void *data, unsigned length, sockaddr *address, socklen_t *addrlen) + { + int nbread; + + nbread = ::recvfrom(sc.handle(), (Arg)data, length, 0, address, addrlen); + + if (nbread == Failure) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) { + nbread = 0; + sc.setCondition(Condition::Readable); + } else { + throw Error{Error::System, "recvfrom"}; + } +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) { + nbread = 0; + sc.setCondition(Condition::Readable); + } else { + throw Error{Error::System, "recvfrom"}; + } +#endif + } + + return static_cast<unsigned>(nbread); + } + + /** + * Send data to an end point. + * + * If the socket is marked non-blocking and the operation would block, then 0 is returned and condition is set to + * Condition::Writable. + * + * If the socket is blocking, this functions blocks until the data has been sent. + * + * @param sc the socket + * @param data the buffer + * @param length the buffer length + * @param address the client address + * @param addrlen the adderss length + * @return the number of bytes sent + * @throw Error on error + */ + template <typename Address> + unsigned sendto(Socket<Address, Udp> &sc, const void *data, unsigned length, const sockaddr *address, socklen_t addrlen) + { + int nbsent; + + nbsent = ::sendto(sc.handle(), (ConstArg)data, length, 0, address, addrlen); + if (nbsent == Failure) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) { + nbsent = 0; + sc.setCondition(Condition::Writable); + } else { + throw Error{Error::System, "sendto", error}; + } +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) { + nbsent = 0; + sc.setCondition(Condition::Writable); + } else { + throw Error{Error::System, "sendto"}; + } +#endif + } + + return static_cast<unsigned>(nbsent); + } +}; + +/* }}} */ + +/* {{{ Tls */ + +#if !defined(SOCKET_NO_SSL) + +/** + * @class Tls + * @brief OpenSSL secure layer for TCP. + * + * **Note:** This protocol is much more difficult to use with non-blocking sockets, if some operations would block, the + * user is responsible of calling the function again by waiting for the appropriate condition. See the functions for + * more details. + * + * @see Tls::accept + * @see Tls::connect + * @see Tls::recv + * @see Tls::send + */ +class Tls : private Tcp { +private: + using Context = std::shared_ptr<SSL_CTX>; + using Ssl = std::unique_ptr<SSL, void (*)(SSL *)>; + + /* OpenSSL objects */ + Context m_context; + Ssl m_ssl{nullptr, nullptr}; + + /* Status */ + bool m_tcpconnected{false}; + + /* + * User definable parameters + */ + ssl::Method m_method{ssl::Tlsv1}; + std::string m_key; + std::string m_certificate; + bool m_verify{false}; + + /* + * Construct with a context and ssl, for Tls::accept. + */ + Tls(Context context, Ssl ssl) + : m_context{std::move(context)} + , m_ssl{std::move(ssl)} + { + } + + /* + * Get the OpenSSL error message. + */ + inline std::string error(int error) + { + auto msg = ERR_reason_error_string(error); + + return msg == nullptr ? "" : msg; + } + + /* + * Update the states after an uncompleted operation. + */ + template <typename Address, typename Protocol> + inline void updateStates(Socket<Address, Protocol> &sc, State state, Action action, int code) + { + assert(code == SSL_ERROR_WANT_READ || code == SSL_ERROR_WANT_WRITE); + + sc.setState(state); + sc.setAction(action); + + if (code == SSL_ERROR_WANT_READ) { + sc.setCondition(Condition::Readable); + } else { + sc.setCondition(Condition::Writable); + } + } + + /* + * Continue the connect operation. + */ + template <typename Address, typename Protocol> + void processConnect(Socket<Address, Protocol> &sc) + { + int ret = SSL_connect(m_ssl.get()); + + if (ret <= 0) { + int no = SSL_get_error(m_ssl.get(), ret); + + if (no == SSL_ERROR_WANT_READ || no == SSL_ERROR_WANT_WRITE) { + updateStates(sc, State::Connecting, Action::Connect, no); + } else { + sc.setState(State::Disconnected); + throw Error{Error::System, "connect", error(no)}; + } + } else { + sc.setState(State::Connected); + } + } + + /* + * Continue accept. + */ + template <typename Address, typename Protocol> + void processAccept(Socket<Address, Protocol> &sc) + { + int ret = SSL_accept(m_ssl.get()); + + if (ret <= 0) { + int no = SSL_get_error(m_ssl.get(), ret); + + if (no == SSL_ERROR_WANT_READ || no == SSL_ERROR_WANT_WRITE) { + updateStates(sc, State::Accepting, Action::Accept, no); + } else { + sc.setState(State::Disconnected); + throw Error(Error::System, "accept", error(no)); + } + } else { + sc.setState(State::Accepted); + } + } + +public: + /** + * @copydoc Tcp::type + */ + inline int type() const noexcept + { + return SOCK_STREAM; + } + + /** + * Empty TLS constructor. + */ + Tls() + { +#if !defined(SOCKET_NO_SSL_AUTO_INIT) + ::net::ssl::init(); +#endif + } + + /** + * Set the method. + * + * @param method the method + * @pre the socket must not be already created + */ + inline void setMethod(ssl::Method method) noexcept + { + assert(!m_context); + assert(!m_ssl); + + m_method = method; + } + + /** + * Use the specified private key file. + * + * @param file the path to the private key + */ + inline void setPrivateKey(std::string file) noexcept + { + m_key = std::move(file); + } + + /** + * Use the specified certificate file. + * + * @param file the path to the file + */ + inline void setCertificate(std::string file) noexcept + { + m_certificate = std::move(file); + } + + /** + * Set to true if we must verify the certificate and private key. + * + * @param verify the mode + */ + inline void setVerify(bool verify = true) noexcept + { + m_verify = verify; + } + + /** + * Initialize the SSL objects after have created. + * + * @param sc the socket + * @throw net::Error on errors + */ + template <typename Address> + inline void create(Socket<Address, Tls> &sc) + { + auto method = (m_method == ssl::Tlsv1) ? TLSv1_method() : SSLv3_method(); + + m_context = {SSL_CTX_new(method), SSL_CTX_free}; + m_ssl = {SSL_new(m_context.get()), SSL_free}; + + SSL_set_fd(m_ssl.get(), sc.handle()); + + /* Load certificates */ + if (m_certificate.size() > 0) { + SSL_CTX_use_certificate_file(m_context.get(), m_certificate.c_str(), SSL_FILETYPE_PEM); + } + if (m_key.size() > 0) { + SSL_CTX_use_PrivateKey_file(m_context.get(), m_key.c_str(), SSL_FILETYPE_PEM); + } + if (m_verify && !SSL_CTX_check_private_key(m_context.get())) { + throw Error{Error::System, "(openssl)", "unable to verify key"}; + } + } + + /** + * Connect to a secure host. + * + * If the socket is marked non-blocking and the connection cannot be established yet, then the state is set + * to State::Connecting, the condition is set to Condition::Readable or Condition::Writable, the user must + * wait for the appropriate condition before calling the overload connect which takes 0 argument. + * + * If the socket is blocking, this functions blocks until the connection is complete. + * + * If the connection was completed correctly the state is set to State::Connected. + * + * @param sc the socket + * @param address the address + * @param length the address length + * @throw net::Error on errors + */ + template <typename Address, typename Protocol> + void connect(Socket<Address, Protocol> &sc, const sockaddr *address, socklen_t length) + { + /* 1. Connect using raw TCP */ + Tcp::connect(sc, address, length); + + /* 2. If the connection is complete (e.g. non-blocking), try handshake */ + if (sc.state() == State::Connected) { + m_tcpconnected = true; + processConnect(sc); + } + } + + /** + * Continue the connection. + * + * This function must be called when the socket is ready for reading or writing (check with Socket::condition), + * the state may change exactly like the initial connect call. + * + * @param sc the socket + * @throw net::Error on errors + */ + template <typename Address, typename Protocol> + void connect(Socket<Address, Protocol> &sc) + { + /* 1. Be sure to complete standard connect before */ + if (!m_tcpconnected) { + Tcp::connect(sc); + m_tcpconnected = sc.state() == State::Connected; + } + + if (m_tcpconnected) { + processConnect(sc); + } + } + + /** + * Accept a secure client. + * + * Because SSL needs several round-trips, if the socket is marked non-blocking and the connection is not + * completed yet, a new socket is returned but with the State::Accepting state. Its condition is set to + * Condition::Readable or Condition::Writable, the user is responsible of calling accept overload which takes + * 0 arguments on the returned socket when the condition is met. + * + * If the socket is blocking, this function blocks until the client is accepted and returned. + * + * If the client is accepted correctly, its state is set to State::Accepted. This instance does not change. + * + * @param sc the socket + * @param address the address destination + * @param length the address length + * @return the client + * @throw net::Error on errors + */ + template <typename Address> + Socket<Address, Tls> accept(Socket<Address, Tls> &sc, sockaddr *address, socklen_t *length) + { + Socket<Address, Tls> client = Tcp::accept(sc, address, length); + Tls &proto = client.protocol(); + + /* 1. Share the context */ + proto.m_context = m_context; + + /* 2. Create new SSL instance */ + proto.m_ssl = Ssl{SSL_new(m_context.get()), SSL_free}; + SSL_set_fd(proto.m_ssl.get(), client.handle()); + + /* 3. Try accept process on the **new** client */ + proto.processAccept(client); + + return client; + } + + /** + * Continue accept. + * + * This function must be called on the client that is being accepted. + * + * Like accept or connect, user is responsible of calling this function until the connection is complete. + * + * @param sc the socket + * @throw net::Error on errors + */ + template <typename Address, typename Protocol> + inline void accept(Socket<Address, Protocol> &sc) + { + processAccept(sc); + } + + /** + * Receive some secure data. + * + * If the socket is marked non-blocking, 0 is returned if no data is available yet or if the connection + * needs renegociation. If renegociation is required case, the action is set to Action::Receive and condition + * is set to Condition::Readable or Condition::Writable. The user must wait that the condition is met and + * call this function again. + * + * @param sc the socket + * @param data the destination + * @param len the buffer length + * @return the number of bytes read + * @throw net::Error on errors + */ + template <typename Address> + unsigned recv(Socket<Address, Tls> &sc, void *data, unsigned len) + { + auto nbread = SSL_read(m_ssl.get(), data, len); + + if (nbread <= 0) { + auto no = SSL_get_error(m_ssl.get(), nbread); + + if (no == SSL_ERROR_WANT_READ || no == SSL_ERROR_WANT_WRITE) { + nbread = 0; + updateStates(sc, sc.state(), Action::Receive, no); + } else { + throw Error{Error::System, "recv", error(no)}; + } + } + + return nbread; + } + + /** + * Send some data. + * + * Like recv, if the socket is marked non-blocking and no data can be sent or a negociation is required, + * condition and action are set. See receive for more details + * + * @param sc the socket + * @param data the data to send + * @param len the buffer length + * @return the number of bytes sent + * @throw net::Error on errors + */ + template <typename Address> + unsigned send(Socket<Address, Tls> &sc, const void *data, unsigned len) + { + auto nbsent = SSL_write(m_ssl.get(), data, len); + + if (nbsent <= 0) { + auto no = SSL_get_error(m_ssl.get(), nbsent); + + if (no == SSL_ERROR_WANT_READ || no == SSL_ERROR_WANT_WRITE) { + nbsent = 0; + updateStates(sc, sc.state(), Action::Send, no); + } else { + throw Error{Error::System, "send", error(no)}; + } + } + + return nbsent; + } +}; + +#endif // !SOCKET_NO_SSL + +/* }}} */ + +} // !protocol + +/* }}} */ + +/* + * Convenient helpers + * ------------------------------------------------------------------ + * + * - SocketTcp<Address>, for TCP sockets, + * - SocketUdp<Address>, for UDP sockets, + * - SocketTls<Address>, for secure TCP sockets. + */ + +/* {{{ Helpers */ + +/** + * Helper to create TCP sockets. + */ +template <typename Address> +using SocketTcp = Socket<Address, protocol::Tcp>; + +/** + * Helper to create TCP/IP sockets. + */ +using SocketTcpIp = Socket<address::Ip, protocol::Tcp>; + +#if !defined(_WIN32) + +/** + * Helper to create TCP/Local sockets. + */ +using SocketTcpLocal = Socket<address::Local, protocol::Tcp>; + +#endif + +/** + * Helper to create UDP sockets. + */ +template <typename Address> +using SocketUdp = Socket<Address, protocol::Udp>; + +/** + * Helper to create UDP/IP sockets. + */ +using SocketUdpIp = Socket<address::Ip, protocol::Udp>; + +#if !defined(SOCKET_NO_SSL) + +/** + * Helper to create OpenSSL TCP sockets. + */ +template <typename Address> +using SocketTls = Socket<Address, protocol::Tls>; + +/** + * Helper to create OpenSSL TCP/Ip sockets. + */ +using SocketTlsIp = Socket<address::Ip, protocol::Tls>; + +#endif // !SOCKET_NO_SSL + +/* }}} */ + +/* + * Select wrapper + * ------------------------------------------------------------------ + * + * Wrapper for select(2) and other various implementations. + */ + +/* {{{ Listener */ + +/** + * @class ListenerStatus + * @brief Result of polling + * + * Result of a select call, returns the first ready socket found with its + * flags. + */ +class ListenerStatus { +public: + Handle socket; //!< which socket is ready + Condition flags; //!< the flags +}; + +/** + * Table used in the socket listener to store which sockets have been + * set in which directions. + */ +using ListenerTable = std::map<Handle, Condition>; + +/** + * @class Select + * @brief Implements select(2) + * + * This class is the fallback of any other method, it is not preferred at all for many reasons. + */ +class Select { +public: + /** + * No-op, uses the ListenerTable directly. + */ + inline void set(const ListenerTable &, Handle, Condition, bool) noexcept {} + + /** + * No-op, uses the ListenerTable directly. + */ + inline void unset(const ListenerTable &, Handle, Condition, bool) noexcept {} + + /** + * Return the sockets + */ + std::vector<ListenerStatus> wait(const ListenerTable &table, int ms); + + /** + * Backend identifier + */ + inline const char *name() const noexcept + { + return "select"; + } +}; + +#if defined(SOCKET_HAVE_POLL) + +/** + * @class Poll + * @brief Implements poll(2). + * + * Poll is widely supported and is better than select(2). It is still not the + * best option as selecting the sockets is O(n). + */ +class Poll { +private: + std::vector<pollfd> m_fds; + + short toPoll(Condition flags) const noexcept; + Condition toCondition(short &event) const noexcept; + +public: + /** + * Set the handle. + */ + void set(const ListenerTable &, Handle, Condition, bool); + + /** + * Unset the handle. + */ + void unset(const ListenerTable &, Handle, Condition, bool); + + /** + * Wait for events. + */ + std::vector<ListenerStatus> wait(const ListenerTable &, int ms); + + /** + * Backend identifier + */ + inline const char *name() const noexcept + { + return "poll"; + } +}; + +#endif + +#if defined(SOCKET_HAVE_EPOLL) + +/** + * @class Epoll + * @brief Linux's epoll. + */ +class Epoll { +private: + int m_handle; + std::vector<epoll_event> m_events; + + Epoll(const Epoll &) = delete; + Epoll &operator=(const Epoll &) = delete; + Epoll(const Epoll &&) = delete; + Epoll &operator=(const Epoll &&) = delete; + + uint32_t toEpoll(Condition flags) const noexcept; + Condition toCondition(uint32_t events) const noexcept; + void update(Handle sc, int op, int eflags); + +public: + /** + * Construct the epoll instance. + */ + Epoll(); + + /** + * Close the epoll instance. + */ + ~Epoll(); + + /** + * Set the handle. + */ + void set(const ListenerTable &, Handle, Condition, bool); + + /** + * Unset the handle. + */ + void unset(const ListenerTable &, Handle, Condition, bool); + + /** + * Wait for events. + */ + std::vector<ListenerStatus> wait(const ListenerTable &, int); + + /** + * Backend identifier + */ + inline const char *name() const noexcept + { + return "epoll"; + } +}; + +#endif + +#if defined(SOCKET_HAVE_KQUEUE) + +/** + * @class Kqueue + * @brief Implements kqueue(2). + * + * This implementation is available on all BSD and Mac OS X. It is better than + * poll(2) because it's O(1), however it's a bit more memory consuming. + */ +class Kqueue { +private: + std::vector<struct kevent> m_result; + int m_handle; + + Kqueue(const Kqueue &) = delete; + Kqueue &operator=(const Kqueue &) = delete; + Kqueue(Kqueue &&) = delete; + Kqueue &operator=(Kqueue &&) = delete; + + void update(Handle sc, int filter, int kflags); + +public: + /** + * Construct the kqueue instance. + */ + Kqueue(); + + /** + * Destroy the kqueue instance. + */ + ~Kqueue(); + + /** + * Set the handle. + */ + void set(const ListenerTable &, Handle, Condition, bool); + + /** + * Unset the handle. + */ + void unset(const ListenerTable &, Handle, Condition, bool); + + /** + * Wait for events. + */ + std::vector<ListenerStatus> wait(const ListenerTable &, int); + + /** + * Backend identifier + */ + inline const char *name() const noexcept + { + return "kqueue"; + } +}; + +#endif + +/** + * @class Listener + * @brief Synchronous multiplexing + * + * Convenient wrapper around the select() system call. + * + * This class is implemented using a bridge pattern to allow different uses + * of listener implementation. + * + * You should not reinstanciate a new Listener at each iteartion of your + * main loop as it can be extremely costly. Instead use the same listener that + * you can safely modify on the fly. + * + * Currently, poll, epoll, select and kqueue are available. + * + * To implement the backend, the following functions must be available: + * + * ### Set + * + * @code + * void set(const ListenerTable &, Handle sc, Condition condition, bool add); + * @endcode + * + * This function, takes the socket to be added and the flags. The condition is + * always guaranteed to be correct and the function will never be called twice + * even if the user tries to set the same flag again. + * + * An optional add argument is added for backends which needs to do different + * operation depending if the socket was already set before or if it is the + * first time (e.g EPOLL_CTL_ADD vs EPOLL_CTL_MOD for epoll(7). + * + * ### Unset + * + * @code + * void unset(const ListenerTable &, Handle sc, Condition condition, bool remove); + * @endcode + * + * Like set, this function is only called if the condition is actually set and will + * not be called multiple times. + * + * Also like set, an optional remove argument is set if the socket is being + * completely removed (e.g no more flags are set for this socket). + * + * ### Wait + * + * @code + * std::vector<ListenerStatus> wait(const ListenerTable &, int ms); + * @endcode + * + * Wait for the sockets to be ready with the specified milliseconds. Must return a list of ListenerStatus, + * may throw any exceptions. + * + * ### Name + * + * @code + * inline const char *name() const noexcept + * @endcode + * + * Returns the backend name. Usually the class in lower case. + */ +template <typename Backend = SOCKET_DEFAULT_BACKEND> +class Listener { +private: + Backend m_backend; + ListenerTable m_table; + +public: + /** + * Construct an empty listener. + */ + Listener() = default; + + /** + * Get the backend. + * + * @return the backend + */ + inline const Backend &backend() const noexcept + { + return m_backend; + } + + /** + * Get the non-modifiable table. + * + * @return the table + */ + inline const ListenerTable &table() const noexcept + { + return m_table; + } + + /** + * Overloaded function. + * + * @return the iterator + */ + inline ListenerTable::const_iterator begin() const noexcept + { + return m_table.begin(); + } + + /** + * Overloaded function. + * + * @return the iterator + */ + inline ListenerTable::const_iterator cbegin() const noexcept + { + return m_table.cbegin(); + } + + /** + * Overloaded function. + * + * @return the iterator + */ + inline ListenerTable::const_iterator end() const noexcept + { + return m_table.end(); + } + + /** + * Overloaded function. + * + * @return the iterator + */ + inline ListenerTable::const_iterator cend() const noexcept + { + return m_table.cend(); + } + + /** + * Add or update a socket to the listener. + * + * If the socket is already placed with the appropriate flags, the + * function is a no-op. + * + * If incorrect flags are passed, the function does nothing. + * + * @param sc the socket + * @param condition the condition (may be OR'ed) + * @throw Error if the backend failed to set + */ + void set(Handle sc, Condition condition) + { + /* Invalid or useless flags */ + if (condition == Condition::None || static_cast<int>(condition) > 0x3) + return; + + auto it = m_table.find(sc); + + /* + * Do not update the table if the backend failed to add + * or update. + */ + if (it == m_table.end()) { + m_backend.set(m_table, sc, condition, true); + m_table.emplace(sc, condition); + } else { + /* Remove flag if already present */ + if ((condition & Condition::Readable) == Condition::Readable && + (it->second & Condition::Readable) == Condition::Readable) { + condition &= ~(Condition::Readable); + } + if ((condition & Condition::Writable) == Condition::Writable && + (it->second & Condition::Writable) == Condition::Writable) { + condition &= ~(Condition::Writable); + } + + /* Still need a call? */ + if (condition != Condition::None) { + m_backend.set(m_table, sc, condition, false); + it->second |= condition; + } + } + } + + /** + * Unset a socket from the listener, only the flags is removed + * unless the two flagss are requested. + * + * For example, if you added a socket for both reading and writing, + * unsetting the write flags will keep the socket for reading. + * + * @param sc the socket + * @param condition the condition (may be OR'ed) + * @see remove + */ + void unset(Handle sc, Condition condition) + { + auto it = m_table.find(sc); + + /* Invalid or useless flags */ + if (condition == Condition::None || static_cast<int>(condition) > 0x3 || it == m_table.end()) + return; + + /* + * Like set, do not update if the socket is already at the appropriate + * state. + */ + if ((condition & Condition::Readable) == Condition::Readable && + (it->second & Condition::Readable) != Condition::Readable) { + condition &= ~(Condition::Readable); + } + if ((condition & Condition::Writable) == Condition::Writable && + (it->second & Condition::Writable) != Condition::Writable) { + condition &= ~(Condition::Writable); + } + + if (condition != Condition::None) { + /* Determine if it's a complete removal */ + bool removal = ((it->second) & ~(condition)) == Condition::None; + + m_backend.unset(m_table, sc, condition, removal); + + if (removal) { + m_table.erase(it); + } else { + it->second &= ~(condition); + } + } + } + + /** + * Remove completely the socket from the listener. + * + * It is a shorthand for unset(sc, Condition::Readable | Condition::Writable); + * + * @param sc the socket + */ + inline void remove(Handle sc) + { + unset(sc, Condition::Readable | Condition::Writable); + } + + /** + * Remove all sockets. + */ + inline void clear() + { + while (!m_table.empty()) { + remove(m_table.begin()->first); + } + } + + /** + * Get the number of sockets in the listener. + */ + inline ListenerTable::size_type size() const noexcept + { + return m_table.size(); + } + + /** + * Select a socket. Waits for a specific amount of time specified as the duration. + * + * @param duration the duration + * @return the socket ready + */ + template <typename Rep, typename Ratio> + inline ListenerStatus wait(const std::chrono::duration<Rep, Ratio> &duration) + { + auto cvt = std::chrono::duration_cast<std::chrono::milliseconds>(duration); + + return m_backend.wait(m_table, cvt.count())[0]; + } + + /** + * Overload with milliseconds. + * + * @param timeout the optional timeout in milliseconds + * @return the socket ready + */ + inline ListenerStatus wait(int timeout = -1) + { + return wait(std::chrono::milliseconds(timeout)); + } + + /** + * Select multiple sockets. + * + * @param duration the duration + * @return the socket ready + */ + template <typename Rep, typename Ratio> + inline std::vector<ListenerStatus> waitMultiple(const std::chrono::duration<Rep, Ratio> &duration) + { + auto cvt = std::chrono::duration_cast<std::chrono::milliseconds>(duration); + + return m_backend.wait(m_table, cvt.count()); + } + + /** + * Overload with milliseconds. + * + * @return the socket ready + */ + inline std::vector<ListenerStatus> waitMultiple(int timeout = -1) + { + return waitMultiple(std::chrono::milliseconds(timeout)); + } +}; + +/* }}} */ + +/* + * Callback + * ------------------------------------------------------------------ + * + * Function owner with tests. + */ + +/* {{{ Callback */ + +/** + * @class Callback + * @brief Convenient signal owner that checks if the target is valid. + * + * This class also catch all errors thrown from signals to avoid interfering with our process. + */ +template <typename... Args> +class Callback : public std::function<void (Args...)> { +public: + /** + * Inherited constructors. + */ + using std::function<void (Args...)>::function; + + /** + * Execute the callback only if a target is set. + */ + void operator()(Args... args) const + { + if (*this) { + try { + std::function<void (Args...)>::operator()(args...); + } catch (...) { + } + } + } +}; + +/* }}} */ + +/* + * StreamConnection + * ------------------------------------------------------------------ + * + * Client connected on the server side. + */ + +/* {{{ StreamConnection */ + +/** + * @class StreamConnection + * @brief Connected client on the server side. + * + * This object is created from StreamServer when a new client is connected, it is the higher + * level object of sockets and completely asynchronous. + */ +template <typename Address, typename Protocol> +class StreamConnection { +public: + /** + * Called when the output has changed. + */ + using WriteHandler = Callback<>; + +private: + /* Signals */ + WriteHandler m_onWrite; + + /* Sockets and output buffer */ + Socket<Address, Protocol> m_socket; + std::string m_output; + +public: + /** + * Create the connection. + * + * @param s the socket + */ + StreamConnection(Socket<Address, Protocol> s) + : m_socket{std::move(s)} + { + m_socket.set(net::option::SockBlockMode{false}); + } + + /** + * Access the underlying socket. + * + * @return the socket + * @warning use with care + */ + inline Socket<Address, Protocol> &socket() noexcept + { + return m_socket; + } + + /** + * Access the current output. + * + * @return the output + */ + inline const std::string &output() const noexcept + { + return m_output; + } + + /** + * Overloaded function + * + * @return the output + * @warning use with care, avoid modifying the output if you don't know what you're doing + */ + inline std::string &output() noexcept + { + return m_output; + } + + /** + * Post some data to be sent asynchronously. + * + * @param str the data to append + */ + inline void send(std::string str) + { + m_output += str; + m_onWrite(); + } + + /** + * Kill the client. + */ + inline void close() + { + m_socket.close(); + } + + /** + * Set the write handler, the signal is emitted when the output has changed so that the StreamServer owner + * knows that there are some data to send. + * + * @param handler the handler + * @warning you usually never need to set this yourself + */ + inline void setWriteHandler(WriteHandler handler) + { + m_onWrite = std::move(handler); + } +}; + +/* }}} */ + +/* + * StreamServer + * ------------------------------------------------------------------ + * + * Convenient stream oriented server. + */ + +/* {{{ StreamServer */ + +/** + * @class StreamServer + * @brief Convenient stream server for TCP and TLS. + * + * This class does all the things for you as accepting new clients, listening for it and sending data. It works + * asynchronously without blocking to let you control your process workflow. + * + * This class is not thread safe and you must not call any of the functions from different threads. + */ +template <typename Address, typename Protocol> +class StreamServer { +public: + /** + * Handler when a new client is connected. + */ + using ConnectionHandler = Callback<const std::shared_ptr<StreamConnection<Address, Protocol>> &>; + + /** + * Handler when a client is disconnected. + */ + using DisconnectionHandler = Callback<const std::shared_ptr<StreamConnection<Address, Protocol>> &>; + + /** + * Handler when data has been received from a client. + */ + using ReadHandler = Callback<const std::shared_ptr<StreamConnection<Address, Protocol>> &, const std::string &>; + + /** + * Handler when data has been correctly sent to a client. + */ + using WriteHandler = Callback<const std::shared_ptr<StreamConnection<Address, Protocol>> &, const std::string &>; + + /** + * Handler when an error occured. + */ + using ErrorHandler = Callback<const Error &>; + + /** + * Handler when there was a timeout. + */ + using TimeoutHandler = Callback<>; + +private: + using ClientMap = std::map<Handle, std::shared_ptr<StreamConnection<Address, Protocol>>>; + + /* Signals */ + ConnectionHandler m_onConnection; + DisconnectionHandler m_onDisconnection; + ReadHandler m_onRead; + WriteHandler m_onWrite; + ErrorHandler m_onError; + TimeoutHandler m_onTimeout; + + /* Sockets */ + Socket<Address, Protocol> m_master; + Listener<> m_listener; + ClientMap m_clients; + + /* + * Update flags depending on the required condition. + */ + void updateFlags(std::shared_ptr<StreamConnection<Address, Protocol>> &client) + { + assert(client->socket().action() != Action::None); + + m_listener.remove(client->socket().handle()); + m_listener.set(client->socket().handle(), client->socket().condition()); + } + + /* + * Continue accept process. + */ + template <typename AcceptCall> + void processAccept(std::shared_ptr<StreamConnection<Address, Protocol>> &client, const AcceptCall &acceptFunc) + { + try { + /* Do the accept */ + acceptFunc(); + + /* 1. First remove completely the client */ + m_listener.remove(client->socket().handle()); + + /* 2. If accept is not finished, wait for the appropriate condition */ + if (client->socket().state() == State::Accepted) { + /* 3. Client is accepted, notify the user */ + m_listener.set(client->socket().handle(), Condition::Readable); + m_onConnection(client); + } else { + /* Operation still in progress */ + updateFlags(client); + } + } catch (const Error &error) { + m_clients.erase(client->socket().handle()); + m_listener.remove(client->socket().handle()); + m_onError(error); + } + } + + /* + * Process initial accept of master socket, this is the initial accepting process. Except on errors, the + * socket is stored but the user will be notified only once the socket is completely accepted. + */ + void processInitialAccept() + { + // TODO: store address too. + std::shared_ptr<StreamConnection<Address, Protocol>> client = std::make_shared<StreamConnection<Address, Protocol>>(m_master.accept(nullptr)); + std::weak_ptr<StreamConnection<Address, Protocol>> ptr{client}; + + /* 1. Register output changed to update listener */ + client->setWriteHandler([this, ptr] () { + auto client = ptr.lock(); + + /* Do not update the listener immediately if an action is pending */ + if (client && client->socket().action() == Action::None && !client->output().empty()) { + m_listener.set(client->socket().handle(), Condition::Writable); + } + }); + + /* 2. Add the client */ + m_clients.insert(std::make_pair(client->socket().handle(), client)); + + /* + * 2. Do an initial check to set the listener flags, at this moment the socket may or not be + * completely accepted. + */ + processAccept(client, [&] () {}); + } + + /* + * Read or complete the read operation. + */ + void processRead(std::shared_ptr<StreamConnection<Address, Protocol>> &client) + { + /* + * Read because there is something to read or because the pending operation is + * read and must complete. + */ + auto buffer = client->socket().recv(512); + + /* + * Now the receive operation may be completed, in that case, two possibilities: + * + * 1. The action is set to None (completed) + * 2. The action is still not complete, update the flags + */ + if (client->socket().action() == Action::None) { + /* Empty mean normal disconnection */ + if (buffer.empty()) { + m_listener.remove(client->socket().handle()); + m_clients.erase(client->socket().handle()); + m_onDisconnection(client); + } else { + /* + * At this step, it is possible that we were completing a receive operation, in this + * case the write flag may be removed, add it if required. + */ + if (!client->output().empty()) { + m_listener.set(client->socket().handle(), Condition::Writable); + } + + m_onRead(client, buffer); + } + } else { + /* Operation in progress */ + updateFlags(client); + } + } + + /* + * Flush the output buffer. + */ + void processWrite(std::shared_ptr<StreamConnection<Address, Protocol>> &client) + { + auto &output = client->output(); + auto nsent = client->socket().send(output); + + if (client->socket().action() == Action::None) { + /* 1. Create a copy of content that has been sent */ + auto sent = output.substr(0, nsent); + + /* 2. Erase the content sent */ + output.erase(0, nsent); + + /* 3. Update listener */ + if (output.empty()) { + m_listener.unset(client->socket().handle(), Condition::Writable); + } + + /* 4. Notify user */ + m_onWrite(client, sent); + } else { + updateFlags(client); + } + } + + void processSync(std::shared_ptr<StreamConnection<Address, Protocol>> &client, Condition flags) + { + try { + auto action = client->socket().action(); + + if (action == Action::Receive || + (action == Action::None && (flags & Condition::Readable) == Condition::Readable)) { + processRead(client); + } else if ((flags & Condition::Writable) == Condition::Writable) { + processWrite(client); + } + } catch (const Error &error) { + m_onDisconnection(client); + m_listener.remove(client->socket().handle()); + m_clients.erase(client->socket().handle()); + } + } + +public: + /** + * Create a stream server with the specified address to bind. + * + * @param protocol the protocol (Tcp or Tls) + * @param address the address to bind + * @param max the max number to listen + * @throw Error on errors + */ + StreamServer(Protocol protocol, const Address &address, int max = 128) + : m_master{std::move(protocol), address} + { + // TODO: m_onError + m_master.set(SOL_SOCKET, SO_REUSEADDR, 1); + m_master.bind(address); + m_master.listen(max); + m_listener.set(m_master.handle(), Condition::Readable); + } + + /** + * Set the connection handler, called when a new client is connected. + * + * @param handler the handler + */ + inline void setConnectionHandler(ConnectionHandler handler) + { + m_onConnection = std::move(handler); + } + + /** + * Set the disconnection handler, called when a client died. + * + * @param handler the handler + */ + inline void setDisconnectionHandler(DisconnectionHandler handler) + { + m_onDisconnection = std::move(handler); + } + + /** + * Set the receive handler, called when a client has sent something. + * + * @param handler the handler + */ + inline void setReadHandler(ReadHandler handler) + { + m_onRead = std::move(handler); + } + + /** + * Set the writing handler, called when some data has been sent to a client. + * + * @param handler the handler + */ + inline void setWriteHandler(WriteHandler handler) + { + m_onWrite = std::move(handler); + } + + /** + * Set the error handler, called when unrecoverable error has occured. + * + * @param handler the handler + */ + inline void setErrorHandler(ErrorHandler handler) + { + m_onError = std::move(handler); + } + + /** + * Set the timeout handler, called when the selection has timeout. + * + * @param handler the handler + */ + inline void setTimeoutHandler(TimeoutHandler handler) + { + m_onTimeout = std::move(handler); + } + + /** + * Poll for the next event. + * + * @param timeout the timeout (-1 for indefinitely) + * @throw Error on errors + */ + void poll(int timeout = -1) + { + try { + auto st = m_listener.wait(timeout); + + if (st.socket == m_master.handle()) { + /* New client */ + processInitialAccept(); + } else { + /* Recv / Send / Accept on a client */ + auto client = m_clients[st.socket]; + + if (client->socket().state() == State::Accepted) { + processSync(client, st.flags); + } else { + processAccept(client, [&] () { client->socket().accept(); }); + } + } + } catch (const Error &error) { + if (error.code() == Error::Timeout) { + m_onTimeout(); + } else { + m_onError(error); + } + } + } +}; + +/* }}} */ + +/* + * StreamClient + * ------------------------------------------------------------------ + */ + +/* {{{ StreamClient */ + +/** + * @class StreamClient + * @brief Client side connection to a server. + * + * This class is not thread safe and you must not call any of the functions from different threads. + */ +template <typename Address, typename Protocol> +class StreamClient { +public: + /** + * Handler when connection is complete. + */ + using ConnectionHandler = Callback<>; + + /** + * Handler when data has been received. + */ + using ReadHandler = Callback<const std::string &>; + + /** + * Handler when data has been sent correctly. + */ + using WriteHandler = Callback<const std::string &>; + + /** + * Handler when disconnected. + */ + using DisconnectionHandler = Callback<>; + + /** + * Handler on unrecoverable error. + */ + using ErrorHandler = Callback<const Error &>; + + /** + * Handler when timeout occured. + */ + using TimeoutHandler = Callback<>; + +private: + /* Signals */ + ConnectionHandler m_onConnection; + ReadHandler m_onRead; + WriteHandler m_onWrite; + DisconnectionHandler m_onDisconnection; + ErrorHandler m_onError; + TimeoutHandler m_onTimeout; + + /* Socket */ + Socket<Address, Protocol> m_socket; + Listener<> m_listener; + + /* Output buffer */ + std::string m_output; + + /* + * Update the flags after an uncompleted operation. This function must only be called when the operation + * has not complete (e.g. connect, recv, send). + */ + void updateFlags() + { + assert(m_socket.action() != Action::None); + + m_listener.remove(m_socket.handle()); + m_listener.set(m_socket.handle(), m_socket.condition()); + } + + /* + * This is the generic connect helper, it will be used to both initiate the connection or to continue the + * connection process if needed. + * + * Thus the template parameter is the appropriate function to call either, m_socket.connect(address) or + * m_socket.connect(). + * + * See poll() and connect() to understand. + */ + template <typename ConnectCall> + void processConnect(const ConnectCall &connectFunc) + { + /* Call m_socket.connect() or m_socket.connect(address) */ + connectFunc(); + + /* Remove entirely */ + m_listener.remove(m_socket.handle()); + + if (m_socket.state() == State::Connected) { + m_onConnection(); + m_listener.set(m_socket.handle(), Condition::Readable); + } else { + /* Connection still in progress */ + updateFlags(); + } + } + + /* + * Receive or complete the receive command, if the command is not complete, the listener is updated + * accordingly. + */ + void processRead() + { + auto received = m_socket.recv(512); + + if (m_socket.action() == Action::None) { + /* 0 means disconnection */ + if (received.empty()) { + m_onDisconnection(); + } else { + /* + * At this step, it is possible that we were completing a receive operation, in this + * case the write flag may be removed, add it if required. + */ + if (m_output.empty()) { + m_listener.unset(m_socket.handle(), Condition::Writable); + } + + m_onRead(received); + } + } else { + /* Receive operation in progress */ + updateFlags(); + } + } + + /* + * Send or complete the send command, if the command is not complete, the listener is updated + * accordingly. + */ + void processWrite() + { + auto nsent = m_socket.send(m_output); + + if (m_socket.action() == Action::None) { + /* 1. Make a copy of what has been sent */ + auto sent = m_output.substr(0, nsent); + + /* 2. Erase sent content */ + m_output.erase(0, nsent); + + /* 3. Update flags if needed */ + if (m_output.empty()) { + m_listener.unset(m_socket.handle(), Condition::Writable); + } + + /* 4. Notify user */ + m_onWrite(sent); + } else { + /* Send operation in progress */ + updateFlags(); + } + } + + /* + * Receive or send. + */ + void processSync(Condition condition) + { + if ((m_socket.action() == Action::Receive) || + (m_socket.action() == Action::None && (condition & Condition::Readable) == Condition::Readable)) { + processRead(); + } else { + processWrite(); + } + } + +public: + /** + * Create a client. The client is automatically marked as non-blocking. + * + * @param protocol the protocol (Tcp or Tls) + * @param address the optional address + * @throw net::Error on failures + */ + StreamClient(Protocol protocol = {}, const Address &address = {}) + : m_socket{std::move(protocol), address} + { + m_socket.set(net::option::SockBlockMode{false}); + m_listener.set(m_socket.handle(), Condition::Readable); + } + + /** + * Set the connection handler, called when the connection succeed. + * + * @param handler the handler + */ + inline void setConnectionHandler(ConnectionHandler handler) + { + m_onConnection = std::move(handler); + } + + /** + * Set the disconnection handler, called when the server closed the connection. + * + * @param handler the handler + */ + inline void setDisconnectionHandler(DisconnectionHandler handler) + { + m_onDisconnection = std::move(handler); + } + + /** + * Set the read handler, called when we received something. + * + * @param handler the handler + */ + inline void setReadHandler(ReadHandler handler) + { + m_onRead = std::move(handler); + } + + /** + * Set the write handler, called when we successfully sent data. + * + * @param handler the handler + */ + inline void setWriteHandler(WriteHandler handler) + { + m_onWrite = std::move(handler); + } + + /** + * Set the error handler, called when unexpected error occurs. + * + * @param handler the handler + */ + inline void setErrorHandler(ErrorHandler handler) + { + m_onError = std::move(handler); + } + + /** + * Connect to a server, this function may connect immediately or not in any case the connection handler + * will be called when the connection completed. + * + * @param address the address to connect to + */ + void connect(const Address &address) noexcept + { + assert(m_socket.state() == State::Open); + + processConnect([&] () { m_socket.connect(address); }); + } + + /** + * Asynchronously send data to the server. + * + * @param str the data to append + */ + void send(std::string str) + { + m_output += str; + + /* Don't update the listener if there is a pending operation */ + if (m_socket.state() == State::Connected && m_socket.action() == Action::None && !m_output.empty()) { + m_listener.set(m_socket.handle(), Condition::Writable); + } + } + + /** + * Wait for the next event. + * + * @param timeout the time to wait in milliseconds + * @throw Error on errors + */ + void poll(int timeout = -1) noexcept + { + try { + auto st = m_listener.wait(timeout); + + if (m_socket.state() != State::Connected) { + /* Continue the connection */ + processConnect([&] () { m_socket.connect(); }); + } else { + /* Read / Write */ + processSync(st.flags); + } + } catch (const Error &error) { + if (error.code() == Error::Timeout) { + m_onTimeout(); + } else { + m_listener.remove(m_socket.handle()); + m_onError(error); + } + } + } +}; + +/* }}} */ + +} // !net + +#endif // !_SOCKETS_H_
--- a/C++/modules/Xdg/Xdg.cpp Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,125 +0,0 @@ -/* - * Xdg.cpp -- XDG directory specifications - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <cstdlib> -#include <stdexcept> -#include <sstream> - -#include "Xdg.h" - -namespace { - -bool isabsolute(const std::string &path) -{ - return path.length() > 0 && path[0] == '/'; -} - -std::vector<std::string> split(const std::string &arg) -{ - std::stringstream iss(arg); - std::string item; - std::vector<std::string> elems; - - while (std::getline(iss, item, ':')) - if (isabsolute(item)) - elems.push_back(item); - - return elems; -} - -std::string envOrHome(const std::string &var, const std::string &repl) -{ - auto value = getenv(var.c_str()); - - if (value == nullptr || !isabsolute(value)) { - auto home = getenv("HOME"); - - if (home == nullptr) - throw std::runtime_error("could not get home directory"); - - return std::string(home) + "/" + repl; - } - - return value; -} - -std::vector<std::string> listOrDefaults(const std::string &var, const std::vector<std::string> &list) -{ - auto value = getenv(var.c_str()); - - if (!value) - return list; - - // No valid item at all? Use defaults - auto result = split(value); - - return (result.size() == 0) ? list : result; -} - -} // !namespace - -Xdg::Xdg() -{ - m_configHome = envOrHome("XDG_CONFIG_HOME", ".config"); - m_dataHome = envOrHome("XDG_DATA_HOME", ".local/share"); - m_cacheHome = envOrHome("XDG_CACHE_HOME", ".cache"); - - m_configDirs = listOrDefaults("XDG_CONFIG_DIRS", { "/etc/xdg" }); - m_dataDirs = listOrDefaults("XDG_DATA_DIRS", { "/usr/local/share", "/usr/share" }); - - /* - * Runtime directory is a special case and does not have a replacement, the - * application should manage this by itself. - */ - auto runtime = getenv("XDG_RUNTIME_DIR"); - if (runtime && isabsolute(runtime)) - m_runtimeDir = runtime; -} - -const std::string &Xdg::configHome() const noexcept -{ - return m_configHome; -} - -const std::string &Xdg::dataHome() const noexcept -{ - return m_dataHome; -} - -const std::string &Xdg::cacheHome() const noexcept -{ - return m_cacheHome; -} - -const std::string &Xdg::runtimeDir() const -{ - if (m_runtimeDir.size() == 0) - throw std::runtime_error("XDG_RUNTIME_DIR is not set"); - - return m_runtimeDir; -} - -const Xdg::List &Xdg::configDirs() const noexcept -{ - return m_configDirs; -} - -const Xdg::List &Xdg::dataDirs() const noexcept -{ - return m_dataDirs; -}
--- a/C++/modules/Xdg/Xdg.h Thu Nov 12 11:46:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,100 +0,0 @@ -/* - * Xdg.h -- XDG directory specifications - * - * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef _XDG_H_ -#define _XDG_H_ - -#include <vector> -#include <string> - -/** - * @class Xdg - * @brief XDG specifications - * - * Read and get XDG directories. This file contains exports thingies so it can - * compiles successfully on Windows but its usage is discouraged. - */ -class Xdg { -public: - using List = std::vector<std::string>; - -private: - std::string m_configHome; - std::string m_dataHome; - std::string m_cacheHome; - std::string m_runtimeDir; - List m_configDirs; - List m_dataDirs; - -public: - /** - * Open an xdg instance and load directories. - * - * @throw std::runtime_error on failures - */ - Xdg(); - - /** - * Get the config directory. ${XDG_CONFIG_HOME} or ${HOME}/.config - * - * @return the config directory - */ - const std::string &configHome() const noexcept; - - /** - * Get the data directory. ${XDG_DATA_HOME} or ${HOME}/.local/share - * - * @return the data directory - */ - const std::string &dataHome() const noexcept; - - /** - * Get the cache directory. ${XDG_CACHE_HOME} or ${HOME}/.cache - * - * @return the cache directory - */ - const std::string &cacheHome() const noexcept; - - /** - * Get the runtime directory. ${XDG_RUNTIME_DIR} must be set, - * if not, it throws an exception. - * - * The XDG standard says that application should handle XDG_RUNTIME_DIR by - * themselves. - * - * @return the runtime directory - * @throw std::runtime_error on error - */ - const std::string &runtimeDir() const; - - /** - * Get the standard config directories. ${XDG_CONFIG_DIRS} or { "/etc/xdg" } - * - * @return the list of config directories - */ - const List &configDirs() const noexcept; - - /** - * Get the data directories. ${XDG_DATA_DIRS} or { "/usr/local/share", "/usr/share" } - * - * @return the list of data directories - */ - const List &dataDirs() const noexcept; -}; - -#endif // !_XDG_H_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Xdg/xdg.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,125 @@ +/* + * xdg.cpp -- XDG directory specifications + * + * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <cstdlib> +#include <stdexcept> +#include <sstream> + +#include "xdg.h" + +namespace { + +bool isabsolute(const std::string &path) +{ + return path.length() > 0 && path[0] == '/'; +} + +std::vector<std::string> split(const std::string &arg) +{ + std::stringstream iss(arg); + std::string item; + std::vector<std::string> elems; + + while (std::getline(iss, item, ':')) + if (isabsolute(item)) + elems.push_back(item); + + return elems; +} + +std::string envOrHome(const std::string &var, const std::string &repl) +{ + auto value = getenv(var.c_str()); + + if (value == nullptr || !isabsolute(value)) { + auto home = getenv("HOME"); + + if (home == nullptr) + throw std::runtime_error("could not get home directory"); + + return std::string(home) + "/" + repl; + } + + return value; +} + +std::vector<std::string> listOrDefaults(const std::string &var, const std::vector<std::string> &list) +{ + auto value = getenv(var.c_str()); + + if (!value) + return list; + + // No valid item at all? Use defaults + auto result = split(value); + + return (result.size() == 0) ? list : result; +} + +} // !namespace + +Xdg::Xdg() +{ + m_configHome = envOrHome("XDG_CONFIG_HOME", ".config"); + m_dataHome = envOrHome("XDG_DATA_HOME", ".local/share"); + m_cacheHome = envOrHome("XDG_CACHE_HOME", ".cache"); + + m_configDirs = listOrDefaults("XDG_CONFIG_DIRS", { "/etc/xdg" }); + m_dataDirs = listOrDefaults("XDG_DATA_DIRS", { "/usr/local/share", "/usr/share" }); + + /* + * Runtime directory is a special case and does not have a replacement, the + * application should manage this by itself. + */ + auto runtime = getenv("XDG_RUNTIME_DIR"); + if (runtime && isabsolute(runtime)) + m_runtimeDir = runtime; +} + +const std::string &Xdg::configHome() const noexcept +{ + return m_configHome; +} + +const std::string &Xdg::dataHome() const noexcept +{ + return m_dataHome; +} + +const std::string &Xdg::cacheHome() const noexcept +{ + return m_cacheHome; +} + +const std::string &Xdg::runtimeDir() const +{ + if (m_runtimeDir.size() == 0) + throw std::runtime_error("XDG_RUNTIME_DIR is not set"); + + return m_runtimeDir; +} + +const Xdg::List &Xdg::configDirs() const noexcept +{ + return m_configDirs; +} + +const Xdg::List &Xdg::dataDirs() const noexcept +{ + return m_dataDirs; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Xdg/xdg.h Thu Nov 12 21:53:36 2015 +0100 @@ -0,0 +1,100 @@ +/* + * xdg.h -- XDG directory specifications + * + * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _XDG_H_ +#define _XDG_H_ + +#include <vector> +#include <string> + +/** + * @class Xdg + * @brief XDG specifications + * + * Read and get XDG directories. This file contains exports thingies so it can + * compiles successfully on Windows but its usage is discouraged. + */ +class Xdg { +public: + using List = std::vector<std::string>; + +private: + std::string m_configHome; + std::string m_dataHome; + std::string m_cacheHome; + std::string m_runtimeDir; + List m_configDirs; + List m_dataDirs; + +public: + /** + * Open an xdg instance and load directories. + * + * @throw std::runtime_error on failures + */ + Xdg(); + + /** + * Get the config directory. ${XDG_CONFIG_HOME} or ${HOME}/.config + * + * @return the config directory + */ + const std::string &configHome() const noexcept; + + /** + * Get the data directory. ${XDG_DATA_HOME} or ${HOME}/.local/share + * + * @return the data directory + */ + const std::string &dataHome() const noexcept; + + /** + * Get the cache directory. ${XDG_CACHE_HOME} or ${HOME}/.cache + * + * @return the cache directory + */ + const std::string &cacheHome() const noexcept; + + /** + * Get the runtime directory. ${XDG_RUNTIME_DIR} must be set, + * if not, it throws an exception. + * + * The XDG standard says that application should handle XDG_RUNTIME_DIR by + * themselves. + * + * @return the runtime directory + * @throw std::runtime_error on error + */ + const std::string &runtimeDir() const; + + /** + * Get the standard config directories. ${XDG_CONFIG_DIRS} or { "/etc/xdg" } + * + * @return the list of config directories + */ + const List &configDirs() const noexcept; + + /** + * Get the data directories. ${XDG_DATA_DIRS} or { "/usr/local/share", "/usr/share" } + * + * @return the list of data directories + */ + const List &dataDirs() const noexcept; +}; + +#endif // !_XDG_H_
--- a/C++/tests/Base64/main.cpp Thu Nov 12 11:46:04 2015 +0100 +++ b/C++/tests/Base64/main.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -18,7 +18,7 @@ #include <gtest/gtest.h> -#include <Base64.h> +#include <base64.h> TEST(Lookup, lookup) {
--- a/C++/tests/Directory/main.cpp Thu Nov 12 11:46:04 2015 +0100 +++ b/C++/tests/Directory/main.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -18,7 +18,7 @@ #include <gtest/gtest.h> -#include <Directory.h> +#include <directory.h> TEST(Filter, withDot) { @@ -112,4 +112,4 @@ testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); -} \ No newline at end of file +}
--- a/C++/tests/Dynlib/Plugin.cpp Thu Nov 12 11:46:04 2015 +0100 +++ b/C++/tests/Dynlib/Plugin.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -1,6 +1,6 @@ #include <string> -#include <Dynlib.h> +#include <dynlib.h> extern "C" {
--- a/C++/tests/Dynlib/main.cpp Thu Nov 12 11:46:04 2015 +0100 +++ b/C++/tests/Dynlib/main.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -20,7 +20,7 @@ #include <gtest/gtest.h> -#include <Dynlib.h> +#include <dynlib.h> /* * NOTE: the EXTENSION is defined by CMake in the form of a string
--- a/C++/tests/Hash/main.cpp Thu Nov 12 11:46:04 2015 +0100 +++ b/C++/tests/Hash/main.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -18,7 +18,7 @@ #include <gtest/gtest.h> -#include <Hash.h> +#include <hash.h> /* * We test the "Hello World" message in all cryptographic functions.
--- a/C++/tests/Ini/main.cpp Thu Nov 12 11:46:04 2015 +0100 +++ b/C++/tests/Ini/main.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -20,7 +20,7 @@ #include <gtest/gtest.h> -#include <Ini.h> +#include <ini.h> class BasicTest : public testing::Test { protected:
--- a/C++/tests/Js/main.cpp Thu Nov 12 11:46:04 2015 +0100 +++ b/C++/tests/Js/main.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -18,7 +18,7 @@ #include <gtest/gtest.h> -#include <Js.h> +#include <js.h> /* * TODO: @@ -751,12 +751,12 @@ { Context context; - + ASSERT_EQ(0, context.top()); context.push(Pointer<Cat>{p}); Cat *p2 = context.get<Pointer<Cat>>(-1); ASSERT_EQ(1, context.top()); - + p2->m_updated = true; ASSERT_TRUE(p->m_updated); }
--- a/C++/tests/Json/main.cpp Thu Nov 12 11:46:04 2015 +0100 +++ b/C++/tests/Json/main.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -21,7 +21,7 @@ #include <gtest/gtest.h> -#include "Json.h" +#include "json.h" /* * Miscellaneous
--- a/C++/tests/OptionParser/main.cpp Thu Nov 12 11:46:04 2015 +0100 +++ b/C++/tests/OptionParser/main.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -18,7 +18,7 @@ #include <gtest/gtest.h> -#include <OptionParser.h> +#include <options.h> /* -------------------------------------------------------- * Short options @@ -28,12 +28,12 @@ { std::vector<std::string> args{"-a", "-b"}; - parser::Options options{ + option::Options options{ { "-a", false }, { "-b", false } }; - parser::Result pack = parser::read(args, options); + option::Result pack = option::read(args, options); ASSERT_EQ(2U, pack.size()); ASSERT_EQ(0U, args.size()); @@ -46,12 +46,12 @@ { std::vector<std::string> args{"-v", "-cfoo.conf"}; - parser::Options options{ + option::Options options{ { "-v", false }, { "-c", true } }; - parser::Result pack = parser::read(args, options); + option::Result pack = option::read(args, options); ASSERT_EQ(2U, pack.size()); ASSERT_EQ(0U, args.size()); @@ -64,12 +64,12 @@ { std::vector<std::string> args{"-v", "-c", "foo.conf"}; - parser::Options options{ + option::Options options{ { "-v", false }, { "-c", true } }; - parser::Result pack = parser::read(args, options); + option::Result pack = option::read(args, options); ASSERT_EQ(2U, pack.size()); ASSERT_EQ(0U, args.size()); @@ -82,13 +82,13 @@ { std::vector<std::string> args{"-abc"}; - parser::Options options{ + option::Options options{ { "-a", false }, { "-b", false }, { "-c", false }, }; - parser::Result pack = parser::read(args, options); + option::Result pack = option::read(args, options); ASSERT_EQ(3U, pack.size()); ASSERT_EQ(0U, args.size()); @@ -102,13 +102,13 @@ { std::vector<std::string> args{"-vdcfoo.conf"}; - parser::Options options{ + option::Options options{ { "-v", false }, { "-d", false }, { "-c", true }, }; - parser::Result pack = parser::read(args, options); + option::Result pack = option::read(args, options); ASSERT_EQ(3U, pack.size()); ASSERT_EQ(0U, args.size()); @@ -126,12 +126,12 @@ { std::vector<std::string> args{"--fullscreen"}; - parser::Options options{ + option::Options options{ { "--verbose", false }, { "--fullscreen", false } }; - parser::Result pack = parser::read(args, options); + option::Result pack = option::read(args, options); ASSERT_EQ(1U, pack.size()); ASSERT_EQ(0U, args.size()); @@ -143,12 +143,12 @@ { std::vector<std::string> args{"--config", "config.conf", "--level", "2"}; - parser::Options options{ + option::Options options{ { "--config", true }, { "--level", true } }; - parser::Result pack = parser::read(args, options); + option::Result pack = option::read(args, options); ASSERT_EQ(2U, pack.size()); ASSERT_EQ(0U, args.size()); @@ -166,11 +166,11 @@ std::vector<std::string> args{"-v", "install", "-y", "irccd"}; std::vector<std::string> expected{"install", "-y", "irccd"}; - parser::Options options{ + option::Options options{ { "-v", false } }; - parser::Result pack = parser::read(args, options); + option::Result pack = option::read(args, options); ASSERT_EQ(1U, pack.size()); ASSERT_EQ(3U, args.size()); @@ -183,15 +183,15 @@ { std::vector<std::string> args{"-c"}; - parser::Options options{ + option::Options options{ { "-c", true } }; try { - parser::Result pack = parser::read(args, options); + option::Result pack = option::read(args, options); FAIL() << "exception expected"; - } catch (const parser::MissingValue &) { + } catch (const option::MissingValue &) { } } @@ -199,16 +199,16 @@ { std::vector<std::string> args{"-vc"}; - parser::Options options{ + option::Options options{ { "-v", false }, { "-c", true } }; try { - parser::Result pack = parser::read(args, options); + option::Result pack = option::read(args, options); FAIL() << "exception expected"; - } catch (const parser::MissingValue &) { + } catch (const option::MissingValue &) { } } @@ -216,15 +216,15 @@ { std::vector<std::string> args{"--config"}; - parser::Options options{ + option::Options options{ { "--config", true } }; try { - parser::Result pack = parser::read(args, options); + option::Result pack = option::read(args, options); FAIL() << "exception expected"; - } catch (const parser::MissingValue &) { + } catch (const option::MissingValue &) { } } @@ -232,15 +232,15 @@ { std::vector<std::string> args{"-x"}; - parser::Options options{ + option::Options options{ { "-v", true } }; try { - parser::Result pack = parser::read(args, options); + option::Result pack = option::read(args, options); FAIL() << "exception expected"; - } catch (const parser::InvalidOption &) { + } catch (const option::InvalidOption &) { } } @@ -248,15 +248,15 @@ { std::vector<std::string> args{"--destroy"}; - parser::Options options{ + option::Options options{ { "--verbose", true } }; try { - parser::Result pack = parser::read(args, options); + option::Result pack = option::read(args, options); FAIL() << "exception expected"; - } catch (const parser::InvalidOption &) { + } catch (const option::InvalidOption &) { } } @@ -264,15 +264,15 @@ { std::vector<std::string> args{"-vx"}; - parser::Options options{ + option::Options options{ { "-x", true } }; try { - parser::Result pack = parser::read(args, options); + option::Result pack = option::read(args, options); FAIL() << "exception expected"; - } catch (const parser::InvalidOption &) { + } catch (const option::InvalidOption &) { } }
--- a/C++/tests/Socket/main.cpp Thu Nov 12 11:46:04 2015 +0100 +++ b/C++/tests/Socket/main.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -24,7 +24,7 @@ #include <gtest/gtest.h> -#include <Sockets.h> +#include <sockets.h> using namespace net; using namespace net::address;
--- a/C++/tests/Xdg/main.cpp Thu Nov 12 11:46:04 2015 +0100 +++ b/C++/tests/Xdg/main.cpp Thu Nov 12 21:53:36 2015 +0100 @@ -18,7 +18,7 @@ #include <gtest/gtest.h> -#include <Xdg.h> +#include <xdg.h> using namespace testing;
--- a/CMakeLists.txt Thu Nov 12 11:46:04 2015 +0100 +++ b/CMakeLists.txt Thu Nov 12 21:53:36 2015 +0100 @@ -123,8 +123,8 @@ NAME Base64 DIRECTORY Base64 SOURCES - ${code_SOURCE_DIR}/C++/modules/Base64/Base64.cpp - ${code_SOURCE_DIR}/C++/modules/Base64/Base64.h + ${code_SOURCE_DIR}/C++/modules/Base64/base64.cpp + ${code_SOURCE_DIR}/C++/modules/Base64/base64.h ) # --------------------------------------------------------- @@ -142,8 +142,8 @@ NAME Directory DIRECTORY Directory SOURCES - ${code_SOURCE_DIR}/C++/modules/Directory/Directory.cpp - ${code_SOURCE_DIR}/C++/modules/Directory/Directory.h + ${code_SOURCE_DIR}/C++/modules/Directory/directory.cpp + ${code_SOURCE_DIR}/C++/modules/Directory/directory.h ) # --------------------------------------------------------- @@ -165,8 +165,8 @@ NAME Dynlib DIRECTORY Dynlib SOURCES - ${code_SOURCE_DIR}/C++/modules/Dynlib/Dynlib.cpp - ${code_SOURCE_DIR}/C++/modules/Dynlib/Dynlib.h + ${code_SOURCE_DIR}/C++/modules/Dynlib/dynlib.cpp + ${code_SOURCE_DIR}/C++/modules/Dynlib/dynlib.h ) if (WITH_DYNLIB) @@ -208,8 +208,8 @@ LIBRARIES ${OPENSSL_LIBRARIES} INCLUDES ${OPENSSL_INCLUDE_DIR} SOURCES - ${code_SOURCE_DIR}/C++/modules/Hash/Hash.cpp - ${code_SOURCE_DIR}/C++/modules/Hash/Hash.h + ${code_SOURCE_DIR}/C++/modules/Hash/hash.cpp + ${code_SOURCE_DIR}/C++/modules/Hash/hash.h ) endif () @@ -226,8 +226,8 @@ NAME Ini DIRECTORY Ini SOURCES - ${code_SOURCE_DIR}/C++/modules/Ini/Ini.cpp - ${code_SOURCE_DIR}/C++/modules/Ini/Ini.h + ${code_SOURCE_DIR}/C++/modules/Ini/ini.cpp + ${code_SOURCE_DIR}/C++/modules/Ini/ini.h LIBRARIES ${INI_LIBRARIES} RESOURCES @@ -256,8 +256,8 @@ DIRECTORY Js LIBRARIES duktape SOURCES - ${code_SOURCE_DIR}/C++/modules/Js/Js.cpp - ${code_SOURCE_DIR}/C++/modules/Js/Js.h + ${code_SOURCE_DIR}/C++/modules/Js/js.cpp + ${code_SOURCE_DIR}/C++/modules/Js/js.h ) # --------------------------------------------------------- @@ -272,8 +272,8 @@ INCLUDES ${Jansson_INCLUDE_DIRS} LIBRARIES ${Jansson_LIBRARIES} SOURCES - ${code_SOURCE_DIR}/C++/modules/Json/Json.cpp - ${code_SOURCE_DIR}/C++/modules/Json/Json.h + ${code_SOURCE_DIR}/C++/modules/Json/json.cpp + ${code_SOURCE_DIR}/C++/modules/Json/json.h RESOURCES ${code_SOURCE_DIR}/C++/tests/Json/data/array-all.json ${code_SOURCE_DIR}/C++/tests/Json/data/array.json @@ -292,8 +292,8 @@ NAME OptionParser DIRECTORY OptionParser SOURCES - ${code_SOURCE_DIR}/C++/modules/OptionParser/OptionParser.cpp - ${code_SOURCE_DIR}/C++/modules/OptionParser/OptionParser.h + ${code_SOURCE_DIR}/C++/modules/OptionParser/options.cpp + ${code_SOURCE_DIR}/C++/modules/OptionParser/options.h ) # --------------------------------------------------------- @@ -314,8 +314,8 @@ ${SOCKET_LIBRARIES} ${OPENSSL_LIBRARIES} SOURCES - ${code_SOURCE_DIR}/C++/modules/Socket/Sockets.cpp - ${code_SOURCE_DIR}/C++/modules/Socket/Sockets.h + ${code_SOURCE_DIR}/C++/modules/Socket/sockets.cpp + ${code_SOURCE_DIR}/C++/modules/Socket/sockets.h RESOURCES ${code_SOURCE_DIR}/C++/tests/Socket/test.crt ${code_SOURCE_DIR}/C++/tests/Socket/test.key @@ -357,8 +357,8 @@ NAME Xdg DIRECTORY Xdg SOURCES - ${code_SOURCE_DIR}/C++/modules/Xdg/Xdg.cpp - ${code_SOURCE_DIR}/C++/modules/Xdg/Xdg.h + ${code_SOURCE_DIR}/C++/modules/Xdg/xdg.cpp + ${code_SOURCE_DIR}/C++/modules/Xdg/xdg.h ) endif ()