/*-
 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
 *
 * Copyright (c) 2010-2012, by Michael Tuexen. All rights reserved.
 * Copyright (c) 2010-2012, by Randall Stewart. All rights reserved.
 * Copyright (c) 2010-2012, by Robin Seggelmann. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * a) Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * b) Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#if defined(__FreeBSD__) && !defined(__Userspace__)
#include <sys/cdefs.h>
__FBSDID("$FreeBSD: head/sys/netinet/sctp_ss_functions.c 362173 2020-06-14 09:50:00Z tuexen $");
#endif

#include <netinet/sctp_pcb.h>
#if defined(__Userspace__)
#include <netinet/sctp_os_userspace.h>
#endif

/*
 * Default simple round-robin algorithm.
 * Just interates the streams in the order they appear.
 */

static void
sctp_ss_default_add(struct sctp_tcb *, struct sctp_association *,
                    struct sctp_stream_out *,
                    struct sctp_stream_queue_pending *, int);

static void
sctp_ss_default_remove(struct sctp_tcb *, struct sctp_association *,
                       struct sctp_stream_out *,
                       struct sctp_stream_queue_pending *, int);

static void
sctp_ss_default_init(struct sctp_tcb *stcb, struct sctp_association *asoc,
                     int holds_lock)
{
	uint16_t i;

	if (holds_lock == 0) {
		SCTP_TCB_SEND_LOCK(stcb);
	}
	asoc->ss_data.locked_on_sending = NULL;
	asoc->ss_data.last_out_stream = NULL;
	TAILQ_INIT(&asoc->ss_data.out.wheel);
	/*
	 * If there is data in the stream queues already,
	 * the scheduler of an existing association has
	 * been changed. We need to add all stream queues
	 * to the wheel.
	 */
	for (i = 0; i < stcb->asoc.streamoutcnt; i++) {
		stcb->asoc.ss_functions.sctp_ss_add_to_stream(stcb, &stcb->asoc,
		                                              &stcb->asoc.strmout[i],
		                                              NULL, 1);
	}
	if (holds_lock == 0) {
		SCTP_TCB_SEND_UNLOCK(stcb);
	}
	return;
}

static void
sctp_ss_default_clear(struct sctp_tcb *stcb, struct sctp_association *asoc,
                      int clear_values SCTP_UNUSED, int holds_lock)
{
	if (holds_lock == 0) {
		SCTP_TCB_SEND_LOCK(stcb);
	}
	while (!TAILQ_EMPTY(&asoc->ss_data.out.wheel)) {
		struct sctp_stream_out *strq;

		strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
		TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.rr.next_spoke);
		strq->ss_params.rr.next_spoke.tqe_next = NULL;
		strq->ss_params.rr.next_spoke.tqe_prev = NULL;
	}
	asoc->ss_data.last_out_stream = NULL;
	if (holds_lock == 0) {
		SCTP_TCB_SEND_UNLOCK(stcb);
	}
	return;
}

static void
sctp_ss_default_init_stream(struct sctp_tcb *stcb, struct sctp_stream_out *strq, struct sctp_stream_out *with_strq)
{
	if (with_strq != NULL) {
		if (stcb->asoc.ss_data.locked_on_sending == with_strq) {
			stcb->asoc.ss_data.locked_on_sending = strq;
		}
		if (stcb->asoc.ss_data.last_out_stream == with_strq) {
			stcb->asoc.ss_data.last_out_stream = strq;
		}
	}
	strq->ss_params.rr.next_spoke.tqe_next = NULL;
	strq->ss_params.rr.next_spoke.tqe_prev = NULL;
	return;
}

static void
sctp_ss_default_add(struct sctp_tcb *stcb, struct sctp_association *asoc,
                    struct sctp_stream_out *strq,
                    struct sctp_stream_queue_pending *sp SCTP_UNUSED, int holds_lock)
{
	if (holds_lock == 0) {
		SCTP_TCB_SEND_LOCK(stcb);
	}
	/* Add to wheel if not already on it and stream queue not empty */
	if (!TAILQ_EMPTY(&strq->outqueue) &&
	    (strq->ss_params.rr.next_spoke.tqe_next == NULL) &&
	    (strq->ss_params.rr.next_spoke.tqe_prev == NULL)) {
		TAILQ_INSERT_TAIL(&asoc->ss_data.out.wheel,
		                  strq, ss_params.rr.next_spoke);
	}
	if (holds_lock == 0) {
		SCTP_TCB_SEND_UNLOCK(stcb);
	}
	return;
}

static int
sctp_ss_default_is_empty(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc)
{
	if (TAILQ_EMPTY(&asoc->ss_data.out.wheel)) {
		return (1);
	} else {
		return (0);
	}
}

static void
sctp_ss_default_remove(struct sctp_tcb *stcb, struct sctp_association *asoc,
                       struct sctp_stream_out *strq,
                       struct sctp_stream_queue_pending *sp SCTP_UNUSED, int holds_lock)
{
	if (holds_lock == 0) {
		SCTP_TCB_SEND_LOCK(stcb);
	}
	/* Remove from wheel if stream queue is empty and actually is on the wheel */
	if (TAILQ_EMPTY(&strq->outqueue) &&
	    (strq->ss_params.rr.next_spoke.tqe_next != NULL ||
	    strq->ss_params.rr.next_spoke.tqe_prev != NULL)) {
		if (asoc->ss_data.last_out_stream == strq) {
			asoc->ss_data.last_out_stream = TAILQ_PREV(asoc->ss_data.last_out_stream,
			                                   sctpwheel_listhead,
			                                   ss_params.rr.next_spoke);
			if (asoc->ss_data.last_out_stream == NULL) {
				asoc->ss_data.last_out_stream = TAILQ_LAST(&asoc->ss_data.out.wheel,
				                                   sctpwheel_listhead);
			}
			if (asoc->ss_data.last_out_stream == strq) {
				asoc->ss_data.last_out_stream = NULL;
			}
		}
		TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.rr.next_spoke);
		strq->ss_params.rr.next_spoke.tqe_next = NULL;
		strq->ss_params.rr.next_spoke.tqe_prev = NULL;
	}
	if (holds_lock == 0) {
		SCTP_TCB_SEND_UNLOCK(stcb);
	}
	return;
}


static struct sctp_stream_out *
sctp_ss_default_select(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net,
                       struct sctp_association *asoc)
{
	struct sctp_stream_out *strq, *strqt;

	if (asoc->ss_data.locked_on_sending) {
		return (asoc->ss_data.locked_on_sending);
	}
	strqt = asoc->ss_data.last_out_stream;
default_again:
	/* Find the next stream to use */
	if (strqt == NULL) {
		strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
	} else {
		strq = TAILQ_NEXT(strqt, ss_params.rr.next_spoke);
		if (strq == NULL) {
			strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
		}
	}

	/* If CMT is off, we must validate that
	 * the stream in question has the first
	 * item pointed towards are network destination
	 * requested by the caller. Note that if we
	 * turn out to be locked to a stream (assigning
	 * TSN's then we must stop, since we cannot
	 * look for another stream with data to send
	 * to that destination). In CMT's case, by
	 * skipping this check, we will send one
	 * data packet towards the requested net.
	 */
	if (net != NULL && strq != NULL &&
	    SCTP_BASE_SYSCTL(sctp_cmt_on_off) == 0) {
		if (TAILQ_FIRST(&strq->outqueue) &&
		    TAILQ_FIRST(&strq->outqueue)->net != NULL &&
		    TAILQ_FIRST(&strq->outqueue)->net != net) {
			if (strq == asoc->ss_data.last_out_stream) {
				return (NULL);
			} else {
				strqt = strq;
				goto default_again;
			}
		}
	}
	return (strq);
}

static void
sctp_ss_default_scheduled(struct sctp_tcb *stcb,
                          struct sctp_nets *net SCTP_UNUSED,
                          struct sctp_association *asoc,
                          struct sctp_stream_out *strq,
                          int moved_how_much SCTP_UNUSED)
{
	struct sctp_stream_queue_pending *sp;

	asoc->ss_data.last_out_stream = strq;
	if (stcb->asoc.idata_supported == 0) {
		sp = TAILQ_FIRST(&strq->outqueue);
		if ((sp != NULL) && (sp->some_taken == 1)) {
			stcb->asoc.ss_data.locked_on_sending = strq;
		} else {
			stcb->asoc.ss_data.locked_on_sending = NULL;
		}
	} else {
		stcb->asoc.ss_data.locked_on_sending = NULL;
	}
	return;
}

static void
sctp_ss_default_packet_done(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net SCTP_UNUSED,
                            struct sctp_association *asoc SCTP_UNUSED)
{
	/* Nothing to be done here */
	return;
}

static int
sctp_ss_default_get_value(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc SCTP_UNUSED,
                          struct sctp_stream_out *strq SCTP_UNUSED, uint16_t *value SCTP_UNUSED)
{
	/* Nothing to be done here */
	return (-1);
}

static int
sctp_ss_default_set_value(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc SCTP_UNUSED,
                          struct sctp_stream_out *strq SCTP_UNUSED, uint16_t value SCTP_UNUSED)
{
	/* Nothing to be done here */
	return (-1);
}

static int
sctp_ss_default_is_user_msgs_incomplete(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc)
{
	struct sctp_stream_out *strq;
	struct sctp_stream_queue_pending *sp;

	if (asoc->stream_queue_cnt != 1) {
		return (0);
	}
	strq = asoc->ss_data.locked_on_sending;
	if (strq == NULL) {
		return (0);
	}
	sp = TAILQ_FIRST(&strq->outqueue);
	if (sp == NULL) {
		return (0);
	}
	return (!sp->msg_is_complete);
}

/*
 * Real round-robin algorithm.
 * Always interates the streams in ascending order.
 */
static void
sctp_ss_rr_add(struct sctp_tcb *stcb, struct sctp_association *asoc,
               struct sctp_stream_out *strq,
               struct sctp_stream_queue_pending *sp SCTP_UNUSED, int holds_lock)
{
	struct sctp_stream_out *strqt;

	if (holds_lock == 0) {
		SCTP_TCB_SEND_LOCK(stcb);
	}
	if (!TAILQ_EMPTY(&strq->outqueue) &&
	    (strq->ss_params.rr.next_spoke.tqe_next == NULL) &&
	    (strq->ss_params.rr.next_spoke.tqe_prev == NULL)) {
		if (TAILQ_EMPTY(&asoc->ss_data.out.wheel)) {
			TAILQ_INSERT_HEAD(&asoc->ss_data.out.wheel, strq, ss_params.rr.next_spoke);
		} else {
			strqt = TAILQ_FIRST(&asoc->ss_data.out.wheel);
			while (strqt != NULL && (strqt->sid < strq->sid)) {
				strqt = TAILQ_NEXT(strqt, ss_params.rr.next_spoke);
			}
			if (strqt != NULL) {
				TAILQ_INSERT_BEFORE(strqt, strq, ss_params.rr.next_spoke);
			} else {
				TAILQ_INSERT_TAIL(&asoc->ss_data.out.wheel, strq, ss_params.rr.next_spoke);
			}
		}
	}
	if (holds_lock == 0) {
		SCTP_TCB_SEND_UNLOCK(stcb);
	}
	return;
}

/*
 * Real round-robin per packet algorithm.
 * Always interates the streams in ascending order and
 * only fills messages of the same stream in a packet.
 */
static struct sctp_stream_out *
sctp_ss_rrp_select(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net SCTP_UNUSED,
                   struct sctp_association *asoc)
{
	return (asoc->ss_data.last_out_stream);
}

static void
sctp_ss_rrp_packet_done(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net,
                        struct sctp_association *asoc)
{
	struct sctp_stream_out *strq, *strqt;

	strqt = asoc->ss_data.last_out_stream;
rrp_again:
	/* Find the next stream to use */
	if (strqt == NULL) {
		strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
	} else {
		strq = TAILQ_NEXT(strqt, ss_params.rr.next_spoke);
		if (strq == NULL) {
			strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
		}
	}

	/* If CMT is off, we must validate that
	 * the stream in question has the first
	 * item pointed towards are network destination
	 * requested by the caller. Note that if we
	 * turn out to be locked to a stream (assigning
	 * TSN's then we must stop, since we cannot
	 * look for another stream with data to send
	 * to that destination). In CMT's case, by
	 * skipping this check, we will send one
	 * data packet towards the requested net.
	 */
	if (net != NULL && strq != NULL &&
	    SCTP_BASE_SYSCTL(sctp_cmt_on_off) == 0) {
		if (TAILQ_FIRST(&strq->outqueue) &&
		    TAILQ_FIRST(&strq->outqueue)->net != NULL &&
		    TAILQ_FIRST(&strq->outqueue)->net != net) {
			if (strq == asoc->ss_data.last_out_stream) {
				strq = NULL;
			} else {
				strqt = strq;
				goto rrp_again;
			}
		}
	}
	asoc->ss_data.last_out_stream = strq;
	return;
}


/*
 * Priority algorithm.
 * Always prefers streams based on their priority id.
 */
static void
sctp_ss_prio_clear(struct sctp_tcb *stcb, struct sctp_association *asoc,
                   int clear_values, int holds_lock)
{
	if (holds_lock == 0) {
		SCTP_TCB_SEND_LOCK(stcb);
	}
	while (!TAILQ_EMPTY(&asoc->ss_data.out.wheel)) {
		struct sctp_stream_out *strq;

		strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
		if (clear_values) {
			strq->ss_params.prio.priority = 0;
		}
		TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.prio.next_spoke);
		strq->ss_params.prio.next_spoke.tqe_next = NULL;
		strq->ss_params.prio.next_spoke.tqe_prev = NULL;

	}
	asoc->ss_data.last_out_stream = NULL;
	if (holds_lock == 0) {
		SCTP_TCB_SEND_UNLOCK(stcb);
	}
	return;
}

static void
sctp_ss_prio_init_stream(struct sctp_tcb *stcb, struct sctp_stream_out *strq, struct sctp_stream_out *with_strq)
{
	if (with_strq != NULL) {
		if (stcb->asoc.ss_data.locked_on_sending == with_strq) {
			stcb->asoc.ss_data.locked_on_sending = strq;
		}
		if (stcb->asoc.ss_data.last_out_stream == with_strq) {
			stcb->asoc.ss_data.last_out_stream = strq;
		}
	}
	strq->ss_params.prio.next_spoke.tqe_next = NULL;
	strq->ss_params.prio.next_spoke.tqe_prev = NULL;
	if (with_strq != NULL) {
		strq->ss_params.prio.priority = with_strq->ss_params.prio.priority;
	} else {
		strq->ss_params.prio.priority = 0;
	}
	return;
}

static void
sctp_ss_prio_add(struct sctp_tcb *stcb, struct sctp_association *asoc,
                 struct sctp_stream_out *strq, struct sctp_stream_queue_pending *sp SCTP_UNUSED,
                 int holds_lock)
{
	struct sctp_stream_out *strqt;

	if (holds_lock == 0) {
		SCTP_TCB_SEND_LOCK(stcb);
	}
	/* Add to wheel if not already on it and stream queue not empty */
	if (!TAILQ_EMPTY(&strq->outqueue) &&
	    (strq->ss_params.prio.next_spoke.tqe_next == NULL) &&
	    (strq->ss_params.prio.next_spoke.tqe_prev == NULL)) {
		if (TAILQ_EMPTY(&asoc->ss_data.out.wheel)) {
			TAILQ_INSERT_HEAD(&asoc->ss_data.out.wheel, strq, ss_params.prio.next_spoke);
		} else {
			strqt = TAILQ_FIRST(&asoc->ss_data.out.wheel);
			while (strqt != NULL && strqt->ss_params.prio.priority < strq->ss_params.prio.priority) {
				strqt = TAILQ_NEXT(strqt, ss_params.prio.next_spoke);
			}
			if (strqt != NULL) {
				TAILQ_INSERT_BEFORE(strqt, strq, ss_params.prio.next_spoke);
			} else {
				TAILQ_INSERT_TAIL(&asoc->ss_data.out.wheel, strq, ss_params.prio.next_spoke);
			}
		}
	}
	if (holds_lock == 0) {
		SCTP_TCB_SEND_UNLOCK(stcb);
	}
	return;
}

static void
sctp_ss_prio_remove(struct sctp_tcb *stcb, struct sctp_association *asoc,
                    struct sctp_stream_out *strq, struct sctp_stream_queue_pending *sp SCTP_UNUSED,
                    int holds_lock)
{
	if (holds_lock == 0) {
		SCTP_TCB_SEND_LOCK(stcb);
	}
	/* Remove from wheel if stream queue is empty and actually is on the wheel */
	if (TAILQ_EMPTY(&strq->outqueue) &&
	    (strq->ss_params.prio.next_spoke.tqe_next != NULL ||
	    strq->ss_params.prio.next_spoke.tqe_prev != NULL)) {
		if (asoc->ss_data.last_out_stream == strq) {
			asoc->ss_data.last_out_stream = TAILQ_PREV(asoc->ss_data.last_out_stream, sctpwheel_listhead,
			                                   ss_params.prio.next_spoke);
			if (asoc->ss_data.last_out_stream == NULL) {
				asoc->ss_data.last_out_stream = TAILQ_LAST(&asoc->ss_data.out.wheel,
				                                   sctpwheel_listhead);
			}
			if (asoc->ss_data.last_out_stream == strq) {
				asoc->ss_data.last_out_stream = NULL;
			}
		}
		TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.prio.next_spoke);
		strq->ss_params.prio.next_spoke.tqe_next = NULL;
		strq->ss_params.prio.next_spoke.tqe_prev = NULL;
	}
	if (holds_lock == 0) {
		SCTP_TCB_SEND_UNLOCK(stcb);
	}
	return;
}

static struct sctp_stream_out*
sctp_ss_prio_select(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net,
                    struct sctp_association *asoc)
{
	struct sctp_stream_out *strq, *strqt, *strqn;

	if (asoc->ss_data.locked_on_sending) {
		return (asoc->ss_data.locked_on_sending);
	}
	strqt = asoc->ss_data.last_out_stream;
prio_again:
	/* Find the next stream to use */
	if (strqt == NULL) {
		strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
	} else {
		strqn = TAILQ_NEXT(strqt, ss_params.prio.next_spoke);
		if (strqn != NULL &&
		    strqn->ss_params.prio.priority == strqt->ss_params.prio.priority) {
			strq = strqn;
		} else {
			strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
		}
	}

	/* If CMT is off, we must validate that
	 * the stream in question has the first
	 * item pointed towards are network destination
	 * requested by the caller. Note that if we
	 * turn out to be locked to a stream (assigning
	 * TSN's then we must stop, since we cannot
	 * look for another stream with data to send
	 * to that destination). In CMT's case, by
	 * skipping this check, we will send one
	 * data packet towards the requested net.
	 */
	if (net != NULL && strq != NULL &&
	    SCTP_BASE_SYSCTL(sctp_cmt_on_off) == 0) {
		if (TAILQ_FIRST(&strq->outqueue) &&
		    TAILQ_FIRST(&strq->outqueue)->net != NULL &&
		    TAILQ_FIRST(&strq->outqueue)->net != net) {
			if (strq == asoc->ss_data.last_out_stream) {
				return (NULL);
			} else {
				strqt = strq;
				goto prio_again;
			}
		}
	}
	return (strq);
}

static int
sctp_ss_prio_get_value(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc SCTP_UNUSED,
                       struct sctp_stream_out *strq, uint16_t *value)
{
	if (strq == NULL) {
		return (-1);
	}
	*value = strq->ss_params.prio.priority;
	return (1);
}

static int
sctp_ss_prio_set_value(struct sctp_tcb *stcb, struct sctp_association *asoc,
                       struct sctp_stream_out *strq, uint16_t value)
{
	if (strq == NULL) {
		return (-1);
	}
	strq->ss_params.prio.priority = value;
	sctp_ss_prio_remove(stcb, asoc, strq, NULL, 1);
	sctp_ss_prio_add(stcb, asoc, strq, NULL, 1);
	return (1);
}

/*
 * Fair bandwidth algorithm.
 * Maintains an equal troughput per stream.
 */
static void
sctp_ss_fb_clear(struct sctp_tcb *stcb, struct sctp_association *asoc,
                   int clear_values, int holds_lock)
{
	if (holds_lock == 0) {
		SCTP_TCB_SEND_LOCK(stcb);
	}
	while (!TAILQ_EMPTY(&asoc->ss_data.out.wheel)) {
		struct sctp_stream_out *strq;

		strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
		if (clear_values) {
			strq->ss_params.fb.rounds = -1;
		}
		TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.fb.next_spoke);
		strq->ss_params.fb.next_spoke.tqe_next = NULL;
		strq->ss_params.fb.next_spoke.tqe_prev = NULL;
	}
	asoc->ss_data.last_out_stream = NULL;
	if (holds_lock == 0) {
		SCTP_TCB_SEND_UNLOCK(stcb);
	}
	return;
}

static void
sctp_ss_fb_init_stream(struct sctp_tcb *stcb, struct sctp_stream_out *strq, struct sctp_stream_out *with_strq)
{
	if (with_strq != NULL) {
		if (stcb->asoc.ss_data.locked_on_sending == with_strq) {
			stcb->asoc.ss_data.locked_on_sending = strq;
		}
		if (stcb->asoc.ss_data.last_out_stream == with_strq) {
			stcb->asoc.ss_data.last_out_stream = strq;
		}
	}
	strq->ss_params.fb.next_spoke.tqe_next = NULL;
	strq->ss_params.fb.next_spoke.tqe_prev = NULL;
	if (with_strq != NULL) {
		strq->ss_params.fb.rounds = with_strq->ss_params.fb.rounds;
	} else {
		strq->ss_params.fb.rounds = -1;
	}
	return;
}

static void
sctp_ss_fb_add(struct sctp_tcb *stcb, struct sctp_association *asoc,
               struct sctp_stream_out *strq, struct sctp_stream_queue_pending *sp SCTP_UNUSED,
               int holds_lock)
{
	if (holds_lock == 0) {
		SCTP_TCB_SEND_LOCK(stcb);
	}
	if (!TAILQ_EMPTY(&strq->outqueue) &&
	    (strq->ss_params.fb.next_spoke.tqe_next == NULL) &&
	    (strq->ss_params.fb.next_spoke.tqe_prev == NULL)) {
		if (strq->ss_params.fb.rounds < 0)
			strq->ss_params.fb.rounds = TAILQ_FIRST(&strq->outqueue)->length;
		TAILQ_INSERT_TAIL(&asoc->ss_data.out.wheel, strq, ss_params.fb.next_spoke);
	}
	if (holds_lock == 0) {
		SCTP_TCB_SEND_UNLOCK(stcb);
	}
	return;
}

static void
sctp_ss_fb_remove(struct sctp_tcb *stcb, struct sctp_association *asoc,
                  struct sctp_stream_out *strq, struct sctp_stream_queue_pending *sp SCTP_UNUSED,
                  int holds_lock)
{
	if (holds_lock == 0) {
		SCTP_TCB_SEND_LOCK(stcb);
	}
	/* Remove from wheel if stream queue is empty and actually is on the wheel */
	if (TAILQ_EMPTY(&strq->outqueue) &&
	    (strq->ss_params.fb.next_spoke.tqe_next != NULL ||
	    strq->ss_params.fb.next_spoke.tqe_prev != NULL)) {
		if (asoc->ss_data.last_out_stream == strq) {
			asoc->ss_data.last_out_stream = TAILQ_PREV(asoc->ss_data.last_out_stream, sctpwheel_listhead,
			                                   ss_params.fb.next_spoke);
			if (asoc->ss_data.last_out_stream == NULL) {
				asoc->ss_data.last_out_stream = TAILQ_LAST(&asoc->ss_data.out.wheel,
				                                   sctpwheel_listhead);
			}
			if (asoc->ss_data.last_out_stream == strq) {
				asoc->ss_data.last_out_stream = NULL;
			}
		}
		TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.fb.next_spoke);
		strq->ss_params.fb.next_spoke.tqe_next = NULL;
		strq->ss_params.fb.next_spoke.tqe_prev = NULL;
	}
	if (holds_lock == 0) {
		SCTP_TCB_SEND_UNLOCK(stcb);
	}
	return;
}

static struct sctp_stream_out*
sctp_ss_fb_select(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net,
                  struct sctp_association *asoc)
{
	struct sctp_stream_out *strq = NULL, *strqt;

	if (asoc->ss_data.locked_on_sending) {
		return (asoc->ss_data.locked_on_sending);
	}
	if (asoc->ss_data.last_out_stream == NULL ||
	    TAILQ_FIRST(&asoc->ss_data.out.wheel) == TAILQ_LAST(&asoc->ss_data.out.wheel, sctpwheel_listhead)) {
		strqt = TAILQ_FIRST(&asoc->ss_data.out.wheel);
	} else {
		strqt = TAILQ_NEXT(asoc->ss_data.last_out_stream, ss_params.fb.next_spoke);
	}
	do {
		if ((strqt != NULL) &&
		    ((SCTP_BASE_SYSCTL(sctp_cmt_on_off) > 0) ||
		     (SCTP_BASE_SYSCTL(sctp_cmt_on_off) == 0 &&
		      (net == NULL || (TAILQ_FIRST(&strqt->outqueue) && TAILQ_FIRST(&strqt->outqueue)->net == NULL) ||
		       (net != NULL && TAILQ_FIRST(&strqt->outqueue) && TAILQ_FIRST(&strqt->outqueue)->net != NULL &&
		        TAILQ_FIRST(&strqt->outqueue)->net == net))))) {
			if ((strqt->ss_params.fb.rounds >= 0) && (strq == NULL ||
				strqt->ss_params.fb.rounds < strq->ss_params.fb.rounds)) {
				strq = strqt;
			}
		}
		if (strqt != NULL) {
			strqt = TAILQ_NEXT(strqt, ss_params.fb.next_spoke);
		} else {
			strqt = TAILQ_FIRST(&asoc->ss_data.out.wheel);
		}
	} while (strqt != strq);
	return (strq);
}

static void
sctp_ss_fb_scheduled(struct sctp_tcb *stcb, struct sctp_nets *net SCTP_UNUSED,
                     struct sctp_association *asoc, struct sctp_stream_out *strq,
                     int moved_how_much SCTP_UNUSED)
{
	struct sctp_stream_queue_pending *sp;
	struct sctp_stream_out *strqt;
	int subtract;

	if (stcb->asoc.idata_supported == 0) {
		sp = TAILQ_FIRST(&strq->outqueue);
		if ((sp != NULL) && (sp->some_taken == 1)) {
			stcb->asoc.ss_data.locked_on_sending = strq;
		} else {
			stcb->asoc.ss_data.locked_on_sending = NULL;
		}
	} else {
		stcb->asoc.ss_data.locked_on_sending = NULL;
	}
	subtract = strq->ss_params.fb.rounds;
	TAILQ_FOREACH(strqt, &asoc->ss_data.out.wheel, ss_params.fb.next_spoke) {
		strqt->ss_params.fb.rounds -= subtract;
		if (strqt->ss_params.fb.rounds < 0)
			strqt->ss_params.fb.rounds = 0;
	}
	if (TAILQ_FIRST(&strq->outqueue)) {
		strq->ss_params.fb.rounds = TAILQ_FIRST(&strq->outqueue)->length;
	} else {
		strq->ss_params.fb.rounds = -1;
	}
	asoc->ss_data.last_out_stream = strq;
	return;
}

/*
 * First-come, first-serve algorithm.
 * Maintains the order provided by the application.
 */
static void
sctp_ss_fcfs_add(struct sctp_tcb *stcb, struct sctp_association *asoc,
                 struct sctp_stream_out *strq SCTP_UNUSED,
                 struct sctp_stream_queue_pending *sp, int holds_lock);

static void
sctp_ss_fcfs_init(struct sctp_tcb *stcb, struct sctp_association *asoc,
                  int holds_lock)
{
	uint32_t x, n = 0, add_more = 1;
	struct sctp_stream_queue_pending *sp;
	uint16_t i;

	if (holds_lock == 0) {
		SCTP_TCB_SEND_LOCK(stcb);
	}
	TAILQ_INIT(&asoc->ss_data.out.list);
	/*
	 * If there is data in the stream queues already,
	 * the scheduler of an existing association has
	 * been changed. We can only cycle through the
	 * stream queues and add everything to the FCFS
	 * queue.
	 */
	while (add_more) {
		add_more = 0;
		for (i = 0; i < stcb->asoc.streamoutcnt; i++) {
			sp = TAILQ_FIRST(&stcb->asoc.strmout[i].outqueue);
			x = 0;
			/* Find n. message in current stream queue */
			while (sp != NULL && x < n) {
				sp = TAILQ_NEXT(sp, next);
				x++;
			}
			if (sp != NULL) {
				sctp_ss_fcfs_add(stcb, &stcb->asoc, &stcb->asoc.strmout[i], sp, 1);
				add_more = 1;
			}
		}
		n++;
	}
	if (holds_lock == 0) {
		SCTP_TCB_SEND_UNLOCK(stcb);
	}
	return;
}

static void
sctp_ss_fcfs_clear(struct sctp_tcb *stcb, struct sctp_association *asoc,
                   int clear_values, int holds_lock)
{
	struct sctp_stream_queue_pending *sp;

	if (clear_values) {
		if (holds_lock == 0) {
			SCTP_TCB_SEND_LOCK(stcb);
		}
		while (!TAILQ_EMPTY(&asoc->ss_data.out.list)) {
			sp = TAILQ_FIRST(&asoc->ss_data.out.list);
			TAILQ_REMOVE(&asoc->ss_data.out.list, sp, ss_next);
			sp->ss_next.tqe_next = NULL;
			sp->ss_next.tqe_prev = NULL;
		}
		if (holds_lock == 0) {
			SCTP_TCB_SEND_UNLOCK(stcb);
		}
	}
	return;
}

static void
sctp_ss_fcfs_init_stream(struct sctp_tcb *stcb, struct sctp_stream_out *strq, struct sctp_stream_out *with_strq)
{
	if (with_strq != NULL) {
		if (stcb->asoc.ss_data.locked_on_sending == with_strq) {
			stcb->asoc.ss_data.locked_on_sending = strq;
		}
		if (stcb->asoc.ss_data.last_out_stream == with_strq) {
			stcb->asoc.ss_data.last_out_stream = strq;
		}
	}
	strq->ss_params.fb.next_spoke.tqe_next = NULL;
	strq->ss_params.fb.next_spoke.tqe_prev = NULL;
	return;
}

static void
sctp_ss_fcfs_add(struct sctp_tcb *stcb, struct sctp_association *asoc,
                 struct sctp_stream_out *strq SCTP_UNUSED, struct sctp_stream_queue_pending *sp,
                 int holds_lock)
{
	if (holds_lock == 0) {
		SCTP_TCB_SEND_LOCK(stcb);
	}
	if (sp && (sp->ss_next.tqe_next == NULL) &&
	    (sp->ss_next.tqe_prev == NULL)) {
		TAILQ_INSERT_TAIL(&asoc->ss_data.out.list, sp, ss_next);
	}
	if (holds_lock == 0) {
		SCTP_TCB_SEND_UNLOCK(stcb);
	}
	return;
}

static int
sctp_ss_fcfs_is_empty(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc)
{
	if (TAILQ_EMPTY(&asoc->ss_data.out.list)) {
		return (1);
	} else {
		return (0);
	}
}

static void
sctp_ss_fcfs_remove(struct sctp_tcb *stcb, struct sctp_association *asoc,
                    struct sctp_stream_out *strq SCTP_UNUSED, struct sctp_stream_queue_pending *sp,
                    int holds_lock)
{
	if (holds_lock == 0) {
		SCTP_TCB_SEND_LOCK(stcb);
	}
	if (sp &&
	    ((sp->ss_next.tqe_next != NULL) ||
	     (sp->ss_next.tqe_prev != NULL))) {
		TAILQ_REMOVE(&asoc->ss_data.out.list, sp, ss_next);
		sp->ss_next.tqe_next = NULL;
		sp->ss_next.tqe_prev = NULL;
	}
	if (holds_lock == 0) {
		SCTP_TCB_SEND_UNLOCK(stcb);
	}
	return;
}


static struct sctp_stream_out *
sctp_ss_fcfs_select(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_nets *net,
                    struct sctp_association *asoc)
{
	struct sctp_stream_out *strq;
	struct sctp_stream_queue_pending *sp;

	if (asoc->ss_data.locked_on_sending) {
		return (asoc->ss_data.locked_on_sending);
	}
	sp = TAILQ_FIRST(&asoc->ss_data.out.list);
default_again:
	if (sp != NULL) {
		strq = &asoc->strmout[sp->sid];
	} else {
		strq = NULL;
	}

	/*
	 * If CMT is off, we must validate that
	 * the stream in question has the first
	 * item pointed towards are network destination
	 * requested by the caller. Note that if we
	 * turn out to be locked to a stream (assigning
	 * TSN's then we must stop, since we cannot
	 * look for another stream with data to send
	 * to that destination). In CMT's case, by
	 * skipping this check, we will send one
	 * data packet towards the requested net.
	 */
	if (net != NULL && strq != NULL &&
	    SCTP_BASE_SYSCTL(sctp_cmt_on_off) == 0) {
		if (TAILQ_FIRST(&strq->outqueue) &&
		    TAILQ_FIRST(&strq->outqueue)->net != NULL &&
		    TAILQ_FIRST(&strq->outqueue)->net != net) {
			sp = TAILQ_NEXT(sp, ss_next);
			goto default_again;
		}
	}
	return (strq);
}

const struct sctp_ss_functions sctp_ss_functions[] = {
/* SCTP_SS_DEFAULT */
{
#if defined(_WIN32)
	sctp_ss_default_init,
	sctp_ss_default_clear,
	sctp_ss_default_init_stream,
	sctp_ss_default_add,
	sctp_ss_default_is_empty,
	sctp_ss_default_remove,
	sctp_ss_default_select,
	sctp_ss_default_scheduled,
	sctp_ss_default_packet_done,
	sctp_ss_default_get_value,
	sctp_ss_default_set_value,
	sctp_ss_default_is_user_msgs_incomplete
#else
	.sctp_ss_init = sctp_ss_default_init,
	.sctp_ss_clear = sctp_ss_default_clear,
	.sctp_ss_init_stream = sctp_ss_default_init_stream,
	.sctp_ss_add_to_stream = sctp_ss_default_add,
	.sctp_ss_is_empty = sctp_ss_default_is_empty,
	.sctp_ss_remove_from_stream = sctp_ss_default_remove,
	.sctp_ss_select_stream = sctp_ss_default_select,
	.sctp_ss_scheduled = sctp_ss_default_scheduled,
	.sctp_ss_packet_done = sctp_ss_default_packet_done,
	.sctp_ss_get_value = sctp_ss_default_get_value,
	.sctp_ss_set_value = sctp_ss_default_set_value,
	.sctp_ss_is_user_msgs_incomplete = sctp_ss_default_is_user_msgs_incomplete
#endif
},
/* SCTP_SS_ROUND_ROBIN */
{
#if defined(_WIN32)
	sctp_ss_default_init,
	sctp_ss_default_clear,
	sctp_ss_default_init_stream,
	sctp_ss_rr_add,
	sctp_ss_default_is_empty,
	sctp_ss_default_remove,
	sctp_ss_default_select,
	sctp_ss_default_scheduled,
	sctp_ss_default_packet_done,
	sctp_ss_default_get_value,
	sctp_ss_default_set_value,
	sctp_ss_default_is_user_msgs_incomplete
#else
	.sctp_ss_init = sctp_ss_default_init,
	.sctp_ss_clear = sctp_ss_default_clear,
	.sctp_ss_init_stream = sctp_ss_default_init_stream,
	.sctp_ss_add_to_stream = sctp_ss_rr_add,
	.sctp_ss_is_empty = sctp_ss_default_is_empty,
	.sctp_ss_remove_from_stream = sctp_ss_default_remove,
	.sctp_ss_select_stream = sctp_ss_default_select,
	.sctp_ss_scheduled = sctp_ss_default_scheduled,
	.sctp_ss_packet_done = sctp_ss_default_packet_done,
	.sctp_ss_get_value = sctp_ss_default_get_value,
	.sctp_ss_set_value = sctp_ss_default_set_value,
	.sctp_ss_is_user_msgs_incomplete = sctp_ss_default_is_user_msgs_incomplete
#endif
},
/* SCTP_SS_ROUND_ROBIN_PACKET */
{
#if defined(_WIN32)
	sctp_ss_default_init,
	sctp_ss_default_clear,
	sctp_ss_default_init_stream,
	sctp_ss_rr_add,
	sctp_ss_default_is_empty,
	sctp_ss_default_remove,
	sctp_ss_rrp_select,
	sctp_ss_default_scheduled,
	sctp_ss_rrp_packet_done,
	sctp_ss_default_get_value,
	sctp_ss_default_set_value,
	sctp_ss_default_is_user_msgs_incomplete
#else
	.sctp_ss_init = sctp_ss_default_init,
	.sctp_ss_clear = sctp_ss_default_clear,
	.sctp_ss_init_stream = sctp_ss_default_init_stream,
	.sctp_ss_add_to_stream = sctp_ss_rr_add,
	.sctp_ss_is_empty = sctp_ss_default_is_empty,
	.sctp_ss_remove_from_stream = sctp_ss_default_remove,
	.sctp_ss_select_stream = sctp_ss_rrp_select,
	.sctp_ss_scheduled = sctp_ss_default_scheduled,
	.sctp_ss_packet_done = sctp_ss_rrp_packet_done,
	.sctp_ss_get_value = sctp_ss_default_get_value,
	.sctp_ss_set_value = sctp_ss_default_set_value,
	.sctp_ss_is_user_msgs_incomplete = sctp_ss_default_is_user_msgs_incomplete
#endif
},
/* SCTP_SS_PRIORITY */
{
#if defined(_WIN32)
	sctp_ss_default_init,
	sctp_ss_prio_clear,
	sctp_ss_prio_init_stream,
	sctp_ss_prio_add,
	sctp_ss_default_is_empty,
	sctp_ss_prio_remove,
	sctp_ss_prio_select,
	sctp_ss_default_scheduled,
	sctp_ss_default_packet_done,
	sctp_ss_prio_get_value,
	sctp_ss_prio_set_value,
	sctp_ss_default_is_user_msgs_incomplete
#else
	.sctp_ss_init = sctp_ss_default_init,
	.sctp_ss_clear = sctp_ss_prio_clear,
	.sctp_ss_init_stream = sctp_ss_prio_init_stream,
	.sctp_ss_add_to_stream = sctp_ss_prio_add,
	.sctp_ss_is_empty = sctp_ss_default_is_empty,
	.sctp_ss_remove_from_stream = sctp_ss_prio_remove,
	.sctp_ss_select_stream = sctp_ss_prio_select,
	.sctp_ss_scheduled = sctp_ss_default_scheduled,
	.sctp_ss_packet_done = sctp_ss_default_packet_done,
	.sctp_ss_get_value = sctp_ss_prio_get_value,
	.sctp_ss_set_value = sctp_ss_prio_set_value,
	.sctp_ss_is_user_msgs_incomplete = sctp_ss_default_is_user_msgs_incomplete
#endif
},
/* SCTP_SS_FAIR_BANDWITH */
{
#if defined(_WIN32)
	sctp_ss_default_init,
	sctp_ss_fb_clear,
	sctp_ss_fb_init_stream,
	sctp_ss_fb_add,
	sctp_ss_default_is_empty,
	sctp_ss_fb_remove,
	sctp_ss_fb_select,
	sctp_ss_fb_scheduled,
	sctp_ss_default_packet_done,
	sctp_ss_default_get_value,
	sctp_ss_default_set_value,
	sctp_ss_default_is_user_msgs_incomplete
#else
	.sctp_ss_init = sctp_ss_default_init,
	.sctp_ss_clear = sctp_ss_fb_clear,
	.sctp_ss_init_stream = sctp_ss_fb_init_stream,
	.sctp_ss_add_to_stream = sctp_ss_fb_add,
	.sctp_ss_is_empty = sctp_ss_default_is_empty,
	.sctp_ss_remove_from_stream = sctp_ss_fb_remove,
	.sctp_ss_select_stream = sctp_ss_fb_select,
	.sctp_ss_scheduled = sctp_ss_fb_scheduled,
	.sctp_ss_packet_done = sctp_ss_default_packet_done,
	.sctp_ss_get_value = sctp_ss_default_get_value,
	.sctp_ss_set_value = sctp_ss_default_set_value,
	.sctp_ss_is_user_msgs_incomplete = sctp_ss_default_is_user_msgs_incomplete
#endif
},
/* SCTP_SS_FIRST_COME */
{
#if defined(_WIN32)
	sctp_ss_fcfs_init,
	sctp_ss_fcfs_clear,
	sctp_ss_fcfs_init_stream,
	sctp_ss_fcfs_add,
	sctp_ss_fcfs_is_empty,
	sctp_ss_fcfs_remove,
	sctp_ss_fcfs_select,
	sctp_ss_default_scheduled,
	sctp_ss_default_packet_done,
	sctp_ss_default_get_value,
	sctp_ss_default_set_value,
	sctp_ss_default_is_user_msgs_incomplete
#else
	.sctp_ss_init = sctp_ss_fcfs_init,
	.sctp_ss_clear = sctp_ss_fcfs_clear,
	.sctp_ss_init_stream = sctp_ss_fcfs_init_stream,
	.sctp_ss_add_to_stream = sctp_ss_fcfs_add,
	.sctp_ss_is_empty = sctp_ss_fcfs_is_empty,
	.sctp_ss_remove_from_stream = sctp_ss_fcfs_remove,
	.sctp_ss_select_stream = sctp_ss_fcfs_select,
	.sctp_ss_scheduled = sctp_ss_default_scheduled,
	.sctp_ss_packet_done = sctp_ss_default_packet_done,
	.sctp_ss_get_value = sctp_ss_default_get_value,
	.sctp_ss_set_value = sctp_ss_default_set_value,
	.sctp_ss_is_user_msgs_incomplete = sctp_ss_default_is_user_msgs_incomplete
#endif
}
};
