From bd0a607373f53fdbdfa6b40a634ffb98a1e4faf6 Mon Sep 17 00:00:00 2001 From: "Alan M. Carroll" Date: Wed, 17 Apr 2019 11:37:09 -0500 Subject: [PATCH] Updates from working with HTTP Replay. (#8) * Fix IPMask inline. * Fix IP parsing, linking. * Move files. * Fix IP formatting issues. * Fix port byte order problem. * Fix another port byte order problem in formatting. * TextView: Special check on memcmp and strcasecmp for identical views. * TextView: Tweak constructor overloads. * BWF: Split "Optional" in to "Optional" for possibly null strings, and "If" for flag based formatting. * BWF: Fix overload problem with in_addr_t. * BW: Add aux_span method. * Add Scons/Parts build files * swoc_file: Add parent_path(). * Fix ContextNames to be thread safe in normal use. * Prep for release 1.0.5 --- Sconstruct | 4 + doc/Doxyfile | 2 +- doc/conf.py | 2 +- swoc++/CMakeLists.txt | 6 +- swoc++/include/swoc/BufferWriter.h | 13 + swoc++/include/swoc/IntrusiveHashMap.h | 2 + {include => swoc++/include}/swoc/RBTree.h | 0 swoc++/include/swoc/TextView.h | 22 +- swoc++/include/swoc/bwf_base.h | 71 ++-- swoc++/include/swoc/bwf_ex.h | 6 +- swoc++/include/swoc/bwf_ip.h | 10 +- swoc++/include/swoc/bwf_std.h | 1 + swoc++/include/swoc/swoc_file.h | 2 + swoc++/include/swoc/swoc_ip.h | 55 +++- swoc++/include/swoc/swoc_version.h | 2 +- {src => swoc++/src}/RBTree.cc | 0 swoc++/src/TextView.cc | 10 + swoc++/src/bw_format.cc | 2 +- {src => swoc++/src}/bw_ip_format.cc | 16 +- swoc++/src/swoc_file.cc | 12 +- swoc++/src/swoc_ip.cc | 379 ++++++++++++---------- swoc++/swoc++.part | 27 ++ unit_tests/CMakeLists.txt | 1 + unit_tests/ex_bw_format.cc | 8 +- unit_tests/test_Scalar.cc | 8 +- unit_tests/test_TextView.cc | 6 +- unit_tests/test_bw_format.cc | 6 +- unit_tests/test_ip.cc | 258 +++++++++++++++ unit_tests/test_swoc_file.cc | 3 + unit_tests/unit_tests.part | 40 +++ 30 files changed, 738 insertions(+), 236 deletions(-) create mode 100644 Sconstruct rename {include => swoc++/include}/swoc/RBTree.h (100%) rename {src => swoc++/src}/RBTree.cc (100%) rename {src => swoc++/src}/bw_ip_format.cc (94%) create mode 100644 swoc++/swoc++.part create mode 100644 unit_tests/test_ip.cc create mode 100644 unit_tests/unit_tests.part diff --git a/Sconstruct b/Sconstruct new file mode 100644 index 0000000..1293d0d --- /dev/null +++ b/Sconstruct @@ -0,0 +1,4 @@ +from parts import * + +Part("swoc++/swoc++.part") +Part("unit_tests/unit_tests.part") diff --git a/doc/Doxyfile b/doc/Doxyfile index 7572a4f..3655a6b 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "LibSWOC++" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = "1.0.4" +PROJECT_NUMBER = "1.0.5" # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/doc/conf.py b/doc/conf.py index 193b0ce..cf47245 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -80,7 +80,7 @@ copyright = u'{}, amc@apache.org'.format(date.today().year) # The full version, including alpha/beta/rc tags. -release = "1.0.4" +release = "1.0.5" # The short X.Y version. version = '.'.join(release.split('.', 2)[:2]) diff --git a/swoc++/CMakeLists.txt b/swoc++/CMakeLists.txt index 8ed2254..804078b 100644 --- a/swoc++/CMakeLists.txt +++ b/swoc++/CMakeLists.txt @@ -5,6 +5,7 @@ set(CMAKE_CXX_STANDARD 17) include(GNUInstallDirs) set(HEADER_FILES + include/swoc/swoc_version.h include/swoc/BufferWriter.h include/swoc/bwf_base.h include/swoc/bwf_std.h @@ -20,7 +21,8 @@ set(HEADER_FILES include/swoc/TextView.h include/swoc/swoc_file.h include/swoc/swoc_meta.h - include/swoc/bwf_ip.h include/swoc/swoc_version.h) + include/swoc/bwf_ip.h + ) # These are external but required. set(EXTERNAL_HEADER_FILES @@ -29,9 +31,11 @@ set(EXTERNAL_HEADER_FILES set(CC_FILES src/bw_format.cc + src/bw_ip_format.cc src/Errata.cc src/swoc_ip.cc src/MemArena.cc + src/RBTree.cc src/swoc_file.cc src/TextView.cc ) diff --git a/swoc++/include/swoc/BufferWriter.h b/swoc++/include/swoc/BufferWriter.h index 48b44d7..570d984 100644 --- a/swoc++/include/swoc/BufferWriter.h +++ b/swoc++/include/swoc/BufferWriter.h @@ -109,6 +109,19 @@ class BufferWriter /// @return The number of bytes which have not yet been written. size_t remaining() const; + /** A memory span of the unused bytes. + * + * @return A span of the unused bytes. + * + * This is a convenience method that is identical to + * @code + * BufferWriter w; + * // ... + * MemSpan{ w.aux_data(), w.remaining() }; + * @endcode + */ + MemSpan aux_span(); + /** Increase the extent by @a n bytes. * * @param n Number of bytes to consume. diff --git a/swoc++/include/swoc/IntrusiveHashMap.h b/swoc++/include/swoc/IntrusiveHashMap.h index 4d909b4..b7cf5d3 100644 --- a/swoc++/include/swoc/IntrusiveHashMap.h +++ b/swoc++/include/swoc/IntrusiveHashMap.h @@ -342,6 +342,8 @@ IntrusiveHashMap::Bucket::clear() _v = nullptr; _count = 0; _mixed_p = false; + // Need to clear these, or they persist after an expansion which breaks the active bucket list. + _link._next = _link._prev = nullptr; } template diff --git a/include/swoc/RBTree.h b/swoc++/include/swoc/RBTree.h similarity index 100% rename from include/swoc/RBTree.h rename to swoc++/include/swoc/RBTree.h diff --git a/swoc++/include/swoc/TextView.h b/swoc++/include/swoc/TextView.h index 9f0612e..95f1707 100644 --- a/swoc++/include/swoc/TextView.h +++ b/swoc++/include/swoc/TextView.h @@ -708,7 +708,6 @@ class TextView : public std::string_view /// @cond OVERLOAD // These methods are all overloads of other methods, defined in order to make the API more // convenient to use. Mostly these overload @c int for @c size_t so naked numbers work as expected. - constexpr TextView(const char *ptr, int n); self_type prefix(int n) const; self_type take_suffix(int n); self_type split_prefix(int n); @@ -807,10 +806,6 @@ inline constexpr TextView::TextView(super_type const &that) : super_type(that) { template constexpr TextView::TextView(const char (&s)[N]) : super_type(s, s[N - 1] ? N : N - 1) {} template constexpr TextView::TextView(const char (&s)[N], size_t n) : super_type(s, n) {} -/// @cond OVERLOAD -inline constexpr TextView::TextView(const char *ptr, int n) : super_type(ptr, n < 0 ? 0 : n) {} -/// @endcond - inline void TextView::init_delimiter_set(std::string_view const &delimiters, std::bitset<256> &set) { @@ -1643,6 +1638,11 @@ transform_view_of(V const &v) } /// @endcond +/** User literals for TextView. + * + * - _tv : TextView + * - _sv : std::string_view + */ namespace literals { /** Literal constructor for @c std::string_view. @@ -1656,6 +1656,18 @@ namespace literals * so hopefully someday this can be removed. */ constexpr std::string_view operator"" _sv(const char *s, size_t n) { return {s, n}; } + + /** Literal constructor for @c swoc::TextView. + * + * @param s The source string. + * @param n Size of the source string. + * @return A @c string_view + * + * @internal This is provided because the STL one does not support @c constexpr which seems + * rather bizarre to me, but there it is. Update: this depends on the version of the compiler, + * so hopefully someday this can be removed. + */ + constexpr swoc::TextView operator"" _tv(const char *s, size_t n) { return {s, n}; } } // namespace literals }; // namespace swoc diff --git a/swoc++/include/swoc/bwf_base.h b/swoc++/include/swoc/bwf_base.h index 219ce69..5913d01 100644 --- a/swoc++/include/swoc/bwf_base.h +++ b/swoc++/include/swoc/bwf_base.h @@ -265,8 +265,8 @@ namespace bwf * @tparam F The function signature for generators in this container. * * This is a base class used by different types of name containers. It is not expected to be used - * directly. The subclass should provide a function type @a F that is suitable for its particular - * generators. + * directly. A subclass should inherit from this by providing a function type @a F that is + * suitable for the subclass generators. */ template class NameMap { @@ -340,15 +340,8 @@ namespace bwf * @a context will be the context for the binding passed to the formatter. * * This is used by the formatting logic by calling the @c bind method with a context object. - * - * This class doubles as a @c NameBinding, such that it passes itself to the formatting logic. - * In actual use that is more convenient for external code to overload name dispatch, which can - * then be done by subclassing this class and overriding the function operator. Otherwise most - * of the class would need to be duplicated in order to override a nested or associated binding - * class. - * */ - template class ContextNames : public NameMap, public NameBinding + template class ContextNames : public NameMap { private: using self_type = ContextNames; ///< self reference type. @@ -364,6 +357,33 @@ namespace bwf using super_type::super_type; // inherit @c super_type constructors. + class Binding : public NameBinding + { + public: + /** Override of virtual method to provide an implementation. + * + * @param w Output. + * @param spec Format specifier for output. + * @return @a w + * + * This is called from the formatting logic to generate output for a named specifier. Subclasses + * that need to handle name dispatch differently need only override this method. + */ + BufferWriter & + operator()(BufferWriter &w, const Spec &spec) const override + { + return _names(w, spec, _ctx); + } + + protected: + Binding(ContextNames const &names, context_type &ctx) : _names(names), _ctx(ctx) {} + + context_type &_ctx; ///< Context for generators. + ContextNames const &_names; ///< Base set of names. + + friend ContextNames; + }; + /** Assign the external generator @a bg to @a name. * * This is used for generators in the namespace that do not use the context. @@ -391,21 +411,21 @@ namespace bwf * * This is used when passing the context name map to the formatter. */ - const NameBinding &bind(context_type &context); + Binding bind(context_type &context); protected: - /** Override of virtual method to provide an implementation. + /** Generate output based on the name in @a spec. * * @param w Output. * @param spec Format specifier for output. + * @param ctx The context object. * @return @a w * * This is called from the formatting logic to generate output for a named specifier. Subclasses - * that need to handle name dispatch differently need only override this method. + * that need to handle name dispatch differently should override this method. This method + * performs a name lookup in the local nameset. */ - BufferWriter &operator()(BufferWriter &w, const Spec &spec) const override; - - context_type *_ctx = nullptr; ///< Context for generators. + virtual BufferWriter &operator()(BufferWriter &w, const Spec &spec, context_type &ctx) const; }; /** Default global names. @@ -501,22 +521,21 @@ namespace bwf } template - inline const NameBinding & - ContextNames::bind(context_type &ctx) + inline auto + ContextNames::bind(context_type &ctx) -> Binding { - _ctx = &ctx; - return *this; + return {*this, ctx}; } template BufferWriter & - ContextNames::operator()(BufferWriter &w, const Spec &spec) const + ContextNames::operator()(BufferWriter &w, const Spec &spec, context_type &ctx) const { if (!spec._name.empty()) { if (auto spot = super_type::_map.find(spec._name); spot != super_type::_map.end()) { - spot->second(w, spec, *_ctx); + spot->second(w, spec, ctx); } else { - this->err_invalid_name(w, spec); + NameBinding::err_invalid_name(w, spec); } } return w; @@ -832,6 +851,12 @@ BufferWriter::print_n(Binding const &names, TextView const &fmt) return print_nfv(names, bwf::Format::bind(fmt), std::make_tuple()); } +inline MemSpan +BufferWriter::aux_span() +{ + return {this->aux_data(), this->remaining()}; +} + // ---- Formatting for specific types. // Must be first because it is used by other formatters, and is not inline. diff --git a/swoc++/include/swoc/bwf_ex.h b/swoc++/include/swoc/bwf_ex.h index 824bf55..40f8a1c 100644 --- a/swoc++/include/swoc/bwf_ex.h +++ b/swoc++/include/swoc/bwf_ex.h @@ -134,15 +134,15 @@ namespace bwf * extra tag with delimiters, e.g. "[tag]", was to be generated, this could be done with * * @code - * w.print("Some other text{}.", bwf::Optional(flag, " [{}]", tag)); + * w.print("Some other text{}.", bwf::If(flag, " [{}]", tag)); * @endcode * * @internal To disambiguate overloads, this is enabled only if there is at least one argument * to be passed to the format string. */ template - auto - Optional(bool flag, TextView const &fmt, Args &&... args) -> typename std::enable_if>::type + SubText + If(bool flag, TextView const &fmt, Args &&... args) { return SubText(flag ? fmt : TextView{}, std::forward_as_tuple(args...)); } diff --git a/swoc++/include/swoc/bwf_ip.h b/swoc++/include/swoc/bwf_ip.h index 5a3cbcb..0ea19b1 100644 --- a/swoc++/include/swoc/bwf_ip.h +++ b/swoc++/include/swoc/bwf_ip.h @@ -21,12 +21,20 @@ #pragma once #include "swoc/bwf_base.h" +#include "swoc/swoc_ip.h" #include namespace swoc { BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, sockaddr const *addr); -BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, in_addr_t addr); +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, in_addr const &addr); BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, in6_addr const &addr); +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, IPAddr const &addr); +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, sockaddr const *addr); +inline BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, IPEndpoint const &addr) +{ + return bwformat(w, spec, &addr.sa); +} } // namespace swoc diff --git a/swoc++/include/swoc/bwf_std.h b/swoc++/include/swoc/bwf_std.h index a11c1d1..0c3065f 100644 --- a/swoc++/include/swoc/bwf_std.h +++ b/swoc++/include/swoc/bwf_std.h @@ -24,6 +24,7 @@ #pragma once #include +#include #include "swoc/bwf_base.h" namespace std diff --git a/swoc++/include/swoc/swoc_file.h b/swoc++/include/swoc/swoc_file.h index 65878bb..b936f70 100644 --- a/swoc++/include/swoc/swoc_file.h +++ b/swoc++/include/swoc/swoc_file.h @@ -89,6 +89,8 @@ namespace file /// Check if the path is not absolute. bool is_relative() const; + self_type parent_path() const; + /// Access the path explicitly. char const *c_str() const; diff --git a/swoc++/include/swoc/swoc_ip.h b/swoc++/include/swoc/swoc_ip.h index 0b9d1a0..dccf0e7 100644 --- a/swoc++/include/swoc/swoc_ip.h +++ b/swoc++/include/swoc/swoc_ip.h @@ -282,7 +282,7 @@ class IPAddr friend class IPRange; using self_type = IPAddr; ///< Self reference type. public: - IPAddr(); ///< Default constructor - invalid result. + IPAddr() = default; ///< Default constructor - invalid result. /// Construct using IPv4 @a addr. explicit constexpr IPAddr(in_addr_t addr); @@ -321,7 +321,7 @@ class IPAddr /** Parse a string for an IP address. - The address resuling from the parse is copied to this object if the conversion is successful, + The address resulting from the parse is copied to this object if the conversion is successful, otherwise this object is invalidated. @return @c true on success, @c false otherwise. @@ -766,6 +766,12 @@ IPEndpoint::invalidate() return *this; } +inline void +IPEndpoint::invalidate(sockaddr *addr) +{ + addr->sa_family = AF_UNSPEC; +} + inline bool IPEndpoint::is_valid() const { @@ -856,9 +862,33 @@ IPEndpoint::host_order_port(sockaddr const *addr) // --- IPAddr variants --- -constexpr IP4Addr::IP4Addr(in_addr_t addr) : _addr(addr) {} +inline constexpr IP4Addr::IP4Addr(in_addr_t addr) : _addr(addr) {} -constexpr IP6Addr::IP6Addr(in6_addr addr) : _addr(addr) {} +inline IP4Addr::operator in_addr_t() const +{ + return _addr; +} + +inline auto +IP4Addr::operator=(in_addr_t ip) -> self_type & +{ + _addr = ip; + return *this; +} + +inline constexpr IP6Addr::IP6Addr(in6_addr addr) : _addr(addr) {} + +inline auto +IP6Addr::operator=(in6_addr ip) -> self_type & +{ + _addr = ip; + return *this; +} + +inline IP6Addr::operator in6_addr const &() const +{ + return _addr; +} // +++ IPRange +++ @@ -896,17 +926,19 @@ IpMask::width() const return _mask; } -bool +inline bool operator==(IpMask const &lhs, IpMask const &rhs) { return lhs.width() == rhs.width(); } -bool + +inline bool operator!=(IpMask const &lhs, IpMask const &rhs) { return lhs.width() != rhs.width(); } -bool + +inline bool operator<(IpMask const &lhs, IpMask const &rhs) { return lhs.width() < rhs.width(); @@ -938,13 +970,4 @@ IpNet::mask() const return _mask; } -// BufferWriter formatting support. -BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, IPAddr const &addr); -BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, sockaddr const *addr); -inline BufferWriter & -bwformat(BufferWriter &w, bwf::Spec const &spec, IPEndpoint const &addr) -{ - return bwformat(w, spec, &addr.sa); -} - } // namespace swoc diff --git a/swoc++/include/swoc/swoc_version.h b/swoc++/include/swoc/swoc_version.h index 82f7340..e233dc7 100644 --- a/swoc++/include/swoc/swoc_version.h +++ b/swoc++/include/swoc/swoc_version.h @@ -39,6 +39,6 @@ namespace swoc { static constexpr unsigned MAJOR_VERION = 1; static constexpr unsigned MINOR_VERSION = 0; -static constexpr unsigned POINT_VERSION = 4; +static constexpr unsigned POINT_VERSION = 5; } // namespace swoc diff --git a/src/RBTree.cc b/swoc++/src/RBTree.cc similarity index 100% rename from src/RBTree.cc rename to swoc++/src/RBTree.cc diff --git a/swoc++/src/TextView.cc b/swoc++/src/TextView.cc index 612327a..859b1fd 100644 --- a/swoc++/src/TextView.cc +++ b/swoc++/src/TextView.cc @@ -29,6 +29,11 @@ int swoc::memcmp(TextView const &lhs, TextView const &rhs) { + // Fast check - if the views are to the same memory, obviously equal. + if (lhs.data() == rhs.data() && lhs.size() == rhs.size()) { + return 0; + } + int zret; size_t n; @@ -51,6 +56,11 @@ swoc::memcmp(TextView const &lhs, TextView const &rhs) int strcasecmp(const std::string_view &lhs, const std::string_view &rhs) { + // Fast check - if the views are to the same memory, obviously equal. + if (lhs.data() == rhs.data() && lhs.size() == rhs.size()) { + return 0; + } + size_t len = std::min(lhs.size(), rhs.size()); int zret = strncasecmp(lhs.data(), rhs.data(), len); if (0 == zret) { diff --git a/swoc++/src/bw_format.cc b/swoc++/src/bw_format.cc index 517cf2b..2be79cd 100644 --- a/swoc++/src/bw_format.cc +++ b/swoc++/src/bw_format.cc @@ -861,7 +861,7 @@ bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Errno const &e) "EHWPOISON: ", }}; // This provides convenient safe access to the errno short name array. - auto short_name = [](int n) { return n < int(SHORT_NAME.size()) ? SHORT_NAME[n] : "Unknown: "sv; }; + auto short_name = [](int n) { return 0 < n && n < int(SHORT_NAME.size()) ? SHORT_NAME[n] : "Unknown: "sv; }; static const bwf::Format number_fmt{"[{}]"sv}; // numeric value format. if (spec.has_numeric_type()) { // if numeric type, print just the numeric // part. diff --git a/src/bw_ip_format.cc b/swoc++/src/bw_ip_format.cc similarity index 94% rename from src/bw_ip_format.cc rename to swoc++/src/bw_ip_format.cc index 10f02a5..2fe2a9d 100644 --- a/src/bw_ip_format.cc +++ b/swoc++/src/bw_ip_format.cc @@ -21,6 +21,8 @@ #include "swoc/swoc_ip.h" #include "swoc/bwf_ip.h" +using namespace swoc::literals; + namespace { std::string_view @@ -47,9 +49,9 @@ namespace swoc using bwf::Spec; BufferWriter & -bwformat(BufferWriter &w, Spec const &spec, in_addr_t addr) +bwformat(BufferWriter &w, Spec const &spec, in_addr const &addr) { - auto *ptr = reinterpret_cast(&addr); + auto *ptr = reinterpret_cast(&addr.s_addr); Spec local_spec{spec}; // Format for address elements. bool align_p = false; @@ -180,7 +182,7 @@ bwformat(BufferWriter &w, Spec const &spec, IPAddr const &addr) if (addr_p) { if (addr.is_ip4()) { - swoc::bwformat(w, spec, addr.raw_ip4()); + swoc::bwformat(w, spec, in_addr{addr.raw_ip4()}); } else if (addr.is_ip6()) { swoc::bwformat(w, spec, addr.raw_ip6()); } else { @@ -251,14 +253,14 @@ bwformat(BufferWriter &w, Spec const &spec, sockaddr const *addr) bool bracket_p = false; switch (addr->sa_family) { case AF_INET: - bwformat(w, spec, reinterpret_cast(addr)); + bwformat(w, spec, reinterpret_cast(addr)->sin_addr); break; case AF_INET6: if (port_p) { w.write('['); bracket_p = true; // take a note - put in the trailing bracket. } - bwformat(w, spec, reinterpret_cast(addr)); + bwformat(w, spec, reinterpret_cast(addr)->sin6_addr); break; default: w.print("*Not IP address [{}]*", addr->sa_family); @@ -277,7 +279,7 @@ bwformat(BufferWriter &w, Spec const &spec, sockaddr const *addr) } else { local_spec._min = 0; } - bwformat(w, local_spec, static_cast(IPEndPoint::port(addr))); + bwformat(w, local_spec, static_cast(IPEndpoint::host_order_port(addr))); } if (family_p) { local_spec._min = 0; @@ -286,7 +288,7 @@ bwformat(BufferWriter &w, Spec const &spec, sockaddr const *addr) if (spec.has_numeric_type()) { bwformat(w, local_spec, static_cast(addr->sa_family)); } else { - swoc::bwformat(w, local_spec, IPEndPoint::family_name(addr->sa_family)); + swoc::bwformat(w, local_spec, IPEndpoint::family_name(addr->sa_family)); } } return w; diff --git a/swoc++/src/swoc_file.cc b/swoc++/src/swoc_file.cc index 553139d..49e0ec9 100644 --- a/swoc++/src/swoc_file.cc +++ b/swoc++/src/swoc_file.cc @@ -22,10 +22,20 @@ #include #include +using namespace swoc::literals; + namespace swoc { namespace file { + path + path::parent_path() const + { + TextView parent{_path}; + parent.split_suffix_at(SEPARATOR); + return parent ? parent : "/"_tv; + }; + path & path::operator/=(std::string_view that) { @@ -119,8 +129,8 @@ namespace file ec = std::error_code(errno, std::system_category()); } } + ::close(fd); } - ::close(fd); return zret; } diff --git a/swoc++/src/swoc_ip.cc b/swoc++/src/swoc_ip.cc index 01b35be..92039e6 100644 --- a/swoc++/src/swoc_ip.cc +++ b/swoc++/src/swoc_ip.cc @@ -18,7 +18,6 @@ the License. */ -#include #include "swoc/swoc_ip.h" #include "swoc/swoc_meta.h" @@ -116,6 +115,86 @@ IPAddr::assign(sockaddr const *addr) return *this; } +bool +IPEndpoint::tokenize(std::string_view str, std::string_view *addr, std::string_view *port, std::string_view *rest) +{ + TextView src(str); /// Easier to work with for parsing. + // In case the incoming arguments are null, set them here and only check for null once. + // it doesn't matter if it's all the same, the results will be thrown away. + std::string_view local; + if (!addr) { + addr = &local; + } + if (!port) { + port = &local; + } + if (!rest) { + rest = &local; + } + + *addr = std::string_view{}; + *port = std::string_view{}; + *rest = std::string_view{}; + + // Let's see if we can find out what's in the address string. + if (src) { + bool colon_p = false; + src.ltrim_if(&isspace); + // Check for brackets. + if ('[' == *src) { + ++src; // skip bracket. + *addr = src.take_prefix_at(']'); + if (':' == *src) { + colon_p = true; + ++src; + } + } else { + TextView::size_type last = src.rfind(':'); + if (last != TextView::npos && last == src.find(':')) { + // Exactly one colon - leave post colon stuff in @a src. + *addr = src.take_prefix(last); + colon_p = true; + } else { // presume no port, use everything. + *addr = src; + src.clear(); + } + } + if (colon_p) { + TextView tmp{src}; + src.ltrim_if(&isdigit); + + if (tmp.data() == src.data()) { // no digits at all + src.assign(tmp.data() - 1, tmp.size() + 1); // back up to include colon + } else { + *port = std::string_view(tmp.data(), src.data() - tmp.data()); + } + } + *rest = src; + } + return !addr->empty(); // true if we found an address. +} + +bool +IPEndpoint::parse(std::string_view const &str) +{ + TextView addr_str, port_str, rest; + TextView src{TextView{str}.trim_if(&isspace)}; + + if (this->tokenize(src, &addr_str, &port_str, &rest)) { + if (rest.empty()) { + IPAddr addr; + if (addr.parse(addr_str)) { + auto n{swoc::svto_radix<10>(port_str)}; + if (port_str.empty() && 0 < n && n <= std::numeric_limits::max()) { + this->assign(addr, htons(n)); + return true; + } + } + } + } + return false; +} + sockaddr * IPAddr::fill(sockaddr *sa, in_port_t port) const { @@ -199,6 +278,140 @@ IPEndpoint::set_to_loopback(int family) return *this; } +bool +IP4Addr::parse(std::string_view const &text) +{ + TextView src{text}; + unsigned int n = 0; /// # of octets + bool bracket_p = false; + + if (src && '[' == *src) { + ++src; + src.ltrim_if(&isspace); + bracket_p = true; + } + + uint8_t *octet = reinterpret_cast(&_addr); + while (n < SIZE && !src.empty()) { + TextView token{src.take_prefix_at('.')}; + auto x = svto_radix<10>(token); + if (token.empty() && 0 <= x && x <= std::numeric_limits::max()) { + octet[n++] = x; + } else { + break; + } + } + + if (bracket_p) { + src.ltrim_if(&isspace); + if (']' != *src) { + n = 0; // pretend failure. + } + } + + if (n == SIZE && src.empty()) { + return true; + } + _addr = INADDR_ANY; + return false; +} + +bool +IP6Addr::parse(std::string_view const &str) +{ + TextView src{str}; + int n = 0; + int empty_idx = -1; // index of empty quad, -1 if not found yet. + bool bracket_p = false; + QUAD *quad = reinterpret_cast(&_addr); + + if (src && '[' == *src) { + ++src; + src.ltrim_if(&isspace); + bracket_p = true; + } + + while (n < N_QUADS && !src.empty()) { + TextView token{src.take_prefix_at(':')}; + if (token.empty()) { + if (empty_idx > 0 || (empty_idx == 0 && n > 1)) { + // two empty slots OK iff it's the first two (e.g. "::1"), otherwise invalid. + break; + } + empty_idx = n; + } else { + TextView r; + auto x = svtoi(token, &r, 16); + if (r.size() == token.size()) { + quad[n++] = htons(x); + } else { + break; + } + } + } + + // Handle empty quads - invalid if empty and still had a full set of quads + if (empty_idx >= 0 && n < N_QUADS) { + if (static_cast(n) <= empty_idx) { + while (empty_idx < static_cast(N_QUADS)) { + quad[empty_idx++] = 0; + } + } else { + int k = 1; + for (; n - k >= empty_idx; ++k) { + quad[N_QUADS - k] = quad[n - k]; + } + for (; N_QUADS - k >= empty_idx; ++k) { + quad[N_QUADS - k] = 0; + ++n; // track this so the validity check does the right thing. + } + } + } + + if (bracket_p) { + src.ltrim_if(&isspace); + if (']' != *src) { + n = 0; // pretend failure. + } + } + + if (n == N_QUADS && src.empty()) { + return true; + } + memset(&_addr, 0, sizeof(_addr)); + return false; +} + +bool +IPAddr::parse(const std::string_view &str) +{ + TextView src{str}; + src.ltrim_if(&isspace); + + if (TextView::npos != src.prefix(5).find_first_of('.')) { + _family = AF_INET; + } else if (TextView::npos != src.prefix(6).find_first_of(':')) { + _family = AF_INET6; + } else { + _family = AF_UNSPEC; + } + + // Do the real parse now + switch (_family) { + case AF_INET: + if (!_addr._ip4.parse(src)) { + _family = AF_UNSPEC; + } + break; + case AF_INET6: + if (!_addr._ip6.parse(src)) { + _family = AF_UNSPEC; + } + break; + } + return this->is_valid(); +} + #if 0 bool operator==(IPAddr const &lhs, sockaddr const *rhs) @@ -272,99 +485,6 @@ IPAddr::cmp(self_type const &that) const return zret; } -bool -IPAddr::parse(const std::string_view &str) -{ - bool bracket_p = false; - uint16_t family = AF_UNSPEC; - TextView src(str); - _family = AF_UNSPEC; // invalidate until/unless success. - src.trim_if(&isspace); - if (*src == '[') { - bracket_p = true; - family = AF_INET6; - ++src; - } else { // strip leading (hex) digits and see what the delimiter is. - auto tmp = src; - tmp.ltrim_if(&isxdigit); - if (':' == *tmp) { - family = AF_INET6; - } else if ('.' == *tmp) { - family = AF_INET; - } - } - // Do the real parse now - switch (family) { - case AF_INET: { - unsigned int n = 0; /// # of octets - while (n < IP4_SIZE && !src.empty()) { - TextView token{src.take_prefix_at('.')}; - TextView r; - auto x = svtoi(token, &r, 10); - if (r.size() == token.size()) { - _addr._octet[n++] = x; - } else { - break; - } - } - if (n == IP4_SIZE && src.empty()) { - _family = AF_INET; - } - } break; - case AF_INET6: { - int n = 0; - int empty_idx = -1; // index of empty quad, -1 if not found yet. - while (n < IP6_QUADS && !src.empty()) { - TextView token{src.take_prefix_at(':')}; - if (token.empty()) { - if (empty_idx > 0 || (empty_idx == 0 && n > 1)) { - // two empty slots OK iff it's the first two (e.g. "::1"), otherwise invalid. - break; - } - empty_idx = n; - } else { - TextView r; - auto x = svtoi(token, &r, 16); - if (r.size() == token.size()) { - _addr._quad[n++] = htons(x); - } else { - break; - } - } - } - if (bracket_p) { - src.ltrim_if(&isspace); - if (']' != *src) { - break; - } else { - ++src; - } - } - // Handle empty quads - invalid if empty and still had a full set of quads - if (empty_idx >= 0 && n < IP6_QUADS) { - if (static_cast(n) <= empty_idx) { - while (empty_idx < static_cast(IP6_QUADS)) { - _addr._quad[empty_idx++] = 0; - } - } else { - int k = 1; - for (; n - k >= empty_idx; ++k) { - _addr._quad[IP6_QUADS - k] = _addr._quad[n - k]; - } - for (; IP6_QUADS - k >= empty_idx; ++k) { - _addr._quad[IP6_QUADS - k] = 0; - ++n; // track this so the validity check does the right thing. - } - } - } - if (n == IP6_QUADS && src.empty()) { - _family = AF_INET6; - } - } break; - } - return this->is_valid(); -} - #endif bool @@ -375,77 +495,8 @@ IPAddr::is_multicast() const IPEndpoint::IPEndpoint(std::string_view const &text) { - std::string_view addr, port; - this->invalidate(); - if (this->tokenize(text, &addr, &port)) { - IPAddr a(addr); - if (a.is_valid()) { - auto p = svtou(port); - if (0 <= p && p < 65536) { - this->assign(a, p); - } - } - } -} - -bool -IPEndpoint::tokenize(std::string_view str, std::string_view *addr, std::string_view *port, std::string_view *rest) -{ - TextView src(str); /// Easier to work with for parsing. - // In case the incoming arguments are null, set them here and only check for null once. - // it doesn't matter if it's all the same, the results will be thrown away. - std::string_view local; - if (!addr) { - addr = &local; - } - if (!port) { - port = &local; - } - if (!rest) { - rest = &local; - } - - *addr = std::string_view{}; - *port = std::string_view{}; - *rest = std::string_view{}; - - // Let's see if we can find out what's in the address string. - if (src) { - bool colon_p = false; - src.ltrim_if(&isspace); - // Check for brackets. - if ('[' == *src) { - ++src; // skip bracket. - *addr = src.take_prefix_at(']'); - if (':' == *src) { - colon_p = true; - ++src; - } - } else { - TextView::size_type last = src.rfind(':'); - if (last != TextView::npos && last == src.find(':')) { - // Exactly one colon - leave post colon stuff in @a src. - *addr = src.take_prefix_at(last); - colon_p = true; - } else { // presume no port, use everything. - *addr = src; - src.clear(); - } - } - if (colon_p) { - TextView tmp{src}; - src.ltrim_if(&isdigit); - - if (tmp.data() == src.data()) { // no digits at all - src.assign(tmp.data() - 1, tmp.size() + 1); // back up to include colon - } else { - *port = std::string_view(tmp.data(), src.data() - tmp.data()); - } - } - *rest = src; - } - return !addr->empty(); // true if we found an address. + this->parse(text); } #if 0 diff --git a/swoc++/swoc++.part b/swoc++/swoc++.part new file mode 100644 index 0000000..756eb77 --- /dev/null +++ b/swoc++/swoc++.part @@ -0,0 +1,27 @@ +Import("*") +PartVersion("1.0.0") +PartName("swoc++") + +files = [ + "src/bw_format.cc", + "src/bw_ip_format.cc", + "src/Errata.cc", + "src/MemArena.cc", + "src/RBTree.cc", + "src/swoc_file.cc", + "src/swoc_ip.cc", + "src/TextView.cc", +] + +env.AppendUnique( + CCFLAGS=['-std=c++17'], + CPPPATH=["include"], +) + +# build the library +out = env.SharedLibrary("${PART_NAME}",files) +env.InstallLib(out) +# export the include directory +env.InstallInclude( + Pattern(src_dir="include/",includes=["*.h"]), + ) diff --git a/unit_tests/CMakeLists.txt b/unit_tests/CMakeLists.txt index e39c9b4..b9f550d 100644 --- a/unit_tests/CMakeLists.txt +++ b/unit_tests/CMakeLists.txt @@ -10,6 +10,7 @@ add_executable(test_libswoc test_Errata.cc test_IntrusiveDList.cc test_IntrusiveHashMap.cc + test_ip.cc test_Lexicon.cc test_MemSpan.cc test_MemArena.cc diff --git a/unit_tests/ex_bw_format.cc b/unit_tests/ex_bw_format.cc index 10945ac..1c89496 100644 --- a/unit_tests/ex_bw_format.cc +++ b/unit_tests/ex_bw_format.cc @@ -236,7 +236,7 @@ TEST_CASE("BufferWriter Context 2", "[bufferwriter][example][context]") // Intercept name dispatch to check for structured names and handle those. If not structured, // chain up to super class to dispatch normally. BufferWriter & - operator()(BufferWriter &w, Spec const &spec) const + operator()(BufferWriter &w, Spec const &spec, ExContext const &ctx) const override { // Structured name prefixes. static constexpr TextView FIELD_TAG{"field"}; @@ -245,14 +245,14 @@ TEST_CASE("BufferWriter Context 2", "[bufferwriter][example][context]") TextView name{spec._name}; TextView key = name.split_prefix_at('.'); if (key == FIELD_TAG) { - _ctx->field_gen(w, spec, name); + ctx.field_gen(w, spec, name); } else if (key == COOKIE_TAG) { - _ctx->cookie_gen(w, spec, name); + ctx.cookie_gen(w, spec, name); } else if (!key.empty()) { // error case - unrecognized prefix w.print("!{}!", name); } else { // direct name, do normal dispatch. - this->super_type::operator()(w, spec); + this->super_type::operator()(w, spec, ctx); } return w; } diff --git a/unit_tests/test_Scalar.cc b/unit_tests/test_Scalar.cc index 1777bde..2b42d94 100644 --- a/unit_tests/test_Scalar.cc +++ b/unit_tests/test_Scalar.cc @@ -27,7 +27,7 @@ using Paragraphs = swoc::Scalar<16, off_t>; using KB = swoc::Scalar<1024, off_t>; using MB = swoc::Scalar; -TEST_CASE("Scalar", "[libts][Scalar]") +TEST_CASE("Scalar", "[libswoc][Scalar]") { constexpr static int SCALE = 4096; constexpr static int SCALE_1 = 8192; @@ -91,7 +91,7 @@ TEST_CASE("Scalar", "[libts][Scalar]") REQUIRE(1200 == swoc::round_down<100>(1200)); REQUIRE(1200 == swoc::round_down<100>(1210)); } -TEST_CASE("Scalar Factors", "[libts][Scalar][factors]") +TEST_CASE("Scalar Factors", "[libswoc][Scalar][factors]") { constexpr static int SCALE_1 = 30; constexpr static int SCALE_2 = 20; @@ -134,7 +134,7 @@ TEST_CASE("Scalar Factors", "[libts][Scalar][factors]") REQUIRE(m_test.count() == 213); } -TEST_CASE("Scalar Arithmetic", "[libts][Scalar][arithmetic]") +TEST_CASE("Scalar Arithmetic", "[libswoc][Scalar][arithmetic]") { using KBytes = swoc::Scalar<1024>; using KiBytes = swoc::Scalar<1024, long int>; @@ -245,7 +245,7 @@ struct KBytes_tag { static constexpr std::string_view label{" bytes"}; }; -TEST_CASE("Scalar Formatting", "[libts][Scalar][bwf]") +TEST_CASE("Scalar Formatting", "[libswoc][Scalar][bwf]") { using KBytes = swoc::Scalar<1024, long int, KBytes_tag>; using KiBytes = swoc::Scalar<1000, int>; diff --git a/unit_tests/test_TextView.cc b/unit_tests/test_TextView.cc index 6889339..4c90ca2 100644 --- a/unit_tests/test_TextView.cc +++ b/unit_tests/test_TextView.cc @@ -32,12 +32,16 @@ using namespace std::literals; TEST_CASE("TextView Constructor", "[libswoc][TextView]") { static std::string base = "Evil Dave Rulez!"; + unsigned ux = base.size(); TextView tv(base); TextView a{"Evil Dave Rulez"}; TextView b{base.data(), base.size()}; TextView c{std::string_view(base)}; constexpr TextView d{"Grigor!"sv}; -} + TextView e{base.data(), 15}; + TextView f(base.data(), 15); + TextView u{base.data(), ux}; +}; TEST_CASE("TextView Operations", "[libswoc][TextView]") { diff --git a/unit_tests/test_bw_format.cc b/unit_tests/test_bw_format.cc index a45650f..c3953f2 100644 --- a/unit_tests/test_bw_format.cc +++ b/unit_tests/test_bw_format.cc @@ -599,16 +599,18 @@ TEST_CASE("bwstring std formats", "[libswoc][bwprint]") REQUIRE(w.view() == "Upper - |0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ|"); w.clear().print("Leading{}{}{}.", swoc::bwf::Optional(" | {} |", s1), swoc::bwf::Optional(" <{}>", empty), - swoc::bwf::Optional(!s3.empty(), " [{}]", s3)); + swoc::bwf::If(!s3.empty(), " [{}]", s3)); REQUIRE(w.view() == "Leading | Persia | [Leif]."); // Do it again, but this time as C strings (char * variants). w.clear().print("Leading{}{}{}.", swoc::bwf::Optional(" | {} |", s3.data()), swoc::bwf::Optional(" <{}>", empty), - swoc::bwf::Optional(!s3.empty(), " [{}]", s1.c_str())); + swoc::bwf::If(!s3.empty(), " [{}]", s1.c_str())); REQUIRE(w.view() == "Leading | Leif | [Persia]."); // Play with string_view w.clear().print("Clone?{}{}.", swoc::bwf::Optional(" #. {}", s2), swoc::bwf::Optional(" #. {}", s2.data())); REQUIRE(w.view() == "Clone? #. Evil Dave #. Evil Dave."); s2 = ""; + w.clear().print("Leading{}{}{}", swoc::bwf::If(true, " true"), swoc::bwf::If(false, " false"), swoc::bwf::If(true, " Persia")); + REQUIRE(w.view() == "Leading true Persia"); // Differentiate because the C string variant will generate output, as it's not nullptr, // but is a pointer to an empty string. w.clear().print("Clone?{}{}.", swoc::bwf::Optional(" 1. {}", s2), swoc::bwf::Optional(" 2. {}", s2.data())); diff --git a/unit_tests/test_ip.cc b/unit_tests/test_ip.cc new file mode 100644 index 0000000..97dd3a8 --- /dev/null +++ b/unit_tests/test_ip.cc @@ -0,0 +1,258 @@ +/** @file + + IP address support testing. + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one or more contributor license + agreements. See the NOTICE file distributed with this work for additional information regarding + copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with the License. You may + obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include "swoc/swoc_ip.h" +#include "swoc/bwf_ip.h" +#include "catch.hpp" + +using namespace std::literals; +using swoc::TextView; +using swoc::IPEndpoint; + +TEST_CASE("ink_inet", "[libswoc][ip]") +{ + // Use TextView because string_view(nullptr) fails. Gah. + struct ip_parse_spec { + TextView hostspec; + TextView host; + TextView port; + TextView rest; + }; + + constexpr ip_parse_spec names[] = { + {{"::"}, {"::"}, {nullptr}, {nullptr}}, + {{"[::1]:99"}, {"::1"}, {"99"}, {nullptr}}, + {{"127.0.0.1:8080"}, {"127.0.0.1"}, {"8080"}, {nullptr}}, + {{"127.0.0.1:8080-Bob"}, {"127.0.0.1"}, {"8080"}, {"-Bob"}}, + {{"127.0.0.1:"}, {"127.0.0.1"}, {nullptr}, {":"}}, + {{"foo.example.com"}, {"foo.example.com"}, {nullptr}, {nullptr}}, + {{"foo.example.com:99"}, {"foo.example.com"}, {"99"}, {nullptr}}, + {{"ffee::24c3:3349:3cee:0143"}, {"ffee::24c3:3349:3cee:0143"}, {nullptr}, {nullptr}}, + {{"fe80:88b5:4a:20c:29ff:feae:1c33:8080"}, {"fe80:88b5:4a:20c:29ff:feae:1c33:8080"}, {nullptr}, {nullptr}}, + {{"[ffee::24c3:3349:3cee:0143]"}, {"ffee::24c3:3349:3cee:0143"}, {nullptr}, {nullptr}}, + {{"[ffee::24c3:3349:3cee:0143]:80"}, {"ffee::24c3:3349:3cee:0143"}, {"80"}, {nullptr}}, + {{"[ffee::24c3:3349:3cee:0143]:8080x"}, {"ffee::24c3:3349:3cee:0143"}, {"8080"}, {"x"}}, + }; + + for (auto const &s : names) { + std::string_view host, port, rest; + + REQUIRE(IPEndpoint::tokenize(s.hostspec, &host, &port, &rest) == true); + REQUIRE(s.host == host); + REQUIRE(s.port == port); + REQUIRE(s.rest == rest); + } +} + +#if 0 +TEST_CASE("ats_ip_pton", "[libswoc][inet][ink_inet]") +{ + IpEndpoint ep; + IpAddr addr; + IpAddr lower, upper; + + REQUIRE(0 == ats_ip_pton("76.14.64.156", &ep.sa)); + REQUIRE(0 == addr.load("76.14.64.156")); + REQUIRE(addr.family() == ep.family()); + + switch (addr.family()) { + case AF_INET: + REQUIRE(ep.sin.sin_addr.s_addr == addr._addr._ip4); + break; + case AF_INET6: + REQUIRE(0 == memcmp(&ep.sin6.sin6_addr, &addr._addr._ip6, sizeof(in6_addr))); + break; + default:; + } + + REQUIRE(TS_SUCCESS != addr.load("Evil Dave Rulz!")); + + REQUIRE(TS_SUCCESS == ats_ip_range_parse("1.1.1.1-2.2.2.2"sv, lower, upper)); + REQUIRE(TS_SUCCESS != ats_ip_range_parse("172.16.39.0/", lower, upper)); + REQUIRE(TS_SUCCESS == ats_ip_range_parse("172.16.39.0/24", lower, upper)); + REQUIRE(TS_SUCCESS != ats_ip_range_parse("172.16.39.0-", lower, upper)); + REQUIRE(TS_SUCCESS != ats_ip_range_parse("172.16.39.0/35", lower, upper)); + REQUIRE(TS_SUCCESS != ats_ip_range_parse("172.16.39.0/-20", lower, upper)); + REQUIRE(TS_SUCCESS != ats_ip_range_parse("Thanks, Persia! You're the best.", lower, upper)); + + addr.load("172.16.39.0"); + REQUIRE(addr == lower); + addr.load("172.16.39.255"); + REQUIRE(addr == upper); + + REQUIRE(TS_SUCCESS == ats_ip_range_parse("10.169.243.105/23", lower, upper)); + addr.load("10.169.242.0"); + REQUIRE(lower == addr); + addr.load("10.169.243.255"); + REQUIRE(upper == addr); + + REQUIRE(TS_SUCCESS == ats_ip_range_parse("192.168.99.22", lower, upper)); + REQUIRE(lower == upper); + REQUIRE(lower != IpAddr{INADDR_ANY}); + + REQUIRE(TS_SUCCESS == ats_ip_range_parse("0/0", lower, upper)); + REQUIRE(lower == IpAddr{INADDR_ANY}); + REQUIRE(upper == IpAddr{INADDR_BROADCAST}); + + REQUIRE(TS_SUCCESS == ats_ip_range_parse("c600::-d900::"sv, lower, upper)); + REQUIRE(TS_SUCCESS == ats_ip_range_parse("1300::/96", lower, upper)); + REQUIRE(TS_SUCCESS != ats_ip_range_parse("ffee::24c3:3349:3cee:0143/", lower, upper)); + + REQUIRE(TS_SUCCESS == ats_ip_range_parse("ffee:1337:beef:dead:24c3:3349:3cee:0143/80", lower, upper)); + addr.load("ffee:1337:beef:dead:24c3::"sv); + REQUIRE(lower == addr); + addr.load("ffee:1337:beef:dead:24c3:FFFF:FFFF:FFFF"sv); + REQUIRE(upper == addr); + + REQUIRE(TS_SUCCESS == ats_ip_range_parse("ffee:1337:beef:dead:24c3:3349:3cee:0143/57", lower, upper)); + addr.load("ffee:1337:beef:de80::"sv); + REQUIRE(lower == addr); + addr.load("ffee:1337:beef:deff:FFFF:FFFF:FFFF:FFFF"sv); + REQUIRE(upper == addr); + + REQUIRE(TS_SUCCESS == ats_ip_range_parse("ffee::24c3:3349:3cee:0143", lower, upper)); + REQUIRE(lower == upper); + + REQUIRE(TS_SUCCESS == ats_ip_range_parse("::/0", lower, upper)); + REQUIRE(lower._addr._u64[0] == 0); + REQUIRE(lower._addr._u64[1] == 0); + REQUIRE(upper._addr._u64[0] == ~static_cast(0)); + REQUIRE(upper._addr._u64[1] == static_cast(-1)); + + REQUIRE(TS_SUCCESS == ats_ip_range_parse("c000::/32", lower, upper)); + addr.load("c000::"); + REQUIRE(addr == lower); + addr.load("c000::ffff:ffff:ffff:ffff:ffff:ffff"); + REQUIRE(addr == upper); +} +#endif + +TEST_CASE("IP Formatting", "[libswoc][ip][bwformat]") +{ + IPEndpoint ep; + std::string_view addr_1{"[ffee::24c3:3349:3cee:143]:8080"}; + std::string_view addr_2{"172.17.99.231:23995"}; + std::string_view addr_3{"[1337:ded:BEEF::]:53874"}; + std::string_view addr_4{"[1337::ded:BEEF]:53874"}; + std::string_view addr_5{"[1337:0:0:ded:BEEF:0:0:956]:53874"}; + std::string_view addr_6{"[1337:0:0:ded:BEEF:0:0:0]:53874"}; + std::string_view addr_7{"172.19.3.105:4951"}; + std::string_view addr_null{"[::]:53874"}; + swoc::LocalBufferWriter<1024> w; + + REQUIRE(ep.parse(addr_1) == true); + w.print("{}", ep); + REQUIRE(w.view() == addr_1); + w.clear().print("{::p}", ep); + REQUIRE(w.view() == "8080"); + w.clear().print("{::a}", ep); + REQUIRE(w.view() == addr_1.substr(1, 24)); // check the brackets are dropped. + w.clear().print("[{::a}]", ep); + REQUIRE(w.view() == addr_1.substr(0, 26)); // check the brackets are dropped. + w.clear().print("[{0::a}]:{0::p}", ep); + REQUIRE(w.view() == addr_1); // check the brackets are dropped. + w.clear().print("{::=a}", ep); + REQUIRE(w.view() == "ffee:0000:0000:0000:24c3:3349:3cee:0143"); + w.clear().print("{:: =a}", ep); + REQUIRE(w.view() == "ffee: 0: 0: 0:24c3:3349:3cee: 143"); +#if 0 + ep.setToLoopback(AF_INET6); + w.reset().print("{::a}", ep); + REQUIRE(w.view() == "::1"); + REQUIRE(0 == ats_ip_pton(addr_3, &ep.sa)); + w.reset().print("{::a}", ep); + REQUIRE(w.view() == "1337:ded:beef::"); + REQUIRE(0 == ats_ip_pton(addr_4, &ep.sa)); + w.reset().print("{::a}", ep); + REQUIRE(w.view() == "1337::ded:beef"); + + REQUIRE(0 == ats_ip_pton(addr_5, &ep.sa)); + w.reset().print("{:X:a}", ep); + REQUIRE(w.view() == "1337::DED:BEEF:0:0:956"); + + REQUIRE(0 == ats_ip_pton(addr_6, &ep.sa)); + w.reset().print("{::a}", ep); + REQUIRE(w.view() == "1337:0:0:ded:beef::"); + + REQUIRE(0 == ats_ip_pton(addr_null, &ep.sa)); + w.reset().print("{::a}", ep); + REQUIRE(w.view() == "::"); +#endif + + REQUIRE(ep.parse(addr_2) == true); + w.clear().print("{::a}", ep); + REQUIRE(w.view() == addr_2.substr(0, 13)); + w.clear().print("{0::a}", ep); + REQUIRE(w.view() == addr_2.substr(0, 13)); + w.clear().print("{::ap}", ep); + REQUIRE(w.view() == addr_2); + w.clear().print("{::f}", ep); + REQUIRE(w.view() == "ipv4"); + w.clear().print("{::fpa}", ep); + REQUIRE(w.view() == "172.17.99.231:23995 ipv4"); + w.clear().print("{0::a} .. {0::p}", ep); + REQUIRE(w.view() == "172.17.99.231 .. 23995"); + w.clear().print("<+> {0::a} <+> {0::p}", ep); + REQUIRE(w.view() == "<+> 172.17.99.231 <+> 23995"); + w.clear().print("<+> {0::a} <+> {0::p} <+>", ep); + REQUIRE(w.view() == "<+> 172.17.99.231 <+> 23995 <+>"); + w.clear().print("{:: =a}", ep); + REQUIRE(w.view() == "172. 17. 99.231"); + w.clear().print("{::=a}", ep); + REQUIRE(w.view() == "172.017.099.231"); + // w.clear().print("{}", swoc::bwf::Hex_Dump(ep)); + // REQUIRE(w.view() == "ac1163e7"); + // w.clear().print("{:#X}", swoc::bwf::Hex_Dump(ep)); + // REQUIRE(w.view() == "0XAC1163E7"); + +#if 0 + // Documentation examples + REQUIRE(0 == ats_ip_pton(addr_7, &ep.sa)); + w.reset().print("To {}", ep); + REQUIRE(w.view() == "To 172.19.3.105:4951"); + w.reset().print("To {0::a} on port {0::p}", ep); // no need to pass the argument twice. + REQUIRE(w.view() == "To 172.19.3.105 on port 4951"); + w.reset().print("To {::=}", ep); + REQUIRE(w.view() == "To 172.019.003.105:04951"); + w.reset().print("{::a}", ep); + REQUIRE(w.view() == "172.19.3.105"); + w.reset().print("{::=a}", ep); + REQUIRE(w.view() == "172.019.003.105"); + w.reset().print("{::0=a}", ep); + REQUIRE(w.view() == "172.019.003.105"); + w.reset().print("{:: =a}", ep); + REQUIRE(w.view() == "172. 19. 3.105"); + w.reset().print("{:>20:a}", ep); + REQUIRE(w.view() == " 172.19.3.105"); + w.reset().print("{:>20:=a}", ep); + REQUIRE(w.view() == " 172.019.003.105"); + w.reset().print("{:>20: =a}", ep); + REQUIRE(w.view() == " 172. 19. 3.105"); + w.reset().print("{:<20:a}", ep); + REQUIRE(w.view() == "172.19.3.105 "); + + w.reset().print("{:p}", reinterpret_cast(0x1337beef)); + REQUIRE(w.view() == "0x1337beef"); + + ats_ip_pton(addr_1, &ep.sa); + w.reset().print("{}", swoc::bwf::Hex_Dump(ep)); + REQUIRE(w.view() == "ffee00000000000024c333493cee0143"); +#endif +} diff --git a/unit_tests/test_swoc_file.cc b/unit_tests/test_swoc_file.cc index ac78fd9..0ffedcb 100644 --- a/unit_tests/test_swoc_file.cc +++ b/unit_tests/test_swoc_file.cc @@ -40,6 +40,9 @@ TEST_CASE("swoc_file", "[libts][swoc_file]") REQUIRE(p2.string() == "/home/dave"); path p3 = path("/home/dave") / "git/tools"; REQUIRE(p3.string() == "/home/dave/git/tools"); + REQUIRE(p3.parent_path().string() == "/home/dave/git"); + REQUIRE(p3.parent_path().parent_path().string() == "/home/dave"); + REQUIRE(p1.parent_path().string() == "/"); } TEST_CASE("swoc_file_io", "[libts][swoc_file_io]") diff --git a/unit_tests/unit_tests.part b/unit_tests/unit_tests.part new file mode 100644 index 0000000..8f9b304 --- /dev/null +++ b/unit_tests/unit_tests.part @@ -0,0 +1,40 @@ + +Import("*") +PartName("tests") + +DependsOn([ + Component("swoc++",requires=REQ.DEFAULT(internal=False)) + ]) + +env.AppendUnique( + CCFLAGS=['-std=c++17'], +) + +files = [ + "unit_test_main.cc", + + "test_BufferWriter.cc", + "test_bw_format.cc", + "test_Errata.cc", + "test_IntrusiveDList.cc", + "test_IntrusiveHashMap.cc", + "test_ip.cc", + "test_Lexicon.cc", + "test_MemSpan.cc", + "test_MemArena.cc", + "test_meta.cc", + "test_TextView.cc", + "test_Scalar.cc", + "test_swoc_file.cc", + "ex_bw_format.cc", + "ex_IntrusiveDList.cc", + "ex_MemArena.cc", + "ex_TextView.cc", +] +env.UnitTest( + "tests", + files, + # data file we need for the test to pass + data_src=[Pattern(src_dir="#",includes=['doc/conf.py','unit_tests/test_swoc_file.cc'])] + ) +