/* SPDX-License-Identifier: LGPL-2.1-only */
/*
 * Copyright (c) 2012 Shriram Rajagopalan <rshriram@cs.ubc.ca>
 */

/**
 * @ingroup qdisc
 * @defgroup qdisc_plug Plug/Unplug Traffic (PLUG)
 * @brief
 *
 * Queue traffic until an explicit release command.
 *
 * There are two ways to use this qdisc:
 * 1. A simple "instantaneous" plug/unplug operation, by issuing an alternating
 *    sequence of TCQ_PLUG_BUFFER & TCQ_PLUG_RELEASE_INDEFINITE commands.
 *
 * 2. For network output buffering (a.k.a output commit) functionality.
 *    Output commit property is commonly used by applications using checkpoint
 *    based fault-tolerance to ensure that the checkpoint from which a system
 *    is being restored is consistent w.r.t outside world.
 *
 *    Consider for e.g. Remus - a Virtual Machine checkpointing system,
 *    wherein a VM is checkpointed, say every 50ms. The checkpoint is replicated
 *    asynchronously to the backup host, while the VM continues executing the
 *    next epoch speculatively.
 *
 *    The following is a typical sequence of output buffer operations:
 *       1.At epoch i, start_buffer(i)
 *       2. At end of epoch i (i.e. after 50ms):
 *          2.1 Stop VM and take checkpoint(i).
 *          2.2 start_buffer(i+1) and Resume VM
 *       3. While speculatively executing epoch(i+1), asynchronously replicate
 *          checkpoint(i) to backup host.
 *       4. When checkpoint_ack(i) is received from backup, release_buffer(i)
 *    Thus, this Qdisc would receive the following sequence of commands:
 *       TCQ_PLUG_BUFFER (epoch i)
 *       .. TCQ_PLUG_BUFFER (epoch i+1)
 *       ....TCQ_PLUG_RELEASE_ONE (epoch i)
 *       ......TCQ_PLUG_BUFFER (epoch i+2)
 *       ........
 *
 *
 * State of the queue, when used for network output buffering:
 *
 *                 plug(i+1)            plug(i)          head
 * ------------------+--------------------+---------------->
 *                   |                    |
 *                   |                    |
 * pkts_current_epoch| pkts_last_epoch    |pkts_to_release
 * ----------------->|<--------+--------->|+--------------->
 *                   v                    v
 *
 *
 * @{
 */

#include <netlink-private/netlink.h>
#include <netlink-private/tc.h>
#include <netlink/netlink.h>
#include <netlink/utils.h>
#include <netlink-private/route/tc-api.h>
#include <netlink/route/qdisc/plug.h>

static int plug_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
{
	struct rtnl_plug *plug = data;
	struct tc_plug_qopt opts;

	if (!plug)
		return -NLE_INVAL;

	opts.action = plug->action;
	opts.limit  = plug->limit;

	return nlmsg_append(msg, &opts, sizeof(opts), NL_DONTPAD);
}

/**
 * @name Attribute Modification
 * @{
 */

/**
 * Insert a plug into the qdisc and buffer any incoming
 * network traffic.
 * @arg qdisc		PLUG qdisc to be modified.
 */
int rtnl_qdisc_plug_buffer(struct rtnl_qdisc *qdisc)
{
	struct rtnl_plug *plug;

	if (!(plug = rtnl_tc_data(TC_CAST(qdisc))))
		return -NLE_NOMEM;

	plug->action = TCQ_PLUG_BUFFER;
	return 0;
}

/**
 * Unplug the qdisc, releasing packets from queue head
 * to the last complete buffer, while new traffic
 * continues to be buffered.
 * @arg qdisc		PLUG qdisc to be modified.
 */
int rtnl_qdisc_plug_release_one(struct rtnl_qdisc *qdisc)
{
	struct rtnl_plug *plug;

	if (!(plug = rtnl_tc_data(TC_CAST(qdisc))))
		return -NLE_NOMEM;

	plug->action = TCQ_PLUG_RELEASE_ONE;
	return 0;
}

/**
 * Indefinitely unplug the qdisc, releasing all packets.
 * Network traffic will not be buffered until the next
 * buffer command is issued.
 * @arg qdisc		PLUG qdisc to be modified.
 */
int rtnl_qdisc_plug_release_indefinite(struct rtnl_qdisc *qdisc)
{
	struct rtnl_plug *plug;

	if (!(plug = rtnl_tc_data(TC_CAST(qdisc))))
		return -NLE_NOMEM;

	plug->action = TCQ_PLUG_RELEASE_INDEFINITE;
	return 0;
}

/**
 * Set limit of PLUG qdisc.
 * @arg qdisc		PLUG qdisc to be modified.
 * @arg limit		New limit.
 * @return 0 on success or a negative error code.
 */
int rtnl_qdisc_plug_set_limit(struct rtnl_qdisc *qdisc, int limit)
{
	struct rtnl_plug *plug;
	
	if (!(plug = rtnl_tc_data(TC_CAST(qdisc))))
		return -NLE_NOMEM;
		
	plug->action = TCQ_PLUG_LIMIT;
	plug->limit  = limit;

	return 0;
}

/** @} */

static struct rtnl_tc_ops plug_ops = {
	.to_kind		= "plug",
	.to_type		= RTNL_TC_TYPE_QDISC,
	.to_size		= sizeof(struct rtnl_plug),
	.to_msg_fill		= plug_msg_fill,
};

static void __init plug_init(void)
{
	rtnl_tc_register(&plug_ops);
}

static void __exit plug_exit(void)
{
	rtnl_tc_unregister(&plug_ops);
}

/** @} */
