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/programs/CMakeLists.txt b/programs/CMakeLists.txt
new file mode 100644
index 0000000..491d2ac
--- /dev/null
+++ b/programs/CMakeLists.txt
@@ -0,0 +1,192 @@
+#
+# Copyright (C) 2015-2015 Oleg Alexeenkov
+# Copyright (C) 2015-2015 Felix Weinrank
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. 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.
+# 3. Neither the name of the project nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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.
+#
+
+cmake_minimum_required(VERSION 2.6)
+include_directories(../usrsctplib)
+
+
+#################################################
+# INCLUDE MODULES
+#################################################
+
+include(CheckFunctionExists)
+include(CheckStructHasMember)
+include(CheckIncludeFile)
+include(CMakePushCheckState)
+include(CheckTypeSize)
+
+
+#################################################
+# CHECK INCLUDES
+#################################################
+
+check_include_file(usrsctp.h HAVE_USRSCTP_H)
+
+
+#################################################
+# CHECK STRUCT MEMBERS
+#################################################
+
+check_struct_has_member("struct sockaddr" "sa_len" "sys/types.h;sys/socket.h" HAVE_SA_LEN)
+if (HAVE_SA_LEN)
+    add_definitions(-DHAVE_SA_LEN)
+endif ()
+
+check_struct_has_member("struct sockaddr_in" "sin_len" "sys/types.h;netinet/in.h" HAVE_SIN_LEN)
+if (HAVE_SIN_LEN)
+    add_definitions(-DHAVE_SIN_LEN)
+endif ()
+
+check_struct_has_member("struct sockaddr_in6" "sin6_len" "sys/types.h;netinet/in.h" HAVE_SIN6_LEN)
+if (HAVE_SIN6_LEN)
+    add_definitions(-DHAVE_SIN6_LEN)
+endif ()
+
+check_struct_has_member("struct sockaddr_conn" "sconn_len" "usrsctp.h" HAVE_SCONN_LEN)
+if (HAVE_SCONN_LEN)
+    add_definitions(-DHAVE_SCONN_LEN)
+endif ()
+
+
+#################################################
+# CHECK OPTIONS
+#################################################
+
+option(SCTP_DEBUG "Provide debug information" 1)
+if (SCTP_DEBUG)
+    add_definitions(-DSCTP_DEBUG)
+endif ()
+
+option(INET "Support IPv4 " 1)
+if (INET)
+    add_definitions(-DINET)
+endif ()
+
+option(INET6 "Support IPv6 " 1)
+if (INET6)
+    add_definitions(-DINET6)
+endif ()
+
+option(LINK_STATIC "Link static" 0)
+
+# xxx enable W32 support for shared lib ...
+if (LINK_STATIC OR WIN32)
+    set(LINK_STATIC "usrsctp-static")
+else()
+    set(LINK_STATIC "usrsctp")
+endif ()
+
+option(WERROR "Warning as error" ON)
+
+
+#################################################
+# OS DEPENDENT
+#################################################
+
+if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
+    add_definitions(-D_GNU_SOURCE)
+endif ()
+
+if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+    add_definitions(-D__APPLE_USE_RFC_2292)
+endif ()
+
+
+#################################################
+# MISC
+#################################################
+
+find_package(Threads)
+
+
+#################################################
+# PROGRAMS
+#################################################
+
+SET (CHECK_PROGRAMS
+	client.c
+	datachan_serv.c
+	daytime_server.c
+	discard_server.c
+	echo_server.c
+	ekr_client.c
+	ekr_loop.c
+	ekr_peer.c
+	ekr_server.c
+	http_client.c
+	rtcweb.c
+	test_libmgmt.c
+	test_timer.c
+	tsctp.c
+	http_client_upcall.c
+)
+
+# SETTINGS FOR UNIX COMPILER
+if ("x${CMAKE_C_COMPILER_ID}" STREQUAL "xClang" OR "x${CMAKE_C_COMPILER_ID}" STREQUAL "xGNU")
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pedantic -Wall -std=c99")
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic -Wall -std=c99")
+    if (WERROR)
+        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror")
+        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
+    endif ()
+endif ()
+
+# SETTINGS FOR VISUAL STUDIO COMPILER
+if ("x${CMAKE_C_COMPILER_ID}" STREQUAL "xMSVC")
+    if (CMAKE_C_FLAGS MATCHES "/W[0-4]")
+        string(REGEX REPLACE "/W[0-4]" "/W3" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
+    else ()
+        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W3")
+    endif ()
+
+    if (WERROR)
+        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /WX")
+    endif ()
+endif ()
+
+foreach (SOURCE_FILE ${CHECK_PROGRAMS})
+    get_filename_component(SOURCE_FILE_WE ${SOURCE_FILE} NAME_WE)
+    add_executable(
+        ${SOURCE_FILE_WE}
+        ${SOURCE_FILE}
+    )
+
+    target_link_libraries(${SOURCE_FILE_WE}
+        ${LINK_STATIC}
+        ${CMAKE_THREAD_LIBS_INIT}
+    )
+
+    install(TARGETS ${SOURCE_FILE_WE}
+            RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}/libusrsctp
+            BUNDLE DESTINATION  ${CMAKE_INSTALL_LIBDIR}/libusrsctp)
+
+    add_test(${SOURCE_FILE_WE} ${SOURCE_FILE_WE})
+endforeach ()
diff --git a/programs/Makefile.am b/programs/Makefile.am
new file mode 100644
index 0000000..45c248f
--- /dev/null
+++ b/programs/Makefile.am
@@ -0,0 +1,62 @@
+#
+# Copyright (C) 2011-2012 Michael Tuexen
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. 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.
+# 3. Neither the name of the project nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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.
+#
+
+AM_CPPFLAGS = -I$(srcdir)/../usrsctplib
+EXTRA_DIST = Makefile.nmake tsctp.c daytime_server.c discard_server.c echo_server.c client.c rtcweb.c ekr_client.c ekr_server.c ekr_loop.c ekr_loop_offload.c test_libmgmt.c http_client.c
+
+noinst_PROGRAMS = tsctp daytime_server discard_server echo_server client rtcweb ekr_client ekr_server ekr_peer ekr_loop ekr_loop_offload test_libmgmt test_timer http_client
+test_libmgmt_SOURCES = test_libmgmt.c
+test_libmgmt_LDADD = ../usrsctplib/libusrsctp.la
+test_timer_SOURCES = test_timer.c
+test_timer_LDADD = ../usrsctplib/libusrsctp.la
+tsctp_SOURCES = tsctp.c
+tsctp_LDADD = ../usrsctplib/libusrsctp.la
+daytime_server_SOURCES = daytime_server.c
+daytime_server_LDADD = ../usrsctplib/libusrsctp.la
+discard_server_SOURCES = discard_server.c
+discard_server_LDADD = ../usrsctplib/libusrsctp.la
+echo_server_SOURCES = echo_server.c
+echo_server_LDADD = ../usrsctplib/libusrsctp.la
+client_SOURCES = client.c
+client_LDADD = ../usrsctplib/libusrsctp.la
+rtcweb_SOURCES = rtcweb.c
+rtcweb_LDADD = ../usrsctplib/libusrsctp.la
+ekr_server_SOURCES = ekr_server.c
+ekr_server_LDADD = ../usrsctplib/libusrsctp.la
+ekr_client_SOURCES = ekr_client.c
+ekr_client_LDADD = ../usrsctplib/libusrsctp.la
+ekr_peer_SOURCES = ekr_peer.c
+ekr_peer_LDADD = ../usrsctplib/libusrsctp.la
+ekr_loop_SOURCES = ekr_loop.c
+ekr_loop_LDADD = ../usrsctplib/libusrsctp.la
+ekr_loop_offload_SOURCES = ekr_loop_offload.c
+ekr_loop_offload_LDADD = ../usrsctplib/libusrsctp.la
+http_client_SOURCES = http_client.c
+http_client_LDADD = ../usrsctplib/libusrsctp.la
diff --git a/programs/Makefile.nmake b/programs/Makefile.nmake
new file mode 100644
index 0000000..01c411c
--- /dev/null
+++ b/programs/Makefile.nmake
@@ -0,0 +1,126 @@
+#

+# Copyright (C) 2011-2012 Michael Tuexen

+#

+# All rights reserved.

+#

+# Redistribution and use in source and binary forms, with or without

+# modification, are permitted provided that the following conditions

+# are met:

+# 1. Redistributions of source code must retain the above copyright

+#    notice, this list of conditions and the following disclaimer.

+# 2. 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.

+# 3. Neither the name of the project nor the names of its contributors

+#    may be used to endorse or promote products derived from this software

+#    without specific prior written permission.

+#

+# THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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.

+#

+

+CFLAGS=/W3 /WX /I..\usrsctplib

+

+CVARSDLL=-DINET -DINET6

+

+LINKFLAGS=/LIBPATH:..\usrsctplib usrsctp.lib

+

+.c.obj::

+	$(CC) $(CVARSDLL) $(CFLAGS) -Fd.\ -c $<

+

+all: \

+	client \

+	daytime_server \

+	discard_server \

+	echo_server \

+	tsctp \

+	rtcweb \

+	ekr_client \

+	ekr_server \

+	ekr_peer \

+	ekr_loop \

+	test_libmgmt \

+	http_client

+

+client:

+	$(CC) $(CFLAGS) $(CVARSDLL) -c client.c

+	link -out:client.exe client.obj $(LINKFLAGS)

+

+daytime_server:

+	$(CC) $(CFLAGS) $(CVARSDLL) -c daytime_server.c

+	link -out:daytime_server.exe daytime_server.obj $(LINKFLAGS)

+

+discard_server:

+	$(CC) $(CFLAGS) $(CVARSDLL) -c discard_server.c

+	link -out:discard_server.exe discard_server.obj $(LINKFLAGS)

+

+echo_server:

+	$(CC) $(CFLAGS) $(CVARSDLL) -c echo_server.c

+	link -out:echo_server.exe echo_server.obj $(LINKFLAGS)

+

+tsctp:

+	$(CC) $(CFLAGS) $(CVARSDLL) -c tsctp.c

+	link -out:tsctp.exe tsctp.obj $(LINKFLAGS)

+

+rtcweb:

+	$(CC) $(CFLAGS) $(CVARSDLL) -c rtcweb.c

+	link -out:rtcweb.exe rtcweb.obj $(LINKFLAGS)

+

+ekr_client:

+	$(CC) $(CFLAGS) $(CVARSDLL) -c ekr_client.c

+	link -out:ekr_client.exe ekr_client.obj $(LINKFLAGS)

+

+ekr_server:

+	$(CC) $(CFLAGS) $(CVARSDLL) -c ekr_server.c

+	link -out:ekr_server.exe ekr_server.obj $(LINKFLAGS)

+

+ekr_peer:

+	$(CC) $(CFLAGS) $(CVARSDLL) -c ekr_peer.c

+	link -out:ekr_peer.exe ekr_peer.obj $(LINKFLAGS)

+

+ekr_loop:

+	$(CC) $(CFLAGS) $(CVARSDLL) -c ekr_loop.c

+	link -out:ekr_loop.exe ekr_loop.obj $(LINKFLAGS)

+

+test_libmgmt:

+	$(CC) $(CFLAGS) $(CVARSDLL) -c test_libmgmt.c

+	link -out:test_libmgmt.exe test_libmgmt.obj $(LINKFLAGS)

+

+http_client:

+	$(CC) $(CFLAGS) $(CVARSDLL) -c http_client.c

+	link -out:http_client.exe http_client.obj $(LINKFLAGS)

+

+clean:

+	del /F client.exe

+	del /F client.obj

+	del /F daytime_server.exe

+	del /F daytime_server.obj

+	del /F discard_server.exe

+	del /F discard_server.obj

+	del /F echo_server.exe

+	del /F echo_server.obj

+	del /F tsctp.exe

+	del /F tsctp.obj

+	del /F rtcweb.exe

+	del /F rtcweb.obj

+	del /F ekr_client.exe

+	del /F ekr_client.obj

+	del /F ekr_server.exe

+	del /F ekr_server.obj

+	del /F ekr_peer.exe

+	del /F ekr_peer.obj

+	del /F ekr_loop.exe

+	del /F ekr_loop.obj

+	del /F test_libmgmt.exe

+	del /F test_libmgmt.obj

+	del /F http_client.exe

+	del /F http_client.obj

diff --git a/programs/client.c b/programs/client.c
new file mode 100644
index 0000000..8dcad6f
--- /dev/null
+++ b/programs/client.c
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2011-2013 Michael Tuexen
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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.
+ */
+
+/*
+ * Usage: client remote_addr remote_port [local_port] [local_encaps_port] [remote_encaps_port]
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+#include <sys/types.h>
+#ifndef _WIN32
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#else
+#include <io.h>
+#endif
+#include <usrsctp.h>
+
+int done = 0;
+
+#ifdef _WIN32
+typedef char* caddr_t;
+#endif
+
+static int
+receive_cb(struct socket *sock, union sctp_sockstore addr, void *data,
+           size_t datalen, struct sctp_rcvinfo rcv, int flags, void *ulp_info)
+{
+	if (data == NULL) {
+		done = 1;
+		usrsctp_close(sock);
+	} else {
+#ifdef _WIN32
+		_write(_fileno(stdout), data, (unsigned int)datalen);
+#else
+		if (write(fileno(stdout), data, datalen) < 0) {
+			perror("write");
+		}
+#endif
+		free(data);
+	}
+	return (1);
+}
+
+void
+debug_printf(const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+	vprintf(format, ap);
+	va_end(ap);
+}
+
+int
+main(int argc, char *argv[])
+{
+	struct socket *sock;
+	struct sockaddr *addr, *addrs;
+	struct sockaddr_in addr4;
+	struct sockaddr_in6 addr6;
+	struct sctp_udpencaps encaps;
+	struct sctpstat stat;
+	char buffer[80];
+	int i, n;
+
+	if (argc < 3) {
+		printf("%s", "Usage: client remote_addr remote_port local_port local_encaps_port remote_encaps_port\n");
+		return (-1);
+	}
+	if (argc > 4) {
+		usrsctp_init(atoi(argv[4]), NULL, debug_printf);
+	} else {
+		usrsctp_init(9899, NULL, debug_printf);
+	}
+#ifdef SCTP_DEBUG
+	usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_NONE);
+#endif
+	usrsctp_sysctl_set_sctp_blackhole(2);
+	if ((sock = usrsctp_socket(AF_INET6, SOCK_STREAM, IPPROTO_SCTP, receive_cb, NULL, 0, NULL)) == NULL) {
+		perror("usrsctp_socket");
+	}
+	if (argc > 3) {
+		memset((void *)&addr6, 0, sizeof(struct sockaddr_in6));
+#ifdef HAVE_SIN6_LEN
+		addr6.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+		addr6.sin6_family = AF_INET6;
+		addr6.sin6_port = htons(atoi(argv[3]));
+		addr6.sin6_addr = in6addr_any;
+		if (usrsctp_bind(sock, (struct sockaddr *)&addr6, sizeof(struct sockaddr_in6)) < 0) {
+			perror("bind");
+		}
+	}
+	if (argc > 5) {
+		memset(&encaps, 0, sizeof(struct sctp_udpencaps));
+		encaps.sue_address.ss_family = AF_INET6;
+		encaps.sue_port = htons(atoi(argv[5]));
+		if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_REMOTE_UDP_ENCAPS_PORT, (const void*)&encaps, (socklen_t)sizeof(struct sctp_udpencaps)) < 0) {
+			perror("setsockopt");
+		}
+	}
+	memset((void *)&addr4, 0, sizeof(struct sockaddr_in));
+	memset((void *)&addr6, 0, sizeof(struct sockaddr_in6));
+#ifdef HAVE_SIN_LEN
+	addr4.sin_len = sizeof(struct sockaddr_in);
+#endif
+#ifdef HAVE_SIN6_LEN
+	addr6.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+	addr4.sin_family = AF_INET;
+	addr6.sin6_family = AF_INET6;
+	addr4.sin_port = htons(atoi(argv[2]));
+	addr6.sin6_port = htons(atoi(argv[2]));
+	if (inet_pton(AF_INET6, argv[1], &addr6.sin6_addr) == 1) {
+		if (usrsctp_connect(sock, (struct sockaddr *)&addr6, sizeof(struct sockaddr_in6)) < 0) {
+			perror("usrsctp_connect");
+		}
+	} else if (inet_pton(AF_INET, argv[1], &addr4.sin_addr) == 1) {
+		if (usrsctp_connect(sock, (struct sockaddr *)&addr4, sizeof(struct sockaddr_in)) < 0) {
+			perror("usrsctp_connect");
+		}
+	} else {
+		printf("Illegal destination address.\n");
+	}
+	if ((n = usrsctp_getladdrs(sock, 0, &addrs)) < 0) {
+		perror("usrsctp_getladdrs");
+	} else {
+		addr = addrs;
+		printf("Local addresses: ");
+		for (i = 0; i < n; i++) {
+			if (i > 0) {
+				printf("%s", ", ");
+			}
+			switch (addr->sa_family) {
+			case AF_INET:
+			{
+				struct sockaddr_in *sin;
+				char buf[INET_ADDRSTRLEN];
+				const char *name;
+
+				sin = (struct sockaddr_in *)addr;
+				name = inet_ntop(AF_INET, &sin->sin_addr, buf, INET_ADDRSTRLEN);
+				printf("%s", name);
+#ifndef HAVE_SA_LEN
+				addr = (struct sockaddr *)((caddr_t)addr + sizeof(struct sockaddr_in));
+#endif
+				break;
+			}
+			case AF_INET6:
+			{
+				struct sockaddr_in6 *sin6;
+				char buf[INET6_ADDRSTRLEN];
+				const char *name;
+
+				sin6 = (struct sockaddr_in6 *)addr;
+				name = inet_ntop(AF_INET6, &sin6->sin6_addr, buf, INET6_ADDRSTRLEN);
+				printf("%s", name);
+#ifndef HAVE_SA_LEN
+				addr = (struct sockaddr *)((caddr_t)addr + sizeof(struct sockaddr_in6));
+#endif
+				break;
+			}
+			default:
+				break;
+			}
+#ifdef HAVE_SA_LEN
+			addr = (struct sockaddr *)((caddr_t)addr + addr->sa_len);
+#endif
+		}
+		printf(".\n");
+		usrsctp_freeladdrs(addrs);
+	}
+	if ((n = usrsctp_getpaddrs(sock, 0, &addrs)) < 0) {
+		perror("usrsctp_getpaddrs");
+	} else {
+		addr = addrs;
+		printf("Peer addresses: ");
+		for (i = 0; i < n; i++) {
+			if (i > 0) {
+				printf("%s", ", ");
+			}
+			switch (addr->sa_family) {
+			case AF_INET:
+			{
+				struct sockaddr_in *sin;
+				char buf[INET_ADDRSTRLEN];
+				const char *name;
+
+				sin = (struct sockaddr_in *)addr;
+				name = inet_ntop(AF_INET, &sin->sin_addr, buf, INET_ADDRSTRLEN);
+				printf("%s", name);
+#ifndef HAVE_SA_LEN
+				addr = (struct sockaddr *)((caddr_t)addr + sizeof(struct sockaddr_in));
+#endif
+				break;
+			}
+			case AF_INET6:
+			{
+				struct sockaddr_in6 *sin6;
+				char buf[INET6_ADDRSTRLEN];
+				const char *name;
+
+				sin6 = (struct sockaddr_in6 *)addr;
+				name = inet_ntop(AF_INET6, &sin6->sin6_addr, buf, INET6_ADDRSTRLEN);
+				printf("%s", name);
+#ifndef HAVE_SA_LEN
+				addr = (struct sockaddr *)((caddr_t)addr + sizeof(struct sockaddr_in6));
+#endif
+				break;
+			}
+			default:
+				break;
+			}
+#ifdef HAVE_SA_LEN
+			addr = (struct sockaddr *)((caddr_t)addr + addr->sa_len);
+#endif
+		}
+		printf(".\n");
+		usrsctp_freepaddrs(addrs);
+	}
+	while ((fgets(buffer, sizeof(buffer), stdin) != NULL) && !done) {
+		usrsctp_sendv(sock, buffer, strlen(buffer), NULL, 0, NULL, 0, SCTP_SENDV_NOINFO, 0);
+	}
+	if (!done) {
+		if (usrsctp_shutdown(sock, SHUT_WR) < 0) {
+			perror("usrsctp_shutdown");
+		}
+	}
+	while (!done) {
+#ifdef _WIN32
+		Sleep(1*1000);
+#else
+		sleep(1);
+#endif
+	}
+	usrsctp_get_stat(&stat);
+	printf("Number of packets (sent/received): (%u/%u).\n",
+	       stat.sctps_outpackets, stat.sctps_inpackets);
+	while (usrsctp_finish() != 0) {
+#ifdef _WIN32
+		Sleep(1000);
+#else
+		sleep(1);
+#endif
+	}
+	return(0);
+}
diff --git a/programs/datachan.h b/programs/datachan.h
new file mode 100644
index 0000000..c3bfbf7
--- /dev/null
+++ b/programs/datachan.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2012-2015 Michael Tuexen
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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.
+ */
+
+#ifndef DATACHAN_H
+
+#define DATA_CHANNEL_PPID_CONTROL   1
+#define DATA_CHANNEL_PPID_DOMSTRING 2
+#define DATA_CHANNEL_PPID_BINARY    3
+
+struct rtcweb_datachannel_msg {
+  uint8_t  msg_type;
+  uint8_t  channel_type;  
+  uint16_t flags;
+  uint16_t reverse_stream;
+  uint16_t reliability_params;
+  /* msg_type_data follows */
+} SCTP_PACKED;
+
+/* msg_type values: */
+#define DATA_CHANNEL_OPEN                     0
+#define DATA_CHANNEL_OPEN_RESPONSE            1
+
+/* channel_type values: */
+#define DATA_CHANNEL_RELIABLE                 0
+#define DATA_CHANNEL_RELIABLE_STREAM          1
+#define DATA_CHANNEL_UNRELIABLE               2
+#define DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT  3
+#define DATA_CHANNEL_PARTIAL_RELIABLE_TIMED   4
+
+/* flags values: */
+#define DATA_CHANNEL_FLAG_OUT_OF_ORDER_ALLOWED 0x0001
+/* all other bits reserved and should be set to 0 */
+
+/* msg_type_data contains: */
+/*
+   for DATA_CHANNEL_OPEN:
+      a DOMString label for the data channel
+   for DATA_CHANNEL_OPEN_RESPONSE:
+      a 16-bit value for errors or 0 for no error
+*/
+
+#define ERR_DATA_CHANNEL_ALREADY_OPEN   0
+#define ERR_DATA_CHANNEL_NONE_AVAILABLE 1
+
+#endif
diff --git a/programs/datachan_serv.c b/programs/datachan_serv.c
new file mode 100644
index 0000000..5ad6861
--- /dev/null
+++ b/programs/datachan_serv.c
@@ -0,0 +1,535 @@
+/*
+ * Copyright (C) 2012-2013 Michael Tuexen
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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.
+ */
+
+/*
+ * Usage: daytime_server [local_encaps_port] [remote_encaps_port]
+ */
+
+#ifdef _WIN32
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#ifndef _WIN32
+#include <unistd.h>
+#include <time.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <sys/errno.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+#include <usrsctp.h>
+
+#include "datachan.h"
+
+#define SIZEOF_ARRAY(x) (sizeof(x)/sizeof((x)[0]))
+
+#define MAX_INPUT_LINE 1024
+#define MAX_CHANNELS 100
+#define INVALID_STREAM 0xFFFF
+
+struct {
+  int8_t   pending;
+  uint8_t  channel_type;
+  uint16_t flags;
+  uint16_t reverse;
+  uint16_t reliability_params;
+  /* FIX! label */
+} channels_out[MAX_CHANNELS];
+
+struct {
+  uint8_t in_use;
+} channels_in[MAX_CHANNELS];
+
+int num_channels = 0;
+
+void
+send_error_response(struct socket* sock,
+                    struct rtcweb_datachannel_msg *msg,
+                    struct sctp_rcvinfo *rcv,
+                    uint16_t error)
+{
+	struct sctp_sndinfo sndinfo;
+  struct rtcweb_datachannel_msg response[2]; // need extra space for error value
+
+  /* ok, send a response */
+  response[0] = *msg;
+  response[0].msg_type = DATA_CHANNEL_OPEN_RESPONSE;
+  response[0].reverse_stream = rcv->rcv_sid;
+  *((uint16_t *) &((&msg->reliability_params)[1])) = htons(error);
+
+  sndinfo.snd_sid = rcv->rcv_sid;
+	sndinfo.snd_flags = 0;
+	sndinfo.snd_ppid = DATA_CHANNEL_PPID_CONTROL;
+	sndinfo.snd_context = 0;
+	sndinfo.snd_assoc_id = 0;
+
+	if (usrsctp_sendv(sock, &response[0], sizeof(response[0])+sizeof(uint16_t), NULL, 0,
+				              (void *)&sndinfo, (socklen_t)sizeof(struct sctp_sndinfo),
+				              SCTP_SENDV_SNDINFO, 0) < 0) {
+  	printf("error %d sending response\n", errno);
+    /* hard to send an error here... */
+  }
+}
+
+static int
+receive_cb(struct socket *sock, union sctp_sockstore addr, void *data,
+           size_t datalen, struct sctp_rcvinfo rcv, int flags, void *ulp_info)
+{
+	struct sctp_sndinfo sndinfo;
+
+	if (data == NULL) {
+		/* done = 1;*/ /* XXX? */
+		usrsctp_close(sock);
+	} else {
+    struct rtcweb_datachannel_msg *msg;
+    uint16_t forward,reverse;
+    uint16_t error;
+    int i;
+
+    switch (rcv.rcv_ppid)
+    {
+      case DATA_CHANNEL_PPID_CONTROL:
+				msg = (struct rtcweb_datachannel_msg *)data;
+        printf("rtcweb_datachannel_msg = \n"
+               "  type\t\t\t%u\n"
+               "  channel_type\t\t%u\n"
+               "  flags\t\t\t0x%04x\n"
+               "  reverse_stream\t%u\n"
+               "  reliability\t\t0x%04x\n"
+               "  label\t\t\t%s\n",
+               msg->msg_type, msg->channel_type, msg->flags,
+               msg->reverse_stream, msg->reliability_params,
+               (char *) &((&msg->reliability_params)[1]));
+
+        switch (msg->msg_type)
+        {
+          case DATA_CHANNEL_OPEN:
+            if (channels_in[rcv.rcv_sid].in_use)
+            {
+              printf("error, channel %u in use\n",rcv.rcv_sid);
+              send_error_response(sock, msg, &rcv, ERR_DATA_CHANNEL_ALREADY_OPEN);
+              break;
+            }
+            reverse = rcv.rcv_sid;
+            if (channels_out[reverse].reverse == INVALID_STREAM &&
+                !channels_out[reverse].pending)
+            {
+              channels_in[reverse].in_use   = 1;
+              channels_out[reverse].reverse = reverse;
+              forward = reverse;
+            }
+            else
+            {
+              /* some sort of glare, find a spare channel */
+              for (i = 0; i < SIZEOF_ARRAY(channels_out); i++)
+              {
+                if (!(channels_out[i].reverse != INVALID_STREAM &&
+                      !channels_out[i].pending))
+                {
+                  break;
+                }
+              }
+              if (i >= SIZEOF_ARRAY(channels_out))
+              {
+                printf("no reverse channel available!\n");
+                channels_in[reverse].in_use = 0;
+                send_error_response(sock, msg, &rcv ,ERR_DATA_CHANNEL_NONE_AVAILABLE);
+                break;
+              }
+              forward = i;
+              channels_out[forward].reverse = reverse;
+              channels_in[reverse].in_use = 1;
+            }
+
+            /* channels_out[reverse].pending = 0; */
+            channels_out[forward].channel_type       = msg->channel_type;
+            channels_out[forward].flags              = msg->flags;
+            channels_out[forward].reliability_params = msg->reliability_params;
+
+            /* Label is in msg_type_data */
+            /* FIX! */
+
+            {
+              struct rtcweb_datachannel_msg response[2]; // need extra space for error value
+              /* ok, send a response */
+              response[0] = *msg;
+              response[0].msg_type = DATA_CHANNEL_OPEN_RESPONSE;
+              response[0].reverse_stream = reverse;
+              *((uint16_t *) &((&msg->reliability_params)[1])) = /*htons*/(0); /* no error */
+              *((char *) &((&msg->reliability_params)[1])) = /*htons*/(0);
+
+              sndinfo.snd_sid = forward;
+							sndinfo.snd_flags = 0;
+							sndinfo.snd_ppid = DATA_CHANNEL_PPID_CONTROL;
+							sndinfo.snd_context = 0;
+							sndinfo.snd_assoc_id = 0;
+
+							if (usrsctp_sendv(sock, &response[0], sizeof(response[0])+sizeof(uint16_t), NULL, 0,
+				                        (void *)&sndinfo, (socklen_t)sizeof(struct sctp_sndinfo),
+				                        SCTP_SENDV_SNDINFO, 0) < 0) {
+                printf("error %d sending response\n",errno);
+                channels_out[forward].reverse = INVALID_STREAM;
+                channels_in[reverse].in_use = 0;
+                /* hard to send an error here... */
+                break;
+              }
+            }
+            num_channels++;
+            printf("successful open of in: %u, out: %u, total channels %d\n",
+                   reverse, forward, num_channels);
+            /* XXX Notify ondatachannel */
+            break;
+
+          case DATA_CHANNEL_OPEN_RESPONSE:
+            if (!channels_out[msg->reverse_stream].pending)
+            {
+              printf("Error: open_response for non-pending channel %u (on %u)\n",
+                     msg->reverse_stream, rcv.rcv_sid);
+              break;
+            }
+            error = ntohs(*((uint16_t *) &((&msg->reliability_params)[1])));
+            if (error)
+            {
+              printf("Error: open_response for %u returned error %u\n",
+                     msg->reverse_stream, error);
+              break;
+            }
+            channels_out[msg->reverse_stream].pending = 0;
+            channels_in[rcv.rcv_sid].in_use = 1;
+            channels_out[msg->reverse_stream].reverse = rcv.rcv_sid;
+            num_channels++;
+            printf("successful open of in: %u, out: %u, total channels %d\n",
+                   rcv.rcv_sid, msg->reverse_stream, num_channels);
+            /* XXX Notify onopened */
+            break;
+
+          default:
+            printf("Error: Unknown message received: %u\n",msg->msg_type);
+            break;
+        }
+        break;
+
+      case DATA_CHANNEL_PPID_DOMSTRING:
+        printf("Received DOMString, len %d\n", (int)datalen);
+        printf("%s\n", (char *)data);
+        /* XXX Notify onmessage */
+        break;
+
+      case DATA_CHANNEL_PPID_BINARY:
+        printf("Received binary, len %d\n", (int)datalen);
+        {
+          char *buffer = (char *)data;
+          printf("0000: %02x %02x %02x %02x %02x %02x %02x %02x \n",
+                 buffer[0],buffer[1],buffer[2],buffer[3],
+                 buffer[4],buffer[5],buffer[6],buffer[7]);
+        }
+        /* XXX Notify onmessage */
+        break;
+
+      default:
+        printf("Error: Unknown ppid %u\n", rcv.rcv_ppid);
+        break;
+    } /* switch ppid */
+
+		free(data);
+	}
+	return (1);
+}
+
+void
+debug_printf(const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+	vprintf(format, ap);
+	va_end(ap);
+}
+
+int
+main(int argc, char *argv[])
+{
+	struct socket *sock, *conn_sock;
+	struct sockaddr_in addr;
+	struct sockaddr_in addr4;
+	struct sockaddr_in6 addr6;
+	struct sctp_udpencaps encaps;
+	socklen_t addr_len;
+  fd_set fds;
+  int i;
+  struct sctp_sndinfo sndinfo;
+	struct sctp_prinfo prinfo;
+	struct sctp_sendv_spa spa;
+
+  for (i = 0; i < SIZEOF_ARRAY(channels_out); i++)
+  {
+    channels_out[i].reverse = INVALID_STREAM;
+    channels_out[i].pending = 0;
+    channels_in[i].in_use = 0;
+  }
+
+  if (argc > 1) {
+		usrsctp_init(atoi(argv[1]), NULL, debug_printf);
+	} else {
+		usrsctp_init(9899, NULL, debug_printf);
+	}
+#ifdef SCTP_DEBUG
+	usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_NONE);
+#endif
+	usrsctp_sysctl_set_sctp_blackhole(2);
+
+	if ((sock = usrsctp_socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP, receive_cb, NULL, 0, NULL)) == NULL) {
+		perror("usrsctp_socket");
+	}
+	if (argc > 2) {
+		memset(&encaps, 0, sizeof(struct sctp_udpencaps));
+		encaps.sue_address.ss_family = AF_INET;
+		encaps.sue_port = htons(atoi(argv[2]));
+		if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_REMOTE_UDP_ENCAPS_PORT, (const void*)&encaps, (socklen_t)sizeof(struct sctp_udpencaps)) < 0) {
+			perror("setsockopt");
+		}
+	}
+
+  if (argc > 4)
+  {
+    /* Acting as the connector */
+    printf("Connecting to %s %s\n",argv[3],argv[4]);
+    memset((void *)&addr4, 0, sizeof(struct sockaddr_in));
+    memset((void *)&addr6, 0, sizeof(struct sockaddr_in6));
+#ifdef HAVE_SIN_LEN
+    addr4.sin_len = sizeof(struct sockaddr_in);
+#endif
+#ifdef HAVE_SIN6_LEN
+    addr6.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+    addr4.sin_family = AF_INET;
+    addr6.sin6_family = AF_INET6;
+    addr4.sin_port = htons(atoi(argv[4]));
+    addr6.sin6_port = htons(atoi(argv[4]));
+    if (inet_pton(AF_INET6, argv[3], &addr6.sin6_addr) == 1) {
+      if (usrsctp_connect(sock, (struct sockaddr *)&addr6, sizeof(struct sockaddr_in6)) < 0) {
+        perror("usrsctp_connect");
+      }
+    } else if (inet_pton(AF_INET, argv[3], &addr4.sin_addr) == 1) {
+      if (usrsctp_connect(sock, (struct sockaddr *)&addr4, sizeof(struct sockaddr_in)) < 0) {
+        perror("usrsctp_connect");
+      }
+    } else {
+      printf("Illegal destination address.\n");
+    }
+
+  }
+  else
+  {
+    /* Acting as the 'server' */
+    memset((void *)&addr, 0, sizeof(struct sockaddr_in));
+#ifdef HAVE_SIN_LEN
+    addr.sin_len = sizeof(struct sockaddr_in);
+#endif
+    addr.sin_family = AF_INET;
+    addr.sin_port = htons(13);
+    addr.sin_addr.s_addr = htonl(INADDR_ANY);
+    printf("Waiting for connections on port %d\n",ntohs(addr.sin_port));
+    if (usrsctp_bind(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0) {
+      perror("usrsctp_bind");
+    }
+    if (usrsctp_listen(sock, 1) < 0) {
+      perror("usrsctp_listen");
+    }
+  }
+
+  while (sock) {
+    if (argc > 4)
+    {
+      conn_sock = sock;
+      sock = NULL;
+      printf("connect() succeeded!  Entering connected mode\n");
+    }
+    else
+    {
+      addr_len = 0;
+      if ((conn_sock = usrsctp_accept(sock, NULL, &addr_len)) == NULL) {
+        continue;
+      }
+      printf("Accepting incoming connection.  Entering connected mode\n");
+    }
+
+
+    /* control loop and sending */
+    FD_ZERO(&fds);
+    for(;;){
+    /*
+      int nr;
+
+      FD_SET(fileno(stdin), &fds);
+      if ((nr = select(fileno(stdin)+1, &fds, NULL, NULL, NULL)) < 0)
+      {
+        if (errno == EINTR)
+          continue;
+        else
+        {
+          printf("select error\n");
+          exit(1);
+        }
+      }
+      if (FD_ISSET(fileno(stdin), &fds) )
+      {
+    */
+      {
+        char inputline[MAX_INPUT_LINE];
+        if (fgets(inputline, MAX_INPUT_LINE, stdin) == NULL) {
+          /* exit on ^d */
+          printf("exiting..\n");
+          exit(0);
+        }
+        else {
+          struct rtcweb_datachannel_msg msg[4]; /* cheat to get space for label */
+          int stream, reliable;
+          size_t len;
+          uint32_t timeout;
+          uint32_t flags = 0;
+
+          if (sscanf(inputline,"open %d %d:",&stream,&reliable) == 2)
+          {
+            if (stream < 0 || stream >= SIZEOF_ARRAY(channels_out))
+            {
+              printf("stream number %d out of range!\n",stream);
+              continue;
+            }
+            if (reliable < 0 || reliable > DATA_CHANNEL_PARTIAL_RELIABLE_TIMED)
+            {
+              printf("reliability type %d invalid!\n",reliable);
+              continue;
+            }
+            if (channels_out[stream].reverse != INVALID_STREAM ||
+                channels_out[stream].pending != 0)
+            {
+              printf("channel %d already in use!\n",stream);
+              continue;
+            }
+
+            channels_out[stream].pending = 1;
+            channels_out[stream].channel_type = reliable;
+            channels_out[stream].flags = 0; /* XXX */
+            channels_out[stream].reverse = INVALID_STREAM;
+            channels_out[stream].reliability_params = 0;
+
+            msg[0].msg_type = DATA_CHANNEL_OPEN;
+            msg[0].channel_type = reliable;
+            msg[0].flags = 0; /* XXX */
+            msg[0].reverse_stream = INVALID_STREAM;
+            msg[0].reliability_params = 0;
+            sprintf((char *) &((&msg[0].reliability_params)[1]),"chan %d",stream);
+            len = sizeof(msg) + strlen((char *) &((&msg[0].reliability_params)[1]));
+
+            timeout = channels_out[stream].reliability_params;
+            switch(channels_out[stream].channel_type)
+            {
+              case DATA_CHANNEL_RELIABLE:
+                flags = 0;
+                prinfo.pr_policy = 0;
+                break;
+              case DATA_CHANNEL_UNRELIABLE:
+              case DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT:
+                flags = SCTP_UNORDERED;
+                prinfo.pr_policy = SCTP_PR_SCTP_RTX;
+                break;
+              case DATA_CHANNEL_PARTIAL_RELIABLE_TIMED:
+                flags = SCTP_UNORDERED;
+                prinfo.pr_policy = SCTP_PR_SCTP_TTL;
+                break;
+              default:
+                flags = 0;
+                prinfo.pr_policy = 0;
+                break;
+            }
+						sndinfo.snd_sid = stream;
+						sndinfo.snd_flags = flags;
+						sndinfo.snd_ppid = DATA_CHANNEL_PPID_CONTROL;
+						sndinfo.snd_context = 0;
+						sndinfo.snd_assoc_id = 0;
+
+						prinfo.pr_value = timeout;
+
+						spa.sendv_sndinfo = sndinfo;
+						spa.sendv_prinfo = prinfo;
+						spa.sendv_flags = SCTP_SEND_SNDINFO_VALID | SCTP_SEND_PRINFO_VALID;
+						if (usrsctp_sendv(conn_sock, &msg[0], len, NULL, 0,
+				              (void *)&spa, (socklen_t)sizeof(struct sctp_sendv_spa), SCTP_SENDV_SPA,
+				              flags) < 0) {
+              printf("error %d sending open\n",errno);
+              channels_out[stream].pending = 0;
+            }
+          }
+          else if (sscanf(inputline,"send %d:",&stream) == 1)
+          {
+            char *str = strchr(inputline,':');
+            if (!str) /* should be impossible */
+              exit(1);
+            str++;
+            sndinfo.snd_sid = stream;
+						sndinfo.snd_flags = 0;
+						sndinfo.snd_ppid = DATA_CHANNEL_PPID_DOMSTRING;
+						sndinfo.snd_context = 0;
+						sndinfo.snd_assoc_id = 0;
+
+						if (usrsctp_sendv(conn_sock, str, strlen(str), NULL, 0,
+				              (void *)&sndinfo, (socklen_t)sizeof(struct sctp_sndinfo),
+				              SCTP_SENDV_SNDINFO, 0) < 0) {
+            	printf("error %d sending string\n",errno);
+            }
+          }
+          else if (sscanf(inputline,"close %d:",&stream) == 1)
+          {
+          }
+          else
+          {
+            printf("unknown command '%s'\n",inputline);
+          }
+        }
+      } /* if FDSET */
+    }
+		usrsctp_close(conn_sock);
+	}
+	usrsctp_close(sock);
+	while (usrsctp_finish() != 0) {
+#ifdef _WIN32
+		Sleep(1000);
+#else
+		sleep(1);
+#endif
+	}
+	return (0);
+}
diff --git a/programs/daytime_server.c b/programs/daytime_server.c
new file mode 100644
index 0000000..18a0fcb
--- /dev/null
+++ b/programs/daytime_server.c
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2012-2013 Michael Tuexen
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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.
+ */
+
+/*
+ * Usage: daytime_server [local_encaps_port] [remote_encaps_port]
+ */
+
+#ifdef _WIN32
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <time.h>
+#ifndef _WIN32
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+#include <usrsctp.h>
+
+void
+debug_printf(const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+	vprintf(format, ap);
+	va_end(ap);
+}
+
+#define DAYTIME_PPID 40
+int
+main(int argc, char *argv[])
+{
+	struct socket *sock, *conn_sock;
+	struct sockaddr_in addr;
+	struct sctp_udpencaps encaps;
+	socklen_t addr_len;
+	char buffer[80];
+	time_t now;
+	struct sctp_sndinfo sndinfo;
+
+	if (argc > 1) {
+		usrsctp_init(atoi(argv[1]), NULL, debug_printf);
+	} else {
+		usrsctp_init(9899, NULL, debug_printf);
+	}
+#ifdef SCTP_DEBUG
+	usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_NONE);
+#endif
+	usrsctp_sysctl_set_sctp_blackhole(2);
+
+	if ((sock = usrsctp_socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP, NULL, NULL, 0, NULL)) == NULL) {
+		perror("usrsctp_socket");
+	}
+	if (argc > 2) {
+		memset(&encaps, 0, sizeof(struct sctp_udpencaps));
+		encaps.sue_address.ss_family = AF_INET;
+		encaps.sue_port = htons(atoi(argv[2]));
+		if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_REMOTE_UDP_ENCAPS_PORT, (const void*)&encaps, (socklen_t)sizeof(struct sctp_udpencaps)) < 0) {
+			perror("setsockopt");
+		}
+	}
+	memset((void *)&addr, 0, sizeof(struct sockaddr_in));
+#ifdef HAVE_SIN_LEN
+	addr.sin_len = sizeof(struct sockaddr_in);
+#endif
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(13);
+	addr.sin_addr.s_addr = htonl(INADDR_ANY);
+	if (usrsctp_bind(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0) {
+		perror("usrsctp_bind");
+	}
+	if (usrsctp_listen(sock, 1) < 0) {
+		perror("usrsctp_listen");
+	}
+	while (1) {
+		addr_len = 0;
+		if ((conn_sock = usrsctp_accept(sock, NULL, &addr_len)) == NULL) {
+			continue;
+		}
+		time(&now);
+#ifdef _WIN32
+		_snprintf(buffer, sizeof(buffer), "%s", ctime(&now));
+#else
+		snprintf(buffer, sizeof(buffer), "%s", ctime(&now));
+#endif
+		sndinfo.snd_sid = 0;
+		sndinfo.snd_flags = 0;
+		sndinfo.snd_ppid = htonl(DAYTIME_PPID);
+		sndinfo.snd_context = 0;
+		sndinfo.snd_assoc_id = 0;
+		usrsctp_sendv(conn_sock, buffer, strlen(buffer), NULL, 0, (void *)&sndinfo,
+		              (socklen_t)sizeof(struct sctp_sndinfo), SCTP_SENDV_SNDINFO, 0);
+		usrsctp_close(conn_sock);
+	}
+	usrsctp_close(sock);
+	while (usrsctp_finish() != 0) {
+#ifdef _WIN32
+		Sleep(1000);
+#else
+		sleep(1);
+#endif
+	}
+	return (0);
+}
diff --git a/programs/discard_server.c b/programs/discard_server.c
new file mode 100644
index 0000000..e7c789d
--- /dev/null
+++ b/programs/discard_server.c
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2011-2013 Michael Tuexen
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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.
+ */
+
+/*
+ * Usage: discard_server [local_encaps_port] [remote_encaps_port]
+ */
+
+#ifdef _WIN32
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#ifndef _WIN32
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+#include <usrsctp.h>
+
+#define BUFFER_SIZE 10240
+
+const int use_cb = 0;
+
+static int
+receive_cb(struct socket *sock, union sctp_sockstore addr, void *data,
+           size_t datalen, struct sctp_rcvinfo rcv, int flags, void *ulp_info)
+{
+	char namebuf[INET6_ADDRSTRLEN];
+	const char *name;
+	uint16_t port;
+
+	if (data) {
+		if (flags & MSG_NOTIFICATION) {
+			printf("Notification of length %d received.\n", (int)datalen);
+		} else {
+			switch (addr.sa.sa_family) {
+#ifdef INET
+			case AF_INET:
+				name = inet_ntop(AF_INET, &addr.sin.sin_addr, namebuf, INET_ADDRSTRLEN);
+				port = ntohs(addr.sin.sin_port);
+				break;
+#endif
+#ifdef INET6
+			case AF_INET6:
+				name = inet_ntop(AF_INET6, &addr.sin6.sin6_addr, namebuf, INET6_ADDRSTRLEN),
+				port = ntohs(addr.sin6.sin6_port);
+				break;
+#endif
+			case AF_CONN:
+#ifdef _WIN32
+				_snprintf(namebuf, INET6_ADDRSTRLEN, "%p", addr.sconn.sconn_addr);
+#else
+				snprintf(namebuf, INET6_ADDRSTRLEN, "%p", addr.sconn.sconn_addr);
+#endif
+				name = namebuf;
+				port = ntohs(addr.sconn.sconn_port);
+				break;
+			default:
+				name = NULL;
+				port = 0;
+				break;
+			}
+			printf("Msg of length %d received from %s:%u on stream %d with SSN %u and TSN %u, PPID %u, context %u.\n",
+			       (int)datalen,
+			       name,
+			       port,
+			       rcv.rcv_sid,
+			       rcv.rcv_ssn,
+			       rcv.rcv_tsn,
+			       ntohl(rcv.rcv_ppid),
+			       rcv.rcv_context);
+		}
+		free(data);
+	}
+	return (1);
+}
+
+void
+debug_printf(const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+	vprintf(format, ap);
+	va_end(ap);
+}
+
+int
+main(int argc, char *argv[])
+{
+	struct socket *sock;
+	struct sockaddr_in6 addr;
+	struct sctp_udpencaps encaps;
+	struct sctp_event event;
+	uint16_t event_types[] = {SCTP_ASSOC_CHANGE,
+	                          SCTP_PEER_ADDR_CHANGE,
+	                          SCTP_REMOTE_ERROR,
+	                          SCTP_SHUTDOWN_EVENT,
+	                          SCTP_ADAPTATION_INDICATION,
+	                          SCTP_PARTIAL_DELIVERY_EVENT};
+	unsigned int i;
+	struct sctp_assoc_value av;
+	const int on = 1;
+	ssize_t n;
+	int flags;
+	socklen_t from_len;
+	char buffer[BUFFER_SIZE];
+	char name[INET6_ADDRSTRLEN];
+	socklen_t infolen;
+	struct sctp_rcvinfo rcv_info;
+	unsigned int infotype;
+
+	if (argc > 1) {
+		usrsctp_init(atoi(argv[1]), NULL, debug_printf);
+	} else {
+		usrsctp_init(9899, NULL, debug_printf);
+	}
+#ifdef SCTP_DEBUG
+	usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL);
+#endif
+	usrsctp_sysctl_set_sctp_blackhole(2);
+
+	if ((sock = usrsctp_socket(AF_INET6, SOCK_SEQPACKET, IPPROTO_SCTP, use_cb?receive_cb:NULL, NULL, 0, NULL)) == NULL) {
+		perror("usrsctp_socket");
+	}
+	if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_I_WANT_MAPPED_V4_ADDR, (const void*)&on, (socklen_t)sizeof(int)) < 0) {
+		perror("usrsctp_setsockopt SCTP_I_WANT_MAPPED_V4_ADDR");
+	}
+	memset(&av, 0, sizeof(struct sctp_assoc_value));
+	av.assoc_id = SCTP_ALL_ASSOC;
+	av.assoc_value = 47;
+
+	if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_CONTEXT, (const void*)&av, (socklen_t)sizeof(struct sctp_assoc_value)) < 0) {
+		perror("usrsctp_setsockopt SCTP_CONTEXT");
+	}
+	if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_RECVRCVINFO, &on, sizeof(int)) < 0) {
+		perror("usrsctp_setsockopt SCTP_RECVRCVINFO");
+	}
+	if (argc > 2) {
+		memset(&encaps, 0, sizeof(struct sctp_udpencaps));
+		encaps.sue_address.ss_family = AF_INET6;
+		encaps.sue_port = htons(atoi(argv[2]));
+		if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_REMOTE_UDP_ENCAPS_PORT, (const void*)&encaps, (socklen_t)sizeof(struct sctp_udpencaps)) < 0) {
+			perror("usrsctp_setsockopt SCTP_REMOTE_UDP_ENCAPS_PORT");
+		}
+	}
+	memset(&event, 0, sizeof(event));
+	event.se_assoc_id = SCTP_FUTURE_ASSOC;
+	event.se_on = 1;
+	for (i = 0; i < (unsigned int)(sizeof(event_types)/sizeof(uint16_t)); i++) {
+		event.se_type = event_types[i];
+		if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_EVENT, &event, sizeof(struct sctp_event)) < 0) {
+			perror("usrsctp_setsockopt SCTP_EVENT");
+		}
+	}
+	memset((void *)&addr, 0, sizeof(struct sockaddr_in6));
+#ifdef HAVE_SIN6_LEN
+	addr.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+	addr.sin6_family = AF_INET6;
+	addr.sin6_port = htons(9);
+	addr.sin6_addr = in6addr_any;
+	if (usrsctp_bind(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_in6)) < 0) {
+		perror("usrsctp_bind");
+	}
+	if (usrsctp_listen(sock, 1) < 0) {
+		perror("usrsctp_listen");
+	}
+	while (1) {
+		if (use_cb) {
+#ifdef _WIN32
+			Sleep(1*1000);
+#else
+			sleep(1);
+#endif
+		} else {
+			from_len = (socklen_t)sizeof(struct sockaddr_in6);
+			flags = 0;
+			infolen = (socklen_t)sizeof(struct sctp_rcvinfo);
+			n = usrsctp_recvv(sock, (void*)buffer, BUFFER_SIZE, (struct sockaddr *) &addr, &from_len, (void *)&rcv_info,
+			                  &infolen, &infotype, &flags);
+			if (n > 0) {
+				if (flags & MSG_NOTIFICATION) {
+					printf("Notification of length %llu received.\n", (unsigned long long)n);
+				} else {
+					if (infotype == SCTP_RECVV_RCVINFO) {
+						printf("Msg of length %llu received from %s:%u on stream %d with SSN %u and TSN %u, PPID %u, context %u, complete %d.\n",
+						        (unsigned long long)n,
+						        inet_ntop(AF_INET6, &addr.sin6_addr, name, INET6_ADDRSTRLEN), ntohs(addr.sin6_port),
+						        rcv_info.rcv_sid,
+						        rcv_info.rcv_ssn,
+						        rcv_info.rcv_tsn,
+						        ntohl(rcv_info.rcv_ppid),
+						        rcv_info.rcv_context,
+						        (flags & MSG_EOR) ? 1 : 0);
+					} else {
+						printf("Msg of length %llu received from %s:%u, complete %d.\n",
+						        (unsigned long long)n,
+						        inet_ntop(AF_INET6, &addr.sin6_addr, name, INET6_ADDRSTRLEN), ntohs(addr.sin6_port),
+						        (flags & MSG_EOR) ? 1 : 0);
+					}
+				}
+			} else {
+				break;
+			}
+		}
+	}
+	usrsctp_close(sock);
+	while (usrsctp_finish() != 0) {
+#ifdef _WIN32
+		Sleep(1000);
+#else
+		sleep(1);
+#endif
+	}
+	return (0);
+}
diff --git a/programs/echo_server.c b/programs/echo_server.c
new file mode 100644
index 0000000..38f1e13
--- /dev/null
+++ b/programs/echo_server.c
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2011-2013 Michael Tuexen
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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.
+ */
+
+/*
+ * Usage: discard_server [local_encaps_port] [remote_encaps_port]
+ */
+
+#ifdef _WIN32
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#ifndef _WIN32
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+#include <usrsctp.h>
+
+#define BUFFER_SIZE 10240
+
+const int use_cb = 0;
+
+static int
+receive_cb(struct socket *sock, union sctp_sockstore addr, void *data,
+           size_t datalen, struct sctp_rcvinfo rcv, int flags, void *ulp_info)
+{
+	char namebuf[INET6_ADDRSTRLEN];
+	const char *name;
+	uint16_t port;
+
+	if (data) {
+		if (flags & MSG_NOTIFICATION) {
+			printf("Notification of length %d received.\n", (int)datalen);
+		} else {
+			switch (addr.sa.sa_family) {
+#ifdef INET
+			case AF_INET:
+				name = inet_ntop(AF_INET, &addr.sin.sin_addr, namebuf, INET_ADDRSTRLEN);
+				port = ntohs(addr.sin.sin_port);
+				break;
+#endif
+#ifdef INET6
+			case AF_INET6:
+				name = inet_ntop(AF_INET6, &addr.sin6.sin6_addr, namebuf, INET6_ADDRSTRLEN),
+				port = ntohs(addr.sin6.sin6_port);
+				break;
+#endif
+			case AF_CONN:
+#ifdef _WIN32
+				_snprintf(namebuf, INET6_ADDRSTRLEN, "%p", addr.sconn.sconn_addr);
+#else
+				snprintf(namebuf, INET6_ADDRSTRLEN, "%p", addr.sconn.sconn_addr);
+#endif
+				name = namebuf;
+				port = ntohs(addr.sconn.sconn_port);
+				break;
+			default:
+				name = NULL;
+				port = 0;
+				break;
+			}
+			printf("Msg of length %d received from %s:%u on stream %d with SSN %u and TSN %u, PPID %u, context %u.\n",
+			       (int)datalen,
+			       name,
+			       port,
+			       rcv.rcv_sid,
+			       rcv.rcv_ssn,
+			       rcv.rcv_tsn,
+			       ntohl(rcv.rcv_ppid),
+			       rcv.rcv_context);
+			if (flags & MSG_EOR) {
+				struct sctp_sndinfo snd_info;
+
+				snd_info.snd_sid = rcv.rcv_sid;
+				snd_info.snd_flags = 0;
+				if (rcv.rcv_flags & SCTP_UNORDERED) {
+					snd_info.snd_flags |= SCTP_UNORDERED;
+				}
+				snd_info.snd_ppid = rcv.rcv_ppid;
+				snd_info.snd_context = 0;
+				snd_info.snd_assoc_id = rcv.rcv_assoc_id;
+				if (usrsctp_sendv(sock, data, datalen, NULL, 0, &snd_info, sizeof(struct sctp_sndinfo), SCTP_SENDV_SNDINFO, 0) < 0) {
+					perror("sctp_sendv");
+				}
+			}
+		}
+		free(data);
+	}
+	return (1);
+}
+
+void
+debug_printf(const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+	vprintf(format, ap);
+	va_end(ap);
+}
+
+int
+main(int argc, char *argv[])
+{
+	struct socket *sock;
+	struct sockaddr_in6 addr;
+	struct sctp_udpencaps encaps;
+	struct sctp_event event;
+	uint16_t event_types[] = {SCTP_ASSOC_CHANGE,
+	                          SCTP_PEER_ADDR_CHANGE,
+	                          SCTP_REMOTE_ERROR,
+	                          SCTP_SHUTDOWN_EVENT,
+	                          SCTP_ADAPTATION_INDICATION,
+	                          SCTP_PARTIAL_DELIVERY_EVENT};
+	unsigned int i;
+	struct sctp_assoc_value av;
+	const int on = 1;
+	ssize_t n;
+	int flags;
+	socklen_t from_len;
+	char buffer[BUFFER_SIZE];
+	char name[INET6_ADDRSTRLEN];
+	socklen_t infolen;
+	struct sctp_rcvinfo rcv_info;
+	unsigned int infotype;
+
+	if (argc > 1) {
+		usrsctp_init(atoi(argv[1]), NULL, debug_printf);
+	} else {
+		usrsctp_init(9899, NULL, debug_printf);
+	}
+#ifdef SCTP_DEBUG
+	usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_NONE);
+#endif
+	usrsctp_sysctl_set_sctp_blackhole(2);
+
+	if ((sock = usrsctp_socket(AF_INET6, SOCK_SEQPACKET, IPPROTO_SCTP, use_cb?receive_cb:NULL, NULL, 0, NULL)) == NULL) {
+		perror("usrsctp_socket");
+	}
+	if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_I_WANT_MAPPED_V4_ADDR, (const void*)&on, (socklen_t)sizeof(int)) < 0) {
+		perror("usrsctp_setsockopt SCTP_I_WANT_MAPPED_V4_ADDR");
+	}
+	memset(&av, 0, sizeof(struct sctp_assoc_value));
+	av.assoc_id = SCTP_ALL_ASSOC;
+	av.assoc_value = 47;
+
+	if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_CONTEXT, (const void*)&av, (socklen_t)sizeof(struct sctp_assoc_value)) < 0) {
+		perror("usrsctp_setsockopt SCTP_CONTEXT");
+	}
+	if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_RECVRCVINFO, &on, sizeof(int)) < 0) {
+		perror("usrsctp_setsockopt SCTP_RECVRCVINFO");
+	}
+	if (argc > 2) {
+		memset(&encaps, 0, sizeof(struct sctp_udpencaps));
+		encaps.sue_address.ss_family = AF_INET6;
+		encaps.sue_port = htons(atoi(argv[2]));
+		if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_REMOTE_UDP_ENCAPS_PORT, (const void*)&encaps, (socklen_t)sizeof(struct sctp_udpencaps)) < 0) {
+			perror("usrsctp_setsockopt SCTP_REMOTE_UDP_ENCAPS_PORT");
+		}
+	}
+	memset(&event, 0, sizeof(event));
+	event.se_assoc_id = SCTP_FUTURE_ASSOC;
+	event.se_on = 1;
+	for (i = 0; i < (unsigned int)(sizeof(event_types)/sizeof(uint16_t)); i++) {
+		event.se_type = event_types[i];
+		if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_EVENT, &event, sizeof(struct sctp_event)) < 0) {
+			perror("usrsctp_setsockopt SCTP_EVENT");
+		}
+	}
+	memset((void *)&addr, 0, sizeof(struct sockaddr_in6));
+#ifdef HAVE_SIN6_LEN
+	addr.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+	addr.sin6_family = AF_INET6;
+	addr.sin6_port = htons(7);
+	addr.sin6_addr = in6addr_any;
+	if (usrsctp_bind(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_in6)) < 0) {
+		perror("usrsctp_bind");
+	}
+	if (usrsctp_listen(sock, 1) < 0) {
+		perror("usrsctp_listen");
+	}
+	while (1) {
+		if (use_cb) {
+#ifdef _WIN32
+			Sleep(1*1000);
+#else
+			sleep(1);
+#endif
+		} else {
+			from_len = (socklen_t)sizeof(struct sockaddr_in6);
+			flags = 0;
+			infolen = (socklen_t)sizeof(struct sctp_rcvinfo);
+			n = usrsctp_recvv(sock, (void*)buffer, BUFFER_SIZE, (struct sockaddr *) &addr, &from_len, (void *)&rcv_info,
+			                  &infolen, &infotype, &flags);
+			if (n > 0) {
+				if (flags & MSG_NOTIFICATION) {
+					printf("Notification of length %llu received.\n", (unsigned long long)n);
+				} else {
+					if (infotype == SCTP_RECVV_RCVINFO) {
+						printf("Msg of length %llu received from %s:%u on stream %d with SSN %u and TSN %u, PPID %u, context %u, complete %d.\n",
+						        (unsigned long long)n,
+						        inet_ntop(AF_INET6, &addr.sin6_addr, name, INET6_ADDRSTRLEN), ntohs(addr.sin6_port),
+						        rcv_info.rcv_sid,
+						        rcv_info.rcv_ssn,
+						        rcv_info.rcv_tsn,
+						        ntohl(rcv_info.rcv_ppid),
+						        rcv_info.rcv_context,
+						        (flags & MSG_EOR) ? 1 : 0);
+						if (flags & MSG_EOR) {
+							struct sctp_sndinfo snd_info;
+
+							snd_info.snd_sid = rcv_info.rcv_sid;
+							snd_info.snd_flags = 0;
+							if (rcv_info.rcv_flags & SCTP_UNORDERED) {
+								snd_info.snd_flags |= SCTP_UNORDERED;
+							}
+							snd_info.snd_ppid = rcv_info.rcv_ppid;
+							snd_info.snd_context = 0;
+							snd_info.snd_assoc_id = rcv_info.rcv_assoc_id;
+							if (usrsctp_sendv(sock, buffer, (size_t)n, NULL, 0, &snd_info, (socklen_t)sizeof(struct sctp_sndinfo), SCTP_SENDV_SNDINFO, 0) < 0) {
+								perror("sctp_sendv");
+							}
+						}
+					} else {
+						printf("Msg of length %llu received from %s:%u, complete %d.\n",
+						        (unsigned long long)n,
+						        inet_ntop(AF_INET6, &addr.sin6_addr, name, INET6_ADDRSTRLEN), ntohs(addr.sin6_port),
+						        (flags & MSG_EOR) ? 1 : 0);
+					}
+				}
+			} else {
+				break;
+			}
+		}
+	}
+	usrsctp_close(sock);
+	while (usrsctp_finish() != 0) {
+#ifdef _WIN32
+		Sleep(1000);
+#else
+		sleep(1);
+#endif
+	}
+	return (0);
+}
diff --git a/programs/ekr_client.c b/programs/ekr_client.c
new file mode 100644
index 0000000..e1ed509
--- /dev/null
+++ b/programs/ekr_client.c
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2011-2013 Michael Tuexen
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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 _WIN32
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#ifndef _WIN32
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <pthread.h>
+#include <unistd.h>
+#else
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#endif
+#include <usrsctp.h>
+
+#define MAX_PACKET_SIZE (1<<16)
+#define BUFFER_SIZE 80
+#define DISCARD_PPID 39
+
+#ifdef _WIN32
+static DWORD WINAPI
+#else
+static void *
+#endif
+handle_packets(void *arg)
+{
+#ifdef _WIN32
+	SOCKET *fdp;
+#else
+	int *fdp;
+#endif
+	char *dump_buf;
+	ssize_t length;
+	char buf[MAX_PACKET_SIZE];
+
+#ifdef _WIN32
+	fdp = (SOCKET *)arg;
+#else
+	fdp = (int *)arg;
+#endif
+	for (;;) {
+#if defined(__NetBSD__)
+		pthread_testcancel();
+#endif
+		length = recv(*fdp, buf, MAX_PACKET_SIZE, 0);
+		if (length > 0) {
+			if ((dump_buf = usrsctp_dumppacket(buf, (size_t)length, SCTP_DUMP_INBOUND)) != NULL) {
+				fprintf(stderr, "%s", dump_buf);
+				usrsctp_freedumpbuffer(dump_buf);
+			}
+			usrsctp_conninput(fdp, buf, (size_t)length, 0);
+		}
+	}
+#ifdef _WIN32
+	return 0;
+#else
+	return (NULL);
+#endif
+}
+
+static int
+conn_output(void *addr, void *buf, size_t length, uint8_t tos, uint8_t set_df)
+{
+	char *dump_buf;
+#ifdef _WIN32
+	SOCKET *fdp;
+#else
+	int *fdp;
+#endif
+
+#ifdef _WIN32
+	fdp = (SOCKET *)addr;
+#else
+	fdp = (int *)addr;
+#endif
+	if ((dump_buf = usrsctp_dumppacket(buf, length, SCTP_DUMP_OUTBOUND)) != NULL) {
+		fprintf(stderr, "%s", dump_buf);
+		usrsctp_freedumpbuffer(dump_buf);
+	}
+#ifdef _WIN32
+	if (send(*fdp, buf, (int)length, 0) == SOCKET_ERROR) {
+		return (WSAGetLastError());
+#else
+	if (send(*fdp, buf, length, 0) < 0) {
+		return (errno);
+#endif
+	} else {
+		return (0);
+	}
+}
+
+static int
+receive_cb(struct socket *sock, union sctp_sockstore addr, void *data,
+           size_t datalen, struct sctp_rcvinfo rcv, int flags, void *ulp_info)
+{
+
+	if (data) {
+		if (flags & MSG_NOTIFICATION) {
+			printf("Notification of length %d received.\n", (int)datalen);
+		} else {
+			printf("Msg of length %d received via %p:%u on stream %d with SSN %u and TSN %u, PPID %u, context %u.\n",
+			       (int)datalen,
+			       addr.sconn.sconn_addr,
+			       ntohs(addr.sconn.sconn_port),
+			       rcv.rcv_sid,
+			       rcv.rcv_ssn,
+			       rcv.rcv_tsn,
+			       ntohl(rcv.rcv_ppid),
+			       rcv.rcv_context);
+		}
+		free(data);
+	} else {
+		usrsctp_deregister_address(ulp_info);
+		usrsctp_close(sock);
+	}
+	return (1);
+}
+
+void
+debug_printf(const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+	vprintf(format, ap);
+	va_end(ap);
+}
+
+int
+main(int argc, char *argv[])
+{
+	struct sockaddr_in sin;
+	struct sockaddr_conn sconn;
+#ifdef _WIN32
+	SOCKET fd;
+#else
+	int fd;
+#endif
+	struct socket *s;
+#ifdef _WIN32
+	HANDLE tid;
+#else
+	pthread_t tid;
+#endif
+	struct sctp_sndinfo sndinfo;
+	char buffer[BUFFER_SIZE];
+#ifdef _WIN32
+	WSADATA wsaData;
+#endif
+
+	if (argc < 4) {
+		printf("error: this program requires 4 arguments!\n");
+		exit(EXIT_FAILURE);
+	}
+
+#ifdef _WIN32
+	if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {
+		printf("WSAStartup failed\n");
+		exit (EXIT_FAILURE);
+	}
+#endif
+	usrsctp_init(0, conn_output, debug_printf);
+	/* set up a connected UDP socket */
+#ifdef _WIN32
+	if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) {
+		printf("socket() failed with error: %d\n", WSAGetLastError());
+	}
+#else
+	if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
+		perror("socket");
+	}
+#endif
+	memset(&sin, 0, sizeof(struct sockaddr_in));
+	sin.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+	sin.sin_len = sizeof(struct sockaddr_in);
+#endif
+	sin.sin_port = htons(atoi(argv[2]));
+	if (!inet_pton(AF_INET, argv[1], &sin.sin_addr.s_addr)){
+		printf("error: invalid address\n");
+		exit(1);
+	}
+#ifdef _WIN32
+	if (bind(fd, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) == SOCKET_ERROR) {
+		printf("bind() failed with error: %d\n", WSAGetLastError());
+	}
+#else
+	if (bind(fd, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) < 0) {
+		perror("bind");
+	}
+#endif
+	memset(&sin, 0, sizeof(struct sockaddr_in));
+	sin.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+	sin.sin_len = sizeof(struct sockaddr_in);
+#endif
+	sin.sin_port = htons(atoi(argv[4]));
+	if (!inet_pton(AF_INET, argv[3], &sin.sin_addr.s_addr)){
+		printf("error: invalid address\n");
+		exit(1);
+	}
+#ifdef _WIN32
+	if (connect(fd, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) == SOCKET_ERROR) {
+		printf("connect() failed with error: %d\n", WSAGetLastError());
+	}
+#else
+	if (connect(fd, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) < 0) {
+		perror("connect");
+	}
+#endif
+#ifdef SCTP_DEBUG
+	usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_NONE);
+#endif
+	usrsctp_sysctl_set_sctp_ecn_enable(0);
+	usrsctp_register_address((void *)&fd);
+#ifdef _WIN32
+	tid = CreateThread(NULL, 0, &handle_packets, (void *)&fd, 0, NULL);
+#else
+	pthread_create(&tid, NULL, &handle_packets, (void *)&fd);
+#endif
+	if ((s = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, receive_cb, NULL, 0, &fd)) == NULL) {
+		perror("usrsctp_socket");
+	}
+
+	memset(&sconn, 0, sizeof(struct sockaddr_conn));
+	sconn.sconn_family = AF_CONN;
+#ifdef HAVE_SCONN_LEN
+	sconn.sconn_len = sizeof(struct sockaddr_conn);
+#endif
+	sconn.sconn_port = htons(0);
+	sconn.sconn_addr = NULL;
+	if (usrsctp_bind(s, (struct sockaddr *)&sconn, sizeof(struct sockaddr_conn)) < 0) {
+		perror("usrsctp_bind");
+	}
+
+	memset(&sconn, 0, sizeof(struct sockaddr_conn));
+	sconn.sconn_family = AF_CONN;
+#ifdef HAVE_SCONN_LEN
+	sconn.sconn_len = sizeof(struct sockaddr_conn);
+#endif
+	sconn.sconn_port = htons(5001);
+	sconn.sconn_addr = &fd;
+	if (usrsctp_connect(s, (struct sockaddr *)&sconn, sizeof(struct sockaddr_conn)) < 0) {
+		perror("usrsctp_connect");
+	}
+	memset(buffer, 'A', BUFFER_SIZE);
+	sndinfo.snd_sid = 1;
+	sndinfo.snd_flags = 0;
+	sndinfo.snd_ppid = htonl(DISCARD_PPID);
+	sndinfo.snd_context = 0;
+	sndinfo.snd_assoc_id = 0;
+	if (usrsctp_sendv(s, buffer, BUFFER_SIZE, NULL, 0, (void *)&sndinfo,
+	                  (socklen_t)sizeof(struct sctp_sndinfo), SCTP_SENDV_SNDINFO, 0) < 0) {
+		perror("usrsctp_sendv");
+	}
+
+	usrsctp_shutdown(s, SHUT_WR);
+	while (usrsctp_finish() != 0) {
+#ifdef _WIN32
+		Sleep(1000);
+#else
+		sleep(1);
+#endif
+	}
+#ifdef _WIN32
+	TerminateThread(tid, 0);
+	WaitForSingleObject(tid, INFINITE);
+	if (closesocket(fd) == SOCKET_ERROR) {
+		printf("closesocket() failed with error: %d\n", WSAGetLastError());
+	}
+	WSACleanup();
+#else
+	pthread_cancel(tid);
+	pthread_join(tid, NULL);
+	if (close(fd) < 0) {
+		perror("close");
+	}
+#endif
+	return (0);
+}
diff --git a/programs/ekr_loop.c b/programs/ekr_loop.c
new file mode 100644
index 0000000..c81a946
--- /dev/null
+++ b/programs/ekr_loop.c
@@ -0,0 +1,537 @@
+/*
+ * Copyright (C) 2011-2013 Michael Tuexen
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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 _WIN32
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#ifndef _WIN32
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <pthread.h>
+#include <unistd.h>
+#else
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#endif
+#include <usrsctp.h>
+
+#define MAX_PACKET_SIZE (1<<16)
+#define LINE_LENGTH (1<<20)
+#define DISCARD_PPID 39
+
+#ifdef _WIN32
+static DWORD WINAPI
+#else
+static void *
+#endif
+handle_packets(void *arg)
+{
+#ifdef _WIN32
+	SOCKET *fdp;
+#else
+	int *fdp;
+#endif
+	char *dump_buf;
+	ssize_t length;
+	char buf[MAX_PACKET_SIZE];
+
+#ifdef _WIN32
+	fdp = (SOCKET *)arg;
+#else
+	fdp = (int *)arg;
+#endif
+	for (;;) {
+#if defined(__NetBSD__)
+		pthread_testcancel();
+#endif
+		length = recv(*fdp, buf, MAX_PACKET_SIZE, 0);
+		if (length > 0) {
+			if ((dump_buf = usrsctp_dumppacket(buf, (size_t)length, SCTP_DUMP_INBOUND)) != NULL) {
+				//fprintf(stderr, "%s", dump_buf);
+				usrsctp_freedumpbuffer(dump_buf);
+			}
+			usrsctp_conninput(fdp, buf, (size_t)length, 0);
+		}
+	}
+#ifdef _WIN32
+	return 0;
+#else
+	return (NULL);
+#endif
+}
+
+static int
+conn_output(void *addr, void *buf, size_t length, uint8_t tos, uint8_t set_df)
+{
+	char *dump_buf;
+#ifdef _WIN32
+	SOCKET *fdp;
+#else
+	int *fdp;
+#endif
+
+#ifdef _WIN32
+	fdp = (SOCKET *)addr;
+#else
+	fdp = (int *)addr;
+#endif
+	if ((dump_buf = usrsctp_dumppacket(buf, length, SCTP_DUMP_OUTBOUND)) != NULL) {
+		//fprintf(stderr, "%s", dump_buf);
+		usrsctp_freedumpbuffer(dump_buf);
+	}
+#ifdef _WIN32
+	if (send(*fdp, buf, (int)length, 0) == SOCKET_ERROR) {
+		return (WSAGetLastError());
+#else
+	if (send(*fdp, buf, length, 0) < 0) {
+		return (errno);
+#endif
+	} else {
+		return (0);
+	}
+}
+
+static int
+receive_cb(struct socket *sock, union sctp_sockstore addr, void *data,
+           size_t datalen, struct sctp_rcvinfo rcv, int flags, void *ulp_info)
+{
+	printf("Message %p received on sock = %p.\n", data, (void *)sock);
+	if (data) {
+		if ((flags & MSG_NOTIFICATION) == 0) {
+			printf("Messsage of length %d received via %p:%u on stream %d with SSN %u and TSN %u, PPID %u, context %u, flags %x.\n",
+			       (int)datalen,
+			       addr.sconn.sconn_addr,
+			       ntohs(addr.sconn.sconn_port),
+			       rcv.rcv_sid,
+			       rcv.rcv_ssn,
+			       rcv.rcv_tsn,
+			       ntohl(rcv.rcv_ppid),
+			       rcv.rcv_context,
+			       flags);
+		}
+		free(data);
+	} else {
+		usrsctp_deregister_address(ulp_info);
+		usrsctp_close(sock);
+	}
+	return (1);
+}
+
+#if 0
+static void
+print_addresses(struct socket *sock)
+{
+	int i, n;
+	struct sockaddr *addrs, *addr;
+
+	n = usrsctp_getladdrs(sock, 0, &addrs);
+	addr = addrs;
+	for (i = 0; i < n; i++) {
+		switch (addr->sa_family) {
+		case AF_INET:
+		{
+			struct sockaddr_in *sin;
+			char buf[INET_ADDRSTRLEN];
+			const char *name;
+
+			sin = (struct sockaddr_in *)addr;
+			name = inet_ntop(AF_INET, &sin->sin_addr, buf, INET_ADDRSTRLEN);
+			printf("%s:%d", name, ntohs(sin->sin_port));
+			break;
+		}
+		case AF_INET6:
+		{
+			struct sockaddr_in6 *sin6;
+			char buf[INET6_ADDRSTRLEN];
+			const char *name;
+
+			sin6 = (struct sockaddr_in6 *)addr;
+			name = inet_ntop(AF_INET6, &sin6->sin6_addr, buf, INET6_ADDRSTRLEN);
+			printf("%s:%d", name, ntohs(sin6->sin6_port));
+			break;
+		}
+		case AF_CONN:
+		{
+			struct sockaddr_conn *sconn;
+
+			sconn = (struct sockaddr_conn *)addr;
+			printf("%p:%d", sconn->sconn_addr, ntohs(sconn->sconn_port));
+			break;
+		}
+		default:
+			printf("Unknown family: %d", addr->sa_family);
+			break;
+		}
+		addr = (struct sockaddr *)((caddr_t)addr + addr->sa_len);
+		if (i != n - 1) {
+			printf(",");
+		}
+	}
+	if (n > 0) {
+		usrsctp_freeladdrs(addrs);
+	}
+	printf("<->");
+	n = usrsctp_getpaddrs(sock, 0, &addrs);
+	addr = addrs;
+	for (i = 0; i < n; i++) {
+		switch (addr->sa_family) {
+		case AF_INET:
+		{
+			struct sockaddr_in *sin;
+			char buf[INET_ADDRSTRLEN];
+			const char *name;
+
+			sin = (struct sockaddr_in *)addr;
+			name = inet_ntop(AF_INET, &sin->sin_addr, buf, INET_ADDRSTRLEN);
+			printf("%s:%d", name, ntohs(sin->sin_port));
+			break;
+		}
+		case AF_INET6:
+		{
+			struct sockaddr_in6 *sin6;
+			char buf[INET6_ADDRSTRLEN];
+			const char *name;
+
+			sin6 = (struct sockaddr_in6 *)addr;
+			name = inet_ntop(AF_INET6, &sin6->sin6_addr, buf, INET6_ADDRSTRLEN);
+			printf("%s:%d", name, ntohs(sin6->sin6_port));
+			break;
+		}
+		case AF_CONN:
+		{
+			struct sockaddr_conn *sconn;
+
+			sconn = (struct sockaddr_conn *)addr;
+			printf("%p:%d", sconn->sconn_addr, ntohs(sconn->sconn_port));
+			break;
+		}
+		default:
+			printf("Unknown family: %d", addr->sa_family);
+			break;
+		}
+		addr = (struct sockaddr *)((caddr_t)addr + addr->sa_len);
+		if (i != n - 1) {
+			printf(",");
+		}
+	}
+	if (n > 0) {
+		usrsctp_freepaddrs(addrs);
+	}
+	printf("\n");
+}
+#endif
+
+void
+debug_printf(const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+	vprintf(format, ap);
+	va_end(ap);
+}
+
+int
+main(void)
+{
+	struct sockaddr_in sin_s, sin_c;
+	struct sockaddr_conn sconn;
+#ifdef _WIN32
+	SOCKET fd_c, fd_s;
+#else
+	int fd_c, fd_s;
+#endif
+	struct socket *s_c, *s_s, *s_l;
+#ifdef _WIN32
+	HANDLE tid_c, tid_s;
+#else
+	pthread_t tid_c, tid_s;
+#endif
+	int cur_buf_size, snd_buf_size, rcv_buf_size;
+	socklen_t opt_len;
+	struct sctp_sndinfo sndinfo;
+	char *line;
+#ifdef _WIN32
+	WSADATA wsaData;
+#endif
+
+#ifdef _WIN32
+	if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {
+		printf("WSAStartup failed\n");
+		exit (EXIT_FAILURE);
+	}
+#endif
+	usrsctp_init(0, conn_output, debug_printf);
+	/* set up a connected UDP socket */
+#ifdef _WIN32
+	if ((fd_c = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) {
+		printf("socket() failed with error: %d\n", WSAGetLastError());
+		exit(EXIT_FAILURE);
+	}
+	if ((fd_s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) {
+		printf("socket() failed with error: %d\n", WSAGetLastError());
+		exit(EXIT_FAILURE);
+	}
+#else
+	if ((fd_c = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
+		perror("socket");
+		exit(EXIT_FAILURE);
+	}
+	if ((fd_s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
+		perror("socket");
+		exit(EXIT_FAILURE);
+	}
+#endif
+	memset(&sin_c, 0, sizeof(struct sockaddr_in));
+	sin_c.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+	sin_c.sin_len = sizeof(struct sockaddr_in);
+#endif
+	sin_c.sin_port = htons(9900);
+	sin_c.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+	memset(&sin_s, 0, sizeof(struct sockaddr_in));
+	sin_s.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+	sin_s.sin_len = sizeof(struct sockaddr_in);
+#endif
+	sin_s.sin_port = htons(9901);
+	sin_s.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+#ifdef _WIN32
+	if (bind(fd_c, (struct sockaddr *)&sin_c, sizeof(struct sockaddr_in)) == SOCKET_ERROR) {
+		printf("bind() failed with error: %d\n", WSAGetLastError());
+		exit(EXIT_FAILURE);
+	}
+	if (bind(fd_s, (struct sockaddr *)&sin_s, sizeof(struct sockaddr_in)) == SOCKET_ERROR) {
+		printf("bind() failed with error: %d\n", WSAGetLastError());
+		exit(EXIT_FAILURE);
+	}
+#else
+	if (bind(fd_c, (struct sockaddr *)&sin_c, sizeof(struct sockaddr_in)) < 0) {
+		perror("bind");
+		exit(EXIT_FAILURE);
+	}
+	if (bind(fd_s, (struct sockaddr *)&sin_s, sizeof(struct sockaddr_in)) < 0) {
+		perror("bind");
+		exit(EXIT_FAILURE);
+	}
+#endif
+#ifdef _WIN32
+	if (connect(fd_c, (struct sockaddr *)&sin_s, sizeof(struct sockaddr_in)) == SOCKET_ERROR) {
+		printf("connect() failed with error: %d\n", WSAGetLastError());
+		exit(EXIT_FAILURE);
+	}
+	if (connect(fd_s, (struct sockaddr *)&sin_c, sizeof(struct sockaddr_in)) == SOCKET_ERROR) {
+		printf("connect() failed with error: %d\n", WSAGetLastError());
+		exit(EXIT_FAILURE);
+	}
+#else
+	if (connect(fd_c, (struct sockaddr *)&sin_s, sizeof(struct sockaddr_in)) < 0) {
+		perror("connect");
+		exit(EXIT_FAILURE);
+	}
+	if (connect(fd_s, (struct sockaddr *)&sin_c, sizeof(struct sockaddr_in)) < 0) {
+		perror("connect");
+		exit(EXIT_FAILURE);
+	}
+#endif
+#ifdef _WIN32
+	tid_c = CreateThread(NULL, 0, &handle_packets, (void *)&fd_c, 0, NULL);
+	tid_s = CreateThread(NULL, 0, &handle_packets, (void *)&fd_s, 0, NULL);
+#else
+	if (pthread_create(&tid_c, NULL, &handle_packets, (void *)&fd_c)) {
+		perror("pthread_create tid_c");
+		exit(EXIT_FAILURE);
+	}
+
+	if (pthread_create(&tid_s, NULL, &handle_packets, (void *)&fd_s)) {
+		perror("pthread_create tid_s");
+		exit(EXIT_FAILURE);
+	};
+#endif
+#ifdef SCTP_DEBUG
+	usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL);
+#endif
+	usrsctp_sysctl_set_sctp_ecn_enable(0);
+	usrsctp_register_address((void *)&fd_c);
+	usrsctp_register_address((void *)&fd_s);
+	if ((s_c = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, receive_cb, NULL, 0, &fd_c)) == NULL) {
+		perror("usrsctp_socket");
+		exit(EXIT_FAILURE);
+	}
+	opt_len = (socklen_t)sizeof(int);
+	cur_buf_size = 0;
+	if (usrsctp_getsockopt(s_c, SOL_SOCKET, SO_SNDBUF, &cur_buf_size, &opt_len) < 0) {
+		perror("usrsctp_getsockopt");
+		exit(EXIT_FAILURE);
+	}
+	printf("Change send socket buffer size from %d ", cur_buf_size);
+	snd_buf_size = 1<<20; /* 1 MB */
+	if (usrsctp_setsockopt(s_c, SOL_SOCKET, SO_SNDBUF, &snd_buf_size, sizeof(int)) < 0) {
+		perror("usrsctp_setsockopt");
+		exit(EXIT_FAILURE);
+	}
+	opt_len = (socklen_t)sizeof(int);
+	cur_buf_size = 0;
+	if (usrsctp_getsockopt(s_c, SOL_SOCKET, SO_SNDBUF, &cur_buf_size, &opt_len) < 0) {
+		perror("usrsctp_getsockopt");
+		exit(EXIT_FAILURE);
+	}
+	printf("to %d.\n", cur_buf_size);
+	if ((s_l = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, receive_cb, NULL, 0, &fd_s)) == NULL) {
+		perror("usrsctp_socket");
+		exit(EXIT_FAILURE);
+	}
+	opt_len = (socklen_t)sizeof(int);
+	cur_buf_size = 0;
+	if (usrsctp_getsockopt(s_l, SOL_SOCKET, SO_RCVBUF, &cur_buf_size, &opt_len) < 0) {
+		perror("usrsctp_getsockopt");
+		exit(EXIT_FAILURE);
+	}
+	printf("Change receive socket buffer size from %d ", cur_buf_size);
+	rcv_buf_size = 1<<16; /* 64 KB */
+	if (usrsctp_setsockopt(s_l, SOL_SOCKET, SO_RCVBUF, &rcv_buf_size, sizeof(int)) < 0) {
+		perror("usrsctp_setsockopt");
+		exit(EXIT_FAILURE);
+	}
+	opt_len = (socklen_t)sizeof(int);
+	cur_buf_size = 0;
+	if (usrsctp_getsockopt(s_l, SOL_SOCKET, SO_RCVBUF, &cur_buf_size, &opt_len) < 0) {
+		perror("usrsctp_getsockopt");
+		exit(EXIT_FAILURE);
+	}
+	printf("to %d.\n", cur_buf_size);
+	/* Bind the client side. */
+	memset(&sconn, 0, sizeof(struct sockaddr_conn));
+	sconn.sconn_family = AF_CONN;
+#ifdef HAVE_SCONN_LEN
+	sconn.sconn_len = sizeof(struct sockaddr_conn);
+#endif
+	sconn.sconn_port = htons(5001);
+	sconn.sconn_addr = &fd_c;
+	if (usrsctp_bind(s_c, (struct sockaddr *)&sconn, sizeof(struct sockaddr_conn)) < 0) {
+		perror("usrsctp_bind");
+		exit(EXIT_FAILURE);
+	}
+	/* Bind the server side. */
+	memset(&sconn, 0, sizeof(struct sockaddr_conn));
+	sconn.sconn_family = AF_CONN;
+#ifdef HAVE_SCONN_LEN
+	sconn.sconn_len = sizeof(struct sockaddr_conn);
+#endif
+	sconn.sconn_port = htons(5001);
+	sconn.sconn_addr = &fd_s;
+	if (usrsctp_bind(s_l, (struct sockaddr *)&sconn, sizeof(struct sockaddr_conn)) < 0) {
+		perror("usrsctp_bind");
+		exit(EXIT_FAILURE);
+	}
+	/* Make server side passive... */
+	if (usrsctp_listen(s_l, 1) < 0) {
+		perror("usrsctp_listen");
+		exit(EXIT_FAILURE);
+	}
+	/* Initiate the handshake */
+	memset(&sconn, 0, sizeof(struct sockaddr_conn));
+	sconn.sconn_family = AF_CONN;
+#ifdef HAVE_SCONN_LEN
+	sconn.sconn_len = sizeof(struct sockaddr_conn);
+#endif
+	sconn.sconn_port = htons(5001);
+	sconn.sconn_addr = &fd_c;
+	if (usrsctp_connect(s_c, (struct sockaddr *)&sconn, sizeof(struct sockaddr_conn)) < 0) {
+		perror("usrsctp_connect");
+		exit(EXIT_FAILURE);
+	}
+	if ((s_s = usrsctp_accept(s_l, NULL, NULL)) == NULL) {
+		perror("usrsctp_accept");
+		exit(EXIT_FAILURE);
+	}
+	usrsctp_close(s_l);
+	if ((line = malloc(LINE_LENGTH)) == NULL) {
+		exit(EXIT_FAILURE);
+	}
+	memset(line, 'A', LINE_LENGTH);
+	sndinfo.snd_sid = 1;
+	sndinfo.snd_flags = 0;
+	sndinfo.snd_ppid = htonl(DISCARD_PPID);
+	sndinfo.snd_context = 0;
+	sndinfo.snd_assoc_id = 0;
+	/* Send a 1 MB message */
+	if (usrsctp_sendv(s_c, line, LINE_LENGTH, NULL, 0, (void *)&sndinfo,
+	                 (socklen_t)sizeof(struct sctp_sndinfo), SCTP_SENDV_SNDINFO, 0) < 0) {
+		perror("usrsctp_sendv");
+		exit(EXIT_FAILURE);
+	}
+	free(line);
+	usrsctp_shutdown(s_c, SHUT_WR);
+
+	while (usrsctp_finish() != 0) {
+#ifdef _WIN32
+		Sleep(1000);
+#else
+		sleep(1);
+#endif
+	}
+#ifdef _WIN32
+	TerminateThread(tid_c, 0);
+	WaitForSingleObject(tid_c, INFINITE);
+	TerminateThread(tid_s, 0);
+	WaitForSingleObject(tid_s, INFINITE);
+	if (closesocket(fd_c) == SOCKET_ERROR) {
+		printf("closesocket() failed with error: %d\n", WSAGetLastError());
+		exit(EXIT_FAILURE);
+	}
+	if (closesocket(fd_s) == SOCKET_ERROR) {
+		printf("closesocket() failed with error: %d\n", WSAGetLastError());
+		exit(EXIT_FAILURE);
+	}
+	WSACleanup();
+#else
+	pthread_cancel(tid_c);
+	pthread_join(tid_c, NULL);
+	pthread_cancel(tid_s);
+	pthread_join(tid_s, NULL);
+	if (close(fd_c) < 0) {
+		perror("close");
+		exit(EXIT_FAILURE);
+	}
+	if (close(fd_s) < 0) {
+		perror("close");
+		exit(EXIT_FAILURE);
+	}
+#endif
+	return (0);
+}
diff --git a/programs/ekr_loop_offload.c b/programs/ekr_loop_offload.c
new file mode 100644
index 0000000..1296d0f
--- /dev/null
+++ b/programs/ekr_loop_offload.c
@@ -0,0 +1,559 @@
+/*
+ * Copyright (C) 2011-2013 Michael Tuexen
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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 _WIN32
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#ifndef _WIN32
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <pthread.h>
+#include <unistd.h>
+#else
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#endif
+#include <usrsctp.h>
+
+#define MAX_PACKET_SIZE (1<<16)
+#define LINE_LENGTH (1<<20)
+#define DISCARD_PPID 39
+
+#ifdef _WIN32
+static DWORD WINAPI
+#else
+static void *
+#endif
+handle_packets(void *arg)
+{
+#ifdef _WIN32
+	SOCKET *fdp;
+#else
+	int *fdp;
+#endif
+	char *dump_buffer;
+	struct sctp_common_header *hdr;
+	ssize_t length;
+	char buffer[MAX_PACKET_SIZE];
+	uint32_t received_crc32c, computed_crc32c;
+
+#ifdef _WIN32
+	fdp = (SOCKET *)arg;
+#else
+	fdp = (int *)arg;
+#endif
+	for (;;) {
+#if defined(__NetBSD__)
+		pthread_testcancel();
+#endif
+		length = recv(*fdp, buffer, MAX_PACKET_SIZE, 0);
+		if (length > 0) {
+			if ((dump_buffer = usrsctp_dumppacket(buffer, (size_t)length, SCTP_DUMP_INBOUND)) != NULL) {
+				//fprintf(stderr, "%s", dump_buffer);
+				usrsctp_freedumpbuffer(dump_buffer);
+			}
+			if ((size_t)length >= sizeof(struct sctp_common_header)) {
+				hdr = (struct sctp_common_header *)buffer;
+				received_crc32c = hdr->crc32c;
+				hdr->crc32c = htonl(0);
+				computed_crc32c = usrsctp_crc32c(buffer, (size_t)length);
+				hdr->crc32c = received_crc32c;
+				if (received_crc32c == computed_crc32c) {
+					usrsctp_conninput(fdp, buffer, (size_t)length, 0);
+				} else {
+					fprintf(stderr, "Wrong CRC32c: expected %08x received %08x\n",
+					        ntohl(computed_crc32c), ntohl(received_crc32c));
+				}
+			} else {
+				fprintf(stderr, "Packet too short: length %zu", (size_t)length);
+			}
+		}
+	}
+#ifdef _WIN32
+	return 0;
+#else
+	return (NULL);
+#endif
+}
+
+static int
+conn_output(void *addr, void *buffer, size_t length, uint8_t tos, uint8_t set_df)
+{
+	char *dump_buffer;
+	struct sctp_common_header *hdr;
+#ifdef _WIN32
+	SOCKET *fdp;
+#else
+	int *fdp;
+#endif
+
+#ifdef _WIN32
+	fdp = (SOCKET *)addr;
+#else
+	fdp = (int *)addr;
+#endif
+	if (length >= sizeof(struct sctp_common_header)) {
+		hdr = (struct sctp_common_header *)buffer;
+		hdr->crc32c = usrsctp_crc32c(buffer, (size_t)length);
+	}
+	if ((dump_buffer = usrsctp_dumppacket(buffer, length, SCTP_DUMP_OUTBOUND)) != NULL) {
+		//fprintf(stderr, "%s", dump_buffer);
+		usrsctp_freedumpbuffer(dump_buffer);
+	}
+#ifdef _WIN32
+	if (send(*fdp, buffer, (int)length, 0) == SOCKET_ERROR) {
+		return (WSAGetLastError());
+#else
+	if (send(*fdp, buffer, length, 0) < 0) {
+		return (errno);
+#endif
+	} else {
+		return (0);
+	}
+}
+
+static int
+receive_cb(struct socket *sock, union sctp_sockstore addr, void *data,
+           size_t datalen, struct sctp_rcvinfo rcv, int flags, void *ulp_info)
+{
+	printf("Message %p received on sock = %p.\n", data, (void *)sock);
+	if (data) {
+		if ((flags & MSG_NOTIFICATION) == 0) {
+			printf("Messsage of length %d received via %p:%u on stream %d with SSN %u and TSN %u, PPID %u, context %u, flags %x.\n",
+			       (int)datalen,
+			       addr.sconn.sconn_addr,
+			       ntohs(addr.sconn.sconn_port),
+			       rcv.rcv_sid,
+			       rcv.rcv_ssn,
+			       rcv.rcv_tsn,
+			       ntohl(rcv.rcv_ppid),
+			       rcv.rcv_context,
+			       flags);
+		}
+		free(data);
+	} else {
+		usrsctp_deregister_address(ulp_info);
+		usrsctp_close(sock);
+	}
+	return (1);
+}
+
+#if 0
+static void
+print_addresses(struct socket *sock)
+{
+	int i, n;
+	struct sockaddr *addrs, *addr;
+
+	n = usrsctp_getladdrs(sock, 0, &addrs);
+	addr = addrs;
+	for (i = 0; i < n; i++) {
+		switch (addr->sa_family) {
+		case AF_INET:
+		{
+			struct sockaddr_in *sin;
+			char buf[INET_ADDRSTRLEN];
+			const char *name;
+
+			sin = (struct sockaddr_in *)addr;
+			name = inet_ntop(AF_INET, &sin->sin_addr, buf, INET_ADDRSTRLEN);
+			printf("%s:%d", name, ntohs(sin->sin_port));
+			break;
+		}
+		case AF_INET6:
+		{
+			struct sockaddr_in6 *sin6;
+			char buf[INET6_ADDRSTRLEN];
+			const char *name;
+
+			sin6 = (struct sockaddr_in6 *)addr;
+			name = inet_ntop(AF_INET6, &sin6->sin6_addr, buf, INET6_ADDRSTRLEN);
+			printf("%s:%d", name, ntohs(sin6->sin6_port));
+			break;
+		}
+		case AF_CONN:
+		{
+			struct sockaddr_conn *sconn;
+
+			sconn = (struct sockaddr_conn *)addr;
+			printf("%p:%d", sconn->sconn_addr, ntohs(sconn->sconn_port));
+			break;
+		}
+		default:
+			printf("Unknown family: %d", addr->sa_family);
+			break;
+		}
+		addr = (struct sockaddr *)((caddr_t)addr + addr->sa_len);
+		if (i != n - 1) {
+			printf(",");
+		}
+	}
+	if (n > 0) {
+		usrsctp_freeladdrs(addrs);
+	}
+	printf("<->");
+	n = usrsctp_getpaddrs(sock, 0, &addrs);
+	addr = addrs;
+	for (i = 0; i < n; i++) {
+		switch (addr->sa_family) {
+		case AF_INET:
+		{
+			struct sockaddr_in *sin;
+			char buf[INET_ADDRSTRLEN];
+			const char *name;
+
+			sin = (struct sockaddr_in *)addr;
+			name = inet_ntop(AF_INET, &sin->sin_addr, buf, INET_ADDRSTRLEN);
+			printf("%s:%d", name, ntohs(sin->sin_port));
+			break;
+		}
+		case AF_INET6:
+		{
+			struct sockaddr_in6 *sin6;
+			char buf[INET6_ADDRSTRLEN];
+			const char *name;
+
+			sin6 = (struct sockaddr_in6 *)addr;
+			name = inet_ntop(AF_INET6, &sin6->sin6_addr, buf, INET6_ADDRSTRLEN);
+			printf("%s:%d", name, ntohs(sin6->sin6_port));
+			break;
+		}
+		case AF_CONN:
+		{
+			struct sockaddr_conn *sconn;
+
+			sconn = (struct sockaddr_conn *)addr;
+			printf("%p:%d", sconn->sconn_addr, ntohs(sconn->sconn_port));
+			break;
+		}
+		default:
+			printf("Unknown family: %d", addr->sa_family);
+			break;
+		}
+		addr = (struct sockaddr *)((caddr_t)addr + addr->sa_len);
+		if (i != n - 1) {
+			printf(",");
+		}
+	}
+	if (n > 0) {
+		usrsctp_freepaddrs(addrs);
+	}
+	printf("\n");
+}
+#endif
+
+void
+debug_printf(const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+	vprintf(format, ap);
+	va_end(ap);
+}
+
+int
+main(void)
+{
+	struct sockaddr_in sin_s, sin_c;
+	struct sockaddr_conn sconn;
+#ifdef _WIN32
+	SOCKET fd_c, fd_s;
+#else
+	int fd_c, fd_s;
+#endif
+	struct socket *s_c, *s_s, *s_l;
+#ifdef _WIN32
+	HANDLE tid_c, tid_s;
+#else
+	pthread_t tid_c, tid_s;
+#endif
+	int cur_buf_size, snd_buf_size, rcv_buf_size;
+	socklen_t opt_len;
+	struct sctp_sndinfo sndinfo;
+	char *line;
+#ifdef _WIN32
+	WSADATA wsaData;
+#endif
+
+#ifdef _WIN32
+	if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {
+		printf("WSAStartup failed\n");
+		exit (EXIT_FAILURE);
+	}
+#endif
+	usrsctp_init(0, conn_output, debug_printf);
+	usrsctp_enable_crc32c_offload();
+	/* set up a connected UDP socket */
+#ifdef _WIN32
+	if ((fd_c = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) {
+		printf("socket() failed with error: %d\n", WSAGetLastError());
+		exit(EXIT_FAILURE);
+	}
+	if ((fd_s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) {
+		printf("socket() failed with error: %d\n", WSAGetLastError());
+		exit(EXIT_FAILURE);
+	}
+#else
+	if ((fd_c = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
+		perror("socket");
+		exit(EXIT_FAILURE);
+	}
+	if ((fd_s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
+		perror("socket");
+		exit(EXIT_FAILURE);
+	}
+#endif
+	memset(&sin_c, 0, sizeof(struct sockaddr_in));
+	sin_c.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+	sin_c.sin_len = sizeof(struct sockaddr_in);
+#endif
+	sin_c.sin_port = htons(9899);
+	sin_c.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+	memset(&sin_s, 0, sizeof(struct sockaddr_in));
+	sin_s.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+	sin_s.sin_len = sizeof(struct sockaddr_in);
+#endif
+	sin_s.sin_port = htons(9901);
+	sin_s.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+#ifdef _WIN32
+	if (bind(fd_c, (struct sockaddr *)&sin_c, sizeof(struct sockaddr_in)) == SOCKET_ERROR) {
+		printf("bind() failed with error: %d\n", WSAGetLastError());
+		exit(EXIT_FAILURE);
+	}
+	if (bind(fd_s, (struct sockaddr *)&sin_s, sizeof(struct sockaddr_in)) == SOCKET_ERROR) {
+		printf("bind() failed with error: %d\n", WSAGetLastError());
+		exit(EXIT_FAILURE);
+	}
+#else
+	if (bind(fd_c, (struct sockaddr *)&sin_c, sizeof(struct sockaddr_in)) < 0) {
+		perror("bind");
+		exit(EXIT_FAILURE);
+	}
+	if (bind(fd_s, (struct sockaddr *)&sin_s, sizeof(struct sockaddr_in)) < 0) {
+		perror("bind");
+		exit(EXIT_FAILURE);
+	}
+#endif
+#ifdef _WIN32
+	if (connect(fd_c, (struct sockaddr *)&sin_s, sizeof(struct sockaddr_in)) == SOCKET_ERROR) {
+		printf("connect() failed with error: %d\n", WSAGetLastError());
+		exit(EXIT_FAILURE);
+	}
+	if (connect(fd_s, (struct sockaddr *)&sin_c, sizeof(struct sockaddr_in)) == SOCKET_ERROR) {
+		printf("connect() failed with error: %d\n", WSAGetLastError());
+		exit(EXIT_FAILURE);
+	}
+#else
+	if (connect(fd_c, (struct sockaddr *)&sin_s, sizeof(struct sockaddr_in)) < 0) {
+		perror("connect");
+		exit(EXIT_FAILURE);
+	}
+	if (connect(fd_s, (struct sockaddr *)&sin_c, sizeof(struct sockaddr_in)) < 0) {
+		perror("connect");
+		exit(EXIT_FAILURE);
+	}
+#endif
+#ifdef _WIN32
+	tid_c = CreateThread(NULL, 0, &handle_packets, (void *)&fd_c, 0, NULL);
+	tid_s = CreateThread(NULL, 0, &handle_packets, (void *)&fd_s, 0, NULL);
+#else
+	if (pthread_create(&tid_c, NULL, &handle_packets, (void *)&fd_c)) {
+		perror("pthread_create tid_c");
+		exit(EXIT_FAILURE);
+	}
+
+	if (pthread_create(&tid_s, NULL, &handle_packets, (void *)&fd_s)) {
+		perror("pthread_create tid_s");
+		exit(EXIT_FAILURE);
+	};
+#endif
+#ifdef SCTP_DEBUG
+	usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL);
+#endif
+	usrsctp_sysctl_set_sctp_ecn_enable(0);
+	usrsctp_register_address((void *)&fd_c);
+	usrsctp_register_address((void *)&fd_s);
+	if ((s_c = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, receive_cb, NULL, 0, &fd_c)) == NULL) {
+		perror("usrsctp_socket");
+		exit(EXIT_FAILURE);
+	}
+	opt_len = (socklen_t)sizeof(int);
+	cur_buf_size = 0;
+	if (usrsctp_getsockopt(s_c, SOL_SOCKET, SO_SNDBUF, &cur_buf_size, &opt_len) < 0) {
+		perror("usrsctp_getsockopt");
+		exit(EXIT_FAILURE);
+	}
+	printf("Change send socket buffer size from %d ", cur_buf_size);
+	snd_buf_size = 1<<20; /* 1 MB */
+	if (usrsctp_setsockopt(s_c, SOL_SOCKET, SO_SNDBUF, &snd_buf_size, sizeof(int)) < 0) {
+		perror("usrsctp_setsockopt");
+		exit(EXIT_FAILURE);
+	}
+	opt_len = (socklen_t)sizeof(int);
+	cur_buf_size = 0;
+	if (usrsctp_getsockopt(s_c, SOL_SOCKET, SO_SNDBUF, &cur_buf_size, &opt_len) < 0) {
+		perror("usrsctp_getsockopt");
+		exit(EXIT_FAILURE);
+	}
+	printf("to %d.\n", cur_buf_size);
+	if ((s_l = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, receive_cb, NULL, 0, &fd_s)) == NULL) {
+		perror("usrsctp_socket");
+		exit(EXIT_FAILURE);
+	}
+	opt_len = (socklen_t)sizeof(int);
+	cur_buf_size = 0;
+	if (usrsctp_getsockopt(s_l, SOL_SOCKET, SO_RCVBUF, &cur_buf_size, &opt_len) < 0) {
+		perror("usrsctp_getsockopt");
+		exit(EXIT_FAILURE);
+	}
+	printf("Change receive socket buffer size from %d ", cur_buf_size);
+	rcv_buf_size = 1<<16; /* 64 KB */
+	if (usrsctp_setsockopt(s_l, SOL_SOCKET, SO_RCVBUF, &rcv_buf_size, sizeof(int)) < 0) {
+		perror("usrsctp_setsockopt");
+		exit(EXIT_FAILURE);
+	}
+	opt_len = (socklen_t)sizeof(int);
+	cur_buf_size = 0;
+	if (usrsctp_getsockopt(s_l, SOL_SOCKET, SO_RCVBUF, &cur_buf_size, &opt_len) < 0) {
+		perror("usrsctp_getsockopt");
+		exit(EXIT_FAILURE);
+	}
+	printf("to %d.\n", cur_buf_size);
+	/* Bind the client side. */
+	memset(&sconn, 0, sizeof(struct sockaddr_conn));
+	sconn.sconn_family = AF_CONN;
+#ifdef HAVE_SCONN_LEN
+	sconn.sconn_len = sizeof(struct sockaddr_conn);
+#endif
+	sconn.sconn_port = htons(5001);
+	sconn.sconn_addr = &fd_c;
+	if (usrsctp_bind(s_c, (struct sockaddr *)&sconn, sizeof(struct sockaddr_conn)) < 0) {
+		perror("usrsctp_bind");
+		exit(EXIT_FAILURE);
+	}
+	/* Bind the server side. */
+	memset(&sconn, 0, sizeof(struct sockaddr_conn));
+	sconn.sconn_family = AF_CONN;
+#ifdef HAVE_SCONN_LEN
+	sconn.sconn_len = sizeof(struct sockaddr_conn);
+#endif
+	sconn.sconn_port = htons(5001);
+	sconn.sconn_addr = &fd_s;
+	if (usrsctp_bind(s_l, (struct sockaddr *)&sconn, sizeof(struct sockaddr_conn)) < 0) {
+		perror("usrsctp_bind");
+		exit(EXIT_FAILURE);
+	}
+	/* Make server side passive... */
+	if (usrsctp_listen(s_l, 1) < 0) {
+		perror("usrsctp_listen");
+		exit(EXIT_FAILURE);
+	}
+	/* Initiate the handshake */
+	memset(&sconn, 0, sizeof(struct sockaddr_conn));
+	sconn.sconn_family = AF_CONN;
+#ifdef HAVE_SCONN_LEN
+	sconn.sconn_len = sizeof(struct sockaddr_conn);
+#endif
+	sconn.sconn_port = htons(5001);
+	sconn.sconn_addr = &fd_c;
+	if (usrsctp_connect(s_c, (struct sockaddr *)&sconn, sizeof(struct sockaddr_conn)) < 0) {
+		perror("usrsctp_connect");
+		exit(EXIT_FAILURE);
+	}
+	if ((s_s = usrsctp_accept(s_l, NULL, NULL)) == NULL) {
+		perror("usrsctp_accept");
+		exit(EXIT_FAILURE);
+	}
+	usrsctp_close(s_l);
+	if ((line = malloc(LINE_LENGTH)) == NULL) {
+		exit(EXIT_FAILURE);
+	}
+	memset(line, 'A', LINE_LENGTH);
+	sndinfo.snd_sid = 1;
+	sndinfo.snd_flags = 0;
+	sndinfo.snd_ppid = htonl(DISCARD_PPID);
+	sndinfo.snd_context = 0;
+	sndinfo.snd_assoc_id = 0;
+	/* Send a 1 MB message */
+	if (usrsctp_sendv(s_c, line, LINE_LENGTH, NULL, 0, (void *)&sndinfo,
+	                 (socklen_t)sizeof(struct sctp_sndinfo), SCTP_SENDV_SNDINFO, 0) < 0) {
+		perror("usrsctp_sendv");
+		exit(EXIT_FAILURE);
+	}
+	free(line);
+	usrsctp_shutdown(s_c, SHUT_WR);
+
+	while (usrsctp_finish() != 0) {
+#ifdef _WIN32
+		Sleep(1000);
+#else
+		sleep(1);
+#endif
+	}
+#ifdef _WIN32
+	TerminateThread(tid_c, 0);
+	WaitForSingleObject(tid_c, INFINITE);
+	TerminateThread(tid_s, 0);
+	WaitForSingleObject(tid_s, INFINITE);
+	if (closesocket(fd_c) == SOCKET_ERROR) {
+		printf("closesocket() failed with error: %d\n", WSAGetLastError());
+		exit(EXIT_FAILURE);
+	}
+	if (closesocket(fd_s) == SOCKET_ERROR) {
+		printf("closesocket() failed with error: %d\n", WSAGetLastError());
+		exit(EXIT_FAILURE);
+	}
+	WSACleanup();
+#else
+	pthread_cancel(tid_c);
+	pthread_join(tid_c, NULL);
+	pthread_cancel(tid_s);
+	pthread_join(tid_s, NULL);
+	if (close(fd_c) < 0) {
+		perror("close");
+		exit(EXIT_FAILURE);
+	}
+	if (close(fd_s) < 0) {
+		perror("close");
+		exit(EXIT_FAILURE);
+	}
+#endif
+	return (0);
+}
diff --git a/programs/ekr_peer.c b/programs/ekr_peer.c
new file mode 100644
index 0000000..7613e42
--- /dev/null
+++ b/programs/ekr_peer.c
@@ -0,0 +1,538 @@
+/*
+ * Copyright (C) 2011-2013 Michael Tuexen
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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 _WIN32
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#ifndef _WIN32
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <pthread.h>
+#include <unistd.h>
+#else
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#endif
+#include <usrsctp.h>
+
+#define MAX_PACKET_SIZE (1<<16)
+#define LINE_LENGTH 80
+#define DISCARD_PPID 39
+
+#ifdef _WIN32
+static DWORD WINAPI
+#else
+static void *
+#endif
+handle_packets(void *arg)
+{
+#ifdef _WIN32
+	SOCKET *fdp;
+#else
+	int *fdp;
+#endif
+	ssize_t length;
+	char buf[MAX_PACKET_SIZE];
+
+#ifdef _WIN32
+	fdp = (SOCKET *)arg;
+#else
+	fdp = (int *)arg;
+#endif
+	for (;;) {
+#if defined(__NetBSD__)
+		pthread_testcancel();
+#endif
+		length = recv(*fdp, buf, MAX_PACKET_SIZE, 0);
+		if (length > 0) {
+			usrsctp_conninput(fdp, buf, (size_t)length, 0);
+		}
+	}
+#ifdef _WIN32
+	return 0;
+#else
+	return (NULL);
+#endif
+}
+
+static int
+conn_output(void *addr, void *buffer, size_t length, uint8_t tos, uint8_t set_df)
+{
+#ifdef _WIN32
+	SOCKET *fdp;
+#else
+	int *fdp;
+#endif
+
+#ifdef _WIN32
+	fdp = (SOCKET *)addr;
+#else
+	fdp = (int *)addr;
+#endif
+#ifdef _WIN32
+	if (send(*fdp, buffer, (int)length, 0) == SOCKET_ERROR) {
+		return (WSAGetLastError());
+#else
+	if (send(*fdp, buffer, length, 0) < 0) {
+		return (errno);
+#endif
+	} else {
+		return (0);
+	}
+}
+
+static void
+handle_association_change_event(struct sctp_assoc_change *sac)
+{
+	unsigned int i, n;
+
+	printf("Association change ");
+	switch (sac->sac_state) {
+	case SCTP_COMM_UP:
+		printf("SCTP_COMM_UP");
+		break;
+	case SCTP_COMM_LOST:
+		printf("SCTP_COMM_LOST");
+		break;
+	case SCTP_RESTART:
+		printf("SCTP_RESTART");
+		break;
+	case SCTP_SHUTDOWN_COMP:
+		printf("SCTP_SHUTDOWN_COMP");
+		break;
+	case SCTP_CANT_STR_ASSOC:
+		printf("SCTP_CANT_STR_ASSOC");
+		break;
+	default:
+		printf("UNKNOWN");
+		break;
+	}
+	printf(", streams (in/out) = (%u/%u)",
+	       sac->sac_inbound_streams, sac->sac_outbound_streams);
+	n = sac->sac_length - sizeof(struct sctp_assoc_change);
+	if (((sac->sac_state == SCTP_COMM_UP) ||
+	     (sac->sac_state == SCTP_RESTART)) && (n > 0)) {
+		printf(", supports");
+		for (i = 0; i < n; i++) {
+			switch (sac->sac_info[i]) {
+			case SCTP_ASSOC_SUPPORTS_PR:
+				printf(" PR");
+				break;
+			case SCTP_ASSOC_SUPPORTS_AUTH:
+				printf(" AUTH");
+				break;
+			case SCTP_ASSOC_SUPPORTS_ASCONF:
+				printf(" ASCONF");
+				break;
+			case SCTP_ASSOC_SUPPORTS_MULTIBUF:
+				printf(" MULTIBUF");
+				break;
+			case SCTP_ASSOC_SUPPORTS_RE_CONFIG:
+				printf(" RE-CONFIG");
+				break;
+			default:
+				printf(" UNKNOWN(0x%02x)", sac->sac_info[i]);
+				break;
+			}
+		}
+	} else if (((sac->sac_state == SCTP_COMM_LOST) ||
+	            (sac->sac_state == SCTP_CANT_STR_ASSOC)) && (n > 0)) {
+		printf(", ABORT =");
+		for (i = 0; i < n; i++) {
+			printf(" 0x%02x", sac->sac_info[i]);
+		}
+	}
+	printf(".\n");
+	if ((sac->sac_state == SCTP_CANT_STR_ASSOC) ||
+	    (sac->sac_state == SCTP_SHUTDOWN_COMP) ||
+	    (sac->sac_state == SCTP_COMM_LOST)) {
+		exit(0);
+	}
+	return;
+}
+
+static void
+handle_peer_address_change_event(struct sctp_paddr_change *spc)
+{
+	char addr_buf[INET6_ADDRSTRLEN];
+	const char *addr;
+	struct sockaddr_in *sin;
+	struct sockaddr_in6 *sin6;
+	struct sockaddr_conn *sconn;
+
+	switch (spc->spc_aaddr.ss_family) {
+	case AF_INET:
+		sin = (struct sockaddr_in *)&spc->spc_aaddr;
+		addr = inet_ntop(AF_INET, &sin->sin_addr, addr_buf, INET_ADDRSTRLEN);
+		break;
+	case AF_INET6:
+		sin6 = (struct sockaddr_in6 *)&spc->spc_aaddr;
+		addr = inet_ntop(AF_INET6, &sin6->sin6_addr, addr_buf, INET6_ADDRSTRLEN);
+		break;
+	case AF_CONN:
+		sconn = (struct sockaddr_conn *)&spc->spc_aaddr;
+#ifdef _WIN32
+		_snprintf(addr_buf, INET6_ADDRSTRLEN, "%p", sconn->sconn_addr);
+#else
+		snprintf(addr_buf, INET6_ADDRSTRLEN, "%p", sconn->sconn_addr);
+#endif
+		addr = addr_buf;
+		break;
+	default:
+#ifdef _WIN32
+		_snprintf(addr_buf, INET6_ADDRSTRLEN, "Unknown family %d", spc->spc_aaddr.ss_family);
+#else
+		snprintf(addr_buf, INET6_ADDRSTRLEN, "Unknown family %d", spc->spc_aaddr.ss_family);
+#endif
+		addr = addr_buf;
+		break;
+	}
+	printf("Peer address %s is now ", addr);
+	switch (spc->spc_state) {
+	case SCTP_ADDR_AVAILABLE:
+		printf("SCTP_ADDR_AVAILABLE");
+		break;
+	case SCTP_ADDR_UNREACHABLE:
+		printf("SCTP_ADDR_UNREACHABLE");
+		break;
+	case SCTP_ADDR_REMOVED:
+		printf("SCTP_ADDR_REMOVED");
+		break;
+	case SCTP_ADDR_ADDED:
+		printf("SCTP_ADDR_ADDED");
+		break;
+	case SCTP_ADDR_MADE_PRIM:
+		printf("SCTP_ADDR_MADE_PRIM");
+		break;
+	case SCTP_ADDR_CONFIRMED:
+		printf("SCTP_ADDR_CONFIRMED");
+		break;
+	default:
+		printf("UNKNOWN");
+		break;
+	}
+	printf(" (error = 0x%08x).\n", spc->spc_error);
+	return;
+}
+
+static void
+handle_send_failed_event(struct sctp_send_failed_event *ssfe)
+{
+	size_t i, n;
+
+	if (ssfe->ssfe_flags & SCTP_DATA_UNSENT) {
+		printf("Unsent ");
+	}
+	if (ssfe->ssfe_flags & SCTP_DATA_SENT) {
+		printf("Sent ");
+	}
+	if (ssfe->ssfe_flags & ~(SCTP_DATA_SENT | SCTP_DATA_UNSENT)) {
+		printf("(flags = %x) ", ssfe->ssfe_flags);
+	}
+	printf("message with PPID = %u, SID = %d, flags: 0x%04x due to error = 0x%08x",
+	       ntohl(ssfe->ssfe_info.snd_ppid), ssfe->ssfe_info.snd_sid,
+	       ssfe->ssfe_info.snd_flags, ssfe->ssfe_error);
+	n = ssfe->ssfe_length - sizeof(struct sctp_send_failed_event);
+	for (i = 0; i < n; i++) {
+		printf(" 0x%02x", ssfe->ssfe_data[i]);
+	}
+	printf(".\n");
+	return;
+}
+
+static void
+handle_notification(union sctp_notification *notif, size_t n)
+{
+	if (notif->sn_header.sn_length != (uint32_t)n) {
+		return;
+	}
+	switch (notif->sn_header.sn_type) {
+	case SCTP_ASSOC_CHANGE:
+		handle_association_change_event(&(notif->sn_assoc_change));
+		break;
+	case SCTP_PEER_ADDR_CHANGE:
+		handle_peer_address_change_event(&(notif->sn_paddr_change));
+		break;
+	case SCTP_REMOTE_ERROR:
+		break;
+	case SCTP_SHUTDOWN_EVENT:
+		break;
+	case SCTP_ADAPTATION_INDICATION:
+		break;
+	case SCTP_PARTIAL_DELIVERY_EVENT:
+		break;
+	case SCTP_AUTHENTICATION_EVENT:
+		break;
+	case SCTP_SENDER_DRY_EVENT:
+		break;
+	case SCTP_NOTIFICATIONS_STOPPED_EVENT:
+		break;
+	case SCTP_SEND_FAILED_EVENT:
+		handle_send_failed_event(&(notif->sn_send_failed_event));
+		break;
+	case SCTP_STREAM_RESET_EVENT:
+		break;
+	case SCTP_ASSOC_RESET_EVENT:
+		break;
+	case SCTP_STREAM_CHANGE_EVENT:
+		break;
+	default:
+		break;
+	}
+}
+
+static int
+receive_cb(struct socket *sock, union sctp_sockstore addr, void *data,
+           size_t datalen, struct sctp_rcvinfo rcv, int flags, void *ulp_info)
+{
+	if (data) {
+		if (flags & MSG_NOTIFICATION) {
+			handle_notification((union sctp_notification *)data, datalen);
+		} else {
+			printf("Msg of length %d received via %p:%u on stream %d with SSN %u and TSN %u, PPID %u, context %u.\n",
+			       (int)datalen,
+			       addr.sconn.sconn_addr,
+			       ntohs(addr.sconn.sconn_port),
+			       rcv.rcv_sid,
+			       rcv.rcv_ssn,
+			       rcv.rcv_tsn,
+			       ntohl(rcv.rcv_ppid),
+			       rcv.rcv_context);
+		}
+		free(data);
+	} else {
+		usrsctp_deregister_address(ulp_info);
+		usrsctp_close(sock);
+	}
+	return (1);
+}
+
+void
+debug_printf(const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+	vprintf(format, ap);
+	va_end(ap);
+}
+
+int
+main(int argc, char *argv[])
+{
+	struct sockaddr_in sin;
+	struct sockaddr_conn sconn;
+	struct sctp_event event;
+	uint16_t event_types[] = {SCTP_ASSOC_CHANGE,
+	                          SCTP_PEER_ADDR_CHANGE,
+	                          SCTP_SEND_FAILED_EVENT};
+	unsigned int i;
+#ifdef _WIN32
+	SOCKET fd;
+#else
+	int fd;
+#endif
+	struct socket *s;
+#ifdef _WIN32
+	HANDLE tid;
+#else
+	pthread_t tid;
+#endif
+	struct sctp_sndinfo sndinfo;
+	char line[LINE_LENGTH];
+#ifdef _WIN32
+	WSADATA wsaData;
+#endif
+
+	if (argc < 4) {
+		printf("error: this program requires 4 arguments!\n");
+		exit(EXIT_FAILURE);
+	}
+
+#ifdef _WIN32
+	if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {
+		printf("WSAStartup failed\n");
+		exit (EXIT_FAILURE);
+	}
+#endif
+	usrsctp_init(0, conn_output, debug_printf);
+	/* set up a connected UDP socket */
+#ifdef _WIN32
+	if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) {
+		printf("socket() failed with error: %d\n", WSAGetLastError());
+	}
+#else
+	if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
+		perror("socket");
+	}
+#endif
+	memset(&sin, 0, sizeof(struct sockaddr_in));
+	sin.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+	sin.sin_len = sizeof(struct sockaddr_in);
+#endif
+	sin.sin_port = htons(atoi(argv[2]));
+	if (!inet_pton(AF_INET, argv[1], &sin.sin_addr.s_addr)){
+		printf("error: invalid address\n");
+		exit(1);
+	}
+#ifdef _WIN32
+	if (bind(fd, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) == SOCKET_ERROR) {
+		printf("bind() failed with error: %d\n", WSAGetLastError());
+	}
+#else
+	if (bind(fd, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) < 0) {
+		perror("bind");
+	}
+#endif
+	memset(&sin, 0, sizeof(struct sockaddr_in));
+	sin.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+	sin.sin_len = sizeof(struct sockaddr_in);
+#endif
+	sin.sin_port = htons(atoi(argv[4]));
+	if (!inet_pton(AF_INET, argv[3], &sin.sin_addr.s_addr)){
+		printf("error: invalid address\n");
+		exit(1);
+	}
+#ifdef _WIN32
+	if (connect(fd, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) == SOCKET_ERROR) {
+		printf("connect() failed with error: %d\n", WSAGetLastError());
+	}
+#else
+	if (connect(fd, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) < 0) {
+		perror("connect");
+	}
+#endif
+#ifdef _WIN32
+	tid = CreateThread(NULL, 0, &handle_packets, (void *)&fd, 0, NULL);
+#else
+	pthread_create(&tid, NULL, &handle_packets, (void *)&fd);
+#endif
+#ifdef SCTP_DEBUG
+	usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_NONE);
+#endif
+	usrsctp_register_address((void *)&fd);
+	usrsctp_sysctl_set_sctp_ecn_enable(0);
+	if ((s = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, receive_cb, NULL, 0, &fd)) == NULL) {
+		perror("usrsctp_socket");
+	}
+	/* Enable the events of interest. */
+	if (usrsctp_set_non_blocking(s, 1) < 0) {
+		perror("usrsctp_set_non_blocking");
+	}
+	memset(&event, 0, sizeof(event));
+	event.se_assoc_id = SCTP_ALL_ASSOC;
+	event.se_on = 1;
+	for (i = 0; i < sizeof(event_types)/sizeof(uint16_t); i++) {
+		event.se_type = event_types[i];
+		if (usrsctp_setsockopt(s, IPPROTO_SCTP, SCTP_EVENT, &event, sizeof(event)) < 0) {
+			perror("setsockopt SCTP_EVENT");
+		}
+	}
+	memset(&sconn, 0, sizeof(struct sockaddr_conn));
+	sconn.sconn_family = AF_CONN;
+#ifdef HAVE_SCONN_LEN
+	sconn.sconn_len = sizeof(struct sockaddr_conn);
+#endif
+	sconn.sconn_port = htons(atoi(argv[5]));
+	sconn.sconn_addr = &fd;
+	if (usrsctp_bind(s, (struct sockaddr *)&sconn, sizeof(struct sockaddr_conn)) < 0) {
+		perror("usrsctp_bind");
+	}
+	memset(&sconn, 0, sizeof(struct sockaddr_conn));
+	sconn.sconn_family = AF_CONN;
+#ifdef HAVE_SCONN_LEN
+	sconn.sconn_len = sizeof(struct sockaddr_conn);
+#endif
+	sconn.sconn_port = htons(atoi(argv[6]));
+	sconn.sconn_addr = &fd;
+	if (usrsctp_connect(s, (struct sockaddr *)&sconn, sizeof(struct sockaddr_conn)) < 0) {
+		perror("usrsctp_connect");
+	}
+	for (;;) {
+#ifdef _WIN32
+		if (gets_s(line, LINE_LENGTH) == NULL) {
+#else
+		if (fgets(line, LINE_LENGTH, stdin) == NULL) {
+#endif
+			if (usrsctp_shutdown(s, SHUT_WR) < 0) {
+				perror("usrsctp_shutdown");
+			}
+			while (usrsctp_finish() != 0) {
+#ifdef _WIN32
+				Sleep(1000);
+#else
+				sleep(1);
+#endif
+			}
+			break;
+		}
+		sndinfo.snd_sid = 1;
+		sndinfo.snd_flags = 0;
+		sndinfo.snd_ppid = htonl(DISCARD_PPID);
+		sndinfo.snd_context = 0;
+		sndinfo.snd_assoc_id = 0;
+		if (usrsctp_sendv(s, line, strlen(line), NULL, 0, (void *)&sndinfo,
+	                          (socklen_t)sizeof(struct sctp_sndinfo), SCTP_SENDV_SNDINFO, 0) < 0) {
+			perror("usrsctp_sendv");
+		}
+	}
+	while (usrsctp_finish() != 0) {
+#ifdef _WIN32
+		Sleep(1000);
+#else
+		sleep(1);
+#endif
+	}
+#ifdef _WIN32
+	TerminateThread(tid, 0);
+	WaitForSingleObject(tid, INFINITE);
+	if (closesocket(fd) == SOCKET_ERROR) {
+		printf("closesocket() failed with error: %d\n", WSAGetLastError());
+	}
+	WSACleanup();
+#else
+	pthread_cancel(tid);
+	pthread_join(tid, NULL);
+	if (close(fd) < 0) {
+		perror("close");
+	}
+#endif
+	return (0);
+}
diff --git a/programs/ekr_server.c b/programs/ekr_server.c
new file mode 100644
index 0000000..6f81217
--- /dev/null
+++ b/programs/ekr_server.c
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2011-2013 Michael Tuexen
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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 _WIN32
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <stdarg.h>
+#ifndef _WIN32
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <pthread.h>
+#include <unistd.h>
+#else
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#endif
+#include <usrsctp.h>
+
+#define MAX_PACKET_SIZE (1<<16)
+
+#ifdef _WIN32
+static DWORD WINAPI
+#else
+static void *
+#endif
+handle_packets(void *arg)
+{
+#ifdef _WIN32
+	SOCKET *fdp;
+#else
+	int *fdp;
+#endif
+	char buf[MAX_PACKET_SIZE];
+	char *dump_buf;
+	ssize_t length;
+
+#ifdef _WIN32
+	fdp = (SOCKET *)arg;
+#else
+	fdp = (int *)arg;
+#endif
+	for (;;) {
+#if defined(__NetBSD__)
+		pthread_testcancel();
+#endif
+		length = recv(*fdp, buf, MAX_PACKET_SIZE, 0);
+		if (length > 0) {
+			if ((dump_buf = usrsctp_dumppacket(buf, (size_t)length, SCTP_DUMP_INBOUND)) != NULL) {
+				fprintf(stderr, "%s", dump_buf);
+				usrsctp_freedumpbuffer(dump_buf);
+			}
+			usrsctp_conninput(fdp, buf, (size_t)length, 0);
+		}
+	}
+#ifdef _WIN32
+	return 0;
+#else
+	return (NULL);
+#endif
+}
+
+static int
+conn_output(void *addr, void *buf, size_t length, uint8_t tos, uint8_t set_df)
+{
+	char *dump_buf;
+#ifdef _WIN32
+	SOCKET *fdp;
+#else
+	int *fdp;
+#endif
+
+#ifdef _WIN32
+	fdp = (SOCKET *)addr;
+#else
+	fdp = (int *)addr;
+#endif
+	if ((dump_buf = usrsctp_dumppacket(buf, length, SCTP_DUMP_OUTBOUND)) != NULL) {
+		fprintf(stderr, "%s", dump_buf);
+		usrsctp_freedumpbuffer(dump_buf);
+	}
+#ifdef _WIN32
+	if (send(*fdp, buf, (int)length, 0) == SOCKET_ERROR) {
+		return (WSAGetLastError());
+#else
+	if (send(*fdp, buf, length, 0) < 0) {
+		return (errno);
+#endif
+	} else {
+		return (0);
+	}
+}
+
+static int
+receive_cb(struct socket *s, union sctp_sockstore addr, void *data,
+           size_t datalen, struct sctp_rcvinfo rcv, int flags, void *ulp_info)
+{
+
+	if (data) {
+		if (flags & MSG_NOTIFICATION) {
+			printf("Notification of length %d received.\n", (int)datalen);
+		} else {
+			printf("Msg of length %d received via %p:%u on stream %d with SSN %u and TSN %u, PPID %u, context %u.\n",
+			       (int)datalen,
+			       addr.sconn.sconn_addr,
+			       ntohs(addr.sconn.sconn_port),
+			       rcv.rcv_sid,
+			       rcv.rcv_ssn,
+			       rcv.rcv_tsn,
+			       ntohl(rcv.rcv_ppid),
+			       rcv.rcv_context);
+		}
+		free(data);
+	} else {
+		usrsctp_close(s);
+	}
+	return (1);
+}
+
+void
+debug_printf(const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+	vprintf(format, ap);
+	va_end(ap);
+}
+
+int
+main(int argc, char *argv[])
+{
+	struct sockaddr_in sin;
+	struct sockaddr_conn sconn;
+#ifdef _WIN32
+	SOCKET fd;
+#else
+	int fd;
+#endif
+	struct socket *s;
+#ifdef _WIN32
+	HANDLE tid;
+#else
+	pthread_t tid;
+#endif
+#ifdef _WIN32
+	WSADATA wsaData;
+#endif
+
+	if (argc < 4) {
+		printf("error: this program requires 4 arguments!\n");
+		exit(EXIT_FAILURE);
+	}
+
+#ifdef _WIN32
+	if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {
+		printf("WSAStartup failed\n");
+		exit (EXIT_FAILURE);
+	}
+#endif
+	usrsctp_init(0, conn_output, debug_printf);
+	/* set up a connected UDP socket */
+#ifdef _WIN32
+	if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) {
+		printf("socket() failed with error: %d\n", WSAGetLastError());
+	}
+#else
+	if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
+		perror("socket");
+	}
+#endif
+	memset(&sin, 0, sizeof(struct sockaddr_in));
+	sin.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+	sin.sin_len = sizeof(struct sockaddr_in);
+#endif
+	sin.sin_port = htons(atoi(argv[2]));
+	if (!inet_pton(AF_INET, argv[1], &sin.sin_addr.s_addr)){
+		printf("error: invalid address\n");
+		exit(1);
+	}
+#ifdef _WIN32
+	if (bind(fd, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) == SOCKET_ERROR) {
+		printf("bind() failed with error: %d\n", WSAGetLastError());
+	}
+#else
+	if (bind(fd, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) < 0) {
+		perror("bind");
+	}
+#endif
+	memset(&sin, 0, sizeof(struct sockaddr_in));
+	sin.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+	sin.sin_len = sizeof(struct sockaddr_in);
+#endif
+	sin.sin_port = htons(atoi(argv[4]));
+	if (!inet_pton(AF_INET, argv[3], &sin.sin_addr.s_addr)){
+		printf("error: invalid address\n");
+		exit(1);
+	}
+#ifdef _WIN32
+	if (connect(fd, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) == SOCKET_ERROR) {
+		printf("connect() failed with error: %d\n", WSAGetLastError());
+	}
+#else
+	if (connect(fd, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) < 0) {
+		perror("connect");
+	}
+#endif
+#ifdef SCTP_DEBUG
+	usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_NONE);
+#endif
+	usrsctp_sysctl_set_sctp_ecn_enable(0);
+	usrsctp_register_address((void *)&fd);
+#ifdef _WIN32
+	tid = CreateThread(NULL, 0, &handle_packets, (void *)&fd, 0, NULL);
+#else
+	pthread_create(&tid, NULL, &handle_packets, (void *)&fd);
+#endif
+	if ((s = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, receive_cb, NULL, 0, NULL)) == NULL) {
+		perror("usrsctp_socket");
+	}
+	memset(&sconn, 0, sizeof(struct sockaddr_conn));
+	sconn.sconn_family = AF_CONN;
+#ifdef HAVE_SCONN_LEN
+	sconn.sconn_len = sizeof(struct sockaddr_conn);
+#endif
+	sconn.sconn_port = htons(5001);
+	sconn.sconn_addr = (void *)&fd;
+	if (usrsctp_bind(s, (struct sockaddr *)&sconn, sizeof(struct sockaddr_conn)) < 0) {
+		perror("usrsctp_bind");
+	}
+	if (usrsctp_listen(s, 1) < 0) {
+		perror("usrsctp_listen");
+	}
+	while (1) {
+		if (usrsctp_accept(s, NULL, NULL) == NULL) {
+			perror("usrsctp_accept");
+		}
+	}
+	usrsctp_close(s);
+	usrsctp_deregister_address((void *)&fd);
+	while (usrsctp_finish() != 0) {
+#ifdef _WIN32
+		Sleep(1000);
+#else
+		sleep(1);
+#endif
+	}
+#ifdef _WIN32
+	TerminateThread(tid, 0);
+	WaitForSingleObject(tid, INFINITE);
+	if (closesocket(fd) == SOCKET_ERROR) {
+		printf("closesocket() failed with error: %d\n", WSAGetLastError());
+	}
+	WSACleanup();
+#else
+	pthread_cancel(tid);
+	pthread_join(tid, NULL);
+	if (close(fd) < 0) {
+		perror("close");
+	}
+#endif
+	return (0);
+}
diff --git a/programs/http_client.c b/programs/http_client.c
new file mode 100644
index 0000000..6dfd1ee
--- /dev/null
+++ b/programs/http_client.c
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2016 Felix Weinrank
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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.
+ */
+
+/*
+ * Usage: http_client remote_addr remote_port [local_port] [local_encaps_port] [remote_encaps_port] [uri]
+ */
+
+#ifdef _WIN32
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+#include <sys/types.h>
+#ifndef _WIN32
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#else
+#include <io.h>
+#endif
+#include <usrsctp.h>
+
+int done = 0;
+static const char *request_prefix = "GET";
+static const char *request_postfix = "HTTP/1.0\r\nUser-agent: libusrsctp\r\nConnection: close\r\n\r\n";
+char request[512];
+
+#ifdef _WIN32
+typedef char* caddr_t;
+#endif
+
+static int
+receive_cb(struct socket *sock, union sctp_sockstore addr, void *data,
+           size_t datalen, struct sctp_rcvinfo rcv, int flags, void *ulp_info)
+{
+	if (data == NULL) {
+		done = 1;
+		usrsctp_close(sock);
+	} else {
+#ifdef _WIN32
+		_write(_fileno(stdout), data, (unsigned int)datalen);
+#else
+		if (write(fileno(stdout), data, datalen) < 0) {
+			perror("write");
+		}
+#endif
+		free(data);
+	}
+	return (1);
+}
+
+void
+debug_printf(const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+	vprintf(format, ap);
+	va_end(ap);
+}
+
+int
+main(int argc, char *argv[])
+{
+	struct socket *sock;
+	struct sockaddr_in addr4;
+	struct sockaddr_in6 addr6;
+	struct sctp_udpencaps encaps;
+	int result;
+
+    if (argc < 3) {
+        printf("Usage: http_client remote_addr remote_port [local_port] [local_encaps_port] [remote_encaps_port] [uri]\n");
+        return(EXIT_FAILURE);
+    }
+
+	result = 0;
+	if (argc > 4) {
+		usrsctp_init(atoi(argv[4]), NULL, debug_printf);
+	} else {
+		usrsctp_init(9899, NULL, debug_printf);
+	}
+
+#ifdef SCTP_DEBUG
+	usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL);
+#endif
+
+	usrsctp_sysctl_set_sctp_blackhole(2);
+
+	if ((sock = usrsctp_socket(AF_INET6, SOCK_STREAM, IPPROTO_SCTP, receive_cb, NULL, 0, NULL)) == NULL) {
+		perror("usrsctp_socket");
+		result = 1;
+		goto out;
+	}
+
+	if (argc > 3) {
+		memset((void *)&addr6, 0, sizeof(struct sockaddr_in6));
+#ifdef HAVE_SIN6_LEN
+		addr6.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+		addr6.sin6_family = AF_INET6;
+		addr6.sin6_port = htons(atoi(argv[3]));
+		addr6.sin6_addr = in6addr_any;
+		if (usrsctp_bind(sock, (struct sockaddr *)&addr6, sizeof(struct sockaddr_in6)) < 0) {
+			perror("bind");
+			usrsctp_close(sock);
+			result = 2;
+			goto out;
+		}
+	}
+
+	if (argc > 5) {
+		memset(&encaps, 0, sizeof(struct sctp_udpencaps));
+		encaps.sue_address.ss_family = AF_INET6;
+		encaps.sue_port = htons(atoi(argv[5]));
+		if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_REMOTE_UDP_ENCAPS_PORT, (const void *)&encaps, (socklen_t)sizeof(struct sctp_udpencaps)) < 0) {
+			perror("setsockopt");
+			usrsctp_close(sock);
+			result = 3;
+			goto out;
+		}
+	}
+
+	if (argc > 6) {
+#ifdef _WIN32
+		_snprintf(request, sizeof(request), "%s %s %s", request_prefix, argv[6], request_postfix);
+#else
+		snprintf(request, sizeof(request), "%s %s %s", request_prefix, argv[6], request_postfix);
+#endif
+	} else {
+#ifdef _WIN32
+		_snprintf(request, sizeof(request), "%s %s %s", request_prefix, "/", request_postfix);
+#else
+		snprintf(request, sizeof(request), "%s %s %s", request_prefix, "/", request_postfix);
+#endif
+	}
+
+	printf("\nHTTP request:\n%s\n", request);
+	printf("\nHTTP response:\n");
+
+	memset((void *)&addr4, 0, sizeof(struct sockaddr_in));
+	memset((void *)&addr6, 0, sizeof(struct sockaddr_in6));
+#ifdef HAVE_SIN_LEN
+	addr4.sin_len = sizeof(struct sockaddr_in);
+#endif
+#ifdef HAVE_SIN6_LEN
+	addr6.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+	addr4.sin_family = AF_INET;
+	addr6.sin6_family = AF_INET6;
+	addr4.sin_port = htons(atoi(argv[2]));
+	addr6.sin6_port = htons(atoi(argv[2]));
+	if (inet_pton(AF_INET6, argv[1], &addr6.sin6_addr) == 1) {
+		if (usrsctp_connect(sock, (struct sockaddr *)&addr6, sizeof(struct sockaddr_in6)) < 0) {
+			perror("usrsctp_connect");
+			usrsctp_close(sock);
+			result = 4;
+			goto out;
+		}
+	} else if (inet_pton(AF_INET, argv[1], &addr4.sin_addr) == 1) {
+		if (usrsctp_connect(sock, (struct sockaddr *)&addr4, sizeof(struct sockaddr_in)) < 0) {
+			perror("usrsctp_connect");
+			usrsctp_close(sock);
+			result = 5;
+			goto out;
+		}
+	} else {
+		printf("Illegal destination address\n");
+		usrsctp_close(sock);
+		result = 6;
+		goto out;
+	}
+
+	/* send GET request */
+	if (usrsctp_sendv(sock, request, strlen(request), NULL, 0, NULL, 0, SCTP_SENDV_NOINFO, 0) < 0) {
+		perror("usrsctp_sendv");
+		usrsctp_close(sock);
+		result = 6;
+		goto out;
+	}
+
+	while (!done) {
+#ifdef _WIN32
+		Sleep(1*1000);
+#else
+		sleep(1);
+#endif
+	}
+out:
+	while (usrsctp_finish() != 0) {
+#ifdef _WIN32
+		Sleep(1000);
+#else
+		sleep(1);
+#endif
+	}
+	return (result);
+}
diff --git a/programs/http_client_upcall.c b/programs/http_client_upcall.c
new file mode 100644
index 0000000..19cbefe
--- /dev/null
+++ b/programs/http_client_upcall.c
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2016 Felix Weinrank
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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.
+ */
+
+/*
+ * Usage: http_client remote_addr remote_port [local_port] [local_encaps_port] [remote_encaps_port] [uri]
+ */
+
+#ifdef _WIN32
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+#include <sys/types.h>
+#ifndef _WIN32
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#else
+#include <io.h>
+#endif
+#include <usrsctp.h>
+
+int done = 0;
+int writePending = 1;
+
+static const char *request_prefix = "GET";
+static const char *request_postfix = "HTTP/1.0\r\nUser-agent: libusrsctp\r\nConnection: close\r\n\r\n";
+char request[512];
+
+#ifdef _WIN32
+typedef char* caddr_t;
+#endif
+
+#define BUFFERSIZE                 (1<<16)
+
+
+static void handle_upcall(struct socket *sock, void *arg, int flags)
+{
+	int events = usrsctp_get_events(sock);
+	int bytesSent = 0;
+	char *buf;
+
+	if ((events & SCTP_EVENT_WRITE) && writePending) {
+		writePending = 0;
+		printf("\nHTTP request:\n%s\n", request);
+		printf("\nHTTP response:\n");
+
+		/* send GET request */
+		bytesSent = usrsctp_sendv(sock, request, strlen(request), NULL, 0, NULL, 0, SCTP_SENDV_NOINFO, 0);
+		if (bytesSent < 0) {
+			perror("usrsctp_sendv");
+			usrsctp_close(sock);
+		} else {
+			printf("%d bytes sent\n", bytesSent);
+		}
+	}
+
+	if ((events & SCTP_EVENT_READ) && !done) {
+		struct sctp_recvv_rn rn;
+		ssize_t n;
+		struct sockaddr_in addr;
+		buf = malloc(BUFFERSIZE);
+		int flags = 0;
+		socklen_t len = (socklen_t)sizeof(struct sockaddr_in);
+		unsigned int infotype = 0;
+		socklen_t infolen = sizeof(struct sctp_recvv_rn);
+		memset(&rn, 0, sizeof(struct sctp_recvv_rn));
+		n = usrsctp_recvv(sock, buf, BUFFERSIZE, (struct sockaddr *) &addr, &len, (void *)&rn,
+	                 &infolen, &infotype, &flags);
+		if (n > 0)
+			n = write(1, buf, n);
+		done = 1;
+		usrsctp_close(sock);
+		free(buf);
+		return;
+	}
+}
+
+void
+debug_printf(const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+	vprintf(format, ap);
+	va_end(ap);
+}
+
+int
+main(int argc, char *argv[])
+{
+	struct socket *sock;
+	struct sockaddr_in addr4;
+	struct sockaddr_in6 addr6;
+	struct sctp_udpencaps encaps;
+	int result;
+
+    if (argc < 3) {
+        printf("Usage: http_client remote_addr remote_port [local_port] [local_encaps_port] [remote_encaps_port] [uri]\n");
+        return(EXIT_FAILURE);
+    }
+
+	result = 0;
+	if (argc > 4) {
+		usrsctp_init(atoi(argv[4]), NULL, debug_printf);
+	} else {
+		usrsctp_init(9899, NULL, debug_printf);
+	}
+
+#ifdef SCTP_DEBUG
+	usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_NONE);
+#endif
+
+	usrsctp_sysctl_set_sctp_blackhole(2);
+
+	if ((sock = usrsctp_socket(AF_INET6, SOCK_STREAM, IPPROTO_SCTP, NULL, NULL, 0, NULL)) == NULL) {
+		perror("usrsctp_socket");
+		result = 1;
+		goto out;
+	}
+
+	usrsctp_set_non_blocking(sock, 1);
+
+	if (argc > 3) {
+		memset((void *)&addr6, 0, sizeof(struct sockaddr_in6));
+#ifdef HAVE_SIN6_LEN
+		addr6.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+		addr6.sin6_family = AF_INET6;
+		addr6.sin6_port = htons(atoi(argv[3]));
+		addr6.sin6_addr = in6addr_any;
+		if (usrsctp_bind(sock, (struct sockaddr *)&addr6, sizeof(struct sockaddr_in6)) < 0) {
+			perror("bind");
+			usrsctp_close(sock);
+			result = 2;
+			goto out;
+		}
+	}
+
+	if (argc > 5) {
+		memset(&encaps, 0, sizeof(struct sctp_udpencaps));
+		encaps.sue_address.ss_family = AF_INET6;
+		encaps.sue_port = htons(atoi(argv[5]));
+		if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_REMOTE_UDP_ENCAPS_PORT, (const void *)&encaps, (socklen_t)sizeof(struct sctp_udpencaps)) < 0) {
+			perror("setsockopt");
+			usrsctp_close(sock);
+			result = 3;
+			goto out;
+		}
+	}
+
+	if (argc > 6) {
+#ifdef _WIN32
+		_snprintf(request, sizeof(request), "%s %s %s", request_prefix, argv[6], request_postfix);
+#else
+		snprintf(request, sizeof(request), "%s %s %s", request_prefix, argv[6], request_postfix);
+#endif
+	} else {
+#ifdef _WIN32
+		_snprintf(request, sizeof(request), "%s %s %s", request_prefix, "/", request_postfix);
+#else
+		snprintf(request, sizeof(request), "%s %s %s", request_prefix, "/", request_postfix);
+#endif
+	}
+
+	usrsctp_set_upcall(sock, handle_upcall, NULL);
+
+	memset((void *)&addr4, 0, sizeof(struct sockaddr_in));
+	memset((void *)&addr6, 0, sizeof(struct sockaddr_in6));
+#ifdef HAVE_SIN_LEN
+	addr4.sin_len = sizeof(struct sockaddr_in);
+#endif
+#ifdef HAVE_SIN6_LEN
+	addr6.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+	addr4.sin_family = AF_INET;
+	addr6.sin6_family = AF_INET6;
+	addr4.sin_port = htons(atoi(argv[2]));
+	addr6.sin6_port = htons(atoi(argv[2]));
+	if (inet_pton(AF_INET6, argv[1], &addr6.sin6_addr) == 1) {
+		if (usrsctp_connect(sock, (struct sockaddr *)&addr6, sizeof(struct sockaddr_in6)) < 0) {
+			if (errno != EINPROGRESS) {
+				perror("usrsctp_connect");
+				usrsctp_close(sock);
+				result = 4;
+				goto out;
+			}
+		}
+	} else if (inet_pton(AF_INET, argv[1], &addr4.sin_addr) == 1) {
+		if (usrsctp_connect(sock, (struct sockaddr *)&addr4, sizeof(struct sockaddr_in)) < 0) {
+			if (errno != EINPROGRESS) {
+				perror("usrsctp_connect");
+				usrsctp_close(sock);
+				result = 5;
+				goto out;
+			}
+		}
+	} else {
+		printf("Illegal destination address\n");
+		usrsctp_close(sock);
+		result = 6;
+		goto out;
+	}
+	while (!done) {
+#ifdef _WIN32
+		Sleep(1*1000);
+#else
+		sleep(1);
+#endif
+	}
+out:
+	while (usrsctp_finish() != 0) {
+#ifdef _WIN32
+		Sleep(1000);
+#else
+		sleep(1);
+#endif
+	}
+	return (result);
+}
diff --git a/programs/rtcweb.c b/programs/rtcweb.c
new file mode 100644
index 0000000..4b940ed
--- /dev/null
+++ b/programs/rtcweb.c
@@ -0,0 +1,1517 @@
+/*-
+ * Copyright (C) 2012-2013 Michael Tuexen
+ * Copyright (C) 2012-2013 Irene Ruengeler
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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.
+ *
+ * $Id: rtcweb.c,v 1.26 2012-07-17 13:50:02 tuexen Exp $
+ */
+
+/*
+ * gcc -Wall -std=c99 -pedantic -o rtcweb rtcweb.c -lusrsctp
+ */
+
+#include <sys/types.h>
+#ifdef _WIN32
+#define _CRT_SECURE_NO_WARNINGS
+#include <WinSock2.h>
+#include <WS2tcpip.h>
+#include <crtdbg.h>
+#else
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <stdint.h>
+#endif
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <usrsctp.h>
+
+#define LINE_LENGTH (1024)
+#define BUFFER_SIZE (1<<16)
+#define NUMBER_OF_CHANNELS (100)
+#define NUMBER_OF_STREAMS (100)
+
+#define DATA_CHANNEL_PPID_CONTROL   50
+#define DATA_CHANNEL_PPID_DOMSTRING 51
+#define DATA_CHANNEL_PPID_BINARY    52
+
+#define DATA_CHANNEL_CLOSED     0
+#define DATA_CHANNEL_CONNECTING 1
+#define DATA_CHANNEL_OPEN       2
+#define DATA_CHANNEL_CLOSING    3
+
+#define DATA_CHANNEL_FLAGS_SEND_REQ 0x00000001
+#define DATA_CHANNEL_FLAGS_SEND_RSP 0x00000002
+#define DATA_CHANNEL_FLAGS_SEND_ACK 0x00000004
+
+struct channel {
+	uint32_t id;
+	uint32_t pr_value;
+	uint16_t pr_policy;
+	uint16_t i_stream;
+	uint16_t o_stream;
+	uint8_t unordered;
+	uint8_t state;
+	uint32_t flags;
+};
+
+struct peer_connection {
+	struct channel channels[NUMBER_OF_CHANNELS];
+	struct channel *i_stream_channel[NUMBER_OF_STREAMS];
+	struct channel *o_stream_channel[NUMBER_OF_STREAMS];
+	uint16_t o_stream_buffer[NUMBER_OF_STREAMS];
+	uint32_t o_stream_buffer_counter;
+#ifdef _WIN32
+	CRITICAL_SECTION mutex;
+#else
+	pthread_mutex_t mutex;
+#endif
+	struct socket *sock;
+} peer_connection;
+
+#define DATA_CHANNEL_OPEN_REQUEST  0
+#define DATA_CHANNEL_OPEN_RESPONSE 1
+#define DATA_CHANNEL_ACK           2
+
+#define DATA_CHANNEL_RELIABLE                0
+#define DATA_CHANNEL_RELIABLE_STREAM         1
+#define DATA_CHANNEL_UNRELIABLE              2
+#define DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT 3
+#define DATA_CHANNEL_PARTIAL_RELIABLE_TIMED  4
+
+#define DATA_CHANNEL_FLAG_OUT_OF_ORDER_ALLOWED 0x0001
+
+#ifndef _WIN32
+#define SCTP_PACKED __attribute__((packed))
+#else
+#pragma pack (push, 1)
+#define SCTP_PACKED
+#endif
+
+struct rtcweb_datachannel_open_request {
+	uint8_t msg_type; /* DATA_CHANNEL_OPEN_REQUEST */
+	uint8_t channel_type;
+	uint16_t flags;
+	uint16_t reliability_params;
+	int16_t priority;
+	char label[];
+} SCTP_PACKED;
+
+struct rtcweb_datachannel_open_response {
+	uint8_t  msg_type; /* DATA_CHANNEL_OPEN_RESPONSE */
+	uint8_t  error;
+	uint16_t flags;
+	uint16_t reverse_stream;
+} SCTP_PACKED;
+
+struct rtcweb_datachannel_ack {
+	uint8_t  msg_type; /* DATA_CHANNEL_ACK */
+} SCTP_PACKED;
+
+#ifdef _WIN32
+#pragma pack()
+#endif
+
+#undef SCTP_PACKED
+
+static void
+init_peer_connection(struct peer_connection *pc)
+{
+	uint32_t i;
+	struct channel *channel;
+
+	for (i = 0; i < NUMBER_OF_CHANNELS; i++) {
+		channel = &(pc->channels[i]);
+		channel->id = i;
+		channel->state = DATA_CHANNEL_CLOSED;
+		channel->pr_policy = SCTP_PR_SCTP_NONE;
+		channel->pr_value = 0;
+		channel->i_stream = 0;
+		channel->o_stream = 0;
+		channel->unordered = 0;
+		channel->flags = 0;
+	}
+	for (i = 0; i < NUMBER_OF_STREAMS; i++) {
+		pc->i_stream_channel[i] = NULL;
+		pc->o_stream_channel[i] = NULL;
+		pc->o_stream_buffer[i] = 0;
+	}
+	pc->o_stream_buffer_counter = 0;
+	pc->sock = NULL;
+#ifdef _WIN32
+	InitializeCriticalSection(&(pc->mutex));
+#else
+	pthread_mutex_init(&pc->mutex, NULL);
+#endif
+}
+
+static void
+lock_peer_connection(struct peer_connection *pc)
+{
+#ifdef _WIN32
+	EnterCriticalSection(&(pc->mutex));
+#else
+	pthread_mutex_lock(&pc->mutex);
+#endif
+}
+
+static void
+unlock_peer_connection(struct peer_connection *pc)
+{
+#ifdef _WIN32
+	LeaveCriticalSection(&(pc->mutex));
+#else
+	pthread_mutex_unlock(&pc->mutex);
+#endif
+}
+
+static struct channel *
+find_channel_by_i_stream(struct peer_connection *pc, uint16_t i_stream)
+{
+	if (i_stream < NUMBER_OF_STREAMS) {
+		return (pc->i_stream_channel[i_stream]);
+	} else {
+		return (NULL);
+	}
+}
+
+static struct channel *
+find_channel_by_o_stream(struct peer_connection *pc, uint16_t o_stream)
+{
+	if (o_stream < NUMBER_OF_STREAMS) {
+		return (pc->o_stream_channel[o_stream]);
+	} else {
+		return (NULL);
+	}
+}
+
+static struct channel *
+find_free_channel(struct peer_connection *pc)
+{
+	uint32_t i;
+
+	for (i = 0; i < NUMBER_OF_CHANNELS; i++) {
+		if (pc->channels[i].state == DATA_CHANNEL_CLOSED) {
+			break;
+		}
+	}
+	if (i == NUMBER_OF_CHANNELS) {
+		return (NULL);
+	} else {
+		return (&(pc->channels[i]));
+	}
+}
+
+static uint16_t
+find_free_o_stream(struct peer_connection *pc)
+{
+	struct sctp_status status;
+	uint32_t i, limit;
+	socklen_t len;
+
+	len = (socklen_t)sizeof(struct sctp_status);
+	if (usrsctp_getsockopt(pc->sock, IPPROTO_SCTP, SCTP_STATUS, &status, &len) < 0) {
+		perror("getsockopt");
+		return (0);
+	}
+	if (status.sstat_outstrms < NUMBER_OF_STREAMS) {
+		limit = status.sstat_outstrms;
+	} else {
+		limit = NUMBER_OF_STREAMS;
+	}
+	/* stream id 0 is reserved */
+	for (i = 1; i < limit; i++) {
+		if (pc->o_stream_channel[i] == NULL) {
+			break;
+		}
+	}
+	if (i == limit) {
+		return (0);
+	} else {
+		return ((uint16_t)i);
+	}
+}
+
+static void
+request_more_o_streams(struct peer_connection *pc)
+{
+	struct sctp_status status;
+	struct sctp_add_streams sas;
+	uint32_t i, o_streams_needed;
+	socklen_t len;
+
+	o_streams_needed = 0;
+	for (i = 0; i < NUMBER_OF_CHANNELS; i++) {
+		if ((pc->channels[i].state == DATA_CHANNEL_CONNECTING) &&
+		    (pc->channels[i].o_stream == 0)) {
+			o_streams_needed++;
+		}
+	}
+	len = (socklen_t)sizeof(struct sctp_status);
+	if (usrsctp_getsockopt(pc->sock, IPPROTO_SCTP, SCTP_STATUS, &status, &len) < 0) {
+		perror("getsockopt");
+		return;
+	}
+	if (status.sstat_outstrms + o_streams_needed > NUMBER_OF_STREAMS) {
+		o_streams_needed = NUMBER_OF_STREAMS - status.sstat_outstrms;
+	}
+	if (o_streams_needed == 0) {
+		return;
+	}
+	memset(&sas, 0, sizeof(struct sctp_add_streams));
+	sas.sas_instrms = 0;
+	sas.sas_outstrms = (uint16_t)o_streams_needed; /* XXX eror handling */
+	if (usrsctp_setsockopt(pc->sock, IPPROTO_SCTP, SCTP_ADD_STREAMS, &sas, (socklen_t)sizeof(struct sctp_add_streams)) < 0) {
+		perror("setsockopt");
+	}
+	return;
+}
+
+static int
+send_open_request_message(struct socket *sock, uint16_t o_stream, uint8_t unordered, uint16_t pr_policy, uint32_t pr_value)
+{
+	/* XXX: This should be encoded in a better way */
+	struct rtcweb_datachannel_open_request req;
+	struct sctp_sndinfo sndinfo;
+
+	memset(&req, 0, sizeof(struct rtcweb_datachannel_open_request));
+	req.msg_type = DATA_CHANNEL_OPEN_REQUEST;
+	switch (pr_policy) {
+	case SCTP_PR_SCTP_NONE:
+		/* XXX: What about DATA_CHANNEL_RELIABLE_STREAM */
+		req.channel_type = DATA_CHANNEL_RELIABLE;
+		break;
+	case SCTP_PR_SCTP_TTL:
+		/* XXX: What about DATA_CHANNEL_UNRELIABLE */
+		req.channel_type = DATA_CHANNEL_PARTIAL_RELIABLE_TIMED;
+		break;
+	case SCTP_PR_SCTP_RTX:
+		req.channel_type = DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT;
+		break;
+	default:
+		return (0);
+	}
+	req.flags = htons(0);
+	if (unordered) {
+		req.flags |= htons(DATA_CHANNEL_FLAG_OUT_OF_ORDER_ALLOWED);
+	}
+	req.reliability_params = htons((uint16_t)pr_value); /* XXX Why 16-bit */
+	req.priority = htons(0); /* XXX: add support */
+	memset(&sndinfo, 0, sizeof(struct sctp_sndinfo));
+	sndinfo.snd_sid = o_stream;
+	sndinfo.snd_flags = SCTP_EOR;
+	sndinfo.snd_ppid = htonl(DATA_CHANNEL_PPID_CONTROL);
+	if (usrsctp_sendv(sock,
+	                  &req, sizeof(struct rtcweb_datachannel_open_request),
+	                  NULL, 0,
+	                  &sndinfo, (socklen_t)sizeof(struct sctp_sndinfo),
+	                  SCTP_SENDV_SNDINFO, 0) < 0) {
+		perror("sctp_sendv");
+		return (0);
+	} else {
+		return (1);
+	}
+}
+
+static int
+send_open_response_message(struct socket *sock, uint16_t o_stream, uint16_t i_stream)
+{
+	/* XXX: This should be encoded in a better way */
+	struct rtcweb_datachannel_open_response rsp;
+	struct sctp_sndinfo sndinfo;
+
+	memset(&rsp, 0, sizeof(struct rtcweb_datachannel_open_response));
+	rsp.msg_type = DATA_CHANNEL_OPEN_RESPONSE;
+	rsp.error = 0;
+	rsp.flags = htons(0);
+	rsp.reverse_stream = htons(i_stream);
+	memset(&sndinfo, 0, sizeof(struct sctp_sndinfo));
+	sndinfo.snd_sid = o_stream;
+	sndinfo.snd_flags = SCTP_EOR;
+	sndinfo.snd_ppid = htonl(DATA_CHANNEL_PPID_CONTROL);
+	if (usrsctp_sendv(sock,
+	                  &rsp, sizeof(struct rtcweb_datachannel_open_response),
+	                  NULL, 0,
+	                  &sndinfo, (socklen_t)sizeof(struct sctp_sndinfo),
+	                  SCTP_SENDV_SNDINFO, 0) < 0) {
+		perror("sctp_sendv");
+		return (0);
+	} else {
+		return (1);
+	}
+}
+
+static int
+send_open_ack_message(struct socket *sock, uint16_t o_stream)
+{
+	/* XXX: This should be encoded in a better way */
+	struct rtcweb_datachannel_ack ack;
+	struct sctp_sndinfo sndinfo;
+
+	memset(&ack, 0, sizeof(struct rtcweb_datachannel_ack));
+	ack.msg_type = DATA_CHANNEL_ACK;
+	memset(&sndinfo, 0, sizeof(struct sctp_sndinfo));
+	sndinfo.snd_sid = o_stream;
+	sndinfo.snd_flags = SCTP_EOR;
+	sndinfo.snd_ppid = htonl(DATA_CHANNEL_PPID_CONTROL);
+	if (usrsctp_sendv(sock,
+	                  &ack, sizeof(struct rtcweb_datachannel_ack),
+	                  NULL, 0,
+	                  &sndinfo, (socklen_t)sizeof(struct sctp_sndinfo),
+	                  SCTP_SENDV_SNDINFO, 0) < 0) {
+		perror("sctp_sendv");
+		return (0);
+	} else {
+		return (1);
+	}
+}
+
+static void
+send_deferred_messages(struct peer_connection *pc)
+{
+	uint32_t i;
+	struct channel *channel;
+
+	for (i = 0; i < NUMBER_OF_CHANNELS; i++) {
+		channel = &(pc->channels[i]);
+		if (channel->flags & DATA_CHANNEL_FLAGS_SEND_REQ) {
+			if (send_open_request_message(pc->sock, channel->o_stream, channel->unordered, channel->pr_policy, channel->pr_value)) {
+				channel->flags &= ~DATA_CHANNEL_FLAGS_SEND_REQ;
+			} else {
+				if (errno != EAGAIN) {
+					/* XXX: error handling */
+				}
+			}
+		}
+		if (channel->flags & DATA_CHANNEL_FLAGS_SEND_RSP) {
+			if (send_open_response_message(pc->sock, channel->o_stream, channel->i_stream)) {
+				channel->flags &= ~DATA_CHANNEL_FLAGS_SEND_RSP;
+			} else {
+				if (errno != EAGAIN) {
+					/* XXX: error handling */
+				}
+			}
+		}
+		if (channel->flags & DATA_CHANNEL_FLAGS_SEND_ACK) {
+			if (send_open_ack_message(pc->sock, channel->o_stream)) {
+				channel->flags &= ~DATA_CHANNEL_FLAGS_SEND_ACK;
+			} else {
+				if (errno != EAGAIN) {
+					/* XXX: error handling */
+				}
+			}
+		}
+	}
+	return;
+}
+
+static struct channel *
+open_channel(struct peer_connection *pc, uint8_t unordered, uint16_t pr_policy, uint32_t pr_value)
+{
+	struct channel *channel;
+	uint16_t o_stream;
+
+	if ((pr_policy != SCTP_PR_SCTP_NONE) &&
+	    (pr_policy != SCTP_PR_SCTP_TTL) &&
+	    (pr_policy != SCTP_PR_SCTP_RTX)) {
+		return (NULL);
+	}
+	if ((unordered != 0) && (unordered != 1)) {
+		return (NULL);
+	}
+	if ((pr_policy == SCTP_PR_SCTP_NONE) && (pr_value != 0)) {
+		return (NULL);
+	}
+	if ((channel = find_free_channel(pc)) == NULL) {
+		return (NULL);
+	}
+	o_stream = find_free_o_stream(pc);
+	channel->state = DATA_CHANNEL_CONNECTING;
+	channel->unordered = unordered;
+	channel->pr_policy = pr_policy;
+	channel->pr_value = pr_value;
+	channel->o_stream = o_stream;
+	channel->flags = 0;
+	if (o_stream == 0) {
+		request_more_o_streams(pc);
+	} else {
+		if (send_open_request_message(pc->sock, o_stream, unordered, pr_policy, pr_value)) {
+			pc->o_stream_channel[o_stream] = channel;
+		} else {
+			if (errno == EAGAIN) {
+				pc->o_stream_channel[o_stream] = channel;
+				channel->flags |= DATA_CHANNEL_FLAGS_SEND_REQ;
+			} else {
+				channel->state = DATA_CHANNEL_CLOSED;
+				channel->unordered = 0;
+				channel->pr_policy = 0;
+				channel->pr_value = 0;
+				channel->o_stream = 0;
+				channel->flags = 0;
+				channel = NULL;
+			}
+		}
+	}
+	return (channel);
+}
+
+static int
+send_user_message(struct peer_connection *pc, struct channel *channel, char *message, size_t length)
+{
+	struct sctp_sendv_spa spa;
+
+	if (channel == NULL) {
+		return (0);
+	}
+	if ((channel->state != DATA_CHANNEL_OPEN) &&
+	    (channel->state != DATA_CHANNEL_CONNECTING)) {
+		/* XXX: What to do in other states */
+		return (0);
+	}
+
+	memset(&spa, 0, sizeof(struct sctp_sendv_spa));
+	spa.sendv_sndinfo.snd_sid = channel->o_stream;
+	if ((channel->state == DATA_CHANNEL_OPEN) &&
+	    (channel->unordered)) {
+		spa.sendv_sndinfo.snd_flags = SCTP_EOR | SCTP_UNORDERED;
+	} else {
+		spa.sendv_sndinfo.snd_flags = SCTP_EOR;
+	}
+	spa.sendv_sndinfo.snd_ppid = htonl(DATA_CHANNEL_PPID_DOMSTRING);
+	spa.sendv_flags = SCTP_SEND_SNDINFO_VALID;
+	if ((channel->pr_policy == SCTP_PR_SCTP_TTL) ||
+	    (channel->pr_policy == SCTP_PR_SCTP_RTX)) {
+		spa.sendv_prinfo.pr_policy = channel->pr_policy;
+		spa.sendv_prinfo.pr_value = channel->pr_value;
+		spa.sendv_flags |= SCTP_SEND_PRINFO_VALID;
+	}
+	if (usrsctp_sendv(pc->sock,
+	                  message, length,
+	                  NULL, 0,
+	                  &spa, (socklen_t)sizeof(struct sctp_sendv_spa),
+	                  SCTP_SENDV_SPA, 0) < 0) {
+		perror("sctp_sendv");
+		return (0);
+	} else {
+		return (1);
+	}
+}
+
+static void
+reset_outgoing_stream(struct peer_connection *pc, uint16_t o_stream)
+{
+	uint32_t i;
+
+	for (i = 0; i < pc->o_stream_buffer_counter; i++) {
+		if (pc->o_stream_buffer[i] == o_stream) {
+			return;
+		}
+	}
+	pc->o_stream_buffer[pc->o_stream_buffer_counter++] = o_stream;
+	return;
+}
+
+static void
+send_outgoing_stream_reset(struct peer_connection *pc)
+{
+	struct sctp_reset_streams *srs;
+	uint32_t i;
+	size_t len;
+
+	if (pc->o_stream_buffer_counter == 0) {
+		return;
+	}
+	len = sizeof(sctp_assoc_t) + (2 + pc->o_stream_buffer_counter) * sizeof(uint16_t);
+	srs = (struct sctp_reset_streams *)malloc(len);
+	if (srs == NULL) {
+		return;
+	}
+	memset(srs, 0, len);
+	srs->srs_flags = SCTP_STREAM_RESET_OUTGOING;
+	srs->srs_number_streams = pc->o_stream_buffer_counter;
+	for (i = 0; i < pc->o_stream_buffer_counter; i++) {
+		srs->srs_stream_list[i] = pc->o_stream_buffer[i];
+	}
+	if (usrsctp_setsockopt(pc->sock, IPPROTO_SCTP, SCTP_RESET_STREAMS, srs, (socklen_t)len) < 0) {
+		perror("setsockopt");
+	} else {
+		for (i = 0; i < pc->o_stream_buffer_counter; i++) {
+			srs->srs_stream_list[i] = 0;
+		}
+		pc->o_stream_buffer_counter = 0;
+	}
+	free(srs);
+	return;
+}
+
+static void
+close_channel(struct peer_connection *pc, struct channel *channel)
+{
+	if (channel == NULL) {
+		return;
+	}
+	if (channel->state != DATA_CHANNEL_OPEN) {
+		return;
+	}
+	reset_outgoing_stream(pc, channel->o_stream);
+	send_outgoing_stream_reset(pc);
+	channel->state = DATA_CHANNEL_CLOSING;
+	return;
+}
+
+static void
+handle_open_request_message(struct peer_connection *pc,
+                            struct rtcweb_datachannel_open_request *req,
+                            size_t length,
+                            uint16_t i_stream)
+{
+	struct channel *channel;
+	uint32_t pr_value;
+	uint16_t pr_policy;
+	uint16_t o_stream;
+	uint8_t unordered;
+
+	if ((channel = find_channel_by_i_stream(pc, i_stream))) {
+		printf("handle_open_request_message: channel %d is in state %d instead of CLOSED.\n",
+		       channel->id, channel->state);
+		/* XXX: some error handling */
+		return;
+	}
+	if ((channel = find_free_channel(pc)) == NULL) {
+		/* XXX: some error handling */
+		return;
+	}
+	switch (req->channel_type) {
+	case DATA_CHANNEL_RELIABLE:
+		pr_policy = SCTP_PR_SCTP_NONE;
+		break;
+	/* XXX Doesn't make sense */
+	case DATA_CHANNEL_RELIABLE_STREAM:
+		pr_policy = SCTP_PR_SCTP_NONE;
+		break;
+	/* XXX Doesn't make sense */
+	case DATA_CHANNEL_UNRELIABLE:
+		pr_policy = SCTP_PR_SCTP_TTL;
+		break;
+	case DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT:
+		pr_policy = SCTP_PR_SCTP_RTX;
+		break;
+	case DATA_CHANNEL_PARTIAL_RELIABLE_TIMED:
+		pr_policy = SCTP_PR_SCTP_TTL;
+		break;
+	default:
+		pr_policy = SCTP_PR_SCTP_NONE;
+		/* XXX error handling */
+		break;
+	}
+	pr_value = ntohs(req->reliability_params);
+	if (ntohs(req->flags) & DATA_CHANNEL_FLAG_OUT_OF_ORDER_ALLOWED) {
+		unordered = 1;
+	} else {
+		unordered = 0;
+	}
+	o_stream = find_free_o_stream(pc);
+	channel->state = DATA_CHANNEL_CONNECTING;
+	channel->unordered = unordered;
+	channel->pr_policy = pr_policy;
+	channel->pr_value = pr_value;
+	channel->i_stream = i_stream;
+	channel->o_stream = o_stream;
+	channel->flags = 0;
+	pc->i_stream_channel[i_stream] = channel;
+	if (o_stream == 0) {
+		request_more_o_streams(pc);
+	} else {
+		if (send_open_response_message(pc->sock, o_stream, i_stream)) {
+			pc->o_stream_channel[o_stream] = channel;
+		} else {
+			if (errno == EAGAIN) {
+				channel->flags |= DATA_CHANNEL_FLAGS_SEND_RSP;
+				pc->o_stream_channel[o_stream] = channel;
+			} else {
+				/* XXX: Signal error to the other end. */
+				pc->i_stream_channel[i_stream] = NULL;
+				channel->state = DATA_CHANNEL_CLOSED;
+				channel->unordered = 0;
+				channel->pr_policy = 0;
+				channel->pr_value = 0;
+				channel->i_stream = 0;
+				channel->o_stream = 0;
+				channel->flags = 0;
+			}
+		}
+	}
+}
+
+static void
+handle_open_response_message(struct peer_connection *pc,
+                             struct rtcweb_datachannel_open_response *rsp,
+                             size_t length, uint16_t i_stream)
+{
+	uint16_t o_stream;
+	struct channel *channel;
+
+	o_stream = ntohs(rsp->reverse_stream);
+	channel = find_channel_by_o_stream(pc, o_stream);
+	if (channel == NULL) {
+		/* XXX: improve error handling */
+		printf("handle_open_response_message: Can't find channel for outgoing steam %d.\n", o_stream);
+		return;
+	}
+	if (channel->state != DATA_CHANNEL_CONNECTING) {
+		/* XXX: improve error handling */
+		printf("handle_open_response_message: Channel with id %d for outgoing steam %d is in state %d.\n", channel->id, o_stream, channel->state);
+		return;
+	}
+	if (find_channel_by_i_stream(pc, i_stream)) {
+		/* XXX: improve error handling */
+		printf("handle_open_response_message: Channel collision for channel with id %d and streams (in/out) = (%d/%d).\n", channel->id, i_stream, o_stream);
+		return;
+	}
+	channel->i_stream = i_stream;
+	channel->state = DATA_CHANNEL_OPEN;
+	pc->i_stream_channel[i_stream] = channel;
+	if (send_open_ack_message(pc->sock, o_stream)) {
+		channel->flags = 0;
+	} else {
+		channel->flags |= DATA_CHANNEL_FLAGS_SEND_ACK;
+	}
+	return;
+}
+
+static void
+handle_open_ack_message(struct peer_connection *pc,
+                        struct rtcweb_datachannel_ack *ack,
+                        size_t length, uint16_t i_stream)
+{
+	struct channel *channel;
+
+	channel = find_channel_by_i_stream(pc, i_stream);
+	if (channel == NULL) {
+		/* XXX: some error handling */
+		return;
+	}
+	if (channel->state == DATA_CHANNEL_OPEN) {
+		return;
+	}
+	if (channel->state != DATA_CHANNEL_CONNECTING) {
+		/* XXX: error handling */
+		return;
+	}
+	channel->state = DATA_CHANNEL_OPEN;
+	return;
+}
+
+static void
+handle_unknown_message(char *msg, size_t length, uint16_t i_stream)
+{
+	/* XXX: Send an error message */
+	return;
+}
+
+static void
+handle_data_message(struct peer_connection *pc,
+                    char *buffer, size_t length, uint16_t i_stream)
+{
+	struct channel *channel;
+
+	channel = find_channel_by_i_stream(pc, i_stream);
+	if (channel == NULL) {
+		/* XXX: Some error handling */
+		return;
+	}
+	if (channel->state == DATA_CHANNEL_CONNECTING) {
+		/* Implicit ACK */
+		channel->state = DATA_CHANNEL_OPEN;
+	}
+	if (channel->state != DATA_CHANNEL_OPEN) {
+		/* XXX: What about other states? */
+		/* XXX: Some error handling */
+		return;
+	} else {
+		/* Assuming DATA_CHANNEL_PPID_DOMSTRING */
+		/* XXX: Protect for non 0 terminated buffer */
+		printf("Message received of length %zu on channel with id %d: %.*s\n",
+		       length, channel->id, (int)length, buffer);
+	}
+	return;
+}
+
+static void
+handle_message(struct peer_connection *pc, char *buffer, size_t length, uint32_t ppid, uint16_t i_stream)
+{
+	struct rtcweb_datachannel_open_request *req;
+	struct rtcweb_datachannel_open_response *rsp;
+	struct rtcweb_datachannel_ack *ack, *msg;
+
+	switch (ppid) {
+	case DATA_CHANNEL_PPID_CONTROL:
+		if (length < sizeof(struct rtcweb_datachannel_ack)) {
+			return;
+		}
+		msg = (struct rtcweb_datachannel_ack *)buffer;
+		switch (msg->msg_type) {
+		case DATA_CHANNEL_OPEN_REQUEST:
+			if (length < sizeof(struct rtcweb_datachannel_open_request)) {
+				/* XXX: error handling? */
+				return;
+			}
+			req = (struct rtcweb_datachannel_open_request *)buffer;
+			handle_open_request_message(pc, req, length, i_stream);
+			break;
+		case DATA_CHANNEL_OPEN_RESPONSE:
+			if (length < sizeof(struct rtcweb_datachannel_open_response)) {
+				/* XXX: error handling? */
+				return;
+			}
+			rsp = (struct rtcweb_datachannel_open_response *)buffer;
+			handle_open_response_message(pc, rsp, length, i_stream);
+			break;
+		case DATA_CHANNEL_ACK:
+			if (length < sizeof(struct rtcweb_datachannel_ack)) {
+				/* XXX: error handling? */
+				return;
+			}
+			ack = (struct rtcweb_datachannel_ack *)buffer;
+			handle_open_ack_message(pc, ack, length, i_stream);
+			break;
+		default:
+			handle_unknown_message(buffer, length, i_stream);
+			break;
+		}
+		break;
+	case DATA_CHANNEL_PPID_DOMSTRING:
+	case DATA_CHANNEL_PPID_BINARY:
+		handle_data_message(pc, buffer, length, i_stream);
+		break;
+	default:
+		printf("Message of length %zu, PPID %u on stream %u received.\n",
+		       length, ppid, i_stream);
+		break;
+	}
+}
+
+static void
+handle_association_change_event(struct sctp_assoc_change *sac)
+{
+	unsigned int i, n;
+
+	printf("Association change ");
+	switch (sac->sac_state) {
+	case SCTP_COMM_UP:
+		printf("SCTP_COMM_UP");
+		break;
+	case SCTP_COMM_LOST:
+		printf("SCTP_COMM_LOST");
+		break;
+	case SCTP_RESTART:
+		printf("SCTP_RESTART");
+		break;
+	case SCTP_SHUTDOWN_COMP:
+		printf("SCTP_SHUTDOWN_COMP");
+		break;
+	case SCTP_CANT_STR_ASSOC:
+		printf("SCTP_CANT_STR_ASSOC");
+		break;
+	default:
+		printf("UNKNOWN");
+		break;
+	}
+	printf(", streams (in/out) = (%u/%u)",
+	       sac->sac_inbound_streams, sac->sac_outbound_streams);
+	n = sac->sac_length - sizeof(struct sctp_assoc_change);
+	if (((sac->sac_state == SCTP_COMM_UP) ||
+	     (sac->sac_state == SCTP_RESTART)) && (n > 0)) {
+		printf(", supports");
+		for (i = 0; i < n; i++) {
+			switch (sac->sac_info[i]) {
+			case SCTP_ASSOC_SUPPORTS_PR:
+				printf(" PR");
+				break;
+			case SCTP_ASSOC_SUPPORTS_AUTH:
+				printf(" AUTH");
+				break;
+			case SCTP_ASSOC_SUPPORTS_ASCONF:
+				printf(" ASCONF");
+				break;
+			case SCTP_ASSOC_SUPPORTS_MULTIBUF:
+				printf(" MULTIBUF");
+				break;
+			case SCTP_ASSOC_SUPPORTS_RE_CONFIG:
+				printf(" RE-CONFIG");
+				break;
+			default:
+				printf(" UNKNOWN(0x%02x)", sac->sac_info[i]);
+				break;
+			}
+		}
+	} else if (((sac->sac_state == SCTP_COMM_LOST) ||
+	            (sac->sac_state == SCTP_CANT_STR_ASSOC)) && (n > 0)) {
+		printf(", ABORT =");
+		for (i = 0; i < n; i++) {
+			printf(" 0x%02x", sac->sac_info[i]);
+		}
+	}
+	printf(".\n");
+	if ((sac->sac_state == SCTP_CANT_STR_ASSOC) ||
+	    (sac->sac_state == SCTP_SHUTDOWN_COMP) ||
+	    (sac->sac_state == SCTP_COMM_LOST)) {
+		exit(0);
+	}
+	return;
+}
+
+static void
+handle_peer_address_change_event(struct sctp_paddr_change *spc)
+{
+	char addr_buf[INET6_ADDRSTRLEN];
+	const char *addr;
+	struct sockaddr_in *sin;
+	struct sockaddr_in6 *sin6;
+
+	switch (spc->spc_aaddr.ss_family) {
+	case AF_INET:
+		sin = (struct sockaddr_in *)&spc->spc_aaddr;
+		addr = inet_ntop(AF_INET, &sin->sin_addr, addr_buf, INET_ADDRSTRLEN);
+		break;
+	case AF_INET6:
+		sin6 = (struct sockaddr_in6 *)&spc->spc_aaddr;
+		addr = inet_ntop(AF_INET6, &sin6->sin6_addr, addr_buf, INET6_ADDRSTRLEN);
+		break;
+	default:
+#ifdef _WIN32
+		_snprintf(addr_buf, INET6_ADDRSTRLEN, "Unknown family %d", spc->spc_aaddr.ss_family);
+#else
+		snprintf(addr_buf, INET6_ADDRSTRLEN, "Unknown family %d", spc->spc_aaddr.ss_family);
+#endif
+		addr = addr_buf;
+		break;
+	}
+	printf("Peer address %s is now ", addr);
+	switch (spc->spc_state) {
+	case SCTP_ADDR_AVAILABLE:
+		printf("SCTP_ADDR_AVAILABLE");
+		break;
+	case SCTP_ADDR_UNREACHABLE:
+		printf("SCTP_ADDR_UNREACHABLE");
+		break;
+	case SCTP_ADDR_REMOVED:
+		printf("SCTP_ADDR_REMOVED");
+		break;
+	case SCTP_ADDR_ADDED:
+		printf("SCTP_ADDR_ADDED");
+		break;
+	case SCTP_ADDR_MADE_PRIM:
+		printf("SCTP_ADDR_MADE_PRIM");
+		break;
+	case SCTP_ADDR_CONFIRMED:
+		printf("SCTP_ADDR_CONFIRMED");
+		break;
+	default:
+		printf("UNKNOWN");
+		break;
+	}
+	printf(" (error = 0x%08x).\n", spc->spc_error);
+	return;
+}
+
+static void
+handle_adaptation_indication(struct sctp_adaptation_event *sai)
+{
+	printf("Adaptation indication: %x.\n", sai-> sai_adaptation_ind);
+	return;
+}
+
+static void
+handle_shutdown_event(struct sctp_shutdown_event *sse)
+{
+	printf("Shutdown event.\n");
+	/* XXX: notify all channels. */
+	return;
+}
+
+static void
+handle_stream_reset_event(struct peer_connection *pc, struct sctp_stream_reset_event *strrst)
+{
+	uint32_t n, i;
+	struct channel *channel;
+
+	n = (strrst->strreset_length - sizeof(struct sctp_stream_reset_event)) / sizeof(uint16_t);
+	printf("Stream reset event: flags = %x, ", strrst->strreset_flags);
+	if (strrst->strreset_flags & SCTP_STREAM_RESET_INCOMING_SSN) {
+		if (strrst->strreset_flags & SCTP_STREAM_RESET_OUTGOING_SSN) {
+			printf("incoming/");
+		}
+		printf("incoming ");
+	}
+	if (strrst->strreset_flags & SCTP_STREAM_RESET_OUTGOING_SSN) {
+		printf("outgoing ");
+	}
+	printf("stream ids = ");
+	for (i = 0; i < n; i++) {
+		if (i > 0) {
+			printf(", ");
+		}
+		printf("%d", strrst->strreset_stream_list[i]);
+	}
+	printf(".\n");
+	if (!(strrst->strreset_flags & SCTP_STREAM_RESET_DENIED) &&
+	    !(strrst->strreset_flags & SCTP_STREAM_RESET_FAILED)) {
+		for (i = 0; i < n; i++) {
+			if (strrst->strreset_flags & SCTP_STREAM_RESET_INCOMING_SSN) {
+				channel = find_channel_by_i_stream(pc, strrst->strreset_stream_list[i]);
+				if (channel != NULL) {
+					pc->i_stream_channel[channel->i_stream] = NULL;
+					channel->i_stream = 0;
+					if (channel->o_stream == 0) {
+						channel->pr_policy = SCTP_PR_SCTP_NONE;
+						channel->pr_value = 0;
+						channel->unordered = 0;
+						channel->flags = 0;
+						channel->state = DATA_CHANNEL_CLOSED;
+					} else {
+						if (channel->state == DATA_CHANNEL_OPEN) {
+							reset_outgoing_stream(pc, channel->o_stream);
+							channel->state = DATA_CHANNEL_CLOSING;
+						} else {
+							/* XXX: What to do? */
+						}
+					}
+				}
+			}
+			if (strrst->strreset_flags & SCTP_STREAM_RESET_OUTGOING_SSN) {
+				channel = find_channel_by_o_stream(pc, strrst->strreset_stream_list[i]);
+				if (channel != NULL) {
+					pc->o_stream_channel[channel->o_stream] = NULL;
+					channel->o_stream = 0;
+					if (channel->i_stream == 0) {
+						channel->pr_policy = SCTP_PR_SCTP_NONE;
+						channel->pr_value = 0;
+						channel->unordered = 0;
+						channel->flags = 0;
+						channel->state = DATA_CHANNEL_CLOSED;
+					}
+				}
+			}
+		}
+	}
+	return;
+}
+
+static void
+handle_stream_change_event(struct peer_connection *pc, struct sctp_stream_change_event *strchg)
+{
+	uint16_t o_stream;
+	uint32_t i;
+	struct channel *channel;
+
+	printf("Stream change event: streams (in/out) = (%u/%u), flags = %x.\n",
+	       strchg->strchange_instrms, strchg->strchange_outstrms, strchg->strchange_flags);
+	for (i = 0; i < NUMBER_OF_CHANNELS; i++) {
+		channel = &(pc->channels[i]);
+		if ((channel->state == DATA_CHANNEL_CONNECTING) &&
+		    (channel->o_stream == 0)) {
+			if ((strchg->strchange_flags & SCTP_STREAM_CHANGE_DENIED) ||
+			    (strchg->strchange_flags & SCTP_STREAM_CHANGE_FAILED)) {
+				/* XXX: Signal to the other end. */
+				if (channel->i_stream != 0) {
+					pc->i_stream_channel[channel->i_stream] = NULL;
+				}
+				channel->unordered = 0;
+				channel->pr_policy = SCTP_PR_SCTP_NONE;
+				channel->pr_value = 0;
+				channel->i_stream = 0;
+				channel->o_stream = 0;
+				channel->flags = 0;
+				channel->state = DATA_CHANNEL_CLOSED;
+			} else {
+				o_stream = find_free_o_stream(pc);
+				if (o_stream != 0) {
+					channel->o_stream = o_stream;
+					pc->o_stream_channel[o_stream] = channel;
+					if (channel->i_stream == 0) {
+						channel->flags |= DATA_CHANNEL_FLAGS_SEND_REQ;
+					} else {
+						channel->flags |= DATA_CHANNEL_FLAGS_SEND_RSP;
+					}
+				} else {
+					/* We will not find more ... */
+					break;
+				}
+			}
+		}
+	}
+	return;
+}
+
+static void
+handle_remote_error_event(struct sctp_remote_error *sre)
+{
+	size_t i, n;
+
+	n = sre->sre_length - sizeof(struct sctp_remote_error);
+	printf("Remote Error (error = 0x%04x): ", sre->sre_error);
+	for (i = 0; i < n; i++) {
+		printf(" 0x%02x", sre-> sre_data[i]);
+	}
+	printf(".\n");
+	return;
+}
+
+static void
+handle_send_failed_event(struct sctp_send_failed_event *ssfe)
+{
+	size_t i, n;
+
+	if (ssfe->ssfe_flags & SCTP_DATA_UNSENT) {
+		printf("Unsent ");
+	}
+	if (ssfe->ssfe_flags & SCTP_DATA_SENT) {
+		printf("Sent ");
+	}
+	if (ssfe->ssfe_flags & ~(SCTP_DATA_SENT | SCTP_DATA_UNSENT)) {
+		printf("(flags = %x) ", ssfe->ssfe_flags);
+	}
+	printf("message with PPID = %d, SID = %d, flags: 0x%04x due to error = 0x%08x",
+	       ntohl(ssfe->ssfe_info.snd_ppid), ssfe->ssfe_info.snd_sid,
+	       ssfe->ssfe_info.snd_flags, ssfe->ssfe_error);
+	n = ssfe->ssfe_length - sizeof(struct sctp_send_failed_event);
+	for (i = 0; i < n; i++) {
+		printf(" 0x%02x", ssfe->ssfe_data[i]);
+	}
+	printf(".\n");
+	return;
+}
+
+static void
+handle_notification(struct peer_connection *pc, union sctp_notification *notif, size_t n)
+{
+	if (notif->sn_header.sn_length != (uint32_t)n) {
+		return;
+	}
+	switch (notif->sn_header.sn_type) {
+	case SCTP_ASSOC_CHANGE:
+		handle_association_change_event(&(notif->sn_assoc_change));
+		break;
+	case SCTP_PEER_ADDR_CHANGE:
+		handle_peer_address_change_event(&(notif->sn_paddr_change));
+		break;
+	case SCTP_REMOTE_ERROR:
+		handle_remote_error_event(&(notif->sn_remote_error));
+		break;
+	case SCTP_SHUTDOWN_EVENT:
+		handle_shutdown_event(&(notif->sn_shutdown_event));
+		break;
+	case SCTP_ADAPTATION_INDICATION:
+		handle_adaptation_indication(&(notif->sn_adaptation_event));
+		break;
+	case SCTP_PARTIAL_DELIVERY_EVENT:
+		break;
+	case SCTP_AUTHENTICATION_EVENT:
+		break;
+	case SCTP_SENDER_DRY_EVENT:
+		break;
+	case SCTP_NOTIFICATIONS_STOPPED_EVENT:
+		break;
+	case SCTP_SEND_FAILED_EVENT:
+		handle_send_failed_event(&(notif->sn_send_failed_event));
+		break;
+	case SCTP_STREAM_RESET_EVENT:
+		handle_stream_reset_event(pc, &(notif->sn_strreset_event));
+		send_deferred_messages(pc);
+		send_outgoing_stream_reset(pc);
+		request_more_o_streams(pc);
+		break;
+	case SCTP_ASSOC_RESET_EVENT:
+		break;
+	case SCTP_STREAM_CHANGE_EVENT:
+		handle_stream_change_event(pc, &(notif->sn_strchange_event));
+		send_deferred_messages(pc);
+		send_outgoing_stream_reset(pc);
+		request_more_o_streams(pc);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+print_status(struct peer_connection *pc)
+{
+	struct sctp_status status;
+	socklen_t len;
+	uint32_t i;
+	struct channel *channel;
+
+	len = (socklen_t)sizeof(struct sctp_status);
+	if (usrsctp_getsockopt(pc->sock, IPPROTO_SCTP, SCTP_STATUS, &status, &len) < 0) {
+		perror("getsockopt");
+		return;
+	}
+	printf("Association state: ");
+	switch (status.sstat_state) {
+	case SCTP_CLOSED:
+		printf("CLOSED\n");
+		break;
+	case SCTP_BOUND:
+		printf("BOUND\n");
+		break;
+	case SCTP_LISTEN:
+		printf("LISTEN\n");
+		break;
+	case SCTP_COOKIE_WAIT:
+		printf("COOKIE_WAIT\n");
+		break;
+	case SCTP_COOKIE_ECHOED:
+		printf("COOKIE_ECHOED\n");
+		break;
+	case SCTP_ESTABLISHED:
+		printf("ESTABLISHED\n");
+		break;
+	case SCTP_SHUTDOWN_PENDING:
+		printf("SHUTDOWN_PENDING\n");
+		break;
+	case SCTP_SHUTDOWN_SENT:
+		printf("SHUTDOWN_SENT\n");
+		break;
+	case SCTP_SHUTDOWN_RECEIVED:
+		printf("SHUTDOWN_RECEIVED\n");
+		break;
+	case SCTP_SHUTDOWN_ACK_SENT:
+		printf("SHUTDOWN_ACK_SENT\n");
+		break;
+	default:
+		printf("UNKNOWN\n");
+		break;
+	}
+	printf("Number of streams (i/o) = (%u/%u)\n",
+	       status.sstat_instrms, status.sstat_outstrms);
+	for (i = 0; i < NUMBER_OF_CHANNELS; i++) {
+		channel = &(pc->channels[i]);
+		if (channel->state == DATA_CHANNEL_CLOSED) {
+			continue;
+		}
+		printf("Channel with id = %u: state ", channel->id);
+		switch (channel->state) {
+		case DATA_CHANNEL_CLOSED:
+			printf("CLOSED");
+			break;
+		case DATA_CHANNEL_CONNECTING:
+			printf("CONNECTING");
+			break;
+		case DATA_CHANNEL_OPEN:
+			printf("OPEN");
+			break;
+		case DATA_CHANNEL_CLOSING:
+			printf("CLOSING");
+			break;
+		default:
+			printf("UNKNOWN(%d)", channel->state);
+			break;
+		}
+		printf(", flags = 0x%08x, stream id (in/out): (%u/%u), ",
+		       channel->flags,
+		       channel->i_stream,
+		       channel->o_stream);
+		if (channel->unordered) {
+			printf("unordered, ");
+		} else {
+			printf("ordered, ");
+		}
+		switch (channel->pr_policy) {
+		case SCTP_PR_SCTP_NONE:
+			printf("reliable.\n");
+			break;
+		case SCTP_PR_SCTP_TTL:
+			printf("unreliable (timeout %ums).\n", channel->pr_value);
+			break;
+		case SCTP_PR_SCTP_RTX:
+			printf("unreliable (max. %u rtx).\n", channel->pr_value);
+			break;
+		default:
+			printf("unkown policy %u.\n", channel->pr_policy);
+			break;
+		}
+	}
+}
+
+static int
+receive_cb(struct socket *sock, union sctp_sockstore addr, void *data,
+           size_t datalen, struct sctp_rcvinfo rcv, int flags, void *ulp_info)
+{
+	struct peer_connection *pc;
+
+	pc = (struct peer_connection *)ulp_info;
+
+	if (data) {
+		lock_peer_connection(pc);
+		if (flags & MSG_NOTIFICATION) {
+			handle_notification(pc, (union sctp_notification *)data, datalen);
+		} else {
+			handle_message(pc, data, datalen, ntohl(rcv.rcv_ppid), rcv.rcv_sid);
+		}
+		unlock_peer_connection(pc);
+	}
+	return (1);
+}
+
+void
+debug_printf(const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+	vprintf(format, ap);
+	va_end(ap);
+}
+
+int
+main(int argc, char *argv[])
+{
+	struct socket *sock;
+	struct sockaddr_in addr;
+	socklen_t addr_len;
+	char line[LINE_LENGTH + 1];
+	unsigned int unordered, policy, value, id, seconds;
+	unsigned int i;
+	struct channel *channel;
+	const int on = 1;
+	struct sctp_assoc_value av;
+	struct sctp_event event;
+	struct sctp_udpencaps encaps;
+	struct sctp_initmsg initmsg;
+	uint16_t event_types[] = {SCTP_ASSOC_CHANGE,
+	                          SCTP_PEER_ADDR_CHANGE,
+	                          SCTP_REMOTE_ERROR,
+	                          SCTP_SHUTDOWN_EVENT,
+	                          SCTP_ADAPTATION_INDICATION,
+	                          SCTP_SEND_FAILED_EVENT,
+	                          SCTP_STREAM_RESET_EVENT,
+	                          SCTP_STREAM_CHANGE_EVENT};
+	char addrbuf[INET_ADDRSTRLEN];
+
+	if (argc > 1) {
+		usrsctp_init(atoi(argv[1]), NULL, debug_printf);
+	} else {
+		usrsctp_init(9899, NULL, debug_printf);
+	}
+#ifdef SCTP_DEBUG
+	usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_NONE);
+#endif
+	usrsctp_sysctl_set_sctp_blackhole(2);
+
+	if ((sock = usrsctp_socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP, receive_cb, NULL, 0, &peer_connection)) == NULL) {
+		perror("socket");
+	}
+	init_peer_connection(&peer_connection);
+	if (argc > 2) {
+		memset(&encaps, 0, sizeof(struct sctp_udpencaps));
+		encaps.sue_address.ss_family = AF_INET6;
+		encaps.sue_port = htons(atoi(argv[2]));
+		if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_REMOTE_UDP_ENCAPS_PORT, (const void*)&encaps, (socklen_t)sizeof(struct sctp_udpencaps)) < 0) {
+			perror("setsockopt");
+		}
+	}
+	if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_RECVRCVINFO, &on, sizeof(int)) < 0) {
+		perror("setsockopt SCTP_RECVRCVINFO");
+	}
+	if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_EXPLICIT_EOR, &on, sizeof(int)) < 0) {
+		perror("setsockopt SCTP_EXPLICIT_EOR");
+	}
+	/* Allow resetting streams. */
+	av.assoc_id = SCTP_ALL_ASSOC;
+	av.assoc_value = SCTP_ENABLE_RESET_STREAM_REQ | SCTP_ENABLE_CHANGE_ASSOC_REQ;
+	if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_ENABLE_STREAM_RESET, &av, sizeof(struct sctp_assoc_value)) < 0) {
+		perror("setsockopt SCTP_ENABLE_STREAM_RESET");
+	}
+	/* Enable the events of interest. */
+	memset(&event, 0, sizeof(event));
+	event.se_assoc_id = SCTP_ALL_ASSOC;
+	event.se_on = 1;
+	for (i = 0; i < sizeof(event_types)/sizeof(uint16_t); i++) {
+		event.se_type = event_types[i];
+		if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_EVENT, &event, sizeof(event)) < 0) {
+			perror("setsockopt SCTP_EVENT");
+		}
+	}
+	memset(&initmsg, 0, sizeof(struct sctp_initmsg));
+	initmsg.sinit_num_ostreams = 5;
+	initmsg.sinit_max_instreams = 65535;
+	if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, sizeof(struct sctp_initmsg)) < 0) {
+		perror("setsockopt SCTP_INITMSG");
+	}
+
+	if (argc == 5) {
+		/* operating as client */
+		memset(&addr, 0, sizeof(struct sockaddr_in));
+		addr.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+		addr.sin_len = sizeof(struct sockaddr_in);
+#endif
+		if (!inet_pton(AF_INET, argv[3], &addr.sin_addr.s_addr)){
+			printf("error: invalid address\n");
+			exit(1);
+		}
+		addr.sin_port = htons(atoi(argv[4]));
+		if (usrsctp_connect(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0) {
+			perror("connect");
+		}
+
+		printf("Connected to %s:%d.\n", inet_ntop(AF_INET, &(addr.sin_addr), addrbuf, INET_ADDRSTRLEN), ntohs(addr.sin_port));
+	} else if (argc == 4) {
+		struct socket *conn_sock;
+
+		/* operating as server */
+		memset(&addr, 0, sizeof(struct sockaddr_in));
+		addr.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+		addr.sin_len = sizeof(struct sockaddr_in);
+#endif
+		addr.sin_addr.s_addr = INADDR_ANY;
+		addr.sin_port = htons(atoi(argv[3]));
+		if (usrsctp_bind(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0) {
+			perror("bind");
+		}
+		if (usrsctp_listen(sock, 1) < 0) {
+			perror("listen");
+		}
+		addr_len = (socklen_t)sizeof(struct sockaddr_in);
+		memset(&addr, 0, sizeof(struct sockaddr_in));
+		if ((conn_sock = usrsctp_accept(sock, (struct sockaddr *)&addr, &addr_len)) == NULL) {
+			perror("accept");
+		}
+		usrsctp_close(sock);
+		sock = conn_sock;
+		printf("Connected to %s:%d.\n", inet_ntop(AF_INET, &(addr.sin_addr), addrbuf, INET_ADDRSTRLEN), ntohs(addr.sin_port));
+	} else {
+		printf("Usage: %s local_udp_port remote_udp_port local_port when operating as server\n"
+		       "       %s local_udp_port remote_udp_port remote_addr remote_port when operating as client\n",
+		       argv[0], argv[0]);
+		return (0);
+	}
+
+	lock_peer_connection(&peer_connection);
+	peer_connection.sock = sock;
+	unlock_peer_connection(&peer_connection);
+
+	for (;;) {
+#ifdef _WIN32
+		if (gets_s(line, LINE_LENGTH) == NULL) {
+#else
+		if (fgets(line, LINE_LENGTH, stdin) == NULL) {
+#endif
+			if (usrsctp_shutdown(sock, SHUT_WR) < 0) {
+				perror("usrsctp_shutdown");
+			}
+			while (usrsctp_finish() != 0) {
+#ifdef _WIN32
+				Sleep(1000);
+#else
+				sleep(1);
+#endif
+			}
+			break;
+		}
+		if (strncmp(line, "?", strlen("?")) == 0 ||
+		    strncmp(line, "help", strlen("help")) == 0) {
+			printf("Commands:\n"
+			       "open unordered pr_policy pr_value - opens a channel\n"
+			       "close channel - closes the channel\n"
+			       "send channel:string - sends string using channel\n"
+			       "status - prints the status\n"
+			       "sleep n - sleep for n seconds\n"
+			       "help - this message\n");
+		} else if (strncmp(line, "status", strlen("status")) == 0) {
+			lock_peer_connection(&peer_connection);
+			print_status(&peer_connection);
+			unlock_peer_connection(&peer_connection);
+		} else if (strncmp(line, "quit", strlen("quit")) == 0) {
+			if (usrsctp_shutdown(sock, SHUT_WR) < 0) {
+				perror("usrsctp_shutdown");
+			}
+			while (usrsctp_finish() != 0) {
+#ifdef _WIN32
+				Sleep(1000);
+#else
+				sleep(1);
+#endif
+			}
+			break;
+		} else if (sscanf(line, "open %u %u %u", &unordered, &policy, &value) == 3) {
+			lock_peer_connection(&peer_connection);
+			channel = open_channel(&peer_connection, (uint8_t)unordered, (uint16_t)policy, (uint32_t)value);
+			unlock_peer_connection(&peer_connection);
+			if (channel == NULL) {
+				printf("Creating channel failed.\n");
+			} else {
+				printf("Channel with id %u created.\n", channel->id);
+			}
+		} else if (sscanf(line, "close %u", &id) == 1) {
+			if (id < NUMBER_OF_CHANNELS) {
+				lock_peer_connection(&peer_connection);
+				close_channel(&peer_connection, &peer_connection.channels[id]);
+				unlock_peer_connection(&peer_connection);
+			}
+		} else if (sscanf(line, "send %u", &id) == 1) {
+			if (id < NUMBER_OF_CHANNELS) {
+				char *msg;
+
+				msg = strstr(line, ":");
+				if (msg) {
+					msg++;
+					lock_peer_connection(&peer_connection);
+#ifdef _WIN32
+					if (send_user_message(&peer_connection, &peer_connection.channels[id], msg, strlen(msg))) {
+#else
+					if (send_user_message(&peer_connection, &peer_connection.channels[id], msg, strlen(msg) - 1)) {
+#endif
+						printf("Message sent.\n");
+					} else {
+						printf("Message sending failed.\n");
+					}
+					unlock_peer_connection(&peer_connection);
+				}
+			}
+		} else if (sscanf(line, "sleep %u", &seconds) == 1) {
+#ifdef _WIN32
+			Sleep(seconds * 1000);
+#else
+			sleep(seconds);
+#endif
+		} else {
+			printf("Unknown command: %s", line);
+		}
+	}
+	return (0);
+}
diff --git a/programs/test_libmgmt.c b/programs/test_libmgmt.c
new file mode 100644
index 0000000..de5e0f0
--- /dev/null
+++ b/programs/test_libmgmt.c
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2016 Michael Tuexen
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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.
+ */
+
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+#include "usrsctp.h"
+
+int
+main(void)
+{
+	int i;
+
+	for (i = 0; i < 10000; i++) {
+		usrsctp_init(0, NULL, NULL);
+		while (usrsctp_finish() != 0) {
+#ifdef _WIN32
+			Sleep(1000);
+#else
+			sleep(1);
+#endif
+		}
+	}
+	return (0);
+}
diff --git a/programs/test_timer.c b/programs/test_timer.c
new file mode 100644
index 0000000..ba2cb50
--- /dev/null
+++ b/programs/test_timer.c
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 Michael Tuexen
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+#include <usrsctp.h>
+
+int
+main(void) 
+{
+	int i;
+	void *p;
+
+	usrsctp_init(0, NULL, NULL);
+#ifdef SCTP_DEBUG
+	usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL);
+#endif
+	printf("Entering the loop\n");
+	p = &i;
+	for (i = 0; i < 100000; i++) {
+		usrsctp_register_address(p);
+		usrsctp_deregister_address(p);
+	}
+	printf("Exited the loop\n");
+	while (usrsctp_finish() != 0) {
+#ifdef _WIN32
+		Sleep(1000);
+#else
+		sleep(1);
+#endif
+	}
+	return (0);
+}
diff --git a/programs/tsctp.c b/programs/tsctp.c
new file mode 100644
index 0000000..b35fb2a
--- /dev/null
+++ b/programs/tsctp.c
@@ -0,0 +1,827 @@
+/*
+ * Copyright (C) 2005-2013 Michael Tuexen
+ * Copyright (C) 2011-2013 Irene Ruengeler
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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.
+ */
+
+#include <sys/types.h>
+#ifdef _WIN32
+#include <WinSock2.h>
+#include <WS2tcpip.h>
+#include <stdlib.h>
+#include <crtdbg.h>
+#include <sys/timeb.h>
+#else
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <pthread.h>
+#endif
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <errno.h>
+#ifdef LINUX
+#include <getopt.h>
+#endif
+#include <usrsctp.h>
+
+/* global for the send callback, but used in kernel version as well */
+static unsigned long number_of_messages;
+static char *buffer;
+static int length;
+static struct sockaddr_in remote_addr;
+static int unordered;
+uint32_t optval = 1;
+struct socket *psock = NULL;
+
+static struct timeval start_time;
+unsigned int runtime = 0;
+static unsigned long messages = 0;
+static unsigned int first_length = 0;
+static unsigned long long sum = 0;
+static unsigned int use_cb = 0;
+
+#ifndef timersub
+#define timersub(tvp, uvp, vvp)                                   \
+	do {                                                      \
+		(vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec;    \
+		(vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \
+		if ((vvp)->tv_usec < 0) {                         \
+			(vvp)->tv_sec--;                          \
+			(vvp)->tv_usec += 1000000;                \
+		}                                                 \
+	} while (0)
+#endif
+
+
+char Usage[] =
+"Usage: tsctp [options] [address]\n"
+"Options:\n"
+"        -a             set adaptation layer indication\n"
+"        -c             use callback API\n"
+"        -E             local UDP encapsulation port (default 9899)\n"
+"        -f             fragmentation point\n"
+"        -l             size of send/receive buffer\n"
+"        -L             bind to local IP (default INADDR_ANY)\n"
+"        -n             number of messages sent (0 means infinite)/received\n"
+"        -D             turns Nagle off\n"
+"        -R             socket recv buffer\n"
+"        -S             socket send buffer\n"
+"        -T             time to send messages\n"
+"        -u             use unordered user messages\n"
+"        -U             remote UDP encapsulation port\n"
+"        -v             verbose\n"
+"        -V             very verbose\n"
+;
+
+#define DEFAULT_LENGTH             1024
+#define DEFAULT_NUMBER_OF_MESSAGES 1024
+#define DEFAULT_PORT               5001
+#define BUFFERSIZE                 (1<<16)
+
+static int verbose, very_verbose;
+static unsigned int done;
+
+void stop_sender(int sig)
+{
+	done = 1;
+}
+
+#ifdef _WIN32
+static void
+gettimeofday(struct timeval *tv, void *ignore)
+{
+	struct timeb tb;
+
+	ftime(&tb);
+	tv->tv_sec = (long)tb.time;
+ 	tv->tv_usec = tb.millitm * 1000;
+}
+#endif
+
+#ifdef _WIN32
+static DWORD WINAPI
+#else
+static void *
+#endif
+handle_connection(void *arg)
+{
+	ssize_t n;
+	unsigned long long sum = 0, first_length = 0;
+	char *buf;
+#ifdef _WIN32
+	HANDLE tid;
+#else
+	pthread_t tid;
+#endif
+	struct socket *conn_sock;
+	struct timeval start_time, now, diff_time;
+	double seconds;
+	unsigned long messages = 0;
+	unsigned long recv_calls = 0;
+	unsigned long notifications = 0;
+	int flags;
+	struct sockaddr_in addr;
+	socklen_t len;
+	union sctp_notification *snp;
+	struct sctp_paddr_change *spc;
+	struct timeval note_time;
+	unsigned int infotype;
+	struct sctp_recvv_rn rn;
+	socklen_t infolen = sizeof(struct sctp_recvv_rn);
+
+	conn_sock = *(struct socket **)arg;
+#ifdef _WIN32
+	tid = GetCurrentThread();
+#else
+	tid = pthread_self();
+	pthread_detach(tid);
+#endif
+
+	buf = malloc(BUFFERSIZE);
+	flags = 0;
+	len = (socklen_t)sizeof(struct sockaddr_in);
+	infotype = 0;
+	memset(&rn, 0, sizeof(struct sctp_recvv_rn));
+	n = usrsctp_recvv(conn_sock, buf, BUFFERSIZE, (struct sockaddr *) &addr, &len, (void *)&rn,
+	                 &infolen, &infotype, &flags);
+
+	gettimeofday(&start_time, NULL);
+	while (n > 0) {
+		recv_calls++;
+		if (flags & MSG_NOTIFICATION) {
+			notifications++;
+			gettimeofday(&note_time, NULL);
+			printf("notification arrived at %f\n", note_time.tv_sec+(double)note_time.tv_usec/1000000.0);
+			snp = (union sctp_notification *)buf;
+			if (snp->sn_header.sn_type==SCTP_PEER_ADDR_CHANGE)
+			{
+				spc = &snp->sn_paddr_change;
+				printf("SCTP_PEER_ADDR_CHANGE: state=%d, error=%d\n",spc->spc_state, spc->spc_error);
+			}
+		} else {
+			if (very_verbose) {
+				printf("Message received\n");
+			}
+			sum += n;
+			if (flags & MSG_EOR) {
+				messages++;
+				if (first_length == 0)
+					first_length = sum;
+			}
+		}
+		flags = 0;
+		len = (socklen_t)sizeof(struct sockaddr_in);
+		infolen = sizeof(struct sctp_recvv_rn);
+		infotype = 0;
+		memset(&rn, 0, sizeof(struct sctp_recvv_rn));
+		n = usrsctp_recvv(conn_sock, (void *) buf, BUFFERSIZE, (struct sockaddr *) &addr, &len, (void *)&rn,
+		                  &infolen, &infotype, &flags);
+	}
+	if (n < 0)
+		perror("sctp_recvv");
+	gettimeofday(&now, NULL);
+	timersub(&now, &start_time, &diff_time);
+	seconds = diff_time.tv_sec + (double)diff_time.tv_usec/1000000.0;
+	printf("%llu, %lu, %lu, %lu, %llu, %f, %f\n",
+	        first_length, messages, recv_calls, notifications, sum, seconds, (double)first_length * (double)messages / seconds);
+	fflush(stdout);
+	usrsctp_close(conn_sock);
+	free(buf);
+#ifdef _WIN32
+	return 0;
+#else
+	return (NULL);
+#endif
+}
+
+static int
+send_cb(struct socket *sock, uint32_t sb_free) {
+	struct sctp_sndinfo sndinfo;
+
+	if ((messages == 0) & verbose) {
+		printf("Start sending ");
+		if (number_of_messages > 0) {
+			printf("%ld messages ", (long)number_of_messages);
+		}
+		if (runtime > 0) {
+			printf("for %u seconds ...", runtime);
+		}
+		printf("\n");
+		fflush(stdout);
+	}
+
+	sndinfo.snd_sid = 0;
+	sndinfo.snd_flags = 0;
+	if (unordered != 0) {
+		sndinfo.snd_flags |= SCTP_UNORDERED;
+	}
+	sndinfo.snd_ppid = 0;
+	sndinfo.snd_context = 0;
+	sndinfo.snd_assoc_id = 0;
+
+	while (!done && ((number_of_messages == 0) || (messages < (number_of_messages - 1)))) {
+		if (very_verbose) {
+			printf("Sending message number %lu.\n", messages + 1);
+		}
+
+		if (usrsctp_sendv(psock, buffer, length,
+		                  (struct sockaddr *) &remote_addr, 1,
+		                  (void *)&sndinfo, (socklen_t)sizeof(struct sctp_sndinfo), SCTP_SENDV_SNDINFO,
+		                  0) < 0) {
+			if (errno != EWOULDBLOCK && errno != EAGAIN) {
+				perror("usrsctp_sendv (cb)");
+				exit(1);
+			} else {
+				if (very_verbose){
+					printf("EWOULDBLOCK or EAGAIN for message number %lu - will retry\n", messages + 1);
+				}
+				/* send until EWOULDBLOCK then exit callback. */
+				return (1);
+			}
+		}
+		messages++;
+	}
+	if ((done == 1) || (messages == (number_of_messages - 1))) {
+		if (very_verbose)
+			printf("Sending final message number %lu.\n", messages + 1);
+
+		sndinfo.snd_flags |= SCTP_EOF;
+		if (usrsctp_sendv(psock, buffer, length, (struct sockaddr *) &remote_addr, 1,
+		                  (void *)&sndinfo, (socklen_t)sizeof(struct sctp_sndinfo), SCTP_SENDV_SNDINFO,
+		                  0) < 0) {
+			if (errno != EWOULDBLOCK && errno != EAGAIN) {
+				perror("usrsctp_sendv (cb)");
+				exit(1);
+			} else {
+				if (very_verbose){
+					printf("EWOULDBLOCK or EAGAIN for final message number %lu - will retry\n", messages + 1);
+				}
+				/* send until EWOULDBLOCK then exit callback. */
+				return (1);
+			}
+		}
+		messages++;
+		done = 2;
+	}
+
+	return (1);
+}
+
+static int
+server_receive_cb(struct socket *sock, union sctp_sockstore addr, void *data,
+           size_t datalen, struct sctp_rcvinfo rcv, int flags, void *ulp_info)
+{
+	struct timeval now, diff_time;
+	double seconds;
+
+	if (data == NULL) {
+		gettimeofday(&now, NULL);
+		timersub(&now, &start_time, &diff_time);
+		seconds = diff_time.tv_sec + (double)diff_time.tv_usec/1000000.0;
+		printf("%u, %lu, %llu, %f, %f\n",
+			first_length, messages, sum, seconds, (double)first_length * (double)messages / seconds);
+		usrsctp_close(sock);
+		first_length = 0;
+		sum = 0;
+		messages = 0;
+		return (1);
+	}
+	if (first_length == 0) {
+		first_length = (unsigned int)datalen;
+		gettimeofday(&start_time, NULL);
+	}
+	sum += datalen;
+	messages++;
+
+	free(data);
+	return (1);
+}
+
+static int
+client_receive_cb(struct socket *sock, union sctp_sockstore addr, void *data,
+           size_t datalen, struct sctp_rcvinfo rcv, int flags, void *ulp_info)
+{
+	free(data);
+	return (1);
+}
+
+void
+debug_printf(const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+	vprintf(format, ap);
+	va_end(ap);
+}
+
+int main(int argc, char **argv)
+{
+#ifndef _WIN32
+	int c;
+#endif
+	socklen_t addr_len;
+	struct sockaddr_in local_addr;
+	struct timeval start_time, now, diff_time;
+	int client;
+	uint16_t local_port, remote_port, port, local_udp_port, remote_udp_port;
+	int rcvbufsize=0, sndbufsize=0, myrcvbufsize, mysndbufsize;
+	socklen_t intlen;
+	double seconds;
+	double throughput;
+	int nodelay = 0;
+	struct sctp_assoc_value av;
+	struct sctp_udpencaps encaps;
+	struct sctp_sndinfo sndinfo;
+#ifdef _WIN32
+	unsigned long srcAddr;
+	HANDLE tid;
+#else
+	in_addr_t srcAddr;
+	pthread_t tid;
+#endif
+	int fragpoint = 0;
+	struct sctp_setadaptation ind = {0};
+#ifdef _WIN32
+	char *opt;
+	int optind;
+#endif
+	unordered = 0;
+
+	length = DEFAULT_LENGTH;
+	number_of_messages = DEFAULT_NUMBER_OF_MESSAGES;
+	port = DEFAULT_PORT;
+	remote_udp_port = 0;
+	local_udp_port = 9899;
+	verbose = 0;
+	very_verbose = 0;
+	srcAddr = htonl(INADDR_ANY);
+
+	memset((void *) &remote_addr, 0, sizeof(struct sockaddr_in));
+	memset((void *) &local_addr, 0, sizeof(struct sockaddr_in));
+
+#ifndef _WIN32
+	while ((c = getopt(argc, argv, "a:cp:l:E:f:L:n:R:S:T:uU:vVD")) != -1)
+		switch(c) {
+			case 'a':
+				ind.ssb_adaptation_ind = atoi(optarg);
+				break;
+			case 'c':
+				use_cb = 1;
+				break;
+			case 'l':
+				length = atoi(optarg);
+				break;
+			case 'n':
+				number_of_messages = atoi(optarg);
+				break;
+			case 'p':
+				port = atoi(optarg);
+				break;
+			case 'E':
+				local_udp_port = atoi(optarg);
+				break;
+			case 'f':
+				fragpoint = atoi(optarg);
+				break;
+			case 'L':
+				inet_pton(AF_INET, optarg, &srcAddr);
+				break;
+			case 'R':
+				rcvbufsize = atoi(optarg);
+				break;
+			case 'S':
+				sndbufsize = atoi(optarg);
+				break;
+			case 'T':
+				runtime = atoi(optarg);
+				number_of_messages = 0;
+				break;
+			case 'u':
+				unordered = 1;
+				break;
+			case 'U':
+				remote_udp_port = atoi(optarg);
+				break;
+			case 'v':
+				verbose = 1;
+				break;
+			case 'V':
+				verbose = 1;
+				very_verbose = 1;
+				break;
+			case 'D':
+				nodelay = 1;
+				break;
+			default:
+				fprintf(stderr, "%s", Usage);
+				exit(1);
+		}
+#else
+	for (optind = 1; optind < argc; optind++) {
+		if (argv[optind][0] == '-') {
+			switch (argv[optind][1]) {
+				case 'a':
+					if (++optind >= argc) {
+						printf("%s", Usage);
+						exit(1);
+					}
+					opt = argv[optind];
+					ind.ssb_adaptation_ind = atoi(opt);
+					break;
+				case 'c':
+					use_cb = 1;
+					break;
+				case 'l':
+					if (++optind >= argc) {
+						printf("%s", Usage);
+						exit(1);
+					}
+					opt = argv[optind];
+					length = atoi(opt);
+					break;
+				case 'p':
+					if (++optind >= argc) {
+						printf("%s", Usage);
+						exit(1);
+					}
+					opt = argv[optind];
+					port = atoi(opt);
+					break;
+				case 'n':
+					if (++optind >= argc) {
+						printf("%s", Usage);
+						exit(1);
+					}
+					opt = argv[optind];
+					number_of_messages = atoi(opt);
+					break;
+				case 'f':
+					if (++optind >= argc) {
+						printf("%s", Usage);
+						exit(1);
+					}
+					opt = argv[optind];
+					fragpoint = atoi(opt);
+					break;
+				case 'L':
+					if (++optind >= argc) {
+						printf("%s", Usage);
+						exit(1);
+					}
+					opt = argv[optind];
+					inet_pton(AF_INET, opt, &srcAddr);
+					break;
+				case 'U':
+					if (++optind >= argc) {
+						printf("%s", Usage);
+						exit(1);
+					}
+					opt = argv[optind];
+					remote_udp_port = atoi(opt);
+					break;
+				case 'E':
+					if (++optind >= argc) {
+						printf("%s", Usage);
+						exit(1);
+					}
+					opt = argv[optind];
+					local_udp_port = atoi(opt);
+					break;
+				case 'R':
+					if (++optind >= argc) {
+						printf("%s", Usage);
+						exit(1);
+					}
+					opt = argv[optind];
+					rcvbufsize = atoi(opt);
+					break;
+				case 'S':
+					if (++optind >= argc) {
+						printf("%s", Usage);
+						exit(1);
+					}
+					opt = argv[optind];
+					sndbufsize = atoi(opt);
+					break;
+				case 'T':
+					if (++optind >= argc) {
+						printf("%s", Usage);
+						exit(1);
+					}
+					opt = argv[optind];
+					runtime = atoi(opt);
+					number_of_messages = 0;
+					break;
+				case 'u':
+					unordered = 1;
+					break;
+				case 'v':
+					verbose = 1;
+					break;
+				case 'V':
+					verbose = 1;
+					very_verbose = 1;
+					break;
+				case 'D':
+					nodelay = 1;
+					break;
+				default:
+					printf("%s", Usage);
+					exit(1);
+			}
+		} else {
+			break;
+		}
+	}
+#endif
+	if (optind == argc) {
+		client = 0;
+		local_port = port;
+		remote_port = 0;
+	} else {
+		client = 1;
+		local_port = 0;
+		remote_port = port;
+	}
+	local_addr.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+	local_addr.sin_len = sizeof(struct sockaddr_in);
+#endif
+	local_addr.sin_port = htons(local_port);
+	local_addr.sin_addr.s_addr = srcAddr;
+
+	usrsctp_init(local_udp_port, NULL, debug_printf);
+#ifdef SCTP_DEBUG
+	usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL);
+#endif
+	usrsctp_sysctl_set_sctp_blackhole(2);
+	usrsctp_sysctl_set_sctp_enable_sack_immediately(1);
+
+	if (client) {
+		if (use_cb) {
+			if (!(psock = usrsctp_socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP, client_receive_cb, send_cb, length, NULL))) {
+				perror("user_socket");
+				exit(1);
+			}
+		} else {
+			if (!(psock = usrsctp_socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP, NULL, NULL, 0, NULL))) {
+				perror("user_socket");
+				exit(1);
+			}
+		}
+	} else {
+		if (use_cb) {
+			if (!(psock = usrsctp_socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP, server_receive_cb, NULL, 0, NULL))) {
+				perror("user_socket");
+				exit(1);
+			}
+		} else {
+			if (!(psock = usrsctp_socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP, NULL, NULL, 0, NULL))) {
+				perror("user_socket");
+				exit(1);
+			}
+		}
+	}
+
+	if (usrsctp_bind(psock, (struct sockaddr *)&local_addr, sizeof(struct sockaddr_in)) == -1) {
+		perror("usrsctp_bind");
+		exit(1);
+	}
+
+	if (usrsctp_setsockopt(psock, IPPROTO_SCTP, SCTP_ADAPTATION_LAYER, (const void*)&ind, (socklen_t)sizeof(struct sctp_setadaptation)) < 0) {
+		perror("setsockopt");
+	}
+
+	if (!client) {
+		if (rcvbufsize) {
+			if (usrsctp_setsockopt(psock, SOL_SOCKET, SO_RCVBUF, &rcvbufsize, sizeof(int)) < 0) {
+				perror("setsockopt: rcvbuf");
+			}
+		}
+		if (verbose) {
+			intlen = sizeof(int);
+			if (usrsctp_getsockopt(psock, SOL_SOCKET, SO_RCVBUF, &myrcvbufsize, (socklen_t *)&intlen) < 0) {
+				perror("getsockopt: rcvbuf");
+			} else {
+				fprintf(stdout,"Receive buffer size: %d.\n", myrcvbufsize);
+			}
+		}
+
+		if (usrsctp_listen(psock, 1) < 0) {
+			perror("usrsctp_listen");
+			exit(1);
+		}
+
+		while (1) {
+			memset(&remote_addr, 0, sizeof(struct sockaddr_in));
+			addr_len = sizeof(struct sockaddr_in);
+			if (use_cb) {
+				struct socket *conn_sock;
+
+				if ((conn_sock = usrsctp_accept(psock, (struct sockaddr *) &remote_addr, &addr_len))== NULL) {
+					perror("usrsctp_accept");
+					continue;
+				}
+			} else {
+				struct socket **conn_sock;
+
+				conn_sock = (struct socket **)malloc(sizeof(struct socket *));
+				if ((*conn_sock = usrsctp_accept(psock, (struct sockaddr *) &remote_addr, &addr_len))== NULL) {
+					perror("usrsctp_accept");
+					continue;
+				}
+#ifdef _WIN32
+				tid = CreateThread(NULL, 0, &handle_connection, (void *)conn_sock, 0, NULL);
+#else
+				pthread_create(&tid, NULL, &handle_connection, (void *)conn_sock);
+#endif
+			}
+			if (verbose) {
+				// const char *inet_ntop(int af, const void *src, char *dst, socklen_t size)
+				//inet_ntoa(remote_addr.sin_addr)
+				char addrbuf[INET_ADDRSTRLEN];
+				printf("Connection accepted from %s:%d\n", inet_ntop(AF_INET, &(remote_addr.sin_addr), addrbuf, INET_ADDRSTRLEN), ntohs(remote_addr.sin_port));
+			}
+		}
+		usrsctp_close(psock);
+	} else {
+		memset(&encaps, 0, sizeof(struct sctp_udpencaps));
+		encaps.sue_address.ss_family = AF_INET;
+		encaps.sue_port = htons(remote_udp_port);
+		if (usrsctp_setsockopt(psock, IPPROTO_SCTP, SCTP_REMOTE_UDP_ENCAPS_PORT, (const void*)&encaps, (socklen_t)sizeof(struct sctp_udpencaps)) < 0) {
+			perror("setsockopt");
+		}
+
+		remote_addr.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+		remote_addr.sin_len = sizeof(struct sockaddr_in);
+#endif
+		if (!inet_pton(AF_INET, argv[optind], &remote_addr.sin_addr.s_addr)){
+			printf("error: invalid destination address\n");
+			exit(1);
+		}
+		remote_addr.sin_port = htons(remote_port);
+
+		/* TODO fragpoint stuff */
+		if (nodelay == 1) {
+			optval = 1;
+		} else {
+			optval = 0;
+		}
+		usrsctp_setsockopt(psock, IPPROTO_SCTP, SCTP_NODELAY, &optval, sizeof(int));
+
+		if (fragpoint) {
+			av.assoc_id = 0;
+			av.assoc_value = fragpoint;
+			if (usrsctp_setsockopt(psock, IPPROTO_SCTP, SCTP_MAXSEG, &av, sizeof(struct sctp_assoc_value)) < 0) {
+				perror("setsockopt: SCTP_MAXSEG");
+			}
+		}
+
+		if (sndbufsize) {
+			if (usrsctp_setsockopt(psock, SOL_SOCKET, SO_SNDBUF, &sndbufsize, sizeof(int)) < 0) {
+				perror("setsockopt: sndbuf");
+			}
+		}
+		if (verbose) {
+			intlen = sizeof(int);
+			if (usrsctp_getsockopt(psock, SOL_SOCKET, SO_SNDBUF, &mysndbufsize, (socklen_t *)&intlen) < 0) {
+				perror("setsockopt: SO_SNDBUF");
+			} else {
+				fprintf(stdout,"Send buffer size: %d.\n", mysndbufsize);
+			}
+		}
+
+		buffer = malloc(length);
+		memset(buffer, 'b', length);
+
+		if (usrsctp_connect(psock, (struct sockaddr *) &remote_addr, sizeof(struct sockaddr_in)) == -1 ) {
+			perror("usrsctp_connect");
+			exit(1);
+		}
+
+		gettimeofday(&start_time, NULL);
+
+		done = 0;
+
+		if (runtime > 0) {
+#ifndef _WIN32
+			signal(SIGALRM, stop_sender);
+			alarm(runtime);
+#else
+			printf("You cannot set the runtime in Windows yet\n");
+			exit(-1);
+#endif
+		}
+
+		if (use_cb) {
+			while (done < 2 && (messages < (number_of_messages - 1))) {
+#ifdef _WIN32
+				Sleep(1000);
+#else
+				sleep(1);
+#endif
+			}
+		} else {
+			sndinfo.snd_sid = 0;
+			sndinfo.snd_flags = 0;
+			if (unordered != 0) {
+				sndinfo.snd_flags |= SCTP_UNORDERED;
+			}
+			sndinfo.snd_ppid = 0;
+			sndinfo.snd_context = 0;
+			sndinfo.snd_assoc_id = 0;
+			if (verbose) {
+				printf("Start sending ");
+				if (number_of_messages > 0) {
+					printf("%ld messages ", (long)number_of_messages);
+				}
+				if (runtime > 0) {
+					printf("for %u seconds ...", runtime);
+				}
+				printf("\n");
+				fflush(stdout);
+			}
+			while (!done && ((number_of_messages == 0) || (messages < (number_of_messages - 1)))) {
+				if (very_verbose) {
+					printf("Sending message number %lu.\n", messages + 1);
+				}
+
+				if (usrsctp_sendv(psock, buffer, length, (struct sockaddr *) &remote_addr, 1,
+				                  (void *)&sndinfo, (socklen_t)sizeof(struct sctp_sndinfo), SCTP_SENDV_SNDINFO,
+				                  0) < 0) {
+					perror("usrsctp_sendv");
+					exit(1);
+				}
+				messages++;
+			}
+			if (very_verbose) {
+				printf("Sending message number %lu.\n", messages + 1);
+			}
+
+			sndinfo.snd_flags |= SCTP_EOF;
+			if (usrsctp_sendv(psock, buffer, length, (struct sockaddr *) &remote_addr, 1,
+			                  (void *)&sndinfo, (socklen_t)sizeof(struct sctp_sndinfo), SCTP_SENDV_SNDINFO,
+			                  0) < 0) {
+				perror("usrsctp_sendv");
+				exit(1);
+			}
+			messages++;
+		}
+		free (buffer);
+
+		if (verbose) {
+			printf("Closing socket.\n");
+		}
+
+		usrsctp_close(psock);
+		gettimeofday(&now, NULL);
+		timersub(&now, &start_time, &diff_time);
+		seconds = diff_time.tv_sec + (double)diff_time.tv_usec/1000000;
+		printf("%s of %ld messages of length %u took %f seconds.\n",
+		       "Sending", messages, length, seconds);
+		throughput = (double)messages * (double)length / seconds;
+		printf("Throughput was %f Byte/sec.\n", throughput);
+	}
+
+	while (usrsctp_finish() != 0) {
+#ifdef _WIN32
+		Sleep(1000);
+#else
+		sleep(1);
+#endif
+	}
+	return 0;
+}