/*
 * Copyright (C) 2007-2010 B.A.T.M.A.N. contributors:
 *
 * Marek Lindner, Simon Wunderlich
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 *
 * This program 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; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA
 *
 */

#include "main.h"
#include "aggregation.h"
#include "send.h"
#include "routing.h"

/* calculate the size of the hna information for a given packet */
static int hna_len(struct batman_packet *batman_packet)
{
	return batman_packet->num_hna * ETH_ALEN;
}

/* return true if new_packet can be aggregated with forw_packet */
static bool can_aggregate_with(struct batman_packet *new_batman_packet,
			       int packet_len,
			       unsigned long send_time,
			       bool directlink,
			       struct batman_if *if_incoming,
			       struct forw_packet *forw_packet)
{
	struct batman_packet *batman_packet =
		(struct batman_packet *)forw_packet->packet_buff;
	int aggregated_bytes = forw_packet->packet_len + packet_len;

	/**
	 * we can aggregate the current packet to this aggregated packet
	 * if:
	 *
	 * - the send time is within our MAX_AGGREGATION_MS time
	 * - the resulting packet wont be bigger than
	 *   MAX_AGGREGATION_BYTES
	 */

	if (time_before(send_time, forw_packet->send_time) &&
	    time_after_eq(send_time + msecs_to_jiffies(MAX_AGGREGATION_MS),
					forw_packet->send_time) &&
	    (aggregated_bytes <= MAX_AGGREGATION_BYTES)) {

		/**
		 * check aggregation compatibility
		 * -> direct link packets are broadcasted on
		 *    their interface only
		 * -> aggregate packet if the current packet is
		 *    a "global" packet as well as the base
		 *    packet
		 */

		/* packets without direct link flag and high TTL
		 * are flooded through the net  */
		if ((!directlink) &&
		    (!(batman_packet->flags & DIRECTLINK)) &&
		    (batman_packet->ttl != 1) &&

		    /* own packets originating non-primary
		     * interfaces leave only that interface */
		    ((!forw_packet->own) ||
		     (forw_packet->if_incoming->if_num == 0)))
			return true;

		/* if the incoming packet is sent via this one
		 * interface only - we still can aggregate */
		if ((directlink) &&
		    (new_batman_packet->ttl == 1) &&
		    (forw_packet->if_incoming == if_incoming) &&

		    /* packets from direct neighbors or
		     * own secondary interface packets
		     * (= secondary interface packets in general) */
		    (batman_packet->flags & DIRECTLINK ||
		     (forw_packet->own &&
		      forw_packet->if_incoming->if_num != 0)))
			return true;
	}

	return false;
}

#define atomic_dec_not_zero(v)          atomic_add_unless((v), -1, 0)
/* create a new aggregated packet and add this packet to it */
static void new_aggregated_packet(unsigned char *packet_buff,
			   int packet_len,
			   unsigned long send_time,
			   bool direct_link,
			   struct batman_if *if_incoming,
			   int own_packet)
{
	struct forw_packet *forw_packet_aggr;
	unsigned long flags;

	/* own packet should always be scheduled */
	if (!own_packet) {
		if (!atomic_dec_not_zero(&batman_queue_left)) {
			bat_dbg(DBG_BATMAN, "batman packet queue full\n");
			return;
		}
	}

	forw_packet_aggr = kmalloc(sizeof(struct forw_packet), GFP_ATOMIC);
	if (!forw_packet_aggr) {
		if (!own_packet)
			atomic_inc(&batman_queue_left);
		return;
	}

	forw_packet_aggr->packet_buff = kmalloc(MAX_AGGREGATION_BYTES,
						GFP_ATOMIC);
	if (!forw_packet_aggr->packet_buff) {
		if (!own_packet)
			atomic_inc(&batman_queue_left);
		kfree(forw_packet_aggr);
		return;
	}

	INIT_HLIST_NODE(&forw_packet_aggr->list);

	forw_packet_aggr->packet_len = packet_len;
	memcpy(forw_packet_aggr->packet_buff,
	       packet_buff,
	       forw_packet_aggr->packet_len);

	forw_packet_aggr->skb = NULL;
	forw_packet_aggr->own = own_packet;
	forw_packet_aggr->if_incoming = if_incoming;
	forw_packet_aggr->num_packets = 0;
	forw_packet_aggr->direct_link_flags = 0;
	forw_packet_aggr->send_time = send_time;

	/* save packet direct link flag status */
	if (direct_link)
		forw_packet_aggr->direct_link_flags |= 1;

	/* add new packet to packet list */
	spin_lock_irqsave(&forw_bat_list_lock, flags);
	hlist_add_head(&forw_packet_aggr->list, &forw_bat_list);
	spin_unlock_irqrestore(&forw_bat_list_lock, flags);

	/* start timer for this packet */
	INIT_DELAYED_WORK(&forw_packet_aggr->delayed_work,
			  send_outstanding_bat_packet);
	queue_delayed_work(bat_event_workqueue,
			   &forw_packet_aggr->delayed_work,
			   send_time - jiffies);
}

/* aggregate a new packet into the existing aggregation */
static void aggregate(struct forw_packet *forw_packet_aggr,
		      unsigned char *packet_buff,
		      int packet_len,
		      bool direct_link)
{
	memcpy((forw_packet_aggr->packet_buff + forw_packet_aggr->packet_len),
	       packet_buff, packet_len);
	forw_packet_aggr->packet_len += packet_len;
	forw_packet_aggr->num_packets++;

	/* save packet direct link flag status */
	if (direct_link)
		forw_packet_aggr->direct_link_flags |=
			(1 << forw_packet_aggr->num_packets);
}

void add_bat_packet_to_list(struct bat_priv *bat_priv,
			    unsigned char *packet_buff, int packet_len,
			    struct batman_if *if_incoming, char own_packet,
			    unsigned long send_time)
{
	/**
	 * _aggr -> pointer to the packet we want to aggregate with
	 * _pos -> pointer to the position in the queue
	 */
	struct forw_packet *forw_packet_aggr = NULL, *forw_packet_pos = NULL;
	struct hlist_node *tmp_node;
	struct batman_packet *batman_packet =
		(struct batman_packet *)packet_buff;
	bool direct_link = batman_packet->flags & DIRECTLINK ? 1 : 0;
	unsigned long flags;

	/* find position for the packet in the forward queue */
	spin_lock_irqsave(&forw_bat_list_lock, flags);
	/* own packets are not to be aggregated */
	if ((atomic_read(&bat_priv->aggregation_enabled)) && (!own_packet)) {
		hlist_for_each_entry(forw_packet_pos, tmp_node, &forw_bat_list,
				     list) {
			if (can_aggregate_with(batman_packet,
					       packet_len,
					       send_time,
					       direct_link,
					       if_incoming,
					       forw_packet_pos)) {
				forw_packet_aggr = forw_packet_pos;
				break;
			}
		}
	}

	/* nothing to aggregate with - either aggregation disabled or no
	 * suitable aggregation packet found */
	if (forw_packet_aggr == NULL) {
		/* the following section can run without the lock */
		spin_unlock_irqrestore(&forw_bat_list_lock, flags);

		/**
		 * if we could not aggregate this packet with one of the others
		 * we hold it back for a while, so that it might be aggregated
		 * later on
		 */
		if ((!own_packet) &&
		    (atomic_read(&bat_priv->aggregation_enabled)))
			send_time += msecs_to_jiffies(MAX_AGGREGATION_MS);

		new_aggregated_packet(packet_buff, packet_len,
				      send_time, direct_link,
				      if_incoming, own_packet);
	} else {
		aggregate(forw_packet_aggr,
			  packet_buff, packet_len,
			  direct_link);
		spin_unlock_irqrestore(&forw_bat_list_lock, flags);
	}
}

/* unpack the aggregated packets and process them one by one */
void receive_aggr_bat_packet(struct ethhdr *ethhdr, unsigned char *packet_buff,
			     int packet_len, struct batman_if *if_incoming)
{
	struct batman_packet *batman_packet;
	int buff_pos = 0;
	unsigned char *hna_buff;

	batman_packet = (struct batman_packet *)packet_buff;

	while (aggregated_packet(buff_pos, packet_len,
				 batman_packet->num_hna)) {

		/* network to host order for our 16bit seqno, and the
		   orig_interval. */
		batman_packet->seqno = ntohs(batman_packet->seqno);

		hna_buff = packet_buff + buff_pos + BAT_PACKET_LEN;
		receive_bat_packet(ethhdr, batman_packet,
				   hna_buff, hna_len(batman_packet),
				   if_incoming);

		buff_pos += BAT_PACKET_LEN + hna_len(batman_packet);
		batman_packet = (struct batman_packet *)
			(packet_buff + buff_pos);
	}
}
