/*	$OpenBSD: rfc3779.c,v 1.12 2026/03/13 06:47:34 tb Exp $ */
/*
 * Copyright (c) 2021 Theo Buehler <tb@openbsd.org>
 *
 * Permission to use, copy, modify, and 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 <stdio.h>
#include <string.h>

#include <openssl/asn1.h>
#include <openssl/asn1t.h>
#include <openssl/x509v3.h>

#define RAW_ADDRESS_SIZE	16

static void
hexdump(const unsigned char *buf, size_t len)
{
	size_t i;

	for (i = 1; i <= len; i++)
		fprintf(stderr, " 0x%02hhx,%s", buf[i - 1], i % 8 ? "" : "\n");

	if (len % 8)
		fprintf(stderr, "\n");
}

static void
report_hexdump(const char *func, const char *description, const char *msg,
    const unsigned char *want, size_t want_len,
    const unsigned char *got, size_t got_len)
{
	fprintf(stderr, "%s: \"%s\" %s\nwant:\n", func, description, msg);
	hexdump(want, want_len);
	fprintf(stderr, "got:\n");
	hexdump(got, got_len);
}

static int
afi_size(int afi)
{
	switch (afi) {
	case IANA_AFI_IPV4:
		return 4;
	case IANA_AFI_IPV6:
		return 16;
	}
	return 0;
}

struct IPAddressOrRange_test {
	const char	*description;
	const uint8_t	 der[32];
	size_t		 der_len;
	unsigned	 afi;
	const uint8_t	 min[RAW_ADDRESS_SIZE];
	const uint8_t	 max[RAW_ADDRESS_SIZE];
};

const struct IPAddressOrRange_test IPAddressOrRange_test_data[] = {
	/* Examples from RFC 3779, section 2.1.1 */
	{
		.description = "address 10.5.0.4",
		.der = {
			0x03, 0x05, 0x00, 0x0a, 0x05, 0x00, 0x04,
		},
		.der_len = 7,
		.afi = IANA_AFI_IPV4,
		.min = {
			0x0a, 0x05, 0x00, 0x04,
		},
		.max = {
			0x0a, 0x05, 0x00, 0x04,
		}
	},
	{
		.description = "prefix 10.5.0/23",
		.der = {
			0x03, 0x04, 0x01, 0x0a, 0x05, 0x00,
		},
		.der_len = 6,
		.afi = IANA_AFI_IPV4,
		.min = {
			0x0a, 0x05, 0x00, 0x00,
		},
		.max = {
			0x0a, 0x05, 0x01, 0xff,
		}
	},
	{
		.description = "address 2001:0:200:3::1",
		.der = {
			0x03, 0x11, 0x00, 0x20, 0x01, 0x00, 0x00, 0x02,
			0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x01,
		},
		.der_len = 19,
		.afi = IANA_AFI_IPV6,
		.min = {
			0x20, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x03,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
		},
		.max = {
			0x20, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x03,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
		},
	},
	{
		.description = "prefix 2001:0:200/39",
		.der = {
			0x03, 0x06, 0x01, 0x20, 0x01, 0x00, 0x00, 0x02,
		},
		.der_len = 8,
		.afi = IANA_AFI_IPV6,
		.min = {
			0x20, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		},
		.max = {
			0x20, 0x01, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff,
			0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		},
	},

	/* Examples from RFC 3779, Section 2.1.2 */
	{
		.description = "prefix 10.5.0/23 as a range",
		.der = {
			/* Sequence */
			0x30, 0x0b,
			/* 10.5.0.0 */
			0x03, 0x03, 0x00, 0x0a, 0x05,
			/* 10.5.1.255 */
			0x03, 0x04, 0x01, 0x0a, 0x05, 0x00,
		},
		.der_len = 13,
		.afi = IANA_AFI_IPV4,
		.min = {
			0x0a, 0x05, 0x00, 0x00,
		},
		.max = {
			0x0a, 0x05, 0x01, 0xff,
		}
	},
	{
		.description = "prefix 2001:0:200/39 as a range",
		.der = {
			/* Sequence */
			0x30, 0x10,
			/* 2001:0:200:: */
			0x03, 0x06, 0x01, 0x20, 0x01, 0x00, 0x00, 0x02,
			/* 2001:0:3ff:ffff:ffff:ffff:ffff:ffff */
			0x03, 0x06, 0x02, 0x20, 0x01, 0x00, 0x00, 0x00,
		},
		.der_len = 18,
		.afi = IANA_AFI_IPV6,
		.min = {
			0x20, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		},
		.max = {
			0x20, 0x01, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff,
			0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		}
	},
	{
		.description = "prefix 0/0",
		.der = {
			0x03, 0x01, 0x00,
		},
		.der_len = 3,
		.afi = IANA_AFI_IPV4,
		.min = {
			0x00, 0x00, 0x00, 0x00,
		},
		.max = {
			0xff, 0xff, 0xff, 0xff,
		}
	},
	{
		.description = "prefix 10.64/12",
		.der = {
			0x03, 0x03, 0x04, 0x0a, 0x40,
		},
		.der_len = 5,
		.afi = IANA_AFI_IPV4,
		.min = {
			0x0a, 0x40, 0x00, 0x00,
		},
		.max = {
			0x0a, 0x4f, 0xff, 0xff,
		},
	},
	{
		.description = "prefix 10.64/20",
		.der = {
			0x03, 0x04, 0x04, 0x0a, 0x40, 0x00,
		},
		.der_len = 6,
		.afi = IANA_AFI_IPV4,
		.min = {
			0x0a, 0x40, 0x00, 0x00,
		},
		.max = {
			0x0a, 0x40, 0x0f, 0xff,
		},
	},

	/* Part of AfriNIC, first actual range in afrinic.constraints. */
	{
		.description = "range 196.1.7.0 - 196.1.63.255",
		.der = {
			0x30, 0x0c,
			/* 192.1.7.0 */
			0x03, 0x04, 0x00, 0xc4, 0x01, 0x07,
			/* 192.1.63.255 */
			0x03, 0x04, 0x06, 0xc4, 0x01, 0x00,
		},
		.der_len = 14,
		    .afi = IANA_AFI_IPV4,
		.min = {
			0xc4, 0x01, 0x07, 0x00,
		},
		.max = {
			0xc4, 0x01, 0x3f, 0xff,
		},
	},
};

const size_t N_IPADDRESSORRANGE_TESTS =
    sizeof(IPAddressOrRange_test_data) / sizeof(IPAddressOrRange_test_data[0]);

static int
test_IPAddressOrRange(const struct IPAddressOrRange_test *test)
{
	IPAddressOrRange *aor;
	const unsigned char *p;
	unsigned char min[RAW_ADDRESS_SIZE] = {0}, max[RAW_ADDRESS_SIZE] = {0};
	unsigned char *out = NULL;
	int out_len;
	int afi_len;
	int memcmp_failed = 0;
	int failed = 1;

	/*
	 * First, decode DER from the test case.
	 */

	p = &test->der[0];
	if ((aor = d2i_IPAddressOrRange(NULL, &p, test->der_len)) == NULL) {
		fprintf(stderr, "%s: \"%s\" d2i_IPAddressOrRange failed\n",
		    __func__, test->description);
		goto err;
	}

	/*
	 * Now extract minimum and maximum from the parsed range.
	 */

	afi_len = afi_size(test->afi);

	if (X509v3_addr_get_range(aor, test->afi, min, max, sizeof min) !=
	    afi_len) {
		fprintf(stderr, "%s: \"%s\" X509v3_addr_get_range failed\n",
		    __func__, test->description);
		goto err;
	}

	/*
	 * Check that min and max match expectations.
	 */

	if (memcmp(min, test->min, afi_len) != 0) {
		memcmp_failed |= 1;
		report_hexdump(__func__, test->description, "memcmp min failed",
		    test->min, afi_len, min, afi_len);
	}
	if (memcmp(max, test->max, afi_len) != 0) {
		memcmp_failed |= 1;
		report_hexdump(__func__, test->description, "memcmp max failed",
		    test->max, afi_len, max, afi_len);
	}
	if (memcmp_failed)
		goto err;

	/*
	 * Now turn the parsed IPAddressOrRange back into DER and check that
	 * it matches the DER in the test case.
	 */

	out = NULL;
	if ((out_len = i2d_IPAddressOrRange(aor, &out)) <= 0) {
		fprintf(stderr, "%s: \"%s\" i2d_IPAddressOrRange failed\n",
		    __func__, test->description);
		goto err;
	}

	memcmp_failed = (size_t)out_len != test->der_len;
	if (!memcmp_failed)
		memcmp_failed = memcmp(test->der, out, out_len);

	if (memcmp_failed) {
		report_hexdump(__func__, test->description, "memcmp DER failed",
		    test->der, test->der_len, out, out_len);
		goto err;
	}

	failed = 0;
 err:
	IPAddressOrRange_free(aor);
	free(out);

	return failed;
}

static int
run_IPAddressOrRange_tests(void)
{
	size_t i;
	int failed = 0;

	for (i = 0; i < N_IPADDRESSORRANGE_TESTS; i++)
		failed |=
		    test_IPAddressOrRange(&IPAddressOrRange_test_data[i]);

	return failed;
}

/*
 * XXX: These should really be part of the public API...
 */
static IPAddrBlocks *IPAddrBlocks_new(void);
static void IPAddrBlocks_free(IPAddrBlocks *addr);
static IPAddrBlocks *d2i_IPAddrBlocks(IPAddrBlocks **addrs,
    const unsigned char **in, long len);
static int i2d_IPAddrBlocks(IPAddrBlocks *addrs, unsigned char **out);

static IPAddrBlocks *
IPAddrBlocks_new(void)
{
	IPAddrBlocks *addrs;

	/*
	 * XXX The comparison function IPAddressFamily_cmp() isn't public.
	 * Start with the default and exploit a side effect of the lovely API
	 * which helpfully sets the correct function in a few places. Let's
	 * use the cheapest and easiest to reach one.
	 */
	if ((addrs = sk_IPAddressFamily_new_null()) == NULL)
		return NULL;
	if (!X509v3_addr_canonize(addrs)) {
		IPAddrBlocks_free(addrs);
		return NULL;
	}

	return addrs;
}

static void
IPAddrBlocks_free(IPAddrBlocks *addr)
{
	sk_IPAddressFamily_pop_free(addr, IPAddressFamily_free);
}

/*
 * We want {d2i,i2d}_IPAddrBlocks() to play with the DER of the extension.
 * These don't exist, so we have to implement them ourselves.  IPAddrBlocks_it
 * isn't public, so we need to fetch it from the library.  We cache it in a
 * static variable to avoid the cost of a binary search through all supported
 * extensions on each call.
 */

static ASN1_ITEM_EXP *
get_IPAddrBlocks_it(void)
{
	static ASN1_ITEM_EXP *my_IPAddrBlocks_it;
	const X509V3_EXT_METHOD *v3_addr;

	if (my_IPAddrBlocks_it != NULL)
		return my_IPAddrBlocks_it;

	if ((v3_addr = X509V3_EXT_get_nid(NID_sbgp_ipAddrBlock)) == NULL) {
		fprintf(stderr, "could not get v3_addr\n");
		return NULL;
	}

	my_IPAddrBlocks_it = v3_addr->it;

	return my_IPAddrBlocks_it;
}

static IPAddrBlocks *
d2i_IPAddrBlocks(IPAddrBlocks **addrs, const unsigned char **in, long len)
{
	ASN1_ITEM_EXP *my_IPAddrBlocks_it;

	if ((my_IPAddrBlocks_it = get_IPAddrBlocks_it()) == NULL)
		return NULL;

	return (IPAddrBlocks *)ASN1_item_d2i((ASN1_VALUE **)addrs, in, len,
	    my_IPAddrBlocks_it);
}

static int
i2d_IPAddrBlocks(IPAddrBlocks *addrs, unsigned char **out)
{
	ASN1_ITEM_EXP *my_IPAddrBlocks_it;

	if ((my_IPAddrBlocks_it = get_IPAddrBlocks_it()) == NULL)
		return -1;

	return ASN1_item_i2d((ASN1_VALUE *)addrs, out, my_IPAddrBlocks_it);
}

struct ipv4_prefix {
	unsigned char			addr[4];
	size_t				addr_len;
	size_t				prefix_len;
};

struct ipv4_range {
	unsigned char			min[4];
	unsigned char			max[4];
};

union ipv4_choice {
	struct ipv4_prefix	prefix;
	struct ipv4_range	range;
};

struct ipv6_prefix {
	unsigned char		addr[16];
	size_t			addr_len;
	size_t			prefix_len;
};

struct ipv6_range {
	unsigned char		min[16];
	unsigned char		max[16];
};

union ipv6_choice {
	struct ipv6_prefix	prefix;
	struct ipv6_range	range;
};

enum choice_type {
	choice_prefix,
	choice_range,
	choice_inherit,
	choice_last,
};

union ip {
	union ipv4_choice	ipv4;
	union ipv6_choice	ipv6;
};

enum safi {
	safi_none,
	safi_unicast,
	safi_multicast,
};

struct ip_addr_block {
	unsigned int		afi;
	enum safi		safi;
	enum choice_type	type;
	union ip		addr;
};

struct build_addr_block_test_data {
	char			*description;
	struct ip_addr_block	 addrs[16];
	char			 der[128];
	size_t			 der_len;
	int			 is_canonical;
	int			 inherits;
	unsigned int		 afis[4];
	int			 afi_len;
};

const struct build_addr_block_test_data build_addr_block_tests[] = {
	{
		.description = "RFC 3779, Appendix B, example 1",
		.addrs = {
			{
				.afi = IANA_AFI_IPV4,
				.safi = safi_unicast,
				.type = choice_prefix,
				.addr.ipv4.prefix = {
					.addr = {
						10, 0, 32,
					},
					.addr_len = 3,
					.prefix_len = 20,
				},
			},
			{
				.afi = IANA_AFI_IPV4,
				.safi = safi_unicast,
				.type = choice_prefix,
				.addr.ipv4.prefix = {
					.addr = {
						10, 0, 64,
					},
					.addr_len = 3,
					.prefix_len = 24,
				},
			},
			{
				.afi = IANA_AFI_IPV4,
				.safi = safi_unicast,
				.type = choice_prefix,
				.addr.ipv4.prefix = {
					.addr = {
						10, 1,
					},
					.addr_len = 2,
					.prefix_len = 16,
				},
			},
			{
				.afi = IANA_AFI_IPV4,
				.safi = safi_unicast,
				.type = choice_prefix,
				.addr.ipv4.prefix = {
					.addr = {
						10, 2, 48,
					},
					.addr_len = 3,
					.prefix_len = 20,
				},
			},
			{
				.afi = IANA_AFI_IPV4,
				.safi = safi_unicast,
				.type = choice_prefix,
				.addr.ipv4.prefix = {
					.addr = {
						10, 2, 64,
					},
					.addr_len = 3,
					.prefix_len = 24,
				},
			},
			{
				.afi = IANA_AFI_IPV4,
				.safi = safi_unicast,
				.type = choice_prefix,
				.addr.ipv4.prefix = {
					.addr = {
						10, 3,
					},
					.addr_len = 2,
					.prefix_len = 16,
				},
			},
			{
				.afi = IANA_AFI_IPV6,
				.safi = safi_none,
				.type = choice_inherit,
			},
			{
				.type = choice_last,
			},
		},
		.der = {
			0x30, 0x35, 0x30, 0x2b, 0x04, 0x03, 0x00, 0x01,
			0x01, 0x30, 0x24, 0x03, 0x04, 0x04, 0x0a, 0x00,
			0x20, 0x03, 0x04, 0x00, 0x0a, 0x00, 0x40, 0x03,
			0x03, 0x00, 0x0a, 0x01, 0x30, 0x0c, 0x03, 0x04,
			0x04, 0x0a, 0x02, 0x30, 0x03, 0x04, 0x00, 0x0a,
			0x02, 0x40, 0x03, 0x03, 0x00, 0x0a, 0x03, 0x30,
			0x06, 0x04, 0x02, 0x00, 0x02, 0x05, 0x00,
		},
		.der_len = 55,
		.is_canonical = 0,
		.inherits = 1,
		.afis = {
			IANA_AFI_IPV4, IANA_AFI_IPV6,
		},
		.afi_len = 2,
	},
	{
		.description = "RFC 3779, Appendix B, example 1 canonical",
		.addrs = {
			{
				.afi = IANA_AFI_IPV4,
				.safi = safi_unicast,
				.type = choice_prefix,
				.addr.ipv4.prefix = {
					.addr = {
						10, 0, 32,
					},
					.addr_len = 3,
					.prefix_len = 20,
				},
			},
			{
				.afi = IANA_AFI_IPV4,
				.safi = safi_unicast,
				.type = choice_prefix,
				.addr.ipv4.prefix = {
					.addr = {
						10, 0, 64,
					},
					.addr_len = 3,
					.prefix_len = 24,
				},
			},
			{
				.afi = IANA_AFI_IPV4,
				.safi = safi_unicast,
				.type = choice_prefix,
				.addr.ipv4.prefix = {
					.addr = {
						10, 1,
					},
					.addr_len = 2,
					.prefix_len = 16,
				},
			},
			{
				.afi = IANA_AFI_IPV4,
				.safi = safi_unicast,
				.type = choice_range,
				.addr.ipv4.range = {
					.min = {
						10, 2, 48, 00,
					},
					.max = {
						10, 2, 64, 255,
					},
				},
			},
			{
				.afi = IANA_AFI_IPV4,
				.safi = safi_unicast,
				.type = choice_prefix,
				.addr.ipv4.prefix = {
					.addr = {
						10, 3,
					},
					.addr_len = 2,
					.prefix_len = 16,
				},
			},
			{
				.afi = IANA_AFI_IPV6,
				.safi = safi_none,
				.type = choice_inherit,
			},
			{
				.type = choice_last,
			},
		},
		.der = {
			0x30, 0x35, 0x30, 0x2b, 0x04, 0x03, 0x00, 0x01,
			0x01, 0x30, 0x24, 0x03, 0x04, 0x04, 0x0a, 0x00,
			0x20, 0x03, 0x04, 0x00, 0x0a, 0x00, 0x40, 0x03,
			0x03, 0x00, 0x0a, 0x01, 0x30, 0x0c, 0x03, 0x04,
			0x04, 0x0a, 0x02, 0x30, 0x03, 0x04, 0x00, 0x0a,
			0x02, 0x40, 0x03, 0x03, 0x00, 0x0a, 0x03, 0x30,
			0x06, 0x04, 0x02, 0x00, 0x02, 0x05, 0x00,
		},
		.der_len = 55,
		.is_canonical = 1,
		.inherits = 1,
		.afis = {
			IANA_AFI_IPV4, IANA_AFI_IPV6,
		},
		.afi_len = 2,
	},
	{
		.description = "RFC 3779, Appendix B, example 2",
		.addrs = {
			{
				.afi = IANA_AFI_IPV6,
				.safi = safi_none,
				.type = choice_prefix,
				.addr.ipv6.prefix = {
					.addr = {
						0x20, 0x01, 0x00, 0x00,
						0x00, 0x02,
					},
					.addr_len = 6,
					.prefix_len = 48,
				},
			},
			{
				.afi = IANA_AFI_IPV4,
				.safi = safi_unicast,
				.type = choice_prefix,
				.addr.ipv4.prefix = {
					.addr = {
						10,
					},
					.addr_len = 1,
					.prefix_len = 8,
				},
			},
			{
				.afi = IANA_AFI_IPV4,
				.safi = safi_unicast,
				.type = choice_prefix,
				.addr.ipv4.prefix = {
					.addr = {
						172, 16,
					},
					.addr_len = 2,
					.prefix_len = 12,
				},
			},
			{
				.afi = IANA_AFI_IPV4,
				.safi = safi_multicast,
				.type = choice_inherit,
			},
			{
				.type = choice_last,
			},
		},
		.der = {
			0x30, 0x2c, 0x30, 0x10, 0x04, 0x03, 0x00, 0x01,
			0x01, 0x30, 0x09, 0x03, 0x02, 0x00, 0x0a, 0x03,
			0x03, 0x04, 0xac, 0x10, 0x30, 0x07, 0x04, 0x03,
			0x00, 0x01, 0x02, 0x05, 0x00, 0x30, 0x0f, 0x04,
			0x02, 0x00, 0x02, 0x30, 0x09, 0x03, 0x07, 0x00,
			0x20, 0x01, 0x00, 0x00, 0x00, 0x02,
		},
		.der_len = 46,
		.is_canonical = 0,
		.inherits = 1,
		.afis = {
			IANA_AFI_IPV4, IANA_AFI_IPV4,
		},
		.afi_len = 2,
	},
	{
		.description = "Range should be prefix 127/8",
		.addrs = {
			{
				.afi = IANA_AFI_IPV4,
				.safi = safi_none,
				.type = choice_range,
				.addr.ipv4.range = {
					.min = {
						127, 0, 0, 0,
					},
					.max = {
						127, 255, 255, 255,
					},
				},
			},
			{
				.type = choice_last,
			},
		},
		.der = {
			0x30, 0x0c, 0x30, 0x0a, 0x04, 0x02, 0x00, 0x01,
			0x30, 0x04, 0x03, 0x02, 0x00, 0x7f,
		},
		.der_len = 14,
		.is_canonical = 1,
		.inherits = 0,
		.afis = {
			IANA_AFI_IPV4,
		},
		.afi_len = 1,
	},

	/* Part of AfriNIC, first actual range in afrinic.constraints. */
	{
		.description = "range 196.1.7.0 - 196.1.63.255",
		.addrs = {
			{
				.afi = IANA_AFI_IPV4,
				.safi = safi_none,
				.type = choice_range,
				.addr.ipv4.range = {
					.min = {
						196, 1, 7, 0,
					},
					.max = {
						196, 1, 63, 255,
					},
				},
			},
			{
				.type = choice_last,
			},
		},
		.der = {
			0x30, 0x16, 0x30, 0x14, 0x04, 0x02, 0x00, 0x01,
			0x30, 0x0e, 0x30, 0x0c, 0x03, 0x04, 0x00, 0xc4,
			0x01, 0x07, 0x03, 0x04, 0x06, 0xc4, 0x01, 0x00,
		},
		.der_len = 24,
		.is_canonical = 1,
		.inherits = 0,
		.afis = {
			IANA_AFI_IPV4,
		},
		.afi_len = 1,
	},
};

const size_t N_BUILD_ADDR_BLOCK_TESTS =
    sizeof(build_addr_block_tests) / sizeof(build_addr_block_tests[0]);

static unsigned int *
addr_block_get_safi(const struct ip_addr_block *addr)
{
	static unsigned int safi;

	switch (addr->safi) {
	case safi_none:
		return NULL;
	case safi_unicast:
		safi = 1;
		break;
	case safi_multicast:
		safi = 2;
		break;
	}

	return &safi;
}

static int
addr_block_add_ipv4_addr(IPAddrBlocks *block, enum choice_type type,
    const union ipv4_choice *ipv4, unsigned int *safi)
{
	unsigned char addr[RAW_ADDRESS_SIZE] = {0};
	unsigned char min[RAW_ADDRESS_SIZE];
	unsigned char max[RAW_ADDRESS_SIZE];

	switch (type) {
	case choice_prefix:
		memcpy(addr, ipv4->prefix.addr, ipv4->prefix.addr_len);
		return X509v3_addr_add_prefix(block, IANA_AFI_IPV4, safi,
		    addr, ipv4->prefix.prefix_len);
	case choice_range:
		memcpy(min, ipv4->range.min, sizeof(ipv4->range.min));
		memcpy(max, ipv4->range.max, sizeof(ipv4->range.max));
		return X509v3_addr_add_range(block, IANA_AFI_IPV4, safi,
		    min, max);
	case choice_inherit:
		return X509v3_addr_add_inherit(block, IANA_AFI_IPV4, safi);
	case choice_last:
	default:
		return 0;
	}
}

static int
addr_block_add_ipv6_addr(IPAddrBlocks *block, enum choice_type type,
    const union ipv6_choice *ipv6, unsigned int *safi)
{
	unsigned char addr[RAW_ADDRESS_SIZE] = {0};
	unsigned char min[RAW_ADDRESS_SIZE];
	unsigned char max[RAW_ADDRESS_SIZE];

	switch (type) {
	case choice_prefix:
		memcpy(addr, ipv6->prefix.addr, ipv6->prefix.addr_len);
		return X509v3_addr_add_prefix(block, IANA_AFI_IPV6, safi,
		    addr, ipv6->prefix.prefix_len);
	case choice_range:
		memcpy(min, ipv6->range.min, sizeof(ipv6->range.min));
		memcpy(max, ipv6->range.max, sizeof(ipv6->range.max));
		return X509v3_addr_add_range(block, IANA_AFI_IPV6, safi,
		    min, max);
	case choice_inherit:
		return X509v3_addr_add_inherit(block, IANA_AFI_IPV6, safi);
	case choice_last:
	default:
		return 0;
	}
}

static int
addr_block_add_addrs(IPAddrBlocks *block, const struct ip_addr_block addrs[])
{
	const struct ip_addr_block	*addr;
	unsigned int			*safi;

	for (addr = &addrs[0]; addr->type != choice_last; addr++) {
		safi = addr_block_get_safi(addr);
		switch (addr->afi) {
		case IANA_AFI_IPV4:
			if (!addr_block_add_ipv4_addr(block, addr->type,
			    &addr->addr.ipv4, safi))
				return 0;
			break;
		case IANA_AFI_IPV6:
			if (!addr_block_add_ipv6_addr(block, addr->type,
			    &addr->addr.ipv6, safi))
				return 0;
			break;
		default:
			fprintf(stderr, "%s: corrupt test data", __func__);
			exit(1);
		}
	}

	return 1;
}

static int
build_addr_block_test(const struct build_addr_block_test_data *test)
{
	IPAddrBlocks *addrs = NULL, *parsed = NULL;
	const unsigned char *p;
	unsigned char *out = NULL;
	int out_len;
	int i;
	int memcmp_failed = 1;
	int failed = 1;

	if ((addrs = IPAddrBlocks_new()) == NULL)
		goto err;

	if (!addr_block_add_addrs(addrs, test->addrs))
		goto err;

	if (X509v3_addr_is_canonical(addrs) != test->is_canonical) {
		fprintf(stderr, "%s: \"%s\" X509v3_addr_is_canonical not %d\n",
		    __func__, test->description, test->is_canonical);
		goto err;
	}

	if (!X509v3_addr_canonize(addrs)) {
		fprintf(stderr, "%s: \"%s\" failed to canonize\n",
		    __func__, test->description);
		goto err;
	}

	if (!X509v3_addr_is_canonical(addrs)) {
		fprintf(stderr, "%s: \"%s\" canonization wasn't canonical\n",
		    __func__, test->description);
		goto err;
	}

	if ((out_len = i2d_IPAddrBlocks(addrs, &out)) <= 0) {
		fprintf(stderr, "%s: \"%s\" i2d_IPAddrBlocks failed\n",
		    __func__, test->description);
		goto err;
	}

	memcmp_failed = (size_t)out_len != test->der_len;
	if (!memcmp_failed)
		memcmp_failed = memcmp(out, test->der, test->der_len);
	if (memcmp_failed) {
		report_hexdump(__func__, test->description, "memcmp DER failed",
		    test->der, test->der_len, out, out_len);
		goto err;
	}

	if (X509v3_addr_inherits(addrs) != test->inherits) {
		fprintf(stderr, "%s: \"%s\" X509v3_addr_inherits not %d\n",
		    __func__, test->description, test->inherits);
		goto err;
	}

	for (i = 0; i < sk_IPAddressFamily_num(addrs) && i < test->afi_len; i++) {
		IPAddressFamily *family;
		unsigned int afi;

		family = sk_IPAddressFamily_value(addrs, i);

		if ((afi = X509v3_addr_get_afi(family)) == 0) {
			fprintf(stderr, "%s: \"%s\" X509v3_addr_get_afi"
			    " failed\n", __func__, test->description);
			goto err;
		}
		if (test->afis[i] != afi){
			fprintf(stderr, "%s: \"%s\" afi[%d] mismatch. "
			    "want: %u, got: %u\n", __func__,
			    test->description, i, test->afis[i], afi);
			goto err;
		}
	}
	if (i != test->afi_len) {
		fprintf(stderr, "%s: \"%s\" checked %d afis, expected %d\n",
		    __func__, test->description, i, test->afi_len);
		goto err;
	}

	p = test->der;
	if ((parsed = d2i_IPAddrBlocks(NULL, &p, test->der_len)) == NULL) {
		fprintf(stderr, "%s: \"%s\" d2i_IPAddrBlocks failed\n",
		    __func__, test->description);
		goto err;
	}
	if (!X509v3_addr_is_canonical(parsed)) {
		fprintf(stderr, "%s: \"%s\" parsed AddrBlocks isn't canonical\n",
		    __func__, test->description);
		goto err;
	}
	/* Can't compare IPAddrBlocks with inheritance. */
	if (!X509v3_addr_inherits(addrs) && !X509v3_addr_inherits(parsed)) {
		if (!X509v3_addr_subset(addrs, parsed)) {
			fprintf(stderr, "%s: \"%s\" addrs not subset of parsed\n",
			    __func__, test->description);
		}
		if (!X509v3_addr_subset(parsed, addrs)) {
			fprintf(stderr, "%s: \"%s\" parsed not subset of addrs\n",
			    __func__, test->description);
		}
	}

	failed = 0;

 err:
	IPAddrBlocks_free(addrs);
	IPAddrBlocks_free(parsed);
	free(out);

	return failed;
}

static int
run_IPAddrBlock_tests(void)
{
	size_t i;
	int failed = 0;

	for (i = 0; i < N_BUILD_ADDR_BLOCK_TESTS; i++)
		failed |= build_addr_block_test(&build_addr_block_tests[i]);

	return failed;
}

struct asid_or_range {
	int			 type;
	int			 inherit;
	const unsigned char	*min;
	const unsigned char	*max;
};

struct ASIdentifiers_build_test {
	const char		*description;
	int			 should_build;
	int			 inherits;
	int			 canonical;
	int			 should_canonize;
	struct asid_or_range	 delegations[8];
	const unsigned char	 der[128];
	size_t			 der_len;
};

/* Sentinel value used for marking the end of the delegations table. */
#define V3_ASID_END -1

const struct ASIdentifiers_build_test ASIdentifiers_build_data[] = {
	{
		.description = "RFC 3779, Appendix C",
		.should_build = 1,
		.inherits = 1,
		.canonical = 1,
		.delegations = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "135",
				.max = NULL,
			},
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "3000",
				.max = "3999",
			},
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "5001",
				.max = NULL,
			},
			{
				.type = V3_ASID_RDI,
				.inherit = 1,
				.min = NULL,
				.max = NULL,
			},
			{
				.type = V3_ASID_END,
			},
		},
		.der = {
			0x30, 0x1a, 0xa0, 0x14, 0x30, 0x12, 0x02, 0x02,
			0x00, 0x87, 0x30, 0x08, 0x02, 0x02, 0x0b, 0xb8,
			0x02, 0x02, 0x0f, 0x9f, 0x02, 0x02, 0x13, 0x89,
			0xa1, 0x02, 0x05, 0x00,
		},
		.der_len = 28,
	},
	{
		.description = "RFC 3779, Appendix C without rdi",
		.should_build = 1,
		.inherits = 0,
		.canonical = 1,
		.delegations = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "135",
				.max = NULL,
			},
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "3000",
				.max = "3999",
			},
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "5001",
				.max = NULL,
			},
			{
				.type = V3_ASID_END,
			},
		},
		.der = {
			0x30, 0x16, 0xa0, 0x14, 0x30, 0x12, 0x02, 0x02,
			0x00, 0x87, 0x30, 0x08, 0x02, 0x02, 0x0b, 0xb8,
			0x02, 0x02, 0x0f, 0x9f, 0x02, 0x02, 0x13, 0x89,
		},
		.der_len = 24,
	},
	{
		.description = "RFC 3779, Appendix C variant",
		.should_build = 1,
		.inherits = 0,
		.canonical = 1,
		.delegations = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "135",
				.max = NULL,
			},
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "3000",
				.max = "3999",
			},
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "5001",
				.max = NULL,
			},
			{
				.type = V3_ASID_RDI,
				.inherit = 0,
				.min = "135",
				.max = NULL,
			},
			{
				.type = V3_ASID_RDI,
				.inherit = 0,
				.min = "3000",
				.max = "3999",
			},
			{
				.type = V3_ASID_RDI,
				.inherit = 0,
				.min = "5001",
				.max = NULL,
			},
			{
				.type = V3_ASID_END,
			},
		},
		.der = {
			0x30, 0x2c, 0xa0, 0x14, 0x30, 0x12, 0x02, 0x02,
			0x00, 0x87, 0x30, 0x08, 0x02, 0x02, 0x0b, 0xb8,
			0x02, 0x02, 0x0f, 0x9f, 0x02, 0x02, 0x13, 0x89,
			0xa1, 0x14, 0x30, 0x12, 0x02, 0x02, 0x00, 0x87,
			0x30, 0x08, 0x02, 0x02, 0x0b, 0xb8, 0x02, 0x02,
			0x0f, 0x9f, 0x02, 0x02, 0x13, 0x89,
		},
		.der_len = 46,
	},
	{
		.description = "inherit only",
		.should_build = 1,
		.inherits = 1,
		.canonical = 1,
		.delegations = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 1,
			},
			{
				.type = V3_ASID_RDI,
				.inherit = 1,
			},
			{
				.type = V3_ASID_END,
			},
		},
		.der = {
			0x30, 0x08, 0xa0, 0x02, 0x05, 0x00, 0xa1, 0x02,
			0x05, 0x00,
		},
		.der_len = 10,
	},
	{
		.description = "adjacent unsorted ranges are merged",
		.should_build = 1,
		.inherits = 0,
		.canonical = 0,
		.should_canonize = 1,
		.delegations = {
			{
				.type = V3_ASID_RDI,
				.inherit = 0,
				.min = "27",
				.max = NULL,
			},
			{
				.type = V3_ASID_RDI,
				.inherit = 0,
				.min = "28",
				.max = "57",
			},
			{
				.type = V3_ASID_RDI,
				.inherit = 0,
				.min = "66",
				.max = "68",
			},
			{
				.type = V3_ASID_RDI,
				.inherit = 0,
				.min = "58",
				.max = "63",
			},
			{
				.type = V3_ASID_RDI,
				.inherit = 0,
				.min = "64",
				.max = NULL,
			},
			{
				.type = V3_ASID_END,
			},
		},
		.der = {
			0x30, 0x14, 0xa1, 0x12, 0x30, 0x10, 0x30, 0x06,
			0x02, 0x01, 0x1b, 0x02, 0x01, 0x40, 0x30, 0x06,
			0x02, 0x01, 0x42, 0x02, 0x01, 0x44,
		},
		.der_len = 22,
	},
	{
		.description = "range of length 0",
		.should_build = 1,
		.inherits = 1,
		.canonical = 1,
		.should_canonize = 1,
		.delegations = {
			{
				.type = V3_ASID_RDI,
				.inherit = 0,
				.min = "27",
				.max = "27",
			},
			{
				.type = V3_ASID_ASNUM,
				.inherit = 1,
			},
			{
				.type = V3_ASID_END,
			},
		},
		.der = {
			0x30, 0x10, 0xa0, 0x02, 0x05, 0x00, 0xa1, 0x0a,
			0x30, 0x08, 0x30, 0x06, 0x02, 0x01, 0x1b, 0x02,
			0x01, 0x1b,
		},
		.der_len = 18,
	},
	{
		.description = "reversed range doesn't canonize",
		.should_build = 1,
		.inherits = 0,
		.canonical = 0,
		.should_canonize = 0,
		.delegations = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "57",
				.max = "42",
			},
			{
				.type = V3_ASID_END,
			},
		},
	},
	{
		.description = "overlapping ranges don't canonize",
		.should_build = 1,
		.inherits = 0,
		.canonical = 0,
		.should_canonize = 0,
		.delegations = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "42",
				.max = "57",
			},
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "57",
				.max = "60",
			},
			{
				.type = V3_ASID_END,
			},
		},
	},
	{
		.description = "reversed interior range doesn't canonize",
		.should_build = 1,
		.inherits = 0,
		.canonical = 0,
		.should_canonize = 0,
		.delegations = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "1",
				.max = "2",
			},
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "57",
				.max = "42",
			},
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "65523",
				.max = "65535",
			},
			{
				.type = V3_ASID_END,
			},
		},
	},
	{
		.description = "can't inherit and add AS ids",
		.should_build = 0,
		.inherits = 0,
		.canonical = 0,
		.should_canonize = 0,
		.delegations = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "1",
				.max = "2",
			},
			{
				.type = V3_ASID_ASNUM,
				.inherit = 1,
			},
			{
				.type = V3_ASID_END,
			},
		},
	},
	{
		.description = "can't inherit and add rdis",
		.should_build = 0,
		.inherits = 0,
		.canonical = 0,
		.should_canonize = 0,
		.delegations = {
			{
				.type = V3_ASID_RDI,
				.inherit = 0,
				.min = "1",
				.max = "2",
			},
			{
				.type = V3_ASID_RDI,
				.inherit = 1,
			},
			{
				.type = V3_ASID_END,
			},
		},
	},
};

const size_t N_ASIDENTIFIERS_BUILD_TESTS =
    sizeof(ASIdentifiers_build_data) / sizeof(ASIdentifiers_build_data[0]);

static int
add_as_delegation(ASIdentifiers *asid, const struct asid_or_range *delegation)
{
	ASN1_INTEGER	*min = NULL, *max = NULL;
	int		 ret = 0;

	if (delegation->inherit)
		return X509v3_asid_add_inherit(asid, delegation->type);

	if ((min = s2i_ASN1_INTEGER(NULL, delegation->min)) == NULL)
		goto err;

	if (delegation->max != NULL) {
		if ((max = s2i_ASN1_INTEGER(NULL, delegation->max)) == NULL)
			goto err;
	}

	if (!X509v3_asid_add_id_or_range(asid, delegation->type, min, max))
		goto err;
	min = NULL;
	max = NULL;

	ret = 1;

 err:
	ASN1_INTEGER_free(min);
	ASN1_INTEGER_free(max);

	return ret;
}

static ASIdentifiers *
build_asid(const struct asid_or_range delegations[])
{
	ASIdentifiers			*asid = NULL;
	const struct asid_or_range	*delegation;

	if ((asid = ASIdentifiers_new()) == NULL)
		goto err;

	for (delegation = &delegations[0]; delegation->type != V3_ASID_END;
	    delegation++) {
		if (!add_as_delegation(asid, delegation))
			goto err;
	}

	return asid;

 err:
	ASIdentifiers_free(asid);
	return NULL;
}

static int
build_asid_test(const struct ASIdentifiers_build_test *test)
{
	ASIdentifiers	*asid = NULL;
	unsigned char	*out = NULL;
	int		 out_len;
	int		 memcmp_failed = 1;
	int		 failed = 1;

	if ((asid = build_asid(test->delegations)) == NULL) {
		if (!test->should_build) {
			failed = 0;
			return failed;
		}
		fprintf(stderr, "%s: \"%s\" failed to build\n", __func__,
		    test->description);
		return failed;
	}

	if (!test->canonical) {
		if (X509v3_asid_is_canonical(asid)) {
			fprintf(stderr, "%s: \"%s\" shouldn't be canonical\n",
			    __func__, test->description);
			goto err;
		}
		if (X509v3_asid_canonize(asid) != test->should_canonize) {
			fprintf(stderr, "%s: \"%s\" failed to canonize\n",
			    __func__, test->description);
			goto err;
		}
		if (!test->should_canonize) {
			failed = 0;
			goto err;
		}
	}

	/*
	 * Verify that asid is in canonical form before converting it to DER.
	 */
	if (!X509v3_asid_is_canonical(asid)) {
		fprintf(stderr, "%s: asid is not canonical\n", __func__);
		goto err;
	}

	/*
	 * Convert asid to DER and check that it matches expectations
	 */
	out = NULL;
	if ((out_len = i2d_ASIdentifiers(asid, &out)) <= 0) {
		fprintf(stderr, "%s: \"%s\" i2d_ASIdentifiers failed\n",
		    __func__, test->description);
		goto err;
	}


	memcmp_failed = (size_t)out_len != test->der_len;
	if (!memcmp_failed)
		memcmp_failed = memcmp(out, test->der, test->der_len);
	if (memcmp_failed) {
		report_hexdump(__func__, test->description, "memcmp DER failed",
		    test->der, test->der_len, out, out_len);
		goto err;
	}

	/*
	 * Verify that asid inherits as expected
	 */
	if (X509v3_asid_inherits(asid) != test->inherits) {
		fprintf(stderr, "%s: \"%s\" unexpected asid inherit %d\n",
		    __func__, test->description, test->inherits);
		goto err;
	}

	failed = 0;

 err:
	free(out);
	ASIdentifiers_free(asid);

	return failed;
}

static int
run_ASIdentifiers_build_test(void)
{
	size_t i;
	int failed = 0;

	for (i = 0; i < N_ASIDENTIFIERS_BUILD_TESTS; i++)
		failed |= build_asid_test(&ASIdentifiers_build_data[i]);

	return failed;
}

struct ASIdentifiers_subset_test {
	const char		*description;
	struct asid_or_range	 delegationsA[8];
	struct asid_or_range	 delegationsB[8];
	int			 is_subset;
	int			 is_subset_if_canonized;
};

const struct ASIdentifiers_subset_test ASIdentifiers_subset_data[] = {
	{
		.description = "simple subset relation",
		.delegationsA = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "2",
				.max = "4",
			},
			{
				.type = V3_ASID_RDI,
				.inherit = 0,
				.min = "2",
				.max = NULL,
			},
			{
				.type = V3_ASID_END,
			},
		},
		.delegationsB = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "1",
				.max = "5",
			},
			{
				.type = V3_ASID_RDI,
				.inherit = 0,
				.min = "1",
				.max = "5",
			},
			{
				.type = V3_ASID_END,
			},
		},
		.is_subset = 1,
		.is_subset_if_canonized = 1,
	},
	{
		.description = "only asnums",
		.delegationsA = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "2",
				.max = "4",
			},
			{
				.type = V3_ASID_END,
			},
		},
		.delegationsB = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "1",
				.max = "5",
			},
			{
				.type = V3_ASID_END,
			},
		},
		.is_subset = 1,
		.is_subset_if_canonized = 1,
	},
	{
		.description = "only rdis",
		.delegationsA = {
			{
				.type = V3_ASID_RDI,
				.inherit = 0,
				.min = "2",
				.max = NULL,
			},
			{
				.type = V3_ASID_END,
			},
		},
		.delegationsB = {
			{
				.type = V3_ASID_RDI,
				.inherit = 0,
				.min = "1",
				.max = "5",
			},
			{
				.type = V3_ASID_END,
			},
		},
		.is_subset = 1,
		.is_subset_if_canonized = 1,
	},
	{
		.description = "child only has asnums, parent only has rdis",
		.delegationsA = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "2",
				.max = "4",
			},
			{
				.type = V3_ASID_END,
			},
		},
		.delegationsB = {
			{
				.type = V3_ASID_RDI,
				.inherit = 0,
				.min = "1",
				.max = "5",
			},
			{
				.type = V3_ASID_END,
			},
		},
		.is_subset = 0,
		.is_subset_if_canonized = 0,
	},
	{
		.description = "child only has rdis, parent only has asnums",
		.delegationsA = {
			{
				.type = V3_ASID_RDI,
				.inherit = 0,
				.min = "2",
				.max = "4",
			},
			{
				.type = V3_ASID_END,
			},
		},
		.delegationsB = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "1",
				.max = "5",
			},
			{
				.type = V3_ASID_END,
			},
		},
		.is_subset = 0,
		.is_subset_if_canonized = 0,
	},
	{
		.description = "child only has rdis, parent has both",
		.delegationsA = {
			{
				.type = V3_ASID_RDI,
				.inherit = 0,
				.min = "2",
				.max = "4",
			},
			{
				.type = V3_ASID_END,
			},
		},
		.delegationsB = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "1",
				.max = "5",
			},
			{
				.type = V3_ASID_RDI,
				.inherit = 0,
				.min = "1",
				.max = "5",
			},
			{
				.type = V3_ASID_END,
			},
		},
		.is_subset = 1,
		.is_subset_if_canonized = 1,
	},
	{
		.description = "subset relation only after canonization",
		.delegationsA = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "2",
				.max = NULL,
			},
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "3",
				.max = "4",
			},
			{
				.type = V3_ASID_RDI,
				.inherit = 0,
				.min = "2",
				.max = NULL,
			},
			{
				.type = V3_ASID_END,
			},
		},
		.delegationsB = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "1",
				.max = "3",
			},
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "4",
				.max = "5",
			},
			{
				.type = V3_ASID_RDI,
				.inherit = 0,
				.min = "1",
				.max = "5",
			},
			{
				.type = V3_ASID_END,
			},
		},
		.is_subset = 0,
		.is_subset_if_canonized = 1,
	},
	{
		.description = "no subset if A inherits",
		.delegationsA = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "2",
				.max = NULL,
			},
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "3",
				.max = "4",
			},
			{
				.type = V3_ASID_RDI,
				.inherit = 1,
			},
			{
				.type = V3_ASID_END,
			},
		},
		.delegationsB = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "1",
				.max = "3",
			},
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "4",
				.max = "5",
			},
			{
				.type = V3_ASID_RDI,
				.inherit = 0,
				.min = "1",
				.max = "5",
			},
			{
				.type = V3_ASID_END,
			},
		},
		.is_subset = 0,
		.is_subset_if_canonized = 0,
	},
	{
		.description = "no subset if B inherits",
		.delegationsA = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "2",
				.max = NULL,
			},
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "3",
				.max = "4",
			},
			{
				.type = V3_ASID_RDI,
				.inherit = 0,
				.min = "5",
				.max = NULL,
			},
			{
				.type = V3_ASID_END,
			},
		},
		.delegationsB = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "1",
				.max = "3",
			},
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "4",
				.max = "5",
			},
			{
				.type = V3_ASID_RDI,
				.inherit = 1,
			},
			{
				.type = V3_ASID_END,
			},
		},
		.is_subset = 0,
		.is_subset_if_canonized = 0,
	},
	{
		.description = "no subset if both inherit",
		.delegationsA = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "2",
				.max = NULL,
			},
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "3",
				.max = "4",
			},
			{
				.type = V3_ASID_RDI,
				.inherit = 1,
			},
			{
				.type = V3_ASID_END,
			},
		},
		.delegationsB = {
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "1",
				.max = "3",
			},
			{
				.type = V3_ASID_ASNUM,
				.inherit = 0,
				.min = "4",
				.max = "5",
			},
			{
				.type = V3_ASID_RDI,
				.inherit = 1,
			},
			{
				.type = V3_ASID_END,
			},
		},
		.is_subset = 0,
		.is_subset_if_canonized = 0,
	},
};

const size_t N_ASIDENTIFIERS_SUBSET_TESTS =
    sizeof(ASIdentifiers_subset_data) / sizeof(ASIdentifiers_subset_data[0]);

static int
asid_subset_test(const struct ASIdentifiers_subset_test *test)
{
	ASIdentifiers	*asidA = NULL, *asidB = NULL;
	int		 failed = 0;

	if ((asidA = build_asid(test->delegationsA)) == NULL)
		goto err;
	if ((asidB = build_asid(test->delegationsB)) == NULL)
		goto err;

	if (X509v3_asid_subset(asidA, asidB) != test->is_subset) {
		fprintf(stderr, "%s: \"%s\" X509v3_asid_subset failed\n",
		    __func__, test->description);
		failed = 1;
	}

	if (!test->is_subset) {
		if (!X509v3_asid_canonize(asidA))
			goto err;
		if (!X509v3_asid_canonize(asidB))
			goto err;
		if (X509v3_asid_subset(asidA, asidB) !=
		    test->is_subset_if_canonized) {
			fprintf(stderr, "%s: \"%s\" canonized subset failed\n",
			    __func__, test->description);
			failed = 1;
		}
	}

 err:
	ASIdentifiers_free(asidA);
	ASIdentifiers_free(asidB);

	return failed;
}

static int
run_ASIdentifiers_subset_test(void)
{
	size_t i;
	int failed = 0;

	for (i = 0; i < N_ASIDENTIFIERS_SUBSET_TESTS; i++)
		failed |= asid_subset_test(&ASIdentifiers_subset_data[i]);

	return failed;
}

int
main(void)
{
	int failed = 0;

	failed |= run_IPAddressOrRange_tests();
	failed |= run_IPAddrBlock_tests();
	failed |= run_ASIdentifiers_build_test();
	failed |= run_ASIdentifiers_subset_test();

	return failed;
}
