diff --git a/CMakeLists.txt b/CMakeLists.txt index 4816958c..a042957b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,8 +116,7 @@ install(TARGETS skyr-url ARCHIVE DESTINATION lib LIBRARY DESTINATION lib) - - install(EXPORT ${PROJECT_NAME}-targets +install(EXPORT ${PROJECT_NAME}-targets DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${PROJECT_NAME}" NAMESPACE skyr:: FILE "${PROJECT_NAME}-targets.cmake") diff --git a/include/skyr/url.hpp b/include/skyr/url.hpp index 69e5d457..8a25e3a6 100644 --- a/include/skyr/url.hpp +++ b/include/skyr/url.hpp @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include #if defined(SKYR_PLATFORM_MSVC) @@ -364,6 +366,26 @@ class url { /// \returns An error on failure to parse the new URL tl::expected set_hostname(string_view hostname); + /// Checks if the hostname is a valid IPv4 address + [[nodiscard]] bool is_ipv4_address() const; + + /// Returns an optional ipv4_address value if the hostname is a + /// valid IPv4 address + [[nodiscard]] std::optional ipv4_address() const; + + /// Checks if the hostname is a valid IPv6 address + [[nodiscard]] bool is_ipv6_address() const; + + /// Returns an optional ipv6_address value if the hostname is a + /// valid IPv6 address + [[nodiscard]] std::optional ipv6_address() const; + + /// Checks if the hostname is a valid domain name + [[nodiscard]] bool is_domain() const; + + /// Checks if the hostname is a valid domain name + [[nodiscard]] bool is_opaque() const; + /// Returns the [URL port](https://url.spec.whatwg.org/#dom-url-port) /// /// \returns The [URL port](https://url.spec.whatwg.org/#dom-url-port) diff --git a/include/skyr/url/details/endianness.hpp b/include/skyr/url/details/endianness.hpp new file mode 100644 index 00000000..f44b558e --- /dev/null +++ b/include/skyr/url/details/endianness.hpp @@ -0,0 +1,42 @@ +// Copyright 2020 Glyn Matthews. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef SKYR_URL_ENDIANNESS_HPP +#define SKYR_URL_ENDIANNESS_HPP + +#include +#include + +namespace skyr { +inline namespace v1 { +namespace details { +inline bool is_big_endian() noexcept { + const auto word = 0x0001; + auto bytes = static_cast(static_cast(&word)); + return bytes[0] != 0x01; +} + +template +inline intT swap_endianness(intT v, typename std::enable_if::value>::type * = nullptr) noexcept { + constexpr auto byte_count = sizeof(v); + std::array bytes{}; + for (auto i = 0UL; i < byte_count; ++i) { + bytes[i] = (v >> (i * 8)); + } + return *static_cast(static_cast(bytes.data())); +} + +inline unsigned int to_network_byte_order(unsigned int v) noexcept { + return (is_big_endian()) ? v : swap_endianness(v); +} + +inline unsigned int from_network_byte_order(unsigned int v) noexcept { + return (is_big_endian()) ? v : swap_endianness(v); +} +} // namespace details +} // namespace v1 +} // namespace skyr + +#endif //SKYR_URL_ENDIANNESS_HPP diff --git a/src/url/ipv4_address.hpp b/include/skyr/url/ipv4_address.hpp similarity index 74% rename from src/url/ipv4_address.hpp rename to include/skyr/url/ipv4_address.hpp index ac285add..3cac1835 100644 --- a/src/url/ipv4_address.hpp +++ b/include/skyr/url/ipv4_address.hpp @@ -1,4 +1,4 @@ -// Copyright 2018 Glyn Matthews. +// Copyright 2018-20 Glyn Matthews. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) @@ -6,11 +6,13 @@ #ifndef SKYR_IPV4_ADDRESS_INC #define SKYR_IPV4_ADDRESS_INC +#include #include #include #include #include #include +#include namespace skyr { inline namespace v1 { @@ -45,12 +47,23 @@ class ipv4_address { /// Constructor /// \param address Sets the IPv4 address to `address` explicit ipv4_address(unsigned int address) - : address_(address) {} + : address_(details::to_network_byte_order(address)) {} /// The address value /// \returns The address value [[nodiscard]] unsigned int address() const noexcept { - return address_; + return details::from_network_byte_order(address_); + } + + /// The address in bytes in network byte order + /// \returns The address in bytes + [[nodiscard]] std::array to_bytes() const noexcept { + return {{ + static_cast(address_ >> 24u), + static_cast(address_ >> 16u), + static_cast(address_ >> 8u), + static_cast(address_ >> 0u) + }}; } /// \returns The address as a string diff --git a/src/url/ipv6_address.hpp b/include/skyr/url/ipv6_address.hpp similarity index 70% rename from src/url/ipv6_address.hpp rename to include/skyr/url/ipv6_address.hpp index 6731f23d..ce8bca08 100644 --- a/src/url/ipv6_address.hpp +++ b/include/skyr/url/ipv6_address.hpp @@ -1,4 +1,4 @@ -// Copyright 2018 Glyn Matthews. +// Copyright 2018-20 Glyn Matthews. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) @@ -13,6 +13,7 @@ #include #include #include +#include namespace skyr { inline namespace v1 { @@ -42,8 +43,6 @@ class ipv6_address { std::array address_ = {0, 0, 0, 0, 0, 0, 0, 0}; - using repr_type = decltype(address_); - public: /// Constructor @@ -51,10 +50,24 @@ class ipv6_address { /// Constructor /// \param address Sets the IPv6 address to `address` - explicit ipv6_address(std::array address) - : address_(address) {} + explicit ipv6_address(std::array address) { + for (auto i = 0UL; i < address.size(); ++i) { + address_[i] = details::to_network_byte_order(address[i]); + } + } + + /// The address in bytes in network byte order + /// \returns The address in bytes + [[nodiscard]] std::array to_bytes() const noexcept { + std::array bytes{}; + for (auto i = 0UL; i < address_.size(); ++i) { + bytes[i * 2 ] = static_cast(address_[i] >> 8u); + bytes[i * 2 + 1] = static_cast(address_[i]); + } + return bytes; + } - /// \returns The IPv4 address as a string + /// \returns The IPv6 address as a string [[nodiscard]] std::string to_string() const; }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d95925ed..fc85cbdc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,9 +16,7 @@ set(skyr_SRCS url/url_parser_context.cpp url/url_record.cpp url/ipv4_address.cpp - url/ipv4_address.hpp url/ipv6_address.cpp - url/ipv6_address.hpp url/algorithms.hpp url/url_parse.cpp url/url_parse_impl.hpp @@ -33,7 +31,6 @@ set(skyr_SRCS ${PROJECT_SOURCE_DIR}/include/skyr/config.hpp ${PROJECT_SOURCE_DIR}/include/skyr/version.hpp - ${PROJECT_SOURCE_DIR}/include/skyr/url/details/to_bytes.hpp ${PROJECT_SOURCE_DIR}/include/skyr/traits/string_traits.hpp ${PROJECT_SOURCE_DIR}/include/skyr/unicode/errors.hpp ${PROJECT_SOURCE_DIR}/include/skyr/unicode/core.hpp @@ -48,10 +45,14 @@ set(skyr_SRCS ${PROJECT_SOURCE_DIR}/include/skyr/unicode/ranges/transforms/u32_transform.hpp ${PROJECT_SOURCE_DIR}/include/skyr/unicode/idna.hpp ${PROJECT_SOURCE_DIR}/include/skyr/unicode/domain.hpp + ${PROJECT_SOURCE_DIR}/include/skyr/url/details/to_bytes.hpp + ${PROJECT_SOURCE_DIR}/include/skyr/url/details/endianness.hpp ${PROJECT_SOURCE_DIR}/include/skyr/url/percent_encoding/errors.hpp ${PROJECT_SOURCE_DIR}/include/skyr/url/percent_encoding/percent_decode_range.hpp ${PROJECT_SOURCE_DIR}/include/skyr/url/percent_encoding/percent_encode_range.hpp ${PROJECT_SOURCE_DIR}/include/skyr/url/percent_encoding/percent_encoded_char.hpp + ${PROJECT_SOURCE_DIR}/include/skyr/url/ipv4_address.hpp + ${PROJECT_SOURCE_DIR}/include/skyr/url/ipv6_address.hpp ${PROJECT_SOURCE_DIR}/include/skyr/url/url_record.hpp ${PROJECT_SOURCE_DIR}/include/skyr/url/url_parse.hpp ${PROJECT_SOURCE_DIR}/include/skyr/url/url_serialize.hpp @@ -82,6 +83,3 @@ target_include_directories(skyr-url $ PRIVATE ${PROJECT_SOURCE_DIR}/src) -# -##propagate sources to parent scope for one-lib-build -#set(skyr_SRCS ${skyr_SRCS} PARENT_SCOPE) diff --git a/src/url/ipv4_address.cpp b/src/url/ipv4_address.cpp index f2b52542..598189f9 100644 --- a/src/url/ipv4_address.cpp +++ b/src/url/ipv4_address.cpp @@ -11,7 +11,7 @@ #include #include #include -#include "ipv4_address.hpp" +#include namespace skyr { inline namespace v1 { diff --git a/src/url/ipv6_address.cpp b/src/url/ipv6_address.cpp index 6083c96e..28fe06a6 100644 --- a/src/url/ipv6_address.cpp +++ b/src/url/ipv6_address.cpp @@ -1,4 +1,4 @@ -// Copyright 2018 Glyn Matthews. +// Copyright 2018-20 Glyn Matthews. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) @@ -10,7 +10,7 @@ #include #include #include -#include "ipv6_address.hpp" +#include #include "algorithms.hpp" namespace skyr { diff --git a/src/url/url.cpp b/src/url/url.cpp index 333febe7..5336219c 100644 --- a/src/url/url.cpp +++ b/src/url/url.cpp @@ -188,6 +188,47 @@ tl::expected url::set_hostname(string_view hostname) { }); } +bool url::is_ipv4_address() const { + return parse_ipv4_address(hostname()).has_value(); +} + +std::optional url::ipv4_address() const { + auto address = parse_ipv4_address(hostname()); + return address.has_value() ? std::make_optional(address.value()) : std::nullopt; +} + +bool url::is_ipv6_address() const { + if (!url_.host) { + return false; + } + auto view = std::string_view(url_.host.value()); + if ((view.size() <= 2) || view.front() != '[' || view.back() != ']') { + return false; + } + return parse_ipv6_address(view.substr(1, view.size() - 2)).has_value(); +} + +std::optional url::ipv6_address() const { + if (!url_.host) { + return std::nullopt; + } + auto view = std::string_view(url_.host.value()); + if ((view.size() <= 2) || view.front() != '[' || view.back() != ']') { + return std::nullopt; + } + + auto address = parse_ipv6_address(view.substr(1, view.size() - 2)); + return address.has_value() ? std::make_optional(address.value()) : std::nullopt; +} + +bool url::is_domain() const { + return details::is_special(url_.scheme) && !hostname().empty() && !is_ipv4_address() && !is_ipv6_address(); +} + +bool url::is_opaque() const { + return !details::is_special(url_.scheme) && !hostname().empty(); +} + url::string_type url::port() const { if (!url_.port) { return {}; diff --git a/src/url/url_parser_context.cpp b/src/url/url_parser_context.cpp index bc92d93a..4411561f 100644 --- a/src/url/url_parser_context.cpp +++ b/src/url/url_parser_context.cpp @@ -17,8 +17,8 @@ #include #include "url_parser_context.hpp" #include "url_schemes.hpp" -#include "ipv4_address.hpp" -#include "ipv6_address.hpp" +#include "skyr/url/ipv4_address.hpp" +#include "skyr/url/ipv6_address.hpp" #include "algorithms.hpp" namespace skyr { diff --git a/tests/url/ipv4_address_tests.cpp b/tests/url/ipv4_address_tests.cpp index 18b0dcb5..cb5f9583 100644 --- a/tests/url/ipv4_address_tests.cpp +++ b/tests/url/ipv4_address_tests.cpp @@ -5,7 +5,7 @@ #define CATCH_CONFIG_MAIN #include -#include "../../src/url/ipv4_address.hpp" +#include TEST_CASE("ipv4 addresses", "[ipv4]") { using namespace std::string_literals; @@ -46,6 +46,13 @@ TEST_CASE("ipv4 addresses", "[ipv4]") { CHECK(0x814ff5fc == instance.value().address()); } + SECTION("parse_address_looks_short_test") { + const auto address = "129.79.245"s; + auto instance = skyr::parse_ipv4_address(address); + REQUIRE(instance); + CHECK(0x814f00f5 == instance.value().address()); + } + SECTION("parse_address_with_hex") { const auto address = "0x7f.0.0.0x7f"s; auto instance = skyr::parse_ipv4_address(address); @@ -58,4 +65,17 @@ TEST_CASE("ipv4 addresses", "[ipv4]") { auto instance = skyr::parse_ipv4_address(address); REQUIRE_FALSE(instance); } + + SECTION("loopback_as_bytes") { + auto instance = skyr::ipv4_address(0x7f000001); + std::array bytes{{0x7f, 0x00, 0x00, 0x01}}; + CHECK(bytes == instance.to_bytes()); + } + + SECTION("parse_loopback_test_as_bytes") { + auto instance = skyr::parse_ipv4_address("127.0.0.1"s); + REQUIRE(instance); + std::array bytes{{0x7f, 0x00, 0x00, 0x01}}; + CHECK(bytes == instance.value().to_bytes()); + } } diff --git a/tests/url/ipv6_address_tests.cpp b/tests/url/ipv6_address_tests.cpp index 0c7aba90..4ad3c49a 100644 --- a/tests/url/ipv6_address_tests.cpp +++ b/tests/url/ipv6_address_tests.cpp @@ -5,7 +5,7 @@ #define CATCH_CONFIG_MAIN #include -#include "../../src/url/ipv6_address.hpp" +#include TEST_CASE("ipv6_address_tests", "[ipv6]") { using namespace std::string_literals; @@ -133,4 +133,14 @@ TEST_CASE("ipv6_address_tests", "[ipv6]") { REQUIRE(instance); CHECK("::ffff:c000:280" == instance.value().to_string()); } + + SECTION("loopback_test") { + auto address = std::array{{0, 0, 0, 0, 0, 0, 0, 1}}; + auto instance = skyr::ipv6_address(address); + std::array bytes{ + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + }}; + CHECK(bytes == instance.to_bytes()); + } } diff --git a/tests/url/url_tests.cpp b/tests/url/url_tests.cpp index 8f78fd3e..6ed9d524 100644 --- a/tests/url/url_tests.cpp +++ b/tests/url/url_tests.cpp @@ -168,6 +168,7 @@ TEST_CASE("url_tests", "[url]") { auto instance = skyr::url("http://129.79.245.252/"); CHECK("http:" == instance.protocol()); CHECK("129.79.245.252" == instance.host()); + CHECK(instance.is_ipv4_address()); CHECK("/" == instance.pathname()); } @@ -175,6 +176,7 @@ TEST_CASE("url_tests", "[url]") { auto instance = skyr::url("http://127.0.0.1/"); CHECK("http:" == instance.protocol()); CHECK("127.0.0.1" == instance.host()); + CHECK(instance.is_ipv4_address()); CHECK("/" == instance.pathname()); } @@ -182,6 +184,7 @@ TEST_CASE("url_tests", "[url]") { auto instance = skyr::url("http://[1080:0:0:0:8:800:200C:417A]/"); CHECK("http:" == instance.protocol()); CHECK("[1080::8:800:200c:417a]" == instance.host()); + CHECK(instance.is_ipv6_address()); CHECK("/" == instance.pathname()); } @@ -189,6 +192,7 @@ TEST_CASE("url_tests", "[url]") { auto instance = skyr::url("http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]/"); CHECK("http:" == instance.protocol()); CHECK("[2001:db8:85a3:8d3:1319:8a2e:370:7348]" == instance.host()); + CHECK(instance.is_ipv6_address()); CHECK("/" == instance.pathname()); } @@ -196,6 +200,7 @@ TEST_CASE("url_tests", "[url]") { auto instance = skyr::url("http://[2001:db8:85a3:0:0:8a2e:370:7334]/"); CHECK("http:" == instance.protocol()); CHECK("[2001:db8:85a3::8a2e:370:7334]" == instance.host()); + CHECK(instance.is_ipv6_address()); CHECK("/" == instance.pathname()); } @@ -203,6 +208,7 @@ TEST_CASE("url_tests", "[url]") { auto instance = skyr::url("http://[2001:db8:85a3::8a2e:370:7334]/"); CHECK("http:" == instance.protocol()); CHECK("[2001:db8:85a3::8a2e:370:7334]" == instance.host()); + CHECK(instance.is_ipv6_address()); CHECK("/" == instance.pathname()); } @@ -210,6 +216,7 @@ TEST_CASE("url_tests", "[url]") { auto instance = skyr::url("http://[2001:0db8:0000:0000:0000:0000:1428:57ab]/"); CHECK("http:" == instance.protocol()); CHECK("[2001:db8::1428:57ab]" == instance.host()); + CHECK(instance.is_ipv6_address()); CHECK("/" == instance.pathname()); } @@ -217,6 +224,7 @@ TEST_CASE("url_tests", "[url]") { auto instance = skyr::url("http://[2001:0db8:0000:0000:0000::1428:57ab]/"); CHECK("http:" == instance.protocol()); CHECK("[2001:db8::1428:57ab]" == instance.host()); + CHECK(instance.is_ipv6_address()); CHECK("/" == instance.pathname()); } @@ -224,6 +232,7 @@ TEST_CASE("url_tests", "[url]") { auto instance = skyr::url("http://[2001:0db8:0:0:0:0:1428:57ab]/"); CHECK("http:" == instance.protocol()); CHECK("[2001:db8::1428:57ab]" == instance.host()); + CHECK(instance.is_ipv6_address()); CHECK("/" == instance.pathname()); } @@ -231,6 +240,7 @@ TEST_CASE("url_tests", "[url]") { auto instance = skyr::url("http://[2001:0db8:0:0::1428:57ab]/"); CHECK("http:" == instance.protocol()); CHECK("[2001:db8::1428:57ab]" == instance.host()); + CHECK(instance.is_ipv6_address()); CHECK("/" == instance.pathname()); } @@ -238,6 +248,7 @@ TEST_CASE("url_tests", "[url]") { auto instance = skyr::url("http://[2001:0db8::1428:57ab]/"); CHECK("http:" == instance.protocol()); CHECK("[2001:db8::1428:57ab]" == instance.host()); + CHECK(instance.is_ipv6_address()); CHECK("/" == instance.pathname()); } @@ -245,6 +256,7 @@ TEST_CASE("url_tests", "[url]") { auto instance = skyr::url("http://[2001:db8::1428:57ab]/"); CHECK("http:" == instance.protocol()); CHECK("[2001:db8::1428:57ab]" == instance.host()); + CHECK(instance.is_ipv6_address()); CHECK("/" == instance.pathname()); } @@ -252,6 +264,7 @@ TEST_CASE("url_tests", "[url]") { auto instance = skyr::url("http://[::ffff:0c22:384e]/"); CHECK("http:" == instance.protocol()); CHECK("[::ffff:c22:384e]" == instance.host()); + CHECK(instance.is_ipv6_address()); CHECK("/" == instance.pathname()); } @@ -259,6 +272,7 @@ TEST_CASE("url_tests", "[url]") { auto instance = skyr::url("http://[fe80::]/"); CHECK("http:" == instance.protocol()); CHECK("[fe80::]" == instance.host()); + CHECK(instance.is_ipv6_address()); CHECK("/" == instance.pathname()); } @@ -266,6 +280,7 @@ TEST_CASE("url_tests", "[url]") { auto instance = skyr::url("http://[::ffff:c000:280]/"); CHECK("http:" == instance.protocol()); CHECK("[::ffff:c000:280]" == instance.host()); + CHECK(instance.is_ipv6_address()); CHECK("/" == instance.pathname()); } @@ -273,6 +288,7 @@ TEST_CASE("url_tests", "[url]") { auto instance = skyr::url("http://[::1]/"); CHECK("http:" == instance.protocol()); CHECK("[::1]" == instance.host()); + CHECK(instance.is_ipv6_address()); CHECK("/" == instance.pathname()); } @@ -280,6 +296,7 @@ TEST_CASE("url_tests", "[url]") { auto instance = skyr::url("http://[0000:0000:0000:0000:0000:0000:0000:0001]/"); CHECK("http:" == instance.protocol()); CHECK("[::1]" == instance.host()); + CHECK(instance.is_ipv6_address()); CHECK("/" == instance.pathname()); } @@ -287,6 +304,7 @@ TEST_CASE("url_tests", "[url]") { auto instance = skyr::url("http://[::ffff:12.34.56.78]/"); CHECK("http:" == instance.protocol()); CHECK("[::ffff:c22:384e]" == instance.host()); + CHECK(instance.is_ipv6_address()); CHECK("/" == instance.pathname()); } @@ -294,6 +312,7 @@ TEST_CASE("url_tests", "[url]") { auto instance = skyr::url("http://[::ffff:192.0.2.128]/"); CHECK("http:" == instance.protocol()); CHECK("[::ffff:c000:280]" == instance.host()); + CHECK(instance.is_ipv6_address()); CHECK("/" == instance.pathname()); } @@ -569,8 +588,7 @@ TEST_CASE("url_tests", "[url]") { CHECK("?a=b&c=d" == instance.search()); } - SECTION("domain_to_ascii_be_strict_issue_36") - { + SECTION("domain_to_ascii_be_strict_issue_36") { auto instance = skyr::url("https://+:80/vroot/"); CHECK("https:" == instance.protocol()); CHECK("+:80" == instance.host()); @@ -578,4 +596,52 @@ TEST_CASE("url_tests", "[url]") { CHECK("80" == instance.port()); CHECK("/vroot/" == instance.pathname()); } + + SECTION("is_ipv4_accessor_like_short_ipv4_issue_51") { + auto instance = skyr::url("http://198.51.100/"); + CHECK(instance.is_ipv4_address()); + CHECK(!instance.is_ipv6_address()); + CHECK(!instance.is_domain()); + CHECK(!instance.is_opaque()); + } + + SECTION("is_ipv4_accessor_like_long_ipv4_issue_51") { + auto instance = skyr::url("http://198.51.100.0.255/"); + CHECK(!instance.is_ipv4_address()); + CHECK(!instance.is_ipv6_address()); + CHECK(instance.is_domain()); + CHECK(!instance.is_opaque()); + } + + SECTION("is_ipv4_accessor_like_short_ipv4_issue_51") { + auto instance = skyr::url("http://0x7f.0.0.0x7f/"); + CHECK(instance.is_ipv4_address()); + CHECK(!instance.is_ipv6_address()); + CHECK(!instance.is_domain()); + CHECK(!instance.is_opaque()); + } + + SECTION("is_domain_issue_51") { + auto instance = skyr::url("http://www.example.com/"); + CHECK(!instance.is_ipv4_address()); + CHECK(!instance.is_ipv6_address()); + CHECK(instance.is_domain()); + CHECK(!instance.is_opaque()); + } + + SECTION("is_opaque_issue_5") { + auto instance = skyr::url("git://example.com"); + CHECK(!instance.is_ipv4_address()); + CHECK(!instance.is_ipv6_address()); + CHECK(!instance.is_domain()); + CHECK(instance.is_opaque()); + } + + SECTION("is_opaque_ipv6_issue_5") { + auto instance = skyr::url("non-special://[1:2:0:0:5:0:0:0]/"); + CHECK(!instance.is_ipv4_address()); + CHECK(instance.is_ipv6_address()); + CHECK(!instance.is_domain()); + CHECK(instance.is_opaque()); + } }