/* Community attribute related functions.
 * Copyright (C) 1998, 2001 Kunihiro Ishiguro
 *
 * This file is part of GNU Zebra.
 *
 * GNU Zebra is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2, or (at your option) any
 * later version.
 *
 * GNU Zebra is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; see the file COPYING; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <zebra.h>

#include "command.h"
#include "hash.h"
#include "memory.h"
#include "jhash.h"

#include "bgpd/bgp_memory.h"
#include "bgpd/bgp_community.h"

/* Hash of community attribute. */
static struct hash *comhash;

/* Allocate a new communities value.  */
static struct community *community_new(void)
{
	return XCALLOC(MTYPE_COMMUNITY, sizeof(struct community));
}

/* Free communities value.  */
void community_free(struct community **com)
{
	if (!(*com))
		return;

	XFREE(MTYPE_COMMUNITY_VAL, (*com)->val);
	XFREE(MTYPE_COMMUNITY_STR, (*com)->str);

	if ((*com)->json) {
		json_object_free((*com)->json);
		(*com)->json = NULL;
	}

	XFREE(MTYPE_COMMUNITY, (*com));
}

/* Add one community value to the community. */
static void community_add_val(struct community *com, uint32_t val)
{
	com->size++;
	if (com->val)
		com->val = XREALLOC(MTYPE_COMMUNITY_VAL, com->val,
				    com_length(com));
	else
		com->val = XMALLOC(MTYPE_COMMUNITY_VAL, com_length(com));

	val = htonl(val);
	memcpy(com_lastval(com), &val, sizeof(uint32_t));
}

/* Delete one community. */
void community_del_val(struct community *com, uint32_t *val)
{
	int i = 0;
	int c = 0;

	if (!com->val)
		return;

	while (i < com->size) {
		if (memcmp(com->val + i, val, sizeof(uint32_t)) == 0) {
			c = com->size - i - 1;

			if (c > 0)
				memmove(com->val + i, com->val + (i + 1),
					c * sizeof(*val));

			com->size--;

			if (com->size > 0)
				com->val = XREALLOC(MTYPE_COMMUNITY_VAL,
						    com->val, com_length(com));
			else {
				XFREE(MTYPE_COMMUNITY_VAL, com->val);
			}
			return;
		}
		i++;
	}
}

/* Delete all communities listed in com2 from com1 */
struct community *community_delete(struct community *com1,
				   struct community *com2)
{
	int i = 0;

	while (i < com2->size) {
		community_del_val(com1, com2->val + i);
		i++;
	}

	return com1;
}

/* Callback function from qsort(). */
static int community_compare(const void *a1, const void *a2)
{
	uint32_t v1;
	uint32_t v2;

	memcpy(&v1, a1, sizeof(uint32_t));
	memcpy(&v2, a2, sizeof(uint32_t));
	v1 = ntohl(v1);
	v2 = ntohl(v2);

	if (v1 < v2)
		return -1;
	if (v1 > v2)
		return 1;
	return 0;
}

bool community_include(struct community *com, uint32_t val)
{
	int i;

	val = htonl(val);

	for (i = 0; i < com->size; i++)
		if (memcmp(&val, com_nthval(com, i), sizeof(uint32_t)) == 0)
			return true;
	return false;
}

uint32_t community_val_get(struct community *com, int i)
{
	uint8_t *p;
	uint32_t val;

	p = (uint8_t *)com->val;
	p += (i * COMMUNITY_SIZE);

	memcpy(&val, p, sizeof(uint32_t));

	return ntohl(val);
}

/* Sort and uniq given community. */
struct community *community_uniq_sort(struct community *com)
{
	int i;
	struct community *new;
	uint32_t val;

	if (!com)
		return NULL;

	new = community_new();
	new->json = NULL;

	for (i = 0; i < com->size; i++) {
		val = community_val_get(com, i);

		if (!community_include(new, val))
			community_add_val(new, val);
	}

	qsort(new->val, new->size, sizeof(uint32_t), community_compare);

	return new;
}

/* Convert communities attribute to string.

   For Well-known communities value, below keyword is used.

   0x0             "internet"
   0xFFFF0000      "graceful-shutdown"
   0xFFFF0001      "accept-own"
   0xFFFF0002      "route-filter-translated-v4"
   0xFFFF0003      "route-filter-v4"
   0xFFFF0004      "route-filter-translated-v6"
   0xFFFF0005      "route-filter-v6"
   0xFFFF0006      "llgr-stale"
   0xFFFF0007      "no-llgr"
   0xFFFF0008      "accept-own-nexthop"
   0xFFFF029A      "blackhole"
   0xFFFFFF01      "no-export"
   0xFFFFFF02      "no-advertise"
   0xFFFFFF03      "local-AS"
   0xFFFFFF04      "no-peer"

   For other values, "AS:VAL" format is used.  */
static void set_community_string(struct community *com, bool make_json)
{
	int i;
	char *str;
	int len;
	int first;
	uint32_t comval;
	uint16_t as;
	uint16_t val;
	json_object *json_community_list = NULL;
	json_object *json_string = NULL;

	if (!com)
		return;

	if (make_json) {
		com->json = json_object_new_object();
		json_community_list = json_object_new_array();
	}

	/* When communities attribute is empty.  */
	if (com->size == 0) {
		str = XMALLOC(MTYPE_COMMUNITY_STR, 1);
		str[0] = '\0';

		if (make_json) {
			json_object_string_add(com->json, "string", "");
			json_object_object_add(com->json, "list",
					       json_community_list);
		}
		com->str = str;
		return;
	}

	/* Memory allocation is time consuming work.  So we calculate
	   required string length first.  */
	len = 0;

	for (i = 0; i < com->size; i++) {
		memcpy(&comval, com_nthval(com, i), sizeof(uint32_t));
		comval = ntohl(comval);

		switch (comval) {
		case COMMUNITY_INTERNET:
			len += strlen(" internet");
			break;
		case COMMUNITY_GSHUT:
			len += strlen(" graceful-shutdown");
			break;
		case COMMUNITY_ACCEPT_OWN:
			len += strlen(" accept-own");
			break;
		case COMMUNITY_ROUTE_FILTER_TRANSLATED_v4:
			len += strlen(" route-filter-translated-v4");
			break;
		case COMMUNITY_ROUTE_FILTER_v4:
			len += strlen(" route-filter-v4");
			break;
		case COMMUNITY_ROUTE_FILTER_TRANSLATED_v6:
			len += strlen(" route-filter-translated-v6");
			break;
		case COMMUNITY_ROUTE_FILTER_v6:
			len += strlen(" route-filter-v6");
			break;
		case COMMUNITY_LLGR_STALE:
			len += strlen(" llgr-stale");
			break;
		case COMMUNITY_NO_LLGR:
			len += strlen(" no-llgr");
			break;
		case COMMUNITY_ACCEPT_OWN_NEXTHOP:
			len += strlen(" accept-own-nexthop");
			break;
		case COMMUNITY_BLACKHOLE:
			len += strlen(" blackhole");
			break;
		case COMMUNITY_NO_EXPORT:
			len += strlen(" no-export");
			break;
		case COMMUNITY_NO_ADVERTISE:
			len += strlen(" no-advertise");
			break;
		case COMMUNITY_LOCAL_AS:
			len += strlen(" local-AS");
			break;
		case COMMUNITY_NO_PEER:
			len += strlen(" no-peer");
			break;
		default:
			len += strlen(" 65536:65535");
			break;
		}
	}

	/* Allocate memory.  */
	str = XCALLOC(MTYPE_COMMUNITY_STR, len);
	first = 1;

	/* Fill in string.  */
	for (i = 0; i < com->size; i++) {
		memcpy(&comval, com_nthval(com, i), sizeof(uint32_t));
		comval = ntohl(comval);

		if (first)
			first = 0;
		else
			strlcat(str, " ", len);

		switch (comval) {
		case COMMUNITY_INTERNET:
			strlcat(str, "internet", len);
			if (make_json) {
				json_string =
					json_object_new_string("internet");
				json_object_array_add(json_community_list,
						      json_string);
			}
			break;
		case COMMUNITY_GSHUT:
			strlcat(str, "graceful-shutdown", len);
			if (make_json) {
				json_string = json_object_new_string(
					"gracefulShutdown");
				json_object_array_add(json_community_list,
						      json_string);
			}
			break;
		case COMMUNITY_ACCEPT_OWN:
			strlcat(str, "accept-own", len);
			if (make_json) {
				json_string = json_object_new_string(
					"acceptown");
				json_object_array_add(json_community_list,
						      json_string);
			}
			break;
		case COMMUNITY_ROUTE_FILTER_TRANSLATED_v4:
			strlcat(str, "route-filter-translated-v4", len);
			if (make_json) {
				json_string = json_object_new_string(
					"routeFilterTranslatedV4");
				json_object_array_add(json_community_list,
						      json_string);
			}
			break;
		case COMMUNITY_ROUTE_FILTER_v4:
			strlcat(str, "route-filter-v4", len);
			if (make_json) {
				json_string = json_object_new_string(
					"routeFilterV4");
				json_object_array_add(json_community_list,
						      json_string);
			}
			break;
		case COMMUNITY_ROUTE_FILTER_TRANSLATED_v6:
			strlcat(str, "route-filter-translated-v6", len);
			if (make_json) {
				json_string = json_object_new_string(
					"routeFilterTranslatedV6");
				json_object_array_add(json_community_list,
						      json_string);
			}
			break;
		case COMMUNITY_ROUTE_FILTER_v6:
			strlcat(str, "route-filter-v6", len);
			if (make_json) {
				json_string = json_object_new_string(
					"routeFilterV6");
				json_object_array_add(json_community_list,
						      json_string);
			}
			break;
		case COMMUNITY_LLGR_STALE:
			strlcat(str, "llgr-stale", len);
			if (make_json) {
				json_string = json_object_new_string(
					"llgrStale");
				json_object_array_add(json_community_list,
						      json_string);
			}
			break;
		case COMMUNITY_NO_LLGR:
			strlcat(str, "no-llgr", len);
			if (make_json) {
				json_string = json_object_new_string(
					"noLlgr");
				json_object_array_add(json_community_list,
						      json_string);
			}
			break;
		case COMMUNITY_ACCEPT_OWN_NEXTHOP:
			strlcat(str, "accept-own-nexthop", len);
			if (make_json) {
				json_string = json_object_new_string(
					"acceptownnexthop");
				json_object_array_add(json_community_list,
						      json_string);
			}
			break;
		case COMMUNITY_BLACKHOLE:
			strlcat(str, "blackhole", len);
			if (make_json) {
				json_string = json_object_new_string(
					"blackhole");
				json_object_array_add(json_community_list,
						      json_string);
			}
			break;
		case COMMUNITY_NO_EXPORT:
			strlcat(str, "no-export", len);
			if (make_json) {
				json_string =
					json_object_new_string("noExport");
				json_object_array_add(json_community_list,
						      json_string);
			}
			break;
		case COMMUNITY_NO_ADVERTISE:
			strlcat(str, "no-advertise", len);
			if (make_json) {
				json_string =
					json_object_new_string("noAdvertise");
				json_object_array_add(json_community_list,
						      json_string);
			}
			break;
		case COMMUNITY_LOCAL_AS:
			strlcat(str, "local-AS", len);
			if (make_json) {
				json_string = json_object_new_string("localAs");
				json_object_array_add(json_community_list,
						      json_string);
			}
			break;
		case COMMUNITY_NO_PEER:
			strlcat(str, "no-peer", len);
			if (make_json) {
				json_string = json_object_new_string("noPeer");
				json_object_array_add(json_community_list,
						      json_string);
			}
			break;
		default:
			as = (comval >> 16) & 0xFFFF;
			val = comval & 0xFFFF;
			char buf[32];
			snprintf(buf, sizeof(buf), "%u:%d", as, val);
			strlcat(str, buf, len);
			if (make_json) {
				json_string = json_object_new_string(buf);
				json_object_array_add(json_community_list,
						      json_string);
			}
			break;
		}
	}

	if (make_json) {
		json_object_string_add(com->json, "string", str);
		json_object_object_add(com->json, "list", json_community_list);
	}
	com->str = str;
}

/* Intern communities attribute.  */
struct community *community_intern(struct community *com)
{
	struct community *find;

	/* Assert this community structure is not interned. */
	assert(com->refcnt == 0);

	/* Lookup community hash. */
	find = (struct community *)hash_get(comhash, com, hash_alloc_intern);

	/* Arguemnt com is allocated temporary.  So when it is not used in
	   hash, it should be freed.  */
	if (find != com)
		community_free(&com);

	/* Increment refrence counter.  */
	find->refcnt++;

	/* Make string.  */
	if (!find->str)
		set_community_string(find, false);

	return find;
}

/* Free community attribute. */
void community_unintern(struct community **com)
{
	struct community *ret;

	if ((*com)->refcnt)
		(*com)->refcnt--;

	/* Pull off from hash.  */
	if ((*com)->refcnt == 0) {
		/* Community value com must exist in hash. */
		ret = (struct community *)hash_release(comhash, *com);
		assert(ret != NULL);

		community_free(com);
	}
}

/* Create new community attribute. */
struct community *community_parse(uint32_t *pnt, unsigned short length)
{
	struct community tmp;
	struct community *new;

	/* If length is malformed return NULL. */
	if (length % COMMUNITY_SIZE)
		return NULL;

	/* Make temporary community for hash look up. */
	tmp.size = length / COMMUNITY_SIZE;
	tmp.val = pnt;

	new = community_uniq_sort(&tmp);

	return community_intern(new);
}

struct community *community_dup(struct community *com)
{
	struct community *new;

	new = XCALLOC(MTYPE_COMMUNITY, sizeof(struct community));
	new->size = com->size;
	if (new->size) {
		new->val = XMALLOC(MTYPE_COMMUNITY_VAL,
				   com->size * COMMUNITY_SIZE);
		memcpy(new->val, com->val, com->size * COMMUNITY_SIZE);
	} else
		new->val = NULL;
	return new;
}

/* Retrun string representation of communities attribute. */
char *community_str(struct community *com, bool make_json)
{
	if (!com)
		return NULL;

	if (make_json && !com->json && com->str)
		XFREE(MTYPE_COMMUNITY_STR, com->str);

	if (!com->str)
		set_community_string(com, make_json);
	return com->str;
}

/* Make hash value of community attribute. This function is used by
   hash package.*/
unsigned int community_hash_make(const struct community *com)
{
	uint32_t *pnt = com->val;

	return jhash2(pnt, com->size, 0x43ea96c1);
}

bool community_match(const struct community *com1, const struct community *com2)
{
	int i = 0;
	int j = 0;

	if (com1 == NULL && com2 == NULL)
		return true;

	if (com1 == NULL || com2 == NULL)
		return false;

	if (com1->size < com2->size)
		return false;

	/* Every community on com2 needs to be on com1 for this to match */
	while (i < com1->size && j < com2->size) {
		if (memcmp(com1->val + i, com2->val + j, sizeof(uint32_t)) == 0)
			j++;
		i++;
	}

	if (j == com2->size)
		return true;
	else
		return false;
}

/* If two aspath have same value then return 1 else return 0. This
   function is used by hash package. */
bool community_cmp(const struct community *com1, const struct community *com2)
{
	if (com1 == NULL && com2 == NULL)
		return true;
	if (com1 == NULL || com2 == NULL)
		return false;

	if (com1->size == com2->size)
		if (memcmp(com1->val, com2->val, com1->size * COMMUNITY_SIZE)
		    == 0)
			return true;
	return false;
}

/* Add com2 to the end of com1. */
struct community *community_merge(struct community *com1,
				  struct community *com2)
{
	if (com1->val)
		com1->val =
			XREALLOC(MTYPE_COMMUNITY_VAL, com1->val,
				 (com1->size + com2->size) * COMMUNITY_SIZE);
	else
		com1->val = XMALLOC(MTYPE_COMMUNITY_VAL,
				    (com1->size + com2->size) * COMMUNITY_SIZE);

	memcpy(com1->val + com1->size, com2->val, com2->size * COMMUNITY_SIZE);
	com1->size += com2->size;

	return com1;
}

/* Community token enum. */
enum community_token {
	community_token_val,
	community_token_gshut,
	community_token_accept_own,
	community_token_route_filter_translated_v4,
	community_token_route_filter_v4,
	community_token_route_filter_translated_v6,
	community_token_route_filter_v6,
	community_token_llgr_stale,
	community_token_no_llgr,
	community_token_accept_own_nexthop,
	community_token_blackhole,
	community_token_no_export,
	community_token_no_advertise,
	community_token_local_as,
	community_token_no_peer,
	community_token_unknown
};

/* Get next community token from string. */
static const char *
community_gettoken(const char *buf, enum community_token *token, uint32_t *val)
{
	const char *p = buf;

	/* Skip white space. */
	while (isspace((unsigned char)*p))
		p++;

	/* Check the end of the line. */
	if (*p == '\0')
		return NULL;

	/* Well known community string check. */
	if (isalpha((unsigned char)*p)) {
		if (strncmp(p, "internet", strlen("internet")) == 0) {
			*val = COMMUNITY_INTERNET;
			*token = community_token_no_export;
			p += strlen("internet");
			return p;
		}
		if (strncmp(p, "graceful-shutdown", strlen("graceful-shutdown"))
		    == 0) {
			*val = COMMUNITY_GSHUT;
			*token = community_token_gshut;
			p += strlen("graceful-shutdown");
			return p;
		}
		if (strncmp(p, "accept-own-nexthop",
			    strlen("accept-own-nexthop"))
		    == 0) {
			*val = COMMUNITY_ACCEPT_OWN_NEXTHOP;
			*token = community_token_accept_own_nexthop;
			p += strlen("accept-own-nexthop");
			return p;
		}
		if (strncmp(p, "accept-own", strlen("accept-own"))
		    == 0) {
			*val = COMMUNITY_ACCEPT_OWN;
			*token = community_token_accept_own;
			p += strlen("accept-own");
			return p;
		}
		if (strncmp(p, "route-filter-translated-v4",
			strlen("route-filter-translated-v4"))
		    == 0) {
			*val = COMMUNITY_ROUTE_FILTER_TRANSLATED_v4;
			*token = community_token_route_filter_translated_v4;
			p += strlen("route-filter-translated-v4");
			return p;
		}
		if (strncmp(p, "route-filter-v4", strlen("route-filter-v4"))
		    == 0) {
			*val = COMMUNITY_ROUTE_FILTER_v4;
			*token = community_token_route_filter_v4;
			p += strlen("route-filter-v4");
			return p;
		}
		if (strncmp(p, "route-filter-translated-v6",
			strlen("route-filter-translated-v6"))
		    == 0) {
			*val = COMMUNITY_ROUTE_FILTER_TRANSLATED_v6;
			*token = community_token_route_filter_translated_v6;
			p += strlen("route-filter-translated-v6");
			return p;
		}
		if (strncmp(p, "route-filter-v6", strlen("route-filter-v6"))
		    == 0) {
			*val = COMMUNITY_ROUTE_FILTER_v6;
			*token = community_token_route_filter_v6;
			p += strlen("route-filter-v6");
			return p;
		}
		if (strncmp(p, "llgr-stale", strlen("llgr-stale"))
		    == 0) {
			*val = COMMUNITY_LLGR_STALE;
			*token = community_token_llgr_stale;
			p += strlen("llgr-stale");
			return p;
		}
		if (strncmp(p, "no-llgr", strlen("no-llgr"))
		    == 0) {
			*val = COMMUNITY_NO_LLGR;
			*token = community_token_no_llgr;
			p += strlen("no-llgr");
			return p;
		}
		if (strncmp(p, "blackhole", strlen("blackhole"))
		    == 0) {
			*val = COMMUNITY_BLACKHOLE;
			*token = community_token_blackhole;
			p += strlen("blackhole");
			return p;
		}
		if (strncmp(p, "no-export", strlen("no-export")) == 0) {
			*val = COMMUNITY_NO_EXPORT;
			*token = community_token_no_export;
			p += strlen("no-export");
			return p;
		}
		if (strncmp(p, "no-advertise", strlen("no-advertise")) == 0) {
			*val = COMMUNITY_NO_ADVERTISE;
			*token = community_token_no_advertise;
			p += strlen("no-advertise");
			return p;
		}
		if (strncmp(p, "local-AS", strlen("local-AS")) == 0) {
			*val = COMMUNITY_LOCAL_AS;
			*token = community_token_local_as;
			p += strlen("local-AS");
			return p;
		}
		if (strncmp(p, "no-peer", strlen("no-peer")) == 0) {
			*val = COMMUNITY_NO_PEER;
			*token = community_token_no_peer;
			p += strlen("no-peer");
			return p;
		}

		/* Unknown string. */
		*token = community_token_unknown;
		return NULL;
	}

	/* Community value. */
	if (isdigit((unsigned char)*p)) {
		int separator = 0;
		int digit = 0;
		uint32_t community_low = 0;
		uint32_t community_high = 0;

		while (isdigit((unsigned char)*p) || *p == ':') {
			if (*p == ':') {
				if (separator) {
					*token = community_token_unknown;
					return NULL;
				} else {
					separator = 1;
					digit = 0;

					if (community_low > UINT16_MAX) {
						*token =
							community_token_unknown;
						return NULL;
					}

					community_high = community_low << 16;
					community_low = 0;
				}
			} else {
				digit = 1;
				community_low *= 10;
				community_low += (*p - '0');
			}
			p++;
		}
		if (!digit) {
			*token = community_token_unknown;
			return NULL;
		}

		if (community_low > UINT16_MAX) {
			*token = community_token_unknown;
			return NULL;
		}

		*val = community_high + community_low;
		*token = community_token_val;
		return p;
	}
	*token = community_token_unknown;
	return NULL;
}

/* convert string to community structure */
struct community *community_str2com(const char *str)
{
	struct community *com = NULL;
	struct community *com_sort = NULL;
	uint32_t val = 0;
	enum community_token token = community_token_unknown;

	do {
		str = community_gettoken(str, &token, &val);

		switch (token) {
		case community_token_val:
		case community_token_gshut:
		case community_token_accept_own:
		case community_token_route_filter_translated_v4:
		case community_token_route_filter_v4:
		case community_token_route_filter_translated_v6:
		case community_token_route_filter_v6:
		case community_token_llgr_stale:
		case community_token_no_llgr:
		case community_token_accept_own_nexthop:
		case community_token_blackhole:
		case community_token_no_export:
		case community_token_no_advertise:
		case community_token_local_as:
		case community_token_no_peer:
			if (com == NULL) {
				com = community_new();
				com->json = NULL;
			}
			community_add_val(com, val);
			break;
		case community_token_unknown:
			if (com)
				community_free(&com);
			return NULL;
		}
	} while (str);

	com_sort = community_uniq_sort(com);
	community_free(&com);

	return com_sort;
}

/* Return communities hash entry count.  */
unsigned long community_count(void)
{
	return comhash->count;
}

/* Return communities hash.  */
struct hash *community_hash(void)
{
	return comhash;
}

/* Initialize comminity related hash. */
void community_init(void)
{
	comhash =
		hash_create((unsigned int (*)(const void *))community_hash_make,
			    (bool (*)(const void *, const void *))community_cmp,
			    "BGP Community Hash");
}

void community_finish(void)
{
	hash_free(comhash);
	comhash = NULL;
}

static struct community *bgp_aggr_community_lookup(
						struct bgp_aggregate *aggregate,
						struct community *community)
{
	return hash_lookup(aggregate->community_hash, community);
}

static void *bgp_aggr_communty_hash_alloc(void *p)
{
	struct community *ref = (struct community *)p;
	struct community *community = NULL;

	community = community_dup(ref);
	return community;
}

static void bgp_aggr_community_prepare(struct hash_bucket *hb, void *arg)
{
	struct community *hb_community = hb->data;
	struct community **aggr_community = arg;

	if (*aggr_community)
		*aggr_community = community_merge(*aggr_community,
						  hb_community);
	else
		*aggr_community = community_dup(hb_community);
}

void bgp_aggr_community_remove(void *arg)
{
	struct community *community = arg;

	community_free(&community);
}

void bgp_compute_aggregate_community(struct bgp_aggregate *aggregate,
				     struct community *community)
{
	bgp_compute_aggregate_community_hash(aggregate, community);
	bgp_compute_aggregate_community_val(aggregate);
}


void bgp_compute_aggregate_community_hash(struct bgp_aggregate *aggregate,
					  struct community *community)
{
	struct community *aggr_community = NULL;

	if ((aggregate == NULL) || (community == NULL))
		return;

	/* Create hash if not already created.
	 */
	if (aggregate->community_hash == NULL)
		aggregate->community_hash = hash_create(
			(unsigned int (*)(const void *))community_hash_make,
			(bool (*)(const void *, const void *))community_cmp,
			"BGP Aggregator community hash");

	aggr_community = bgp_aggr_community_lookup(aggregate, community);
	if (aggr_community == NULL) {
		/* Insert community into hash.
		 */
		aggr_community = hash_get(aggregate->community_hash, community,
					  bgp_aggr_communty_hash_alloc);
	}

	/* Increment reference counter.
	 */
	aggr_community->refcnt++;
}

void bgp_compute_aggregate_community_val(struct bgp_aggregate *aggregate)
{
	struct community *commerge = NULL;

	if (aggregate == NULL)
		return;

	/* Re-compute aggregate's community.
	 */
	if (aggregate->community)
		community_free(&aggregate->community);
	if (aggregate->community_hash &&
	    aggregate->community_hash->count) {
		hash_iterate(aggregate->community_hash,
			     bgp_aggr_community_prepare,
			     &aggregate->community);
		commerge = aggregate->community;
		aggregate->community = community_uniq_sort(commerge);
		if (commerge)
			community_free(&commerge);
	}
}



void bgp_remove_community_from_aggregate(struct bgp_aggregate *aggregate,
					 struct community *community)
{
	struct community *aggr_community = NULL;
	struct community *ret_comm = NULL;

	if ((!aggregate)
	    || (!aggregate->community_hash)
	    || (!community))
		return;

	/* Look-up the community in the hash.
	 */
	aggr_community = bgp_aggr_community_lookup(aggregate, community);
	if (aggr_community) {
		aggr_community->refcnt--;

		if (aggr_community->refcnt == 0) {
			ret_comm = hash_release(aggregate->community_hash,
						aggr_community);
			community_free(&ret_comm);

			bgp_compute_aggregate_community_val(aggregate);
		}
	}
}

void bgp_remove_comm_from_aggregate_hash(struct bgp_aggregate *aggregate,
		struct community *community)
{

	struct community *aggr_community = NULL;
	struct community *ret_comm = NULL;

	if ((!aggregate)
	    || (!aggregate->community_hash)
	    || (!community))
		return;

	/* Look-up the community in the hash.
	 */
	aggr_community = bgp_aggr_community_lookup(aggregate, community);
	if (aggr_community) {
		aggr_community->refcnt--;

		if (aggr_community->refcnt == 0) {
			ret_comm = hash_release(aggregate->community_hash,
						aggr_community);
			community_free(&ret_comm);
		}
	}
}
