TestIpPrefixFunctions.java

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.facebook.presto.operator.scalar;

import com.facebook.presto.common.type.ArrayType;
import com.google.common.collect.ImmutableList;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import static com.facebook.presto.common.type.BooleanType.BOOLEAN;
import static com.facebook.presto.type.IpAddressType.IPADDRESS;
import static com.facebook.presto.type.IpPrefixType.IPPREFIX;

public class TestIpPrefixFunctions
        extends AbstractTestFunctions
{
    @DataProvider(name = "public-ip-provider")
    public Object[] publicIpProvider()
    {
        return new Object[] {
                "6.7.8.9",
                "157.240.200.99",
                "8.8.8.8",
                "128.1.2.8",
                "2a03:2880:f031:12:face:b00c:0:2",
                "2600:1406:6c00::173c:ad43",
                "2607:f8b0:4007:818::2004"
        };
    }

    @DataProvider(name = "private-ip-provider")
    public Object[][] privateIpProvider()
    {
        return new Object[][] {
                // The first and last IP address in each private range
                {"0.0.0.0"}, {"0.255.255.255"},         // 0.0.0.0/8 RFC1122: "This host on this network"
                {"10.0.0.0"}, {"10.255.255.255"},       // 10.0.0.0/8 RFC1918: Private-Use
                {"100.64.0.0"}, {"100.127.255.255"},    // 100.64.0.0/10 RFC6598: Shared Address Space
                {"127.0.0.0"}, {"127.255.255.255"},     // 127.0.0.0/8 RFC1122: Loopback
                {"169.254.0.0"}, {"169.254.255.255"},   // 169.254.0.0/16 RFC3927: Link Local
                {"172.16.0.0"}, {"172.31.255.255"},     // 172.16.0.0/12 RFC1918: Private-Use
                {"192.0.0.0"}, {"192.0.0.255"},         // 192.0.0.0/24 RFC6890: IETF Protocol Assignments
                {"192.0.2.0"}, {"192.0.2.255"},         // 192.0.2.0/24 RFC5737: Documentation (TEST-NET-1)
                {"192.88.99.0"}, {"192.88.99.255"},     // 192.88.99.0/24 RFC3068: 6to4 Relay anycast
                {"192.168.0.0"}, {"192.168.255.255"},   // 192.168.0.0/16 RFC1918: Private-Use
                {"198.18.0.0"}, {"198.19.255.255"},     // 198.18.0.0/15 RFC2544: Benchmarking
                {"198.51.100.0"}, {"198.51.100.255"},   // 198.51.100.0/24 RFC5737: Documentation (TEST-NET-2)
                {"203.0.113.0"}, {"203.0.113.255"},     // 203.0.113.0/24 RFC5737: Documentation (TEST-NET-3)
                {"240.0.0.0"}, {"255.255.255.255"},     // 240.0.0.0/4 RFC1112: Reserved
                {"::"}, {"::"},                         // ::/128 RFC4291: Unspecified address
                {"::1"}, {"::1"},                       // ::1/128 RFC4291: Loopback address
                {"100::"}, {"100::ffff:ffff:ffff:ffff"},                        // 100::/64 RFC6666: Discard-Only Address Block
                {"64:ff9b:1::"}, {"64:ff9b:1:ffff:ffff:ffff:ffff:ffff"},        // 64:ff9b:1::/48 RFC8215: IPv4-IPv6 Translation
                {"2001:2::"}, {"2001:2:0:ffff:ffff:ffff:ffff:ffff"},            // 2001:2::/48 RFC5180,RFC Errata 1752: Benchmarking
                {"2001:db8::"}, {"2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"},     // 2001:db8::/32 RFC3849: Documentation
                {"2001::"}, {"2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff"},         // 2001::/23 RFC2928: IETF Protocol Assignments
                {"5f00::"}, {"5f00:ffff:ffff:ffff:ffff:ffff:ffff:ffff"},        // 5f00::/16 RFC-ietf-6man-sids-06: Segment Routing (SRv6)
                {"fe80::"}, {"febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff"},        // fe80::/10 RFC4291: Link-Local Unicast
                {"fc00::"}, {"fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"},        // fc00::/7 RFC4193, RFC8190: Unique Local
                // some IPs in the middle of ranges
                {"10.1.2.3"},
                {"100.64.3.2"},
                {"192.168.55.99"},
                {"2001:0DB8:0000:0000:face:b00c:0000:0000"},
                {"0100:0000:0000:0000:ffff:ffff:0000:0000"}
        };
    }

    @Test
    public void testIpAddressIpPrefix()
    {
        assertFunction("IP_PREFIX(IPADDRESS '1.2.3.4', 24)", IPPREFIX, "1.2.3.0/24");
        assertFunction("IP_PREFIX(IPADDRESS '1.2.3.4', 32)", IPPREFIX, "1.2.3.4/32");
        assertFunction("IP_PREFIX(IPADDRESS '1.2.3.4', 0)", IPPREFIX, "0.0.0.0/0");
        assertFunction("IP_PREFIX(IPADDRESS '::ffff:1.2.3.4', 24)", IPPREFIX, "1.2.3.0/24");
        assertFunction("IP_PREFIX(IPADDRESS '64:ff9b::17', 64)", IPPREFIX, "64:ff9b::/64");
        assertFunction("IP_PREFIX(IPADDRESS '64:ff9b::17', 127)", IPPREFIX, "64:ff9b::16/127");
        assertFunction("IP_PREFIX(IPADDRESS '64:ff9b::17', 128)", IPPREFIX, "64:ff9b::17/128");
        assertFunction("IP_PREFIX(IPADDRESS '64:ff9b::17', 0)", IPPREFIX, "::/0");
        assertInvalidFunction("IP_PREFIX(IPADDRESS '::ffff:1.2.3.4', -1)", "IPv4 subnet size must be in range [0, 32]");
        assertInvalidFunction("IP_PREFIX(IPADDRESS '::ffff:1.2.3.4', 33)", "IPv4 subnet size must be in range [0, 32]");
        assertInvalidFunction("IP_PREFIX(IPADDRESS '64:ff9b::10', -1)", "IPv6 subnet size must be in range [0, 128]");
        assertInvalidFunction("IP_PREFIX(IPADDRESS '64:ff9b::10', 129)", "IPv6 subnet size must be in range [0, 128]");
    }

    @Test
    public void testStringIpPrefix()
    {
        assertFunction("IP_PREFIX('1.2.3.4', 24)", IPPREFIX, "1.2.3.0/24");
        assertFunction("IP_PREFIX('1.2.3.4', 32)", IPPREFIX, "1.2.3.4/32");
        assertFunction("IP_PREFIX('1.2.3.4', 0)", IPPREFIX, "0.0.0.0/0");
        assertFunction("IP_PREFIX('::ffff:1.2.3.4', 24)", IPPREFIX, "1.2.3.0/24");
        assertFunction("IP_PREFIX('64:ff9b::17', 64)", IPPREFIX, "64:ff9b::/64");
        assertFunction("IP_PREFIX('64:ff9b::17', 127)", IPPREFIX, "64:ff9b::16/127");
        assertFunction("IP_PREFIX('64:ff9b::17', 128)", IPPREFIX, "64:ff9b::17/128");
        assertFunction("IP_PREFIX('64:ff9b::17', 0)", IPPREFIX, "::/0");
        assertInvalidFunction("IP_PREFIX('::ffff:1.2.3.4', -1)", "IPv4 subnet size must be in range [0, 32]");
        assertInvalidFunction("IP_PREFIX('::ffff:1.2.3.4', 33)", "IPv4 subnet size must be in range [0, 32]");
        assertInvalidFunction("IP_PREFIX('64:ff9b::10', -1)", "IPv6 subnet size must be in range [0, 128]");
        assertInvalidFunction("IP_PREFIX('64:ff9b::10', 129)", "IPv6 subnet size must be in range [0, 128]");
        assertInvalidCast("IP_PREFIX('localhost', 24)", "Cannot cast value to IPADDRESS: localhost");
        assertInvalidCast("IP_PREFIX('64::ff9b::10', 24)", "Cannot cast value to IPADDRESS: 64::ff9b::10");
        assertInvalidCast("IP_PREFIX('64:face:book::10', 24)", "Cannot cast value to IPADDRESS: 64:face:book::10");
        assertInvalidCast("IP_PREFIX('123.456.789.012', 24)", "Cannot cast value to IPADDRESS: 123.456.789.012");
    }

    @Test
    public void testIpSubnetMin()
    {
        assertFunction("IP_SUBNET_MIN(IPPREFIX '1.2.3.4/24')", IPADDRESS, "1.2.3.0");
        assertFunction("IP_SUBNET_MIN(IPPREFIX '1.2.3.4/32')", IPADDRESS, "1.2.3.4");
        assertFunction("IP_SUBNET_MIN(IPPREFIX '64:ff9b::17/64')", IPADDRESS, "64:ff9b::");
        assertFunction("IP_SUBNET_MIN(IPPREFIX '64:ff9b::17/127')", IPADDRESS, "64:ff9b::16");
        assertFunction("IP_SUBNET_MIN(IPPREFIX '64:ff9b::17/128')", IPADDRESS, "64:ff9b::17");
        assertFunction("IP_SUBNET_MIN(IPPREFIX '64:ff9b::17/0')", IPADDRESS, "::");
    }

    @Test
    public void testIpSubnetMax()
    {
        assertFunction("IP_SUBNET_MAX(IPPREFIX '1.2.3.128/26')", IPADDRESS, "1.2.3.191");
        assertFunction("IP_SUBNET_MAX(IPPREFIX '192.168.128.4/32')", IPADDRESS, "192.168.128.4");
        assertFunction("IP_SUBNET_MAX(IPPREFIX '10.1.16.3/9')", IPADDRESS, "10.127.255.255");
        assertFunction("IP_SUBNET_MAX(IPPREFIX '2001:db8::16/127')", IPADDRESS, "2001:db8::17");
        assertFunction("IP_SUBNET_MAX(IPPREFIX '2001:db8::16/128')", IPADDRESS, "2001:db8::16");
        assertFunction("IP_SUBNET_MAX(IPPREFIX '64:ff9b::17/64')", IPADDRESS, "64:ff9b::ffff:ffff:ffff:ffff");
        assertFunction("IP_SUBNET_MAX(IPPREFIX '64:ff9b::17/72')", IPADDRESS, "64:ff9b::ff:ffff:ffff:ffff");
        assertFunction("IP_SUBNET_MAX(IPPREFIX '64:ff9b::17/0')", IPADDRESS, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
    }

    @Test
    public void testIpSubnetRange()
    {
        assertFunction("IP_SUBNET_RANGE(IPPREFIX '1.2.3.160/24')", new ArrayType(IPADDRESS), ImmutableList.of("1.2.3.0", "1.2.3.255"));
        assertFunction("IP_SUBNET_RANGE(IPPREFIX '1.2.3.128/31')", new ArrayType(IPADDRESS), ImmutableList.of("1.2.3.128", "1.2.3.129"));
        assertFunction("IP_SUBNET_RANGE(IPPREFIX '10.1.6.46/32')", new ArrayType(IPADDRESS), ImmutableList.of("10.1.6.46", "10.1.6.46"));
        assertFunction("IP_SUBNET_RANGE(IPPREFIX '10.1.6.46/0')", new ArrayType(IPADDRESS), ImmutableList.of("0.0.0.0", "255.255.255.255"));
        assertFunction("IP_SUBNET_RANGE(IPPREFIX '64:ff9b::17/64')", new ArrayType(IPADDRESS), ImmutableList.of("64:ff9b::", "64:ff9b::ffff:ffff:ffff:ffff"));
        assertFunction("IP_SUBNET_RANGE(IPPREFIX '64:ff9b::52f4/120')", new ArrayType(IPADDRESS), ImmutableList.of("64:ff9b::5200", "64:ff9b::52ff"));
        assertFunction("IP_SUBNET_RANGE(IPPREFIX '64:ff9b::17/128')", new ArrayType(IPADDRESS), ImmutableList.of("64:ff9b::17", "64:ff9b::17"));
    }

    @Test
    public void testIsSubnetOf()
    {
        assertFunction("IS_SUBNET_OF(IPPREFIX '1.2.3.128/26', IPADDRESS '1.2.3.129')", BOOLEAN, true);
        assertFunction("IS_SUBNET_OF(IPPREFIX '1.2.3.128/26', IPADDRESS '1.2.5.1')", BOOLEAN, false);
        assertFunction("IS_SUBNET_OF(IPPREFIX '1.2.3.128/32', IPADDRESS '1.2.3.128')", BOOLEAN, true);
        assertFunction("IS_SUBNET_OF(IPPREFIX '1.2.3.128/0', IPADDRESS '192.168.5.1')", BOOLEAN, true);
        assertFunction("IS_SUBNET_OF(IPPREFIX '64:ff9b::17/64', IPADDRESS '64:ff9b::ffff:ff')", BOOLEAN, true);
        assertFunction("IS_SUBNET_OF(IPPREFIX '64:ff9b::17/64', IPADDRESS '64:ffff::17')", BOOLEAN, false);

        assertFunction("IS_SUBNET_OF(IPPREFIX '192.168.3.131/26', IPPREFIX '192.168.3.144/30')", BOOLEAN, true);
        assertFunction("IS_SUBNET_OF(IPPREFIX '1.2.3.128/26', IPPREFIX '1.2.5.1/30')", BOOLEAN, false);
        assertFunction("IS_SUBNET_OF(IPPREFIX '1.2.3.128/26', IPPREFIX '1.2.3.128/26')", BOOLEAN, true);
        assertFunction("IS_SUBNET_OF(IPPREFIX '64:ff9b::17/64', IPPREFIX '64:ff9b::ff:25/80')", BOOLEAN, true);
        assertFunction("IS_SUBNET_OF(IPPREFIX '64:ff9b::17/64', IPPREFIX '64:ffff::17/64')", BOOLEAN, false);
        assertFunction("IS_SUBNET_OF(IPPREFIX '2804:431:b000::/37', IPPREFIX '2804:431:b000::/38')", BOOLEAN, true);
        assertFunction("IS_SUBNET_OF(IPPREFIX '2804:431:b000::/38', IPPREFIX '2804:431:b000::/37')", BOOLEAN, false);
        assertFunction("IS_SUBNET_OF(IPPREFIX '170.0.52.0/22', IPPREFIX '170.0.52.0/24')", BOOLEAN, true);
        assertFunction("IS_SUBNET_OF(IPPREFIX '170.0.52.0/24', IPPREFIX '170.0.52.0/22')", BOOLEAN, false);
    }

    @Test
    public void testIpv4PrefixCollapse()
    {
        // simple
        assertFunction("IP_PREFIX_COLLAPSE(ARRAY[IPPREFIX '192.168.0.0/24', IPPREFIX '192.168.1.0/24'])",
                new ArrayType(IPPREFIX),
                ImmutableList.of("192.168.0.0/23"));

        // unsorted input, 1 adjacent prefix that cannot be aggregated, and one disjoint.
        assertFunction("IP_PREFIX_COLLAPSE(ARRAY[IPPREFIX '192.168.1.0/24', IPPREFIX '192.168.0.0/24', IPPREFIX '192.168.2.0/24', IPPREFIX '192.168.9.0/24'])",
                new ArrayType(IPPREFIX),
                ImmutableList.of("192.168.0.0/23", "192.168.2.0/24", "192.168.9.0/24"));
    }

    @Test
    public void testIpv6PrefixCollapse()
    {
        // simple
        assertFunction("IP_PREFIX_COLLAPSE(ARRAY[IPPREFIX '2620:10d:c090::/48', IPPREFIX '2620:10d:c091::/48'])",
                new ArrayType(IPPREFIX),
                ImmutableList.of("2620:10d:c090::/47"));

        // unsorted input, 1 adjacent prefix that cannot be aggregated, and one disjoint.
        assertFunction("IP_PREFIX_COLLAPSE(ARRAY[IPPREFIX '2804:13c:4d6:e200::/56', IPPREFIX '2804:13c:4d6:dd00::/56', IPPREFIX '2804:13c:4d6:dc00::/56', IPPREFIX '2804:13c:4d6:de00::/56'])",
                new ArrayType(IPPREFIX),
                ImmutableList.of("2804:13c:4d6:dc00::/55", "2804:13c:4d6:de00::/56", "2804:13c:4d6:e200::/56"));
    }

    @Test
    public void testIpPrefixCollapseIpv4SingleIPs()
    {
        assertFunction("IP_PREFIX_COLLAPSE(ARRAY[IPPREFIX '192.168.0.1/32', IPPREFIX '192.168.33.1/32'])",
                new ArrayType(IPPREFIX),
                ImmutableList.of("192.168.0.1/32", "192.168.33.1/32"));
    }

    @Test
    public void testIpPrefixCollapseIpv6SingleIPs()
    {
        assertFunction("IP_PREFIX_COLLAPSE(ARRAY[IPPREFIX '2620:10d:c090:400::5:a869/128', IPPREFIX '2620:10d:c091:400::5:a869/128'])",
                new ArrayType(IPPREFIX),
                ImmutableList.of("2620:10d:c090:400::5:a869/128", "2620:10d:c091:400::5:a869/128"));
    }

    @Test
    public void testIpPrefixCollapseSinglePrefixReturnsSamePrefix()
    {
        assertFunction("IP_PREFIX_COLLAPSE(ARRAY[IPPREFIX '192.168.0.0/22'])",
                new ArrayType(IPPREFIX),
                ImmutableList.of("192.168.0.0/22"));
    }

    @Test
    public void testIpPrefixCollapseOverlappingPrefixes()
    {
        assertFunction("IP_PREFIX_COLLAPSE(ARRAY[IPPREFIX '192.168.0.0/22', IPPREFIX '192.168.0.0/24'])",
                new ArrayType(IPPREFIX),
                ImmutableList.of("192.168.0.0/22"));
        assertFunction("IP_PREFIX_COLLAPSE(ARRAY[IPPREFIX '192.168.0.0/22', IPPREFIX '192.168.2.0/24'])",
                new ArrayType(IPPREFIX),
                ImmutableList.of("192.168.0.0/22"));
        assertFunction("IP_PREFIX_COLLAPSE(ARRAY[IPPREFIX '192.168.0.0/22', IPPREFIX '192.168.3.0/24'])",
                new ArrayType(IPPREFIX),
                ImmutableList.of("192.168.0.0/22"));
        assertFunction("IP_PREFIX_COLLAPSE(ARRAY[IPPREFIX '10.0.64.0/18', IPPREFIX '10.2.0.0/15', IPPREFIX '10.0.0.0/8', IPPREFIX '11.0.0.0/8', IPPREFIX '172.168.32.0/20', IPPREFIX '172.168.0.0/18'])",
                new ArrayType(IPPREFIX),
                ImmutableList.of("10.0.0.0/7", "172.168.0.0/18"));
        assertFunction("IP_PREFIX_COLLAPSE(ARRAY[IPPREFIX '10.0.0.0/8', IPPREFIX '10.0.0.0/7'])",
                new ArrayType(IPPREFIX),
                ImmutableList.of("10.0.0.0/7"));
    }

    @Test
    public void testIpPrefixCollapseEmptyArrayInput()
    {
        assertFunction("IP_PREFIX_COLLAPSE(CAST(ARRAY[] AS ARRAY(IPPREFIX)))", new ArrayType(IPPREFIX), ImmutableList.of());
    }

    @Test
    public void testIpPrefixCollapseNullInput()
    {
        assertFunction("IP_PREFIX_COLLAPSE(CAST(NULL AS ARRAY(IPPREFIX)))", new ArrayType(IPPREFIX), null);
    }

    @Test
    public void testIpPrefixCollapseNoNullPrefixesError()
    {
        assertInvalidFunction("IP_PREFIX_COLLAPSE(ARRAY[IPPREFIX '192.168.0.0/22', CAST(NULL AS IPPREFIX)])",
                "ip_prefix_collapse does not support null elements");
    }

    @Test
    public void testIpPrefixCollapseMixedIpVersionError()
    {
        assertInvalidFunction("IP_PREFIX_COLLAPSE(ARRAY[IPPREFIX '192.168.0.0/22', IPPREFIX '2409:4043:251a:d200::/56'])",
                "All IPPREFIX elements must be the same IP version.");
    }

    @Test (dataProvider = "private-ip-provider")
    public void testIsPrivateTrue(String ipAddress)
    {
        assertFunction("IS_PRIVATE_IP(IPADDRESS '" + ipAddress + "')", BOOLEAN, true);
    }

    @Test (dataProvider = "public-ip-provider")
    public void testIsPrivateIpFalse(String ipAddress)
    {
        assertFunction("IS_PRIVATE_IP(IPADDRESS '" + ipAddress + "')", BOOLEAN, false);
    }

    @Test
    public void testIsPrivateIpNull()
    {
        assertFunction("IS_PRIVATE_IP(NULL)", BOOLEAN, null);
    }

    @Test
    public void testIpPrefixSubnets()
    {
        assertFunction("IP_PREFIX_SUBNETS(IPPREFIX '192.168.1.0/24', 25)", new ArrayType(IPPREFIX), ImmutableList.of("192.168.1.0/25", "192.168.1.128/25"));
        assertFunction("IP_PREFIX_SUBNETS(IPPREFIX '192.168.0.0/24', 26)", new ArrayType(IPPREFIX), ImmutableList.of("192.168.0.0/26", "192.168.0.64/26", "192.168.0.128/26", "192.168.0.192/26"));
        assertFunction("IP_PREFIX_SUBNETS(IPPREFIX '2A03:2880:C000::/34', 37)",
                new ArrayType(IPPREFIX),
                ImmutableList.of("2a03:2880:c000::/37", "2a03:2880:c800::/37", "2a03:2880:d000::/37", "2a03:2880:d800::/37", "2a03:2880:e000::/37", "2a03:2880:e800::/37", "2a03:2880:f000::/37", "2a03:2880:f800::/37"));
    }

    @Test
    public void testIpPrefixSubnetsReturnSelf()
    {
        assertFunction("IP_PREFIX_SUBNETS(IPPREFIX '192.168.1.0/24', 24)", new ArrayType(IPPREFIX), ImmutableList.of("192.168.1.0/24"));
        assertFunction("IP_PREFIX_SUBNETS(IPPREFIX '2804:431:b000::/38', 38)", new ArrayType(IPPREFIX), ImmutableList.of("2804:431:b000::/38"));
    }

    @Test
    public void testIpPrefixSubnetsNewPrefixLengthLongerReturnsEmpty()
    {
        assertFunction("IP_PREFIX_SUBNETS(IPPREFIX '192.168.0.0/24', 23)", new ArrayType(IPPREFIX), ImmutableList.of());
        assertFunction("IP_PREFIX_SUBNETS(IPPREFIX '64:ff9b::17/64', 48)", new ArrayType(IPPREFIX), ImmutableList.of());
    }

    @Test
    public void testIpPrefixSubnetsInvalidPrefixLengths()
    {
        assertInvalidFunction("IP_PREFIX_SUBNETS(IPPREFIX '192.168.0.0/24', -1)", "Invalid prefix length for IPv4: -1");
        assertInvalidFunction("IP_PREFIX_SUBNETS(IPPREFIX '192.168.0.0/24', 33)", "Invalid prefix length for IPv4: 33");
        assertInvalidFunction("IP_PREFIX_SUBNETS(IPPREFIX '64:ff9b::17/64', -1)", "Invalid prefix length for IPv6: -1");
        assertInvalidFunction("IP_PREFIX_SUBNETS(IPPREFIX '64:ff9b::17/64', 129)", "Invalid prefix length for IPv6: 129");
    }
}