Squashed 'third_party/rawrtc/usrsctp/' content from commit bd1a92db3

Change-Id: If227cd6edd3243ac26044056b7427ae5bca71ef8
git-subtree-dir: third_party/rawrtc/usrsctp
git-subtree-split: bd1a92db338ba1e57453637959a127032bb566ff
diff --git a/usrsctplib/netinet/sctp_ss_functions.c b/usrsctplib/netinet/sctp_ss_functions.c
new file mode 100755
index 0000000..ded79f6
--- /dev/null
+++ b/usrsctplib/netinet/sctp_ss_functions.c
@@ -0,0 +1,1078 @@
+/*-
+ * 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.
+ */
+
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_ss_functions.c 310590 2016-12-26 11:06:41Z 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;
+
+	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, holds_lock);
+	}
+	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 = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+		TAILQ_REMOVE(&asoc->ss_data.out.wheel, TAILQ_FIRST(&asoc->ss_data.out.wheel), 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 SCTP_UNUSED)
+{
+	return (0);
+}
+
+/*
+ * 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 = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+		if (clear_values) {
+			strq->ss_params.prio.priority = 0;
+		}
+		TAILQ_REMOVE(&asoc->ss_data.out.wheel, TAILQ_FIRST(&asoc->ss_data.out.wheel), 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;
+
+	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 = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+		if (clear_values) {
+			strq->ss_params.fb.rounds = -1;
+		}
+		TAILQ_REMOVE(&asoc->ss_data.out.wheel, TAILQ_FIRST(&asoc->ss_data.out.wheel), 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.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, 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;
+
+	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, holds_lock);
+				add_more = 1;
+			}
+		}
+		n++;
+	}
+	return;
+}
+
+static void
+sctp_ss_fcfs_clear(struct sctp_tcb *stcb, struct sctp_association *asoc,
+                   int clear_values, int holds_lock)
+{
+	if (clear_values) {
+		if (holds_lock == 0) {
+			SCTP_TCB_SEND_LOCK(stcb);
+		}
+		while (!TAILQ_EMPTY(&asoc->ss_data.out.list)) {
+			TAILQ_REMOVE(&asoc->ss_data.out.list, TAILQ_FIRST(&asoc->ss_data.out.list), ss_next);
+		}
+		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;
+		}
+	}
+	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);
+	}
+	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;
+
+	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(__Windows__) || defined(__Userspace_os_Windows)
+	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(__Windows__) || defined(__Userspace_os_Windows)
+	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(__Windows__) || defined(__Userspace_os_Windows)
+	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(__Windows__) || defined(__Userspace_os_Windows)
+	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(__Windows__) || defined(__Userspace_os_Windows)
+	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(__Windows__) || defined(__Userspace_os_Windows)
+	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
+}
+};