/*	$OpenBSD: rde_attr.c,v 1.140 2025/12/16 15:38:55 claudio Exp $ */

/*
 * Copyright (c) 2004 Claudio Jeker <claudio@openbsd.org>
 * Copyright (c) 2016 Job Snijders <job@instituut.net>
 * Copyright (c) 2016 Peter Hessler <phessler@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 <sys/queue.h>

#include <endian.h>
#include <limits.h>
#include <siphash.h>
#include <stdlib.h>
#include <string.h>

#include "bgpd.h"
#include "rde.h"
#include "log.h"
#include "chash.h"

int
attr_writebuf(struct ibuf *buf, uint8_t flags, uint8_t type, void *data,
    uint16_t data_len)
{
	u_char	hdr[4];

	flags &= ~ATTR_DEFMASK;
	if (data_len > 255) {
		flags |= ATTR_EXTLEN;
		hdr[2] = (data_len >> 8) & 0xff;
		hdr[3] = data_len & 0xff;
	} else {
		hdr[2] = data_len & 0xff;
	}

	hdr[0] = flags;
	hdr[1] = type;

	if (ibuf_add(buf, hdr, flags & ATTR_EXTLEN ? 4 : 3) == -1)
		return (-1);
	if (data != NULL && ibuf_add(buf, data, data_len) == -1)
		return (-1);
	return (0);
}

/* optional attribute specific functions */
static struct attr *attr_alloc(uint8_t, uint8_t, void *, uint16_t, uint64_t);
static struct attr *attr_lookup(uint8_t, uint8_t, void *, uint16_t, uint64_t);
static void attr_put(struct attr *);

static SIPHASH_KEY	 attrkey;

static inline uint64_t
attr_hash(const struct attr *a)
{
	return a->hash;
}

static uint64_t
attr_calc_hash(uint8_t type, const void *data, uint16_t len)
{
	SIPHASH_CTX ctx;

	SipHash24_Init(&ctx, &attrkey);
	SipHash24_Update(&ctx, data, len);
	SipHash24_Update(&ctx, &type, sizeof(type));
	return SipHash24_End(&ctx);
}

CH_HEAD(attr_tree, attr);
CH_PROTOTYPE(attr_tree, attr, attr_hash);

static struct attr_tree attrtable = CH_INITIALIZER(&attrtable);

void
attr_init(void)
{
	arc4random_buf(&attrkey, sizeof(attrkey));
}

int
attr_optadd(struct rde_aspath *asp, uint8_t flags, uint8_t type,
    void *data, uint16_t len)
{
	uint8_t		 l;
	struct attr	*a, *t;
	struct attr	**p;
	uint64_t	 h;

	/* attribute allowed only once */
	for (l = 0; l < asp->others_len; l++) {
		if (asp->others[l] == NULL)
			break;
		if (type == asp->others[l]->type)
			return (-1);
		if (type < asp->others[l]->type)
			break;
	}

	/* known optional attributes were validated previously */
	h = attr_calc_hash(type, data, len);
	if ((a = attr_lookup(flags, type, data, len, h)) == NULL)
		a = attr_alloc(flags, type, data, len, h);

	/* add attribute to the table but first bump refcnt */
	a->refcnt++;
	rdemem.attr_refs++;

	for (l = 0; l < asp->others_len; l++) {
		if (asp->others[l] == NULL) {
			asp->others[l] = a;
			return (0);
		}
		/* list is sorted */
		if (a->type < asp->others[l]->type) {
			t = asp->others[l];
			asp->others[l] = a;
			a = t;
		}
	}

	/* no empty slot found, need to realloc */
	if (asp->others_len == UCHAR_MAX)
		fatalx("attr_optadd: attribute overflow");

	l = bin_of_attrs(asp->others_len + 1);
	if ((p = reallocarray(asp->others, l, sizeof(struct attr *))) == NULL)
		fatal("%s", __func__);
	memset(&p[asp->others_len], 0, (l - asp->others_len) * sizeof(*p));
	asp->others = p;
	asp->others[asp->others_len] = a;
	asp->others_len = l;

	return (0);
}

struct attr *
attr_optget(const struct rde_aspath *asp, uint8_t type)
{
	uint8_t l;

	for (l = 0; l < asp->others_len; l++) {
		if (asp->others[l] == NULL)
			break;
		if (type == asp->others[l]->type)
			return (asp->others[l]);
		if (type < asp->others[l]->type)
			break;
	}
	return (NULL);
}

void
attr_copy(struct rde_aspath *t, const struct rde_aspath *s)
{
	uint8_t l;

	if (t->others != NULL)
		attr_freeall(t);

	t->others_len = s->others_len;
	if (t->others_len == 0) {
		t->others = NULL;
		return;
	}

	if ((t->others = calloc(s->others_len, sizeof(struct attr *))) == 0)
		fatal("%s", __func__);

	for (l = 0; l < t->others_len; l++) {
		if (s->others[l] == NULL)
			break;
		s->others[l]->refcnt++;
		rdemem.attr_refs++;
		t->others[l] = s->others[l];
	}
}

static inline int
attr_eq(const struct attr *oa, const struct attr *ob)
{
	if (oa->hash != ob->hash)
		return 0;
	if (oa->type != ob->type)
		return 0;
	if (oa->flags != ob->flags)
		return 0;
	if (oa->len != ob->len)
		return 0;
	return (oa->len == 0 || memcmp(oa->data, ob->data, oa->len) == 0);
}

int
attr_equal(const struct rde_aspath *a, const struct rde_aspath *b)
{
	uint8_t l;

	if (a->others_len != b->others_len)
		return (0);
	for (l = 0; l < a->others_len; l++)
		if (a->others[l] != b->others[l])
			return (0);
	return (1);
}

void
attr_free(struct rde_aspath *asp, struct attr *attr)
{
	uint8_t l;

	for (l = 0; l < asp->others_len; l++)
		if (asp->others[l] == attr) {
			attr_put(asp->others[l]);
			for (++l; l < asp->others_len; l++)
				asp->others[l - 1] = asp->others[l];
			asp->others[asp->others_len - 1] = NULL;
			return;
		}

	/* no realloc() because the slot may be reused soon */
}

void
attr_freeall(struct rde_aspath *asp)
{
	uint8_t l;

	for (l = 0; l < asp->others_len; l++)
		attr_put(asp->others[l]);

	free(asp->others);
	asp->others = NULL;
	asp->others_len = 0;
}

struct attr *
attr_alloc(uint8_t flags, uint8_t type, void *data, uint16_t len, uint64_t hash)
{
	struct attr	*a;

	a = calloc(1, sizeof(struct attr));
	if (a == NULL)
		fatal("%s", __func__);
	rdemem.attr_cnt++;

	flags &= ~ATTR_DEFMASK;	/* normalize mask */
	a->flags = flags;
	a->type = type;
	a->len = len;
	if (len != 0) {
		if ((a->data = malloc(len)) == NULL)
			fatal("%s", __func__);

		rdemem.attr_dcnt++;
		rdemem.attr_data += len;
		memcpy(a->data, data, len);
	} else
		a->data = NULL;

	a->hash = hash;

	if (CH_INSERT(attr_tree, &attrtable, a, NULL) != 1)
		fatalx("corrupted attr tree");

	return (a);
}

struct attr *
attr_lookup(uint8_t flags, uint8_t type, void *data, uint16_t len,
    uint64_t hash)
{
	struct attr		needle;

	flags &= ~ATTR_DEFMASK;	/* normalize mask */

	needle.flags = flags;
	needle.type = type;
	needle.len = len;
	needle.data = data;
	needle.hash = hash;

	return CH_FIND(attr_tree, &attrtable, &needle);
}

void
attr_put(struct attr *a)
{
	if (a == NULL)
		return;

	rdemem.attr_refs--;
	if (--a->refcnt > 0)
		/* somebody still holds a reference */
		return;

	/* unlink */
	CH_REMOVE(attr_tree, &attrtable, a);

	if (a->len != 0)
		rdemem.attr_dcnt--;
	rdemem.attr_data -= a->len;
	rdemem.attr_cnt--;
	free(a->data);
	free(a);
}

CH_GENERATE(attr_tree, attr, attr_eq, attr_hash);

/* aspath specific functions */

static uint16_t aspath_count(const void *, uint16_t);
static uint32_t aspath_extract_origin(const void *, uint16_t);
static uint16_t aspath_countlength(struct aspath *, uint16_t, int);
static void	 aspath_countcopy(struct aspath *, uint16_t, uint8_t *,
		    uint16_t, int);

int
aspath_compare(const struct aspath *a1, const struct aspath *a2)
{
	int r;

	if (a1->len > a2->len)
		return (1);
	if (a1->len < a2->len)
		return (-1);
	r = memcmp(a1->data, a2->data, a1->len);
	if (r > 0)
		return (1);
	if (r < 0)
		return (-1);
	return (0);
}

struct aspath *
aspath_get(void *data, uint16_t len)
{
	struct aspath		*aspath;

	aspath = malloc(ASPATH_HEADER_SIZE + len);
	if (aspath == NULL)
		fatal("%s", __func__);

	rdemem.aspath_cnt++;
	rdemem.aspath_size += ASPATH_HEADER_SIZE + len;

	aspath->len = len;
	aspath->ascnt = aspath_count(data, len);
	aspath->source_as = aspath_extract_origin(data, len);
	if (len != 0)
		memcpy(aspath->data, data, len);

	return (aspath);
}

struct aspath *
aspath_copy(struct aspath *a)
{
	struct aspath		*aspath;

	aspath = malloc(ASPATH_HEADER_SIZE + a->len);
	if (aspath == NULL)
		fatal("%s", __func__);

	rdemem.aspath_cnt++;
	rdemem.aspath_size += ASPATH_HEADER_SIZE + a->len;

	memcpy(aspath, a,  ASPATH_HEADER_SIZE + a->len);

	return (aspath);
}

void
aspath_put(struct aspath *aspath)
{
	if (aspath == NULL)
		return;

	rdemem.aspath_cnt--;
	rdemem.aspath_size -= ASPATH_HEADER_SIZE + aspath->len;
	free(aspath);
}

/*
 * convert a 4 byte aspath to a 2 byte one.
 */
u_char *
aspath_deflate(u_char *data, uint16_t *len, int *flagnew)
{
	uint8_t		*seg, *nseg, *ndata = NULL;
	uint32_t	 as;
	int		 i;
	uint16_t	 seg_size, olen, nlen;
	uint8_t		 seg_len;

	/* first calculate the length of the aspath */
	nlen = 0;
	seg = data;
	olen = *len;
	for (; olen > 0; olen -= seg_size, seg += seg_size) {
		seg_len = seg[1];
		seg_size = 2 + sizeof(uint32_t) * seg_len;
		nlen += 2 + sizeof(uint16_t) * seg_len;

		if (seg_size > olen)
			fatalx("%s: would overflow", __func__);
	}

	if (nlen == 0)
		goto done;

	if ((ndata = malloc(nlen)) == NULL)
		fatal("%s", __func__);

	/* then copy the aspath */
	seg = data;
	olen = *len;
	for (nseg = ndata; seg < data + olen; seg += seg_size) {
		*nseg++ = seg[0];
		*nseg++ = seg_len = seg[1];
		seg_size = 2 + sizeof(uint32_t) * seg_len;

		for (i = 0; i < seg_len; i++) {
			as = aspath_extract(seg, i);
			if (as > USHRT_MAX) {
				as = AS_TRANS;
				*flagnew = 1;
			}
			*nseg++ = (as >> 8) & 0xff;
			*nseg++ = as & 0xff;
		}
	}

 done:
	*len = nlen;
	return (ndata);
}

void
aspath_merge(struct rde_aspath *a, struct attr *attr)
{
	uint8_t		*np;
	uint16_t	 ascnt, diff, nlen, difflen;
	int		 hroom = 0;

	ascnt = aspath_count(attr->data, attr->len);
	if (ascnt > a->aspath->ascnt) {
		/* ASPATH is shorter then AS4_PATH no way to merge */
		attr_free(a, attr);
		return;
	}

	diff = a->aspath->ascnt - ascnt;
	if (diff && attr->len > 2 && attr->data[0] == AS_SEQUENCE)
		hroom = attr->data[1];
	difflen = aspath_countlength(a->aspath, diff, hroom);
	nlen = attr->len + difflen;

	if ((np = malloc(nlen)) == NULL)
		fatal("%s", __func__);

	/* copy head from old aspath */
	aspath_countcopy(a->aspath, diff, np, difflen, hroom);

	/* copy tail from new aspath */
	if (hroom > 0)
		memcpy(np + nlen - attr->len + 2, attr->data + 2,
		    attr->len - 2);
	else
		memcpy(np + nlen - attr->len, attr->data, attr->len);

	aspath_put(a->aspath);
	a->aspath = aspath_get(np, nlen);
	free(np);
	attr_free(a, attr);
}

uint32_t
aspath_neighbor(struct aspath *aspath)
{
	/*
	 * Empty aspath is OK -- internal AS route.
	 * Additionally the RFC specifies that if the path starts with an
	 * AS_SET the neighbor AS is also the local AS.
	 */
	if (aspath->len == 0 ||
	    aspath->data[0] != AS_SEQUENCE)
		return (rde_local_as());
	return (aspath_extract(aspath->data, 0));
}

static uint16_t
aspath_count(const void *data, uint16_t len)
{
	const uint8_t	*seg;
	uint16_t	 cnt, seg_size;
	uint8_t		 seg_type, seg_len;

	cnt = 0;
	seg = data;
	for (; len > 0; len -= seg_size, seg += seg_size) {
		seg_type = seg[0];
		seg_len = seg[1];
		seg_size = 2 + sizeof(uint32_t) * seg_len;

		if (seg_type == AS_SET)
			cnt += 1;
		else
			cnt += seg_len;

		if (seg_size > len)
			fatalx("%s: would overflow", __func__);
	}
	return (cnt);
}

/*
 * The origin AS number derived from a Route as follows:
 * o  the rightmost AS in the final segment of the AS_PATH attribute
 *    in the Route if that segment is of type AS_SEQUENCE, or
 * o  the BGP speaker's own AS number if that segment is of type
 *    AS_CONFED_SEQUENCE or AS_CONFED_SET or if the AS_PATH is empty,
 * o  the distinguished value "NONE" if the final segment of the
 *    AS_PATH attribute is of any other type.
 */
static uint32_t
aspath_extract_origin(const void *data, uint16_t len)
{
	const uint8_t	*seg;
	uint32_t	 as = AS_NONE;
	uint16_t	 seg_size;
	uint8_t		 seg_len;

	/* AS_PATH is empty */
	if (len == 0)
		return (rde_local_as());

	seg = data;
	for (; len > 0; len -= seg_size, seg += seg_size) {
		seg_len = seg[1];
		seg_size = 2 + sizeof(uint32_t) * seg_len;

		if (len == seg_size && seg[0] == AS_SEQUENCE) {
			as = aspath_extract(seg, seg_len - 1);
		}
		if (seg_size > len)
			fatalx("%s: would overflow", __func__);
	}
	return (as);
}

static uint16_t
aspath_countlength(struct aspath *aspath, uint16_t cnt, int headcnt)
{
	const uint8_t	*seg;
	uint16_t	 seg_size, len, clen;
	uint8_t		 seg_type = 0, seg_len = 0;

	seg = aspath->data;
	clen = 0;
	for (len = aspath->len; len > 0 && cnt > 0;
	    len -= seg_size, seg += seg_size) {
		seg_type = seg[0];
		seg_len = seg[1];
		seg_size = 2 + sizeof(uint32_t) * seg_len;

		if (seg_type == AS_SET)
			cnt -= 1;
		else if (seg_len > cnt) {
			seg_len = cnt;
			clen += 2 + sizeof(uint32_t) * cnt;
			break;
		} else
			cnt -= seg_len;

		clen += seg_size;

		if (seg_size > len)
			fatalx("%s: would overflow", __func__);
	}
	if (headcnt > 0 && seg_type == AS_SEQUENCE && headcnt + seg_len < 256)
		/* no need for additional header from the new aspath. */
		clen -= 2;

	return (clen);
}

static void
aspath_countcopy(struct aspath *aspath, uint16_t cnt, uint8_t *buf,
    uint16_t size, int headcnt)
{
	const uint8_t	*seg;
	uint16_t	 seg_size, len;
	uint8_t		 seg_type, seg_len;

	if (headcnt > 0)
		/*
		 * additional room because we steal the segment header
		 * from the other aspath
		 */
		size += 2;
	seg = aspath->data;
	for (len = aspath->len; len > 0 && cnt > 0;
	    len -= seg_size, seg += seg_size) {
		seg_type = seg[0];
		seg_len = seg[1];
		seg_size = 2 + sizeof(uint32_t) * seg_len;

		if (seg_type == AS_SET)
			cnt -= 1;
		else if (seg_len > cnt) {
			seg_len = cnt + headcnt;
			seg_size = 2 + sizeof(uint32_t) * cnt;
			cnt = 0;
		} else {
			cnt -= seg_len;
			if (cnt == 0)
				seg_len += headcnt;
		}

		memcpy(buf, seg, seg_size);
		buf[0] = seg_type;
		buf[1] = seg_len;
		buf += seg_size;
		if (size < seg_size)
			fatalx("%s: would overflow", __func__);
		size -= seg_size;
	}
}

int
aspath_loopfree(struct aspath *aspath, uint32_t myAS)
{
	uint8_t		*seg;
	uint16_t	 len, seg_size;
	uint8_t		 i, seg_len;

	seg = aspath->data;
	for (len = aspath->len; len > 0; len -= seg_size, seg += seg_size) {
		seg_len = seg[1];
		seg_size = 2 + sizeof(uint32_t) * seg_len;

		for (i = 0; i < seg_len; i++) {
			if (myAS == aspath_extract(seg, i))
				return (0);
		}

		if (seg_size > len)
			fatalx("%s: would overflow", __func__);
	}
	return (1);
}

static int
as_compare(struct filter_as *f, uint32_t as, uint32_t neighas)
{
	uint32_t match;

	if (f->flags & AS_FLAG_AS_SET_NAME)	/* should not happen */
		return (0);
	if (f->flags & AS_FLAG_AS_SET)
		return (as_set_match(f->aset, as));

	if (f->flags & AS_FLAG_NEIGHBORAS)
		match = neighas;
	else
		match = f->as_min;

	switch (f->op) {
	case OP_NONE:
	case OP_EQ:
		if (as == match)
			return (1);
		break;
	case OP_NE:
		if (as != match)
			return (1);
		break;
	case OP_RANGE:
		if (as >= f->as_min && as <= f->as_max)
			return (1);
		break;
	case OP_XRANGE:
		if (as < f->as_min || as > f->as_max)
			return (1);
		break;
	}
	return (0);
}

/* we need to be able to search more than one as */
int
aspath_match(struct aspath *aspath, struct filter_as *f, uint32_t neighas)
{
	const uint8_t	*seg;
	int		 final;
	uint16_t	 len, seg_size;
	uint8_t		 i, seg_len;
	uint32_t	 as = AS_NONE;

	if (f->type == AS_EMPTY) {
		if (aspath_length(aspath) == 0)
			return (1);
		else
			return (0);
	}

	/* just check the leftmost AS */
	if (f->type == AS_PEER) {
		as = aspath_neighbor(aspath);
		if (as_compare(f, as, neighas))
			return (1);
		else
			return (0);
	}

	seg = aspath->data;
	len = aspath->len;
	for (; len >= 6; len -= seg_size, seg += seg_size) {
		seg_len = seg[1];
		seg_size = 2 + sizeof(uint32_t) * seg_len;

		final = (len == seg_size);

		if (f->type == AS_SOURCE) {
			/*
			 * Just extract the rightmost AS
			 * but if that segment is an AS_SET then the rightmost
			 * AS of a previous AS_SEQUENCE segment should be used.
			 * Because of that just look at AS_SEQUENCE segments.
			 */
			if (seg[0] == AS_SEQUENCE)
				as = aspath_extract(seg, seg_len - 1);
			/* not yet in the final segment */
			if (!final)
				continue;
			if (as_compare(f, as, neighas))
				return (1);
			else
				return (0);
		}
		/* AS_TRANSIT or AS_ALL */
		for (i = 0; i < seg_len; i++) {
			/*
			 * the source (rightmost) AS is excluded from
			 * AS_TRANSIT matches.
			 */
			if (final && i == seg_len - 1 && f->type == AS_TRANSIT)
				return (0);
			as = aspath_extract(seg, i);
			if (as_compare(f, as, neighas))
				return (1);
		}

		if (seg_size > len)
			fatalx("%s: would overflow", __func__);
	}
	return (0);
}

/*
 * Returns a new prepended aspath. Old needs to be freed by caller.
 */
u_char *
aspath_prepend(struct aspath *asp, uint32_t as, int quantum, uint16_t *len)
{
	u_char		*p;
	int		 l, overflow = 0, shift = 0, size, wpos = 0;
	uint8_t		 type;

	/* lunatic prepends are blocked in the parser and limited */

	/* first calculate new size */
	if (asp->len > 0) {
		if (asp->len < 2)
			fatalx("aspath_prepend: bad aspath length");
		type = asp->data[0];
		size = asp->data[1];
	} else {
		/* empty as path */
		type = AS_SET;
		size = 0;
	}

	if (quantum > 255)
		fatalx("aspath_prepend: preposterous prepend");
	if (quantum == 0) {
		/* no change needed but return a copy */
		if (asp->len == 0) {
			*len = 0;
			return (NULL);
		}
		p = malloc(asp->len);
		if (p == NULL)
			fatal("%s", __func__);
		memcpy(p, asp->data, asp->len);
		*len = asp->len;
		return (p);
	} else if (type == AS_SET || size + quantum > 255) {
		/* need to attach a new AS_SEQUENCE */
		l = 2 + quantum * sizeof(uint32_t) + asp->len;
		if (type == AS_SET)
			overflow = quantum;
		else
			overflow = size + quantum - 255;
	} else
		l = quantum * sizeof(uint32_t) + asp->len;

	quantum -= overflow;

	p = malloc(l);
	if (p == NULL)
		fatal("%s", __func__);

	/* first prepends */
	as = htonl(as);
	if (overflow > 0) {
		p[wpos++] = AS_SEQUENCE;
		p[wpos++] = overflow;

		for (; overflow > 0; overflow--) {
			memcpy(p + wpos, &as, sizeof(uint32_t));
			wpos += sizeof(uint32_t);
		}
	}
	if (quantum > 0) {
		shift = 2;
		p[wpos++] = AS_SEQUENCE;
		p[wpos++] = quantum + size;

		for (; quantum > 0; quantum--) {
			memcpy(p + wpos, &as, sizeof(uint32_t));
			wpos += sizeof(uint32_t);
		}
	}
	if (asp->len > shift)
		memcpy(p + wpos, asp->data + shift, asp->len - shift);

	*len = l;
	return (p);
}

/*
 * Returns a new aspath where neighbor_as is replaced by local_as.
 */
u_char *
aspath_override(struct aspath *asp, uint32_t neighbor_as, uint32_t local_as,
    uint16_t *len)
{
	u_char		*p, *seg, *nseg;
	uint32_t	 as;
	uint16_t	 l, seg_size;
	uint8_t		 i, seg_len, seg_type;

	if (asp->len == 0) {
		*len = 0;
		return (NULL);
	}

	p = malloc(asp->len);
	if (p == NULL)
		fatal("%s", __func__);

	seg = asp->data;
	nseg = p;
	for (l = asp->len; l > 0; l -= seg_size, seg += seg_size) {
		*nseg++ = seg_type = seg[0];
		*nseg++ = seg_len = seg[1];
		seg_size = 2 + sizeof(uint32_t) * seg_len;

		for (i = 0; i < seg_len; i++) {
			as = aspath_extract(seg, i);
			if (as == neighbor_as)
				as = local_as;
			as = htonl(as);
			memcpy(nseg, &as, sizeof(as));
			nseg += sizeof(as);
		}

		if (seg_size > l)
			fatalx("%s: would overflow", __func__);
	}

	*len = asp->len;
	return (p);
}

int
aspath_lenmatch(struct aspath *a, enum aslen_spec type, u_int aslen)
{
	uint8_t		*seg;
	uint32_t	 as, lastas = 0;
	u_int		 count = 0;
	uint16_t	 len, seg_size;
	uint8_t		 i, seg_len, seg_type;

	if (type == ASLEN_MAX) {
		if (aslen < aspath_count(a->data, a->len))
			return (1);
		else
			return (0);
	}

	/* type == ASLEN_SEQ */
	seg = a->data;
	for (len = a->len; len > 0; len -= seg_size, seg += seg_size) {
		seg_type = seg[0];
		seg_len = seg[1];
		seg_size = 2 + sizeof(uint32_t) * seg_len;

		for (i = 0; i < seg_len; i++) {
			as = aspath_extract(seg, i);
			if (as == lastas) {
				if (aslen < ++count)
					return (1);
			} else if (seg_type == AS_SET) {
				/* AS path 3 { 4 3 7 } 3 will have count = 3 */
				continue;
			} else
				count = 1;
			lastas = as;
		}

		if (seg_size > len)
			fatalx("%s: would overflow", __func__);
	}
	return (0);
}
