Răsfoiți Sursa

Merged paullouisageneau/libdatachannel with stazio/libdatachannel

Staz M 5 ani în urmă
părinte
comite
bc0b14288b
54 a modificat fișierele cu 2869 adăugiri și 616 ștergeri
  1. 1 1
      .github/workflows/build-gnutls.yml
  2. 1 1
      .github/workflows/build-openssl.yml
  3. 57 12
      CMakeLists.txt
  4. 1 0
      README.md
  5. 1 1
      deps/libjuice
  6. 1 1
      deps/usrsctp
  7. 6 1
      examples/client/CMakeLists.txt
  8. 973 0
      examples/client/getopt.cpp
  9. 136 0
      examples/client/getopt.h
  10. 95 25
      examples/client/main.cpp
  11. 182 0
      examples/client/parse_cl.cpp
  12. 74 0
      examples/client/parse_cl.h
  13. 14 14
      examples/copy-paste-capi/answerer.c
  14. 14 14
      examples/copy-paste-capi/offerer.c
  15. 3 5
      examples/copy-paste/answerer.cpp
  16. 3 5
      examples/copy-paste/offerer.cpp
  17. 1 1
      examples/media/main.cpp
  18. 7 3
      examples/signaling-server-python/signaling-server.py
  19. 1 1
      examples/signaling-server-rust/Cargo.lock
  20. 3 1
      examples/signaling-server-rust/src/main.rs
  21. 4 2
      examples/web/server.js
  22. 23 14
      include/rtc/candidate.hpp
  23. 16 10
      include/rtc/description.hpp
  24. 21 4
      include/rtc/include.hpp
  25. 29 8
      include/rtc/peerconnection.hpp
  26. 43 24
      include/rtc/queue.hpp
  27. 37 15
      include/rtc/rtc.h
  28. 106 36
      src/candidate.cpp
  29. 134 28
      src/capi.cpp
  30. 9 1
      src/certificate.cpp
  31. 4 2
      src/datachannel.cpp
  32. 137 59
      src/description.cpp
  33. 4 1
      src/dtlssrtptransport.hpp
  34. 8 9
      src/dtlstransport.cpp
  35. 65 49
      src/icetransport.cpp
  36. 1 6
      src/icetransport.hpp
  37. 4 0
      src/log.cpp
  38. 388 160
      src/peerconnection.cpp
  39. 8 19
      src/processor.hpp
  40. 1 1
      src/rtcp.cpp
  41. 7 15
      src/sctptransport.cpp
  42. 2 2
      src/sctptransport.hpp
  43. 1 1
      src/tcptransport.cpp
  44. 1 5
      src/threadpool.cpp
  45. 9 2
      src/threadpool.hpp
  46. 4 6
      src/tlstransport.cpp
  47. 2 2
      src/track.cpp
  48. 41 19
      src/websocket.cpp
  49. 7 1
      src/wstransport.cpp
  50. 2 1
      test/benchmark.cpp
  51. 87 17
      test/capi_connectivity.cpp
  52. 8 8
      test/capi_track.cpp
  53. 62 0
      test/connectivity.cpp
  54. 20 3
      test/track.cpp

+ 1 - 1
.github/workflows/build-gnutls.yml

@@ -30,7 +30,7 @@ jobs:
     - name: submodules
     - name: submodules
       run: git submodule update --init --recursive
       run: git submodule update --init --recursive
     - name: cmake
     - name: cmake
-      run: cmake -B build -DUSE_GNUTLS=1 -DWARNINGS_AS_ERRORS=1
+      run: cmake -B build -DUSE_GNUTLS=1 -DWARNINGS_AS_ERRORS=1 -DENABLE_LOCAL_ADDRESS_TRANSLATION=1
     - name: make
     - name: make
       run: (cd build; make -j2)
       run: (cd build; make -j2)
     - name: test
     - name: test

+ 1 - 1
.github/workflows/build-openssl.yml

@@ -30,7 +30,7 @@ jobs:
     - name: submodules
     - name: submodules
       run: git submodule update --init --recursive
       run: git submodule update --init --recursive
     - name: cmake
     - name: cmake
-      run: cmake -B build -DUSE_GNUTLS=0 -WARNINGS_AS_ERRORS=1
+      run: cmake -B build -DUSE_GNUTLS=0 -WARNINGS_AS_ERRORS=1 -DENABLE_LOCAL_ADDRESS_TRANSLATION=1
       env:
       env:
         OPENSSL_ROOT_DIR: /usr/local/opt/openssl
         OPENSSL_ROOT_DIR: /usr/local/opt/openssl
         OPENSSL_LIBRARIES: /usr/local/opt/openssl/lib
         OPENSSL_LIBRARIES: /usr/local/opt/openssl/lib

+ 57 - 12
CMakeLists.txt

@@ -1,8 +1,8 @@
 cmake_minimum_required(VERSION 3.7)
 cmake_minimum_required(VERSION 3.7)
 project(libdatachannel
 project(libdatachannel
-	DESCRIPTION "WebRTC Data Channels Library"
-	VERSION 0.9.0
+	VERSION 0.9.4
 	LANGUAGES CXX)
 	LANGUAGES CXX)
+set(PROJECT_DESCRIPTION "WebRTC Data Channels Library")
 
 
 # Options
 # Options
 option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
 option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
@@ -11,6 +11,11 @@ option(NO_WEBSOCKET "Disable WebSocket support" OFF)
 option(NO_EXAMPLES "Disable examples" OFF)
 option(NO_EXAMPLES "Disable examples" OFF)
 option(NO_TESTS "Disable tests build" OFF)
 option(NO_TESTS "Disable tests build" OFF)
 option(WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
 option(WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
+option(RSA_KEY_BITS_2048 "Use 2048-bit RSA key instead of 3072-bit" OFF)
+option(CAPI_STDCALL "Set calling convention of C API callbacks stdcall" OFF)
+# Option USE_SRTP defaults to AUTO (enabled if libSRTP is found, else disabled)
+set(USE_SRTP AUTO CACHE STRING "Use libSRTP and enable media support")
+set_property(CACHE USE_SRTP PROPERTY STRINGS AUTO ON OFF)
 
 
 if(USE_NICE)
 if(USE_NICE)
 	option(USE_JUICE "Use libjuice" OFF)
 	option(USE_JUICE "Use libjuice" OFF)
@@ -19,9 +24,9 @@ else()
 endif()
 endif()
 
 
 if(USE_GNUTLS)
 if(USE_GNUTLS)
-	option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" ON)
+	option(USE_NETTLE "Use Nettle in libjuice" ON)
 else()
 else()
-	option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" OFF)
+	option(USE_NETTLE "Use Nettle in libjuice" OFF)
 endif()
 endif()
 
 
 set(CMAKE_POSITION_INDEPENDENT_CODE ON)
 set(CMAKE_POSITION_INDEPENDENT_CODE ON)
@@ -157,16 +162,46 @@ target_link_libraries(datachannel-static PUBLIC Threads::Threads plog::plog)
 target_link_libraries(datachannel-static PRIVATE Usrsctp::UsrsctpStatic)
 target_link_libraries(datachannel-static PRIVATE Usrsctp::UsrsctpStatic)
 
 
 if(WIN32)
 if(WIN32)
-	target_link_libraries(datachannel PRIVATE wsock32 ws2_32) # winsock2
-	target_link_libraries(datachannel-static PRIVATE wsock32 ws2_32) # winsock2
+	target_link_libraries(datachannel PRIVATE ws2_32) # winsock2
+	target_link_libraries(datachannel-static PRIVATE ws2_32) # winsock2
 endif()
 endif()
 
 
-add_subdirectory(deps/libsrtp EXCLUDE_FROM_ALL)
-message(STATUS "LibSRTP found, compiling with media transport")
-target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=1)
-target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=1)
-target_link_libraries(datachannel PRIVATE srtp2)
-target_link_libraries(datachannel-static PRIVATE srtp2)
+if(USE_SRTP STREQUAL "AUTO")
+	find_package(SRTP)
+	if(SRTP_FOUND)
+		message(STATUS "LibSRTP found, compiling with media transport")
+	else()
+		message(STATUS "LibSRTP NOT found, compiling WITHOUT media transport")
+	endif()
+elseif (USE_SRTP STREQUAL "COMPILE")
+	message(STATUS "Compiling LibSRTP from source; compiling with media transport")
+	add_subdirectory(deps/libsrtp EXCLUDE_FROM_ALL)
+	target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=1)
+	target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=1)
+	target_compile_definitions(datachannel PUBLIC RTC_SRTP_FROM_SOURCE=1)
+	target_compile_definitions(datachannel-static PUBLIC RTC_SRTP_FROM_SOURCE=1)
+	target_link_libraries(datachannel PRIVATE srtp2)
+	target_link_libraries(datachannel-static PRIVATE srtp2)
+elseif(USE_SRTP)
+	find_package(SRTP REQUIRED)
+endif()
+
+if(USE_SRTP AND SRTP_FOUND)
+	if(NOT TARGET SRTP::SRTP)
+		add_library(SRTP::SRTP UNKNOWN IMPORTED)
+		set_target_properties(SRTP::SRTP PROPERTIES
+			INTERFACE_INCLUDE_DIRECTORIES ${SRTP_INCLUDE_DIRS}
+			IMPORTED_LINK_INTERFACE_LANGUAGES C
+			IMPORTED_LOCATION ${SRTP_LIBRARIES})
+	endif()
+	target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=1)
+	target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=1)
+	target_link_libraries(datachannel PRIVATE SRTP::SRTP)
+	target_link_libraries(datachannel-static PRIVATE SRTP::SRTP)
+elseif (NOT USE_SRTP  STREQUAL "COMPILE")
+	target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=0)
+	target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=0)
+endif()
 
 
 if (USE_GNUTLS)
 if (USE_GNUTLS)
 	find_package(GnuTLS REQUIRED)
 	find_package(GnuTLS REQUIRED)
@@ -204,6 +239,16 @@ else()
 	target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuiceStatic)
 	target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuiceStatic)
 endif()
 endif()
 
 
+if(RSA_KEY_BITS_2048)
+	target_compile_definitions(datachannel PUBLIC RSA_KEY_BITS_2048)
+	target_compile_definitions(datachannel-static PUBLIC RSA_KEY_BITS_2048)
+endif()
+
+if(CAPI_STDCALL)
+	target_compile_definitions(datachannel PUBLIC CAPI_STDCALL)
+	target_compile_definitions(datachannel-static PUBLIC CAPI_STDCALL)
+endif()
+
 add_library(LibDataChannel::LibDataChannel ALIAS datachannel)
 add_library(LibDataChannel::LibDataChannel ALIAS datachannel)
 add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static)
 add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static)
 
 

+ 1 - 0
README.md

@@ -202,5 +202,6 @@ ws->open("wss://my.websocket/service");
 ## External resources
 ## External resources
 - Rust wrapper for libdatachannel: [datachannel-rs](https://github.com/lerouxrgd/datachannel-rs)
 - Rust wrapper for libdatachannel: [datachannel-rs](https://github.com/lerouxrgd/datachannel-rs)
 - Node.js wrapper for libdatachannel: [node-datachannel](https://github.com/murat-dogan/node-datachannel)
 - Node.js wrapper for libdatachannel: [node-datachannel](https://github.com/murat-dogan/node-datachannel)
+- Unity wrapper for Windows 10 and Hololens: [datachannel-unity](https://github.com/hanseuljun/datachannel-unity)
 - WebAssembly wrapper compatible with libdatachannel: [datachannel-wasm](https://github.com/paullouisageneau/datachannel-wasm)
 - WebAssembly wrapper compatible with libdatachannel: [datachannel-wasm](https://github.com/paullouisageneau/datachannel-wasm)
 
 

+ 1 - 1
deps/libjuice

@@ -1 +1 @@
-Subproject commit ddc30648908181f6e6222a2fd648d35c2c28b724
+Subproject commit 0a44ac2d26959fcdda204fa6814b39624ebf84d1

+ 1 - 1
deps/usrsctp

@@ -1 +1 @@
-Subproject commit ffed0925f27d404173c1e3e750d818f432d2c019
+Subproject commit 0db969100094422d9ea74a08ae5e5d9a4cfdb06b

+ 6 - 1
examples/client/CMakeLists.txt

@@ -3,7 +3,12 @@ if(POLICY CMP0079)
 	cmake_policy(SET CMP0079 NEW)
 	cmake_policy(SET CMP0079 NEW)
 endif()
 endif()
 
 
-add_executable(datachannel-client main.cpp)
+if(WIN32)
+add_executable(datachannel-client main.cpp parse_cl.cpp parse_cl.h getopt.cpp getopt.h)
+target_compile_definitions(datachannel-client PUBLIC STATIC_GETOPT)
+else()
+add_executable(datachannel-client main.cpp parse_cl.cpp parse_cl.h)
+endif()
 set_target_properties(datachannel-client PROPERTIES
 set_target_properties(datachannel-client PROPERTIES
 	CXX_STANDARD 17
 	CXX_STANDARD 17
 	OUTPUT_NAME client)
 	OUTPUT_NAME client)

+ 973 - 0
examples/client/getopt.cpp

@@ -0,0 +1,973 @@
+/* Getopt for Microsoft C
+This code is a modification of the Free Software Foundation, Inc.
+Getopt library for parsing command line argument the purpose was
+to provide a Microsoft Visual C friendly derivative. This code
+provides functionality for both Unicode and Multibyte builds.
+
+Date: 02/03/2011 - Ludvik Jerabek - Initial Release
+Version: 1.0
+Comment: Supports getopt, getopt_long, and getopt_long_only
+and POSIXLY_CORRECT environment flag
+License: LGPL
+
+Revisions:
+
+02/03/2011 - Ludvik Jerabek - Initial Release
+02/20/2011 - Ludvik Jerabek - Fixed compiler warnings at Level 4
+07/05/2011 - Ludvik Jerabek - Added no_argument, required_argument, optional_argument defs
+08/03/2011 - Ludvik Jerabek - Fixed non-argument runtime bug which caused runtime exception
+08/09/2011 - Ludvik Jerabek - Added code to export functions for DLL and LIB
+02/15/2012 - Ludvik Jerabek - Fixed _GETOPT_THROW definition missing in implementation file
+08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi
+10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features
+06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable
+
+**DISCLAIMER**
+THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
+EITHER EXPRESS OR IMPLIED, INCLUDING, BUT Not LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE, OR NON-INFRINGEMENT. SOME JURISDICTIONS DO NOT ALLOW THE
+EXCLUSION OF IMPLIED WARRANTIES, SO THE ABOVE EXCLUSION MAY NOT
+APPLY TO YOU. IN NO EVENT WILL I BE LIABLE TO ANY PARTY FOR ANY
+DIRECT, INDIRECT, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES FOR ANY
+USE OF THIS MATERIAL INCLUDING, WITHOUT LIMITATION, ANY LOST
+PROFITS, BUSINESS INTERRUPTION, LOSS OF PROGRAMS OR OTHER DATA ON
+YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE
+EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+*/
+#define _CRT_SECURE_NO_WARNINGS
+#include <stdlib.h>
+#include <stdio.h>
+#include <malloc.h>
+#include "getopt.h"
+
+#ifdef __cplusplus
+	#define _GETOPT_THROW throw()
+#else
+	#define _GETOPT_THROW
+#endif
+
+int optind = 1;
+int opterr = 1;
+int optopt = '?';
+enum ENUM_ORDERING { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER };
+
+//
+//
+//		Ansi structures and functions follow
+// 
+//
+
+static struct _getopt_data_a
+{
+	int optind;
+	int opterr;
+	int optopt;
+	char *optarg;
+	int __initialized;
+	char *__nextchar;
+	enum ENUM_ORDERING __ordering;
+	int __posixly_correct;
+	int __first_nonopt;
+	int __last_nonopt;
+} getopt_data_a;
+char *optarg_a;
+
+static void exchange_a(char **argv, struct _getopt_data_a *d)
+{
+	int bottom = d->__first_nonopt;
+	int middle = d->__last_nonopt;
+	int top = d->optind;
+	char *tem;
+	while (top > middle && middle > bottom)
+	{
+		if (top - middle > middle - bottom)
+		{
+			int len = middle - bottom;
+			int i;
+			for (i = 0; i < len; i++)
+			{
+				tem = argv[bottom + i];
+				argv[bottom + i] = argv[top - (middle - bottom) + i];
+				argv[top - (middle - bottom) + i] = tem;
+			}
+			top -= len;
+		}
+		else
+		{
+			int len = top - middle;
+			int i;
+			for (i = 0; i < len; i++)
+			{
+				tem = argv[bottom + i];
+				argv[bottom + i] = argv[middle + i];
+				argv[middle + i] = tem;
+			}
+			bottom += len;
+		}
+	}
+	d->__first_nonopt += (d->optind - d->__last_nonopt);
+	d->__last_nonopt = d->optind;
+}
+static const char *_getopt_initialize_a (const char *optstring, struct _getopt_data_a *d, int posixly_correct)
+{
+	d->__first_nonopt = d->__last_nonopt = d->optind;
+	d->__nextchar = NULL;
+	d->__posixly_correct = posixly_correct | !!getenv("POSIXLY_CORRECT");
+	if (optstring[0] == '-')
+	{
+		d->__ordering = RETURN_IN_ORDER;
+		++optstring;
+	}
+	else if (optstring[0] == '+')
+	{
+		d->__ordering = REQUIRE_ORDER;
+		++optstring;
+	}
+	else if (d->__posixly_correct)
+		d->__ordering = REQUIRE_ORDER;
+	else
+		d->__ordering = PERMUTE;
+	return optstring;
+}
+int _getopt_internal_r_a (int argc, char *const *argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, struct _getopt_data_a *d, int posixly_correct)
+{
+	int print_errors = d->opterr;
+	if (argc < 1)
+		return -1;
+	d->optarg = NULL;
+	if (d->optind == 0 || !d->__initialized)
+	{
+		if (d->optind == 0)
+			d->optind = 1;
+		optstring = _getopt_initialize_a (optstring, d, posixly_correct);
+		d->__initialized = 1;
+	}
+	else if (optstring[0] == '-' || optstring[0] == '+')
+		optstring++;
+	if (optstring[0] == ':')
+		print_errors = 0;
+	if (d->__nextchar == NULL || *d->__nextchar == '\0')
+	{
+		if (d->__last_nonopt > d->optind)
+			d->__last_nonopt = d->optind;
+		if (d->__first_nonopt > d->optind)
+			d->__first_nonopt = d->optind;
+		if (d->__ordering == PERMUTE)
+		{
+			if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
+				exchange_a ((char **) argv, d);
+			else if (d->__last_nonopt != d->optind)
+				d->__first_nonopt = d->optind;
+			while (d->optind < argc && (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0'))
+				d->optind++;
+			d->__last_nonopt = d->optind;
+		}
+		if (d->optind != argc && !strcmp(argv[d->optind], "--"))
+		{
+			d->optind++;
+			if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
+				exchange_a((char **) argv, d);
+			else if (d->__first_nonopt == d->__last_nonopt)
+				d->__first_nonopt = d->optind;
+			d->__last_nonopt = argc;
+			d->optind = argc;
+		}
+		if (d->optind == argc)
+		{
+			if (d->__first_nonopt != d->__last_nonopt)
+				d->optind = d->__first_nonopt;
+			return -1;
+		}
+		if ((argv[d->optind][0] != '-' || argv[d->optind][1] == '\0'))
+		{
+			if (d->__ordering == REQUIRE_ORDER)
+				return -1;
+			d->optarg = argv[d->optind++];
+			return 1;
+		}
+		d->__nextchar = (argv[d->optind] + 1 + (longopts != NULL && argv[d->optind][1] == '-'));
+	}
+	if (longopts != NULL && (argv[d->optind][1] == '-' || (long_only && (argv[d->optind][2] || !strchr(optstring, argv[d->optind][1])))))
+	{
+		char *nameend;
+		unsigned int namelen;
+		const struct option_a *p;
+		const struct option_a *pfound = NULL;
+		struct option_list
+		{
+			const struct option_a *p;
+			struct option_list *next;
+		} *ambig_list = NULL;
+		int exact = 0;
+		int indfound = -1;
+		int option_index;
+		for (nameend = d->__nextchar; *nameend && *nameend != '='; nameend++);
+		namelen = (unsigned int)(nameend - d->__nextchar);
+		for (p = longopts, option_index = 0; p->name; p++, option_index++)
+			if (!strncmp(p->name, d->__nextchar, namelen))
+			{
+				if (namelen == (unsigned int)strlen(p->name))
+				{
+					pfound = p;
+					indfound = option_index;
+					exact = 1;
+					break;
+				}
+				else if (pfound == NULL)
+				{
+					pfound = p;
+					indfound = option_index;
+				}
+				else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val)
+				{
+					struct option_list *newp = (struct option_list*)alloca(sizeof(*newp));
+					newp->p = p;
+					newp->next = ambig_list;
+					ambig_list = newp;
+				}
+			}
+			if (ambig_list != NULL && !exact)
+			{
+				if (print_errors)
+				{
+					struct option_list first;
+					first.p = pfound;
+					first.next = ambig_list;
+					ambig_list = &first;
+					fprintf (stderr, "%s: option '%s' is ambiguous; possibilities:", argv[0], argv[d->optind]);
+					do
+					{
+						fprintf (stderr, " '--%s'", ambig_list->p->name);
+						ambig_list = ambig_list->next;
+					}
+					while (ambig_list != NULL);
+					fputc ('\n', stderr);
+				}
+				d->__nextchar += strlen(d->__nextchar);
+				d->optind++;
+				d->optopt = 0;
+				return '?';
+			}
+			if (pfound != NULL)
+			{
+				option_index = indfound;
+				d->optind++;
+				if (*nameend)
+				{
+					if (pfound->has_arg)
+						d->optarg = nameend + 1;
+					else
+					{
+						if (print_errors)
+						{
+							if (argv[d->optind - 1][1] == '-')
+							{
+								fprintf(stderr, "%s: option '--%s' doesn't allow an argument\n",argv[0], pfound->name);
+							}
+							else
+							{
+								fprintf(stderr, "%s: option '%c%s' doesn't allow an argument\n",argv[0], argv[d->optind - 1][0],pfound->name);
+							}
+						}
+						d->__nextchar += strlen(d->__nextchar);
+						d->optopt = pfound->val;
+						return '?';
+					}
+				}
+				else if (pfound->has_arg == 1)
+				{
+					if (d->optind < argc)
+						d->optarg = argv[d->optind++];
+					else
+					{
+						if (print_errors)
+						{
+							fprintf(stderr,"%s: option '--%s' requires an argument\n",argv[0], pfound->name);
+						}
+						d->__nextchar += strlen(d->__nextchar);
+						d->optopt = pfound->val;
+						return optstring[0] == ':' ? ':' : '?';
+					}
+				}
+				d->__nextchar += strlen(d->__nextchar);
+				if (longind != NULL)
+					*longind = option_index;
+				if (pfound->flag)
+				{
+					*(pfound->flag) = pfound->val;
+					return 0;
+				}
+				return pfound->val;
+			}
+			if (!long_only || argv[d->optind][1] == '-' || strchr(optstring, *d->__nextchar) == NULL)
+			{
+				if (print_errors)
+				{
+					if (argv[d->optind][1] == '-')
+					{
+						fprintf(stderr, "%s: unrecognized option '--%s'\n",argv[0], d->__nextchar);
+					}
+					else
+					{
+						fprintf(stderr, "%s: unrecognized option '%c%s'\n",argv[0], argv[d->optind][0], d->__nextchar);
+					}
+				}
+				d->__nextchar = (char *)"";
+				d->optind++;
+				d->optopt = 0;
+				return '?';
+			}
+	}
+	{
+		char c = *d->__nextchar++;
+		char *temp = (char*)strchr(optstring, c);
+		if (*d->__nextchar == '\0')
+			++d->optind;
+		if (temp == NULL || c == ':' || c == ';')
+		{
+			if (print_errors)
+			{
+				fprintf(stderr, "%s: invalid option -- '%c'\n", argv[0], c);
+			}
+			d->optopt = c;
+			return '?';
+		}
+		if (temp[0] == 'W' && temp[1] == ';')
+		{
+			char *nameend;
+			const struct option_a *p;
+			const struct option_a *pfound = NULL;
+			int exact = 0;
+			int ambig = 0;
+			int indfound = 0;
+			int option_index;
+			if (longopts == NULL)
+				goto no_longs;
+			if (*d->__nextchar != '\0')
+			{
+				d->optarg = d->__nextchar;
+				d->optind++;
+			}
+			else if (d->optind == argc)
+			{
+				if (print_errors)
+				{
+					fprintf(stderr,"%s: option requires an argument -- '%c'\n",argv[0], c);
+				}
+				d->optopt = c;
+				if (optstring[0] == ':')
+					c = ':';
+				else
+					c = '?';
+				return c;
+			}
+			else
+				d->optarg = argv[d->optind++];
+			for (d->__nextchar = nameend = d->optarg; *nameend && *nameend != '='; nameend++);
+			for (p = longopts, option_index = 0; p->name; p++, option_index++)
+				if (!strncmp(p->name, d->__nextchar, nameend - d->__nextchar))
+				{
+					if ((unsigned int) (nameend - d->__nextchar) == strlen(p->name))
+					{
+						pfound = p;
+						indfound = option_index;
+						exact = 1;
+						break;
+					}
+					else if (pfound == NULL)
+					{
+						pfound = p;
+						indfound = option_index;
+					}
+					else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val)
+						ambig = 1;
+				}
+				if (ambig && !exact)
+				{
+					if (print_errors)
+					{
+						fprintf(stderr, "%s: option '-W %s' is ambiguous\n",argv[0], d->optarg);
+					}
+					d->__nextchar += strlen(d->__nextchar);
+					d->optind++;
+					return '?';
+				}
+				if (pfound != NULL)
+				{
+					option_index = indfound;
+					if (*nameend)
+					{
+						if (pfound->has_arg)
+							d->optarg = nameend + 1;
+						else
+						{
+							if (print_errors)
+							{
+								fprintf(stderr, "%s: option '-W %s' doesn't allow an argument\n",argv[0], pfound->name);
+							}
+							d->__nextchar += strlen(d->__nextchar);
+							return '?';
+						}
+					}
+					else if (pfound->has_arg == 1)
+					{
+						if (d->optind < argc)
+							d->optarg = argv[d->optind++];
+						else
+						{
+							if (print_errors)
+							{
+								fprintf(stderr, "%s: option '-W %s' requires an argument\n",argv[0], pfound->name);
+							}
+							d->__nextchar += strlen(d->__nextchar);
+							return optstring[0] == ':' ? ':' : '?';
+						}
+					}
+					else
+						d->optarg = NULL;
+					d->__nextchar += strlen(d->__nextchar);
+					if (longind != NULL)
+						*longind = option_index;
+					if (pfound->flag)
+					{
+						*(pfound->flag) = pfound->val;
+						return 0;
+					}
+					return pfound->val;
+				}
+no_longs:
+				d->__nextchar = NULL;
+				return 'W';
+		}
+		if (temp[1] == ':')
+		{
+			if (temp[2] == ':')
+			{
+				if (*d->__nextchar != '\0')
+				{
+					d->optarg = d->__nextchar;
+					d->optind++;
+				}
+				else
+					d->optarg = NULL;
+				d->__nextchar = NULL;
+			}
+			else
+			{
+				if (*d->__nextchar != '\0')
+				{
+					d->optarg = d->__nextchar;
+					d->optind++;
+				}
+				else if (d->optind == argc)
+				{
+					if (print_errors)
+					{
+						fprintf(stderr,"%s: option requires an argument -- '%c'\n",argv[0], c);
+					}
+					d->optopt = c;
+					if (optstring[0] == ':')
+						c = ':';
+					else
+						c = '?';
+				}
+				else
+					d->optarg = argv[d->optind++];
+				d->__nextchar = NULL;
+			}
+		}
+		return c;
+	}
+}
+int _getopt_internal_a (int argc, char *const *argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, int posixly_correct)
+{
+	int result;
+	getopt_data_a.optind = optind;
+	getopt_data_a.opterr = opterr;
+	result = _getopt_internal_r_a (argc, argv, optstring, longopts,longind, long_only, &getopt_data_a,posixly_correct);
+	optind = getopt_data_a.optind;
+	optarg_a = getopt_data_a.optarg;
+	optopt = getopt_data_a.optopt;
+	return result;
+}
+int getopt_a (int argc, char *const *argv, const char *optstring) _GETOPT_THROW
+{
+	return _getopt_internal_a (argc, argv, optstring, (const struct option_a *) 0, (int *) 0, 0, 0);
+}
+int getopt_long_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW
+{
+	return _getopt_internal_a (argc, argv, options, long_options, opt_index, 0, 0);
+}
+int getopt_long_only_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW
+{
+	return _getopt_internal_a (argc, argv, options, long_options, opt_index, 1, 0);
+}
+int _getopt_long_r_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index, struct _getopt_data_a *d)
+{
+	return _getopt_internal_r_a (argc, argv, options, long_options, opt_index,0, d, 0);
+}
+int _getopt_long_only_r_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index, struct _getopt_data_a *d)
+{
+	return _getopt_internal_r_a (argc, argv, options, long_options, opt_index, 1, d, 0);
+}
+
+//
+//
+//	Unicode Structures and Functions
+// 
+//
+
+static struct _getopt_data_w
+{
+	int optind;
+	int opterr;
+	int optopt;
+	wchar_t *optarg;
+	int __initialized;
+	wchar_t *__nextchar;
+	enum ENUM_ORDERING __ordering;
+	int __posixly_correct;
+	int __first_nonopt;
+	int __last_nonopt;
+} getopt_data_w;
+wchar_t *optarg_w;
+
+static void exchange_w(wchar_t **argv, struct _getopt_data_w *d)
+{
+	int bottom = d->__first_nonopt;
+	int middle = d->__last_nonopt;
+	int top = d->optind;
+	wchar_t *tem;
+	while (top > middle && middle > bottom)
+	{
+		if (top - middle > middle - bottom)
+		{
+			int len = middle - bottom;
+			int i;
+			for (i = 0; i < len; i++)
+			{
+				tem = argv[bottom + i];
+				argv[bottom + i] = argv[top - (middle - bottom) + i];
+				argv[top - (middle - bottom) + i] = tem;
+			}
+			top -= len;
+		}
+		else
+		{
+			int len = top - middle;
+			int i;
+			for (i = 0; i < len; i++)
+			{
+				tem = argv[bottom + i];
+				argv[bottom + i] = argv[middle + i];
+				argv[middle + i] = tem;
+			}
+			bottom += len;
+		}
+	}
+	d->__first_nonopt += (d->optind - d->__last_nonopt);
+	d->__last_nonopt = d->optind;
+}
+static const wchar_t *_getopt_initialize_w (const wchar_t *optstring, struct _getopt_data_w *d, int posixly_correct)
+{
+	d->__first_nonopt = d->__last_nonopt = d->optind;
+	d->__nextchar = NULL;
+	d->__posixly_correct = posixly_correct | !!_wgetenv(L"POSIXLY_CORRECT");
+	if (optstring[0] == L'-')
+	{
+		d->__ordering = RETURN_IN_ORDER;
+		++optstring;
+	}
+	else if (optstring[0] == L'+')
+	{
+		d->__ordering = REQUIRE_ORDER;
+		++optstring;
+	}
+	else if (d->__posixly_correct)
+		d->__ordering = REQUIRE_ORDER;
+	else
+		d->__ordering = PERMUTE;
+	return optstring;
+}
+int _getopt_internal_r_w (int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, struct _getopt_data_w *d, int posixly_correct)
+{
+	int print_errors = d->opterr;
+	if (argc < 1)
+		return -1;
+	d->optarg = NULL;
+	if (d->optind == 0 || !d->__initialized)
+	{
+		if (d->optind == 0)
+			d->optind = 1;
+		optstring = _getopt_initialize_w (optstring, d, posixly_correct);
+		d->__initialized = 1;
+	}
+	else if (optstring[0] == L'-' || optstring[0] == L'+')
+		optstring++;
+	if (optstring[0] == L':')
+		print_errors = 0;
+	if (d->__nextchar == NULL || *d->__nextchar == L'\0')
+	{
+		if (d->__last_nonopt > d->optind)
+			d->__last_nonopt = d->optind;
+		if (d->__first_nonopt > d->optind)
+			d->__first_nonopt = d->optind;
+		if (d->__ordering == PERMUTE)
+		{
+			if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
+				exchange_w((wchar_t **) argv, d);
+			else if (d->__last_nonopt != d->optind)
+				d->__first_nonopt = d->optind;
+			while (d->optind < argc && (argv[d->optind][0] != L'-' || argv[d->optind][1] == L'\0'))
+				d->optind++;
+			d->__last_nonopt = d->optind;
+		}
+		if (d->optind != argc && !wcscmp(argv[d->optind], L"--"))
+		{
+			d->optind++;
+			if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
+				exchange_w((wchar_t **) argv, d);
+			else if (d->__first_nonopt == d->__last_nonopt)
+				d->__first_nonopt = d->optind;
+			d->__last_nonopt = argc;
+			d->optind = argc;
+		}
+		if (d->optind == argc)
+		{
+			if (d->__first_nonopt != d->__last_nonopt)
+				d->optind = d->__first_nonopt;
+			return -1;
+		}
+		if ((argv[d->optind][0] != L'-' || argv[d->optind][1] == L'\0'))
+		{
+			if (d->__ordering == REQUIRE_ORDER)
+				return -1;
+			d->optarg = argv[d->optind++];
+			return 1;
+		}
+		d->__nextchar = (argv[d->optind] + 1 + (longopts != NULL && argv[d->optind][1] == L'-'));
+	}
+	if (longopts != NULL && (argv[d->optind][1] == L'-' || (long_only && (argv[d->optind][2] || !wcschr(optstring, argv[d->optind][1])))))
+	{
+		wchar_t *nameend;
+		unsigned int namelen;
+		const struct option_w *p;
+		const struct option_w *pfound = NULL;
+		struct option_list
+		{
+			const struct option_w *p;
+			struct option_list *next;
+		} *ambig_list = NULL;
+		int exact = 0;
+		int indfound = -1;
+		int option_index;
+		for (nameend = d->__nextchar; *nameend && *nameend != L'='; nameend++);
+		namelen = (unsigned int)(nameend - d->__nextchar);
+		for (p = longopts, option_index = 0; p->name; p++, option_index++)
+			if (!wcsncmp(p->name, d->__nextchar, namelen))
+			{
+				if (namelen == (unsigned int)wcslen(p->name))
+				{
+					pfound = p;
+					indfound = option_index;
+					exact = 1;
+					break;
+				}
+				else if (pfound == NULL)
+				{
+					pfound = p;
+					indfound = option_index;
+				}
+				else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val)
+				{
+					struct option_list *newp = (struct option_list*)alloca(sizeof(*newp));
+					newp->p = p;
+					newp->next = ambig_list;
+					ambig_list = newp;
+				}
+			}
+			if (ambig_list != NULL && !exact)
+			{
+				if (print_errors)
+				{						
+					struct option_list first;
+					first.p = pfound;
+					first.next = ambig_list;
+					ambig_list = &first;
+					fwprintf(stderr, L"%s: option '%s' is ambiguous; possibilities:", argv[0], argv[d->optind]);
+					do
+					{
+						fwprintf (stderr, L" '--%s'", ambig_list->p->name);
+						ambig_list = ambig_list->next;
+					}
+					while (ambig_list != NULL);
+					fputwc (L'\n', stderr);
+				}
+				d->__nextchar += wcslen(d->__nextchar);
+				d->optind++;
+				d->optopt = 0;
+				return L'?';
+			}
+			if (pfound != NULL)
+			{
+				option_index = indfound;
+				d->optind++;
+				if (*nameend)
+				{
+					if (pfound->has_arg)
+						d->optarg = nameend + 1;
+					else
+					{
+						if (print_errors)
+						{
+							if (argv[d->optind - 1][1] == L'-')
+							{
+								fwprintf(stderr, L"%s: option '--%s' doesn't allow an argument\n",argv[0], pfound->name);
+							}
+							else
+							{
+								fwprintf(stderr, L"%s: option '%c%s' doesn't allow an argument\n",argv[0], argv[d->optind - 1][0],pfound->name);
+							}
+						}
+						d->__nextchar += wcslen(d->__nextchar);
+						d->optopt = pfound->val;
+						return L'?';
+					}
+				}
+				else if (pfound->has_arg == 1)
+				{
+					if (d->optind < argc)
+						d->optarg = argv[d->optind++];
+					else
+					{
+						if (print_errors)
+						{
+							fwprintf(stderr,L"%s: option '--%s' requires an argument\n",argv[0], pfound->name);
+						}
+						d->__nextchar += wcslen(d->__nextchar);
+						d->optopt = pfound->val;
+						return optstring[0] == L':' ? L':' : L'?';
+					}
+				}
+				d->__nextchar += wcslen(d->__nextchar);
+				if (longind != NULL)
+					*longind = option_index;
+				if (pfound->flag)
+				{
+					*(pfound->flag) = pfound->val;
+					return 0;
+				}
+				return pfound->val;
+			}
+			if (!long_only || argv[d->optind][1] == L'-' || wcschr(optstring, *d->__nextchar) == NULL)
+			{
+				if (print_errors)
+				{
+					if (argv[d->optind][1] == L'-')
+					{
+						fwprintf(stderr, L"%s: unrecognized option '--%s'\n",argv[0], d->__nextchar);
+					}
+					else
+					{
+						fwprintf(stderr, L"%s: unrecognized option '%c%s'\n",argv[0], argv[d->optind][0], d->__nextchar);
+					}
+				}
+				d->__nextchar = (wchar_t *)L"";
+				d->optind++;
+				d->optopt = 0;
+				return L'?';
+			}
+	}
+	{
+		wchar_t c = *d->__nextchar++;
+		wchar_t *temp = (wchar_t*)wcschr(optstring, c);
+		if (*d->__nextchar == L'\0')
+			++d->optind;
+		if (temp == NULL || c == L':' || c == L';')
+		{
+			if (print_errors)
+			{
+				fwprintf(stderr, L"%s: invalid option -- '%c'\n", argv[0], c);
+			}
+			d->optopt = c;
+			return L'?';
+		}
+		if (temp[0] == L'W' && temp[1] == L';')
+		{
+			wchar_t *nameend;
+			const struct option_w *p;
+			const struct option_w *pfound = NULL;
+			int exact = 0;
+			int ambig = 0;
+			int indfound = 0;
+			int option_index;
+			if (longopts == NULL)
+				goto no_longs;
+			if (*d->__nextchar != L'\0')
+			{
+				d->optarg = d->__nextchar;
+				d->optind++;
+			}
+			else if (d->optind == argc)
+			{
+				if (print_errors)
+				{
+					fwprintf(stderr,L"%s: option requires an argument -- '%c'\n",argv[0], c);
+				}
+				d->optopt = c;
+				if (optstring[0] == L':')
+					c = L':';
+				else
+					c = L'?';
+				return c;
+			}
+			else
+				d->optarg = argv[d->optind++];
+			for (d->__nextchar = nameend = d->optarg; *nameend && *nameend != L'='; nameend++);
+			for (p = longopts, option_index = 0; p->name; p++, option_index++)
+				if (!wcsncmp(p->name, d->__nextchar, nameend - d->__nextchar))
+				{
+					if ((unsigned int) (nameend - d->__nextchar) == wcslen(p->name))
+					{
+						pfound = p;
+						indfound = option_index;
+						exact = 1;
+						break;
+					}
+					else if (pfound == NULL)
+					{
+						pfound = p;
+						indfound = option_index;
+					}
+					else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val)
+						ambig = 1;
+				}
+				if (ambig && !exact)
+				{
+					if (print_errors)
+					{
+						fwprintf(stderr, L"%s: option '-W %s' is ambiguous\n",argv[0], d->optarg);
+					}
+					d->__nextchar += wcslen(d->__nextchar);
+					d->optind++;
+					return L'?';
+				}
+				if (pfound != NULL)
+				{
+					option_index = indfound;
+					if (*nameend)
+					{
+						if (pfound->has_arg)
+							d->optarg = nameend + 1;
+						else
+						{
+							if (print_errors)
+							{
+								fwprintf(stderr, L"%s: option '-W %s' doesn't allow an argument\n",argv[0], pfound->name);
+							}
+							d->__nextchar += wcslen(d->__nextchar);
+							return L'?';
+						}
+					}
+					else if (pfound->has_arg == 1)
+					{
+						if (d->optind < argc)
+							d->optarg = argv[d->optind++];
+						else
+						{
+							if (print_errors)
+							{
+								fwprintf(stderr, L"%s: option '-W %s' requires an argument\n",argv[0], pfound->name);
+							}
+							d->__nextchar += wcslen(d->__nextchar);
+							return optstring[0] == L':' ? L':' : L'?';
+						}
+					}
+					else
+						d->optarg = NULL;
+					d->__nextchar += wcslen(d->__nextchar);
+					if (longind != NULL)
+						*longind = option_index;
+					if (pfound->flag)
+					{
+						*(pfound->flag) = pfound->val;
+						return 0;
+					}
+					return pfound->val;
+				}
+no_longs:
+				d->__nextchar = NULL;
+				return L'W';
+		}
+		if (temp[1] == L':')
+		{
+			if (temp[2] == L':')
+			{
+				if (*d->__nextchar != L'\0')
+				{
+					d->optarg = d->__nextchar;
+					d->optind++;
+				}
+				else
+					d->optarg = NULL;
+				d->__nextchar = NULL;
+			}
+			else
+			{
+				if (*d->__nextchar != L'\0')
+				{
+					d->optarg = d->__nextchar;
+					d->optind++;
+				}
+				else if (d->optind == argc)
+				{
+					if (print_errors)
+					{
+						fwprintf(stderr,L"%s: option requires an argument -- '%c'\n",argv[0], c);
+					}
+					d->optopt = c;
+					if (optstring[0] == L':')
+						c = L':';
+					else
+						c = L'?';
+				}
+				else
+					d->optarg = argv[d->optind++];
+				d->__nextchar = NULL;
+			}
+		}
+		return c;
+	}
+}
+int _getopt_internal_w (int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, int posixly_correct)
+{
+	int result;
+	getopt_data_w.optind = optind;
+	getopt_data_w.opterr = opterr;
+	result = _getopt_internal_r_w (argc, argv, optstring, longopts,longind, long_only, &getopt_data_w,posixly_correct);
+	optind = getopt_data_w.optind;
+	optarg_w = getopt_data_w.optarg;
+	optopt = getopt_data_w.optopt;
+	return result;
+}
+int getopt_w (int argc, wchar_t *const *argv, const wchar_t *optstring) _GETOPT_THROW
+{
+	return _getopt_internal_w (argc, argv, optstring, (const struct option_w *) 0, (int *) 0, 0, 0);
+}
+int getopt_long_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW
+{
+	return _getopt_internal_w (argc, argv, options, long_options, opt_index, 0, 0);
+}
+int getopt_long_only_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW
+{
+	return _getopt_internal_w (argc, argv, options, long_options, opt_index, 1, 0);
+}
+int _getopt_long_r_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d)
+{
+	return _getopt_internal_r_w (argc, argv, options, long_options, opt_index,0, d, 0);
+}
+int _getopt_long_only_r_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d)
+{
+	return _getopt_internal_r_w (argc, argv, options, long_options, opt_index, 1, d, 0);
+}

+ 136 - 0
examples/client/getopt.h

@@ -0,0 +1,136 @@
+/* Getopt for Microsoft C
+This code is a modification of the Free Software Foundation, Inc.
+Getopt library for parsing command line argument the purpose was
+to provide a Microsoft Visual C friendly derivative. This code
+provides functionality for both Unicode and Multibyte builds.
+
+Date: 02/03/2011 - Ludvik Jerabek - Initial Release
+Version: 1.0
+Comment: Supports getopt, getopt_long, and getopt_long_only
+and POSIXLY_CORRECT environment flag
+License: LGPL
+
+Revisions:
+
+02/03/2011 - Ludvik Jerabek - Initial Release
+02/20/2011 - Ludvik Jerabek - Fixed compiler warnings at Level 4
+07/05/2011 - Ludvik Jerabek - Added no_argument, required_argument, optional_argument defs
+08/03/2011 - Ludvik Jerabek - Fixed non-argument runtime bug which caused runtime exception
+08/09/2011 - Ludvik Jerabek - Added code to export functions for DLL and LIB
+02/15/2012 - Ludvik Jerabek - Fixed _GETOPT_THROW definition missing in implementation file
+08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi
+10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features
+06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable
+
+**DISCLAIMER**
+THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
+EITHER EXPRESS OR IMPLIED, INCLUDING, BUT Not LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE, OR NON-INFRINGEMENT. SOME JURISDICTIONS DO NOT ALLOW THE
+EXCLUSION OF IMPLIED WARRANTIES, SO THE ABOVE EXCLUSION MAY NOT
+APPLY TO YOU. IN NO EVENT WILL I BE LIABLE TO ANY PARTY FOR ANY
+DIRECT, INDIRECT, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES FOR ANY
+USE OF THIS MATERIAL INCLUDING, WITHOUT LIMITATION, ANY LOST
+PROFITS, BUSINESS INTERRUPTION, LOSS OF PROGRAMS OR OTHER DATA ON
+YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE
+EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+*/
+#ifndef __GETOPT_H_
+	#define __GETOPT_H_
+
+	#ifdef _GETOPT_API
+		#undef _GETOPT_API
+	#endif
+
+	#if defined(EXPORTS_GETOPT) && defined(STATIC_GETOPT)
+		#error "The preprocessor definitions of EXPORTS_GETOPT and STATIC_GETOPT can only be used individually"
+	#elif defined(STATIC_GETOPT)
+		#pragma message("Warning static builds of getopt violate the Lesser GNU Public License")
+		#define _GETOPT_API
+	#elif defined(EXPORTS_GETOPT)
+		#pragma message("Exporting getopt library")
+		#define _GETOPT_API __declspec(dllexport)
+	#else
+		#pragma message("Importing getopt library")
+		#define _GETOPT_API __declspec(dllimport)
+	#endif
+
+	// Change behavior for C\C++
+	#ifdef __cplusplus
+		#define _BEGIN_EXTERN_C extern "C" {
+		#define _END_EXTERN_C }
+		#define _GETOPT_THROW throw()
+	#else
+		#define _BEGIN_EXTERN_C
+		#define _END_EXTERN_C
+		#define _GETOPT_THROW
+	#endif
+
+	// Standard GNU options
+	#define	null_argument		0	/*Argument Null*/
+	#define	no_argument			0	/*Argument Switch Only*/
+	#define required_argument	1	/*Argument Required*/
+	#define optional_argument	2	/*Argument Optional*/	
+
+	// Shorter Options
+	#define ARG_NULL	0	/*Argument Null*/
+	#define ARG_NONE	0	/*Argument Switch Only*/
+	#define ARG_REQ		1	/*Argument Required*/
+	#define ARG_OPT		2	/*Argument Optional*/
+
+	#include <string.h>
+	#include <wchar.h>
+
+_BEGIN_EXTERN_C
+
+	extern _GETOPT_API int optind;
+	extern _GETOPT_API int opterr;
+	extern _GETOPT_API int optopt;
+
+	// Ansi
+	struct option_a
+	{
+		const char* name;
+		int has_arg;
+		int *flag;
+		int val;
+	};
+	extern _GETOPT_API char *optarg_a;
+	extern _GETOPT_API int getopt_a(int argc, char *const *argv, const char *optstring) _GETOPT_THROW;
+	extern _GETOPT_API int getopt_long_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW;
+	extern _GETOPT_API int getopt_long_only_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW;
+
+	// Unicode
+	struct option_w
+	{
+		const wchar_t* name;
+		int has_arg;
+		int *flag;
+		int val;
+	};
+	extern _GETOPT_API wchar_t *optarg_w;
+	extern _GETOPT_API int getopt_w(int argc, wchar_t *const *argv, const wchar_t *optstring) _GETOPT_THROW;
+	extern _GETOPT_API int getopt_long_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW;
+	extern _GETOPT_API int getopt_long_only_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW;	
+	
+_END_EXTERN_C
+
+	#undef _BEGIN_EXTERN_C
+	#undef _END_EXTERN_C
+	#undef _GETOPT_THROW
+	#undef _GETOPT_API
+
+	#ifdef _UNICODE
+		#define getopt getopt_w
+		#define getopt_long getopt_long_w
+		#define getopt_long_only getopt_long_only_w
+		#define option option_w
+		#define optarg optarg_w
+	#else
+		#define getopt getopt_a
+		#define getopt_long getopt_long_a
+		#define getopt_long_only getopt_long_only_a
+		#define option option_a
+		#define optarg optarg_a
+	#endif
+#endif  // __GETOPT_H_

+ 95 - 25
examples/client/main.cpp

@@ -5,6 +5,7 @@
  * Copyright (c) 2020 Will Munn
  * Copyright (c) 2020 Will Munn
  * Copyright (c) 2020 Nico Chatzi
  * Copyright (c) 2020 Nico Chatzi
  * Copyright (c) 2020 Lara Mackey
  * Copyright (c) 2020 Lara Mackey
+ * Copyright (c) 2020 Erik Cota-Robles
  *
  *
  * This program is free software; you can redistribute it and/or
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
  * modify it under the terms of the GNU General Public License
@@ -29,7 +30,10 @@
 #include <memory>
 #include <memory>
 #include <random>
 #include <random>
 #include <thread>
 #include <thread>
+#include <future>
+#include <stdexcept>
 #include <unordered_map>
 #include <unordered_map>
+#include "parse_cl.h"
 
 
 using namespace rtc;
 using namespace rtc;
 using namespace std;
 using namespace std;
@@ -43,28 +47,56 @@ unordered_map<string, shared_ptr<PeerConnection>> peerConnectionMap;
 unordered_map<string, shared_ptr<DataChannel>> dataChannelMap;
 unordered_map<string, shared_ptr<DataChannel>> dataChannelMap;
 
 
 string localId;
 string localId;
+bool echoDataChannelMessages = false;
 
 
 shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
 shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
                                                 weak_ptr<WebSocket> wws, string id);
                                                 weak_ptr<WebSocket> wws, string id);
+void printReceived(bool echoed, string id, string type, size_t length);
 string randomId(size_t length);
 string randomId(size_t length);
 
 
-int main(int argc, char **argv) {
+
+int main(int argc, char **argv) try {
+	auto params = std::make_unique<Cmdline>(argc, argv);
+
 	rtc::InitLogger(LogLevel::Debug);
 	rtc::InitLogger(LogLevel::Debug);
 
 
 	Configuration config;
 	Configuration config;
-	config.iceServers.emplace_back("stun:stun.l.google.com:19302"); // change to your STUN server
+	string stunServer = "";
+	if (params->noStun()) {
+		cout << "No STUN server is configured. Only local hosts and public IP addresses supported." << endl;
+	} else {
+		if (params->stunServer().substr(0,5).compare("stun:") != 0) {
+			stunServer = "stun:";
+		}
+		stunServer += params->stunServer() + ":" + to_string(params->stunPort());
+		cout << "Stun server is " << stunServer << endl;
+		config.iceServers.emplace_back(stunServer);
+	}
 
 
 	localId = randomId(4);
 	localId = randomId(4);
 	cout << "The local ID is: " << localId << endl;
 	cout << "The local ID is: " << localId << endl;
 
 
+	echoDataChannelMessages = params->echoDataChannelMessages();
+	cout << "Received data channel messages will be "
+	     << (echoDataChannelMessages ? "echoed back to sender" : "printed to stdout") << endl;
+
 	auto ws = make_shared<WebSocket>();
 	auto ws = make_shared<WebSocket>();
 
 
-	ws->onOpen([]() { cout << "WebSocket connected, signaling ready" << endl; });
+	std::promise<void> wsPromise;
+	auto wsFuture = wsPromise.get_future();
+	
+	ws->onOpen([&wsPromise]() { 
+		cout << "WebSocket connected, signaling ready" << endl;
+		wsPromise.set_value();
+	});
 
 
+	ws->onError([&wsPromise](string s) { 
+		cout << "WebSocket error" << endl;
+		wsPromise.set_exception(std::make_exception_ptr(std::runtime_error(s)));
+	});
+	
 	ws->onClosed([]() { cout << "WebSocket closed" << endl; });
 	ws->onClosed([]() { cout << "WebSocket closed" << endl; });
-
-	ws->onError([](const string &error) { cout << "WebSocket failed: " << error << endl; });
-
+	
 	ws->onMessage([&](variant<binary, string> data) {
 	ws->onMessage([&](variant<binary, string> data) {
 		if (!holds_alternative<string>(data))
 		if (!holds_alternative<string>(data))
 			return;
 			return;
@@ -101,15 +133,17 @@ int main(int argc, char **argv) {
 		}
 		}
 	});
 	});
 
 
-	const string url = "ws://localhost:8000/" + localId;
+	string wsPrefix = "";
+	if (params->webSocketServer().substr(0,5).compare("ws://") != 0) {
+		wsPrefix = "ws://";
+	}
+	const string url = wsPrefix + params->webSocketServer() + ":" +
+		to_string(params->webSocketPort()) + "/" + localId;
+	cout << "Url is " << url << endl;
 	ws->open(url);
 	ws->open(url);
-
+	
 	cout << "Waiting for signaling to be connected..." << endl;
 	cout << "Waiting for signaling to be connected..." << endl;
-	while (!ws->isOpen()) {
-		if (ws->isClosed())
-			return 1;
-		this_thread::sleep_for(100ms);
-	}
+	wsFuture.get();
 
 
 	while (true) {
 	while (true) {
 		string id;
 		string id;
@@ -137,16 +171,23 @@ int main(int argc, char **argv) {
 
 
 		dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
 		dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
 
 
-		dc->onMessage([id](const variant<binary, string> &message) {
-			if (!holds_alternative<string>(message))
-				return;
-
-			cout << "Message from " << id << " received: " << get<string>(message) << endl;
+		dc->onMessage([id, wdc = make_weak_ptr(dc)](const variant<binary, string> &message) {
+			static bool firstMessage = true;
+			if (holds_alternative<string>(message) && (!echoDataChannelMessages || firstMessage)) {
+				cout << "Message from " << id << " received: " << get<string>(message) << endl;
+				firstMessage = false;
+			} else if (echoDataChannelMessages) {
+				bool echoed = false;
+				if (auto dc = wdc.lock()) {
+					dc->send(message);
+					echoed = true;
+				}
+				printReceived(echoed, id, (holds_alternative<string>(message) ? "text" : "binary"),
+				      get<string>(message).length());
+			}
 		});
 		});
 
 
 		dataChannelMap.emplace(id, dc);
 		dataChannelMap.emplace(id, dc);
-
-		this_thread::sleep_for(1s);
 	}
 	}
 
 
 	cout << "Cleaning up..." << endl;
 	cout << "Cleaning up..." << endl;
@@ -154,6 +195,12 @@ int main(int argc, char **argv) {
 	dataChannelMap.clear();
 	dataChannelMap.clear();
 	peerConnectionMap.clear();
 	peerConnectionMap.clear();
 	return 0;
 	return 0;
+
+} catch (const std::exception &e) {
+	std::cout << "Error: " << e.what() << std::endl;
+	dataChannelMap.clear();
+	peerConnectionMap.clear();
+	return -1;
 }
 }
 
 
 // Create and setup a PeerConnection
 // Create and setup a PeerConnection
@@ -190,11 +237,20 @@ shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
 
 
 		dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
 		dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
 
 
-		dc->onMessage([id](const variant<binary, string> &message) {
-			if (!holds_alternative<string>(message))
-				return;
-
-			cout << "Message from " << id << " received: " << get<string>(message) << endl;
+		dc->onMessage([id, wdc = make_weak_ptr(dc)](const variant<binary, string> &message) {
+			static bool firstMessage = true;
+			if (holds_alternative<string>(message) && (!echoDataChannelMessages || firstMessage)) {
+				cout << "Message from " << id << " received: " << get<string>(message) << endl;
+				firstMessage = false;
+			} else if (echoDataChannelMessages) {
+				bool echoed = false;
+				if (auto dc = wdc.lock()) {
+					dc->send(message);
+					echoed = true;
+				}
+				printReceived(echoed, id, (holds_alternative<string>(message) ? "text" : "binary"),
+						get<string>(message).length());
+			}
 		});
 		});
 
 
 		dc->send("Hello from " + localId);
 		dc->send("Hello from " + localId);
@@ -206,6 +262,20 @@ shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
 	return pc;
 	return pc;
 };
 };
 
 
+// Helper function to print received pings
+void printReceived(bool echoed, string id, string type, size_t length) {
+	static long count = 0;
+	static long freq = 100;
+	if (!(++count%freq)) {
+		cout << "Received " << count << " pings in total from " << id << ", most recent of type "
+		     << type << " and " << (echoed ? "" : "un") << "successfully echoed most recent ping of size "
+		     << length << " back to " << id << endl;
+		if (count >= (freq * 10) && freq < 1000000) {
+			freq *= 10;
+		}
+	}
+}
+
 // Helper function to generate a random ID
 // Helper function to generate a random ID
 string randomId(size_t length) {
 string randomId(size_t length) {
 	static const string characters(
 	static const string characters(

+ 182 - 0
examples/client/parse_cl.cpp

@@ -0,0 +1,182 @@
+/******************************************************************************
+**
+** parse_cl.cpp
+**
+** Thu Aug  6 19:42:25 2020
+** Linux 5.4.0-42-generic (#46-Ubuntu SMP Fri Jul 10 00:24:02 UTC 2020) x86_64
+** cerik@Erik-VBox-Ubuntu (Erik Cota-Robles)
+**
+** Copyright (c) 2020 Erik Cota-Robles
+**
+** Definition of command line parser class
+**
+** Automatically created by genparse v0.9.3
+**
+** See http://genparse.sourceforge.net for details and updates
+**
+**
+******************************************************************************/
+
+#include <stdlib.h>
+
+#if defined(_WIN32) || defined(WIN32)
+#include "getopt.h"
+#else
+#include <getopt.h>
+#endif
+
+#include "parse_cl.h"
+
+/*----------------------------------------------------------------------------
+**
+** Cmdline::Cmdline ()
+**
+** Constructor method.
+**
+**--------------------------------------------------------------------------*/
+
+Cmdline::Cmdline (int argc, char *argv[]) // ISO C++17 not allowed: throw (std::string )
+{
+  extern char *optarg;
+  extern int optind;
+  int c;
+
+  static struct option long_options[] =
+  {
+    {"echo", no_argument, NULL, 'e'},
+    {"noStun", no_argument, NULL, 'n'},
+    {"stunServer", required_argument, NULL, 's'},
+    {"stunPort", required_argument, NULL, 't'},
+    {"webSocketServer", required_argument, NULL, 'w'},
+    {"webSocketPort", required_argument, NULL, 'x'},
+    {"help", no_argument, NULL, 'h'},
+    {"version", no_argument, NULL, 'v'},
+    {NULL, 0, NULL, 0}
+  };
+
+  _program_name += argv[0];
+
+  /* default values */
+  _e = false;
+  _n = false;
+  _s = "stun.l.google.com";
+  _t = 19302;
+  _w = "localhost";
+  _x = 8000;
+  _h = false;
+  _v = false;
+
+  optind = 0;
+  while ((c = getopt_long (argc, argv, "s:t:w:x:enhv", long_options, &optind)) != - 1)
+    {
+      switch (c)
+        {
+        case 'e': 
+          _e = true;
+          break;
+
+        case 'n': 
+          _n = true;
+          break;
+
+        case 's': 
+          _s = optarg;
+          break;
+
+        case 't': 
+          _t = atoi (optarg);
+          if (_t < 0)
+            {
+              std::string err;
+              err += "parameter range error: t must be >= 0";
+              throw (std::range_error(err));
+            }
+          if (_t > 65535)
+            {
+              std::string err;
+              err += "parameter range error: t must be <= 65535";
+              throw (std::range_error(err));
+            }
+          break;
+
+        case 'w': 
+          _w = optarg;
+          break;
+
+        case 'x': 
+          _x = atoi (optarg);
+          if (_x < 0)
+            {
+              std::string err;
+              err += "parameter range error: x must be >= 0";
+              throw (std::range_error(err));
+            }
+          if (_x > 65535)
+            {
+              std::string err;
+              err += "parameter range error: x must be <= 65535";
+              throw (std::range_error(err));
+            }
+          break;
+
+        case 'h': 
+          _h = true;
+          this->usage (EXIT_SUCCESS);
+          break;
+
+        case 'v': 
+          _v = true;
+          this->version (EXIT_SUCCESS);
+          break;
+
+        default:
+          this->usage (EXIT_FAILURE);
+
+        }
+    } /* while */
+
+  _optind = optind;
+}
+
+/*----------------------------------------------------------------------------
+**
+** Cmdline::usage () and version()
+**
+** Print out usage (or version) information, then exit.
+**
+**--------------------------------------------------------------------------*/
+
+void Cmdline::usage (int status)
+{
+  if (status != EXIT_SUCCESS)
+    std::cerr << "Try `" << _program_name << " --help' for more information.\n";
+  else
+    {
+      std::cout << "\
+usage: " << _program_name << " [ -enstwxhv ] \n\
+libdatachannel client implementing WebRTC Data Channels with WebSocket signaling\n\
+   [ -e ] [ --echo ] (type=FLAG)\n\
+          Echo data channel messages back to sender rather than putting to stdout.\n\
+   [ -n ] [ --noStun ] (type=FLAG)\n\
+          Do NOT use a stun server (overrides -s and -t).\n\
+   [ -s ] [ --stunServer ] (type=STRING, default=stun.l.google.com)\n\
+          Stun server URL or IP address.\n\
+   [ -t ] [ --stunPort ] (type=INTEGER, range=0...65535, default=19302)\n\
+          Stun server port.\n\
+   [ -w ] [ --webSocketServer ] (type=STRING, default=localhost)\n\
+          Web socket server URL or IP address.\n\
+   [ -x ] [ --webSocketPort ] (type=INTEGER, range=0...65535, default=8000)\n\
+          Web socket server port.\n\
+   [ -h ] [ --help ] (type=FLAG)\n\
+          Display this help and exit.\n\
+   [ -v ] [ --version ] (type=FLAG)\n\
+          Output version information and exit.\n";
+    }
+  exit (status);
+}
+
+void Cmdline::version (int status)
+{
+  std::cout << _program_name << " v0.5\n";
+  exit (status);
+}

+ 74 - 0
examples/client/parse_cl.h

@@ -0,0 +1,74 @@
+/******************************************************************************
+**
+** parse_cl.h
+**
+** Thu Aug  6 19:42:25 2020
+** Linux 5.4.0-42-generic (#46-Ubuntu SMP Fri Jul 10 00:24:02 UTC 2020) x86_64
+** cerik@Erik-VBox-Ubuntu (Erik Cota-Robles)
+**
+** Copyright (c) 2020 Erik Cota-Robles
+**
+** Header file for command line parser class
+**
+** Automatically created by genparse v0.9.3
+**
+** See http://genparse.sourceforge.net for details and updates
+**
+******************************************************************************/
+
+#ifndef CMDLINE_H
+#define CMDLINE_H
+
+#include <iostream>
+#include <string>
+
+/*----------------------------------------------------------------------------
+**
+** class Cmdline
+**
+** command line parser class
+**
+**--------------------------------------------------------------------------*/
+
+class Cmdline
+{
+private:
+  /* parameters */
+  bool _e;
+  bool _n;
+  std::string _s;
+  int _t;
+  std::string _w;
+  int _x;
+  bool _h;
+  bool _v;
+
+  /* other stuff to keep track of */
+  std::string _program_name;
+  int _optind;
+
+public:
+  /* constructor and destructor */
+  Cmdline (int, char **); // ISO C++17 not allowed: throw (std::string);
+  ~Cmdline (){}
+
+  /* usage function */
+  void usage (int status);
+
+  /* version function */
+  void version (int status);
+
+  /* return next (non-option) parameter */
+  int next_param () { return _optind; }
+
+  bool echoDataChannelMessages () const { return _e; }
+  bool noStun () const { return _n; }
+  std::string stunServer () const { return _s; }
+  int stunPort () const { return _t; }
+  std::string webSocketServer () const { return _w; }
+  int webSocketPort () const { return _x; }
+  bool h () const { return _h; }
+  bool v () const { return _v; }
+};
+
+#endif

+ 14 - 14
examples/copy-paste-capi/answerer.c

@@ -41,13 +41,13 @@ typedef struct {
         bool connected;
         bool connected;
 } Peer;
 } Peer;
 
 
-static void dataChannelCallback(int dc, void *ptr);
-static void descriptionCallback(const char *sdp, const char *type, void *ptr);
-static void candidateCallback(const char *cand, const char *mid, void *ptr);
-static void stateChangeCallback(rtcState state, void *ptr);
-static void gatheringStateCallback(rtcGatheringState state, void *ptr);
-static void closedCallback(void *ptr);
-static void messageCallback(const char *message, int size, void *ptr);
+static void dataChannelCallback(int pc, int dc, void *ptr);
+static void descriptionCallback(int pc, const char *sdp, const char *type, void *ptr);
+static void candidateCallback(int pc, const char *cand, const char *mid, void *ptr);
+static void stateChangeCallback(int pc, rtcState state, void *ptr);
+static void gatheringStateCallback(int pc, rtcGatheringState state, void *ptr);
+static void closedCallback(int id, void *ptr);
+static void messageCallback(int id, const char *message, int size, void *ptr);
 static void deletePeer(Peer *peer);
 static void deletePeer(Peer *peer);
 
 
 char* state_print(rtcState state);
 char* state_print(rtcState state);
@@ -194,35 +194,35 @@ int main(int argc, char **argv) {
         return 0;
         return 0;
 }
 }
 
 
-static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
+static void descriptionCallback(int pc, const char *sdp, const char *type, void *ptr) {
         // Peer *peer = (Peer *)ptr;
         // Peer *peer = (Peer *)ptr;
         printf("Description %s:\n%s\n", "answerer", sdp);
         printf("Description %s:\n%s\n", "answerer", sdp);
 }
 }
 
 
-static void candidateCallback(const char *cand, const char *mid, void *ptr) {
+static void candidateCallback(int pc, const char *cand, const char *mid, void *ptr) {
         // Peer *peer = (Peer *)ptr;
         // Peer *peer = (Peer *)ptr;
         printf("Candidate %s: %s\n", "answerer", cand);
         printf("Candidate %s: %s\n", "answerer", cand);
 
 
 }
 }
 
 
-static void stateChangeCallback(rtcState state, void *ptr) {
+static void stateChangeCallback(int pc, rtcState state, void *ptr) {
         Peer *peer = (Peer *)ptr;
         Peer *peer = (Peer *)ptr;
         peer->state = state;
         peer->state = state;
         printf("State %s: %s\n", "answerer", state_print(state));
         printf("State %s: %s\n", "answerer", state_print(state));
 }
 }
 
 
-static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
+static void gatheringStateCallback(int pc, rtcGatheringState state, void *ptr) {
         Peer *peer = (Peer *)ptr;
         Peer *peer = (Peer *)ptr;
         peer->gatheringState = state;
         peer->gatheringState = state;
         printf("Gathering state %s: %s\n", "answerer", rtcGatheringState_print(state));
         printf("Gathering state %s: %s\n", "answerer", rtcGatheringState_print(state));
 }
 }
 
 
-static void closedCallback(void *ptr) {
+static void closedCallback(int id, void *ptr) {
         Peer *peer = (Peer *)ptr;
         Peer *peer = (Peer *)ptr;
         peer->connected = false;
         peer->connected = false;
 }
 }
 
 
-static void messageCallback(const char *message, int size, void *ptr) {
+static void messageCallback(int id, const char *message, int size, void *ptr) {
         if (size < 0) { // negative size indicates a null-terminated string
         if (size < 0) { // negative size indicates a null-terminated string
                 printf("Message %s: %s\n", "answerer", message);
                 printf("Message %s: %s\n", "answerer", message);
         } else {
         } else {
@@ -240,7 +240,7 @@ static void deletePeer(Peer *peer) {
         }
         }
 }
 }
 
 
-static void dataChannelCallback(int dc, void *ptr) {
+static void dataChannelCallback(int pc, int dc, void *ptr) {
         Peer *peer = (Peer *)ptr;
         Peer *peer = (Peer *)ptr;
         peer->dc = dc;
         peer->dc = dc;
         peer->connected = true;
         peer->connected = true;

+ 14 - 14
examples/copy-paste-capi/offerer.c

@@ -41,13 +41,13 @@ typedef struct {
         bool connected;
         bool connected;
 } Peer;
 } Peer;
 
 
-static void descriptionCallback(const char *sdp, const char *type, void *ptr);
-static void candidateCallback(const char *cand, const char *mid, void *ptr);
-static void stateChangeCallback(rtcState state, void *ptr);
-static void gatheringStateCallback(rtcGatheringState state, void *ptr);
-static void openCallback(void *ptr);
-static void closedCallback(void *ptr);
-static void messageCallback(const char *message, int size, void *ptr);
+static void descriptionCallback(int pc, const char *sdp, const char *type, void *ptr);
+static void candidateCallback(int pc, const char *cand, const char *mid, void *ptr);
+static void stateChangeCallback(int pc, rtcState state, void *ptr);
+static void gatheringStateCallback(int pc, rtcGatheringState state, void *ptr);
+static void openCallback(int id, void *ptr);
+static void closedCallback(int id, void *ptr);
+static void messageCallback(int id, const char *message, int size, void *ptr);
 static void deletePeer(Peer *peer);
 static void deletePeer(Peer *peer);
 
 
 char *state_print(rtcState state);
 char *state_print(rtcState state);
@@ -199,30 +199,30 @@ int main(int argc, char **argv){
         return 0;
         return 0;
 }
 }
 
 
-static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
+static void descriptionCallback(int pc, const char *sdp, const char *type, void *ptr) {
         // Peer *peer = (Peer *)ptr;
         // Peer *peer = (Peer *)ptr;
         printf("Description %s:\n%s\n", "offerer", sdp);
         printf("Description %s:\n%s\n", "offerer", sdp);
 }
 }
 
 
-static void candidateCallback(const char *cand, const char *mid, void *ptr) {
+static void candidateCallback(int pc, const char *cand, const char *mid, void *ptr) {
         // Peer *peer = (Peer *)ptr;
         // Peer *peer = (Peer *)ptr;
         printf("Candidate %s: %s\n", "offerer", cand);
         printf("Candidate %s: %s\n", "offerer", cand);
 
 
 }
 }
 
 
-static void stateChangeCallback(rtcState state, void *ptr) {
+static void stateChangeCallback(int pc, rtcState state, void *ptr) {
         Peer *peer = (Peer *)ptr;
         Peer *peer = (Peer *)ptr;
         peer->state = state;
         peer->state = state;
         printf("State %s: %s\n", "offerer", state_print(state));
         printf("State %s: %s\n", "offerer", state_print(state));
 }
 }
 
 
-static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
+static void gatheringStateCallback(int pc, rtcGatheringState state, void *ptr) {
         Peer *peer = (Peer *)ptr;
         Peer *peer = (Peer *)ptr;
         peer->gatheringState = state;
         peer->gatheringState = state;
         printf("Gathering state %s: %s\n", "offerer", rtcGatheringState_print(state));
         printf("Gathering state %s: %s\n", "offerer", rtcGatheringState_print(state));
 }
 }
 
 
-static void openCallback(void *ptr) {
+static void openCallback(int id, void *ptr) {
         Peer *peer = (Peer *)ptr;
         Peer *peer = (Peer *)ptr;
         peer->connected = true;
         peer->connected = true;
         char buffer[256];
         char buffer[256];
@@ -230,12 +230,12 @@ static void openCallback(void *ptr) {
                 printf("DataChannel %s: Received with label \"%s\"\n","offerer", buffer);
                 printf("DataChannel %s: Received with label \"%s\"\n","offerer", buffer);
 }
 }
 
 
-static void closedCallback(void *ptr) {
+static void closedCallback(int id, void *ptr) {
         Peer *peer = (Peer *)ptr;
         Peer *peer = (Peer *)ptr;
         peer->connected = false;
         peer->connected = false;
 }
 }
 
 
-static void messageCallback(const char *message, int size, void *ptr) {
+static void messageCallback(int id, const char *message, int size, void *ptr) {
         // Peer *peer = (Peer *)ptr;
         // Peer *peer = (Peer *)ptr;
         if (size < 0) { // negative size indicates a null-terminated string
         if (size < 0) { // negative size indicates a null-terminated string
                 printf("Message %s: %s\n", "offerer", message);
                 printf("Message %s: %s\n", "offerer", message);

+ 3 - 5
examples/copy-paste/answerer.cpp

@@ -127,13 +127,11 @@ int main(int argc, char **argv) {
 				cout << "** Channel is not Open ** ";
 				cout << "** Channel is not Open ** ";
 				break;
 				break;
 			}
 			}
-			CandidateInfo local, remote;
+			Candidate local, remote;
 			std::optional<std::chrono::milliseconds> rtt = pc->rtt();
 			std::optional<std::chrono::milliseconds> rtt = pc->rtt();
 			if (pc->getSelectedCandidatePair(&local, &remote)) {
 			if (pc->getSelectedCandidatePair(&local, &remote)) {
-				cout << "Local: " << local.address << ":" << local.port << " " << local.type << " "
-				     << local.transportType << endl;
-				cout << "Remote: " << remote.address << ":" << remote.port << " " << remote.type
-				     << " " << remote.transportType << endl;
+				cout << "Local: " << local << endl;
+				cout << "Remote: " << remote << endl;
 				cout << "Bytes Sent:" << pc->bytesSent()
 				cout << "Bytes Sent:" << pc->bytesSent()
 				     << " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
 				     << " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
 				if (rtt.has_value())
 				if (rtt.has_value())

+ 3 - 5
examples/copy-paste/offerer.cpp

@@ -127,13 +127,11 @@ int main(int argc, char **argv) {
 				cout << "** Channel is not Open ** ";
 				cout << "** Channel is not Open ** ";
 				break;
 				break;
 			}
 			}
-			CandidateInfo local, remote;
+			Candidate local, remote;
 			std::optional<std::chrono::milliseconds> rtt = pc->rtt();
 			std::optional<std::chrono::milliseconds> rtt = pc->rtt();
 			if (pc->getSelectedCandidatePair(&local, &remote)) {
 			if (pc->getSelectedCandidatePair(&local, &remote)) {
-				cout << "Local: " << local.address << ":" << local.port << " " << local.type << " "
-				     << local.transportType << endl;
-				cout << "Remote: " << remote.address << ":" << remote.port << " " << remote.type
-				     << " " << remote.transportType << endl;
+				cout << "Local: " << local << endl;
+				cout << "Remote: " << remote << endl;
 				cout << "Bytes Sent:" << pc->bytesSent()
 				cout << "Bytes Sent:" << pc->bytesSent()
 				     << " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
 				     << " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
 				if (rtt.has_value())
 				if (rtt.has_value())

+ 1 - 1
examples/media/main.cpp

@@ -73,7 +73,7 @@ int main() {
 		track->onMessage(
 		track->onMessage(
 		    [session, sock, addr](rtc::binary message) {
 		    [session, sock, addr](rtc::binary message) {
 			    // This is an RTP packet
 			    // This is an RTP packet
-			    sendto(sock, reinterpret_cast<const char *>(message.data()), message.size(), 0,
+			    sendto(sock, reinterpret_cast<const char *>(message.data()), int(message.size()), 0,
 			           reinterpret_cast<const struct sockaddr *>(&addr), sizeof(addr));
 			           reinterpret_cast<const struct sockaddr *>(&addr), sizeof(addr));
 		    },
 		    },
 		    nullptr);
 		    nullptr);

+ 7 - 3
examples/signaling-server-python/signaling-server.py

@@ -63,16 +63,20 @@ async def handle_websocket(websocket, path):
             print('Client {} disconnected'.format(client_id))
             print('Client {} disconnected'.format(client_id))
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
-    port = int(sys.argv[1]) if len(sys.argv) > 1 else 8000
+    # Usage: ./server.py [[host:]port] [SSL certificate file]
+    endpoint_or_port = sys.argv[1] if len(sys.argv) > 1 else "8000"
     ssl_cert = sys.argv[2] if len(sys.argv) > 2 else None
     ssl_cert = sys.argv[2] if len(sys.argv) > 2 else None
 
 
+    endpoint = endpoint_or_port if ':' in endpoint_or_port else "127.0.0.1:" + endpoint_or_port
+
     if ssl_cert:
     if ssl_cert:
         ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
         ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
         ssl_context.load_cert_chain(ssl_cert)
         ssl_context.load_cert_chain(ssl_cert)
     else:
     else:
         ssl_context = None
         ssl_context = None
 
 
-    print('Listening on port {}'.format(port))
-    start_server = websockets.serve(handle_websocket, '127.0.0.1', port, ssl=ssl_context)
+    print('Listening on {}'.format(endpoint))
+    host, port = endpoint.rsplit(':', 1)
+    start_server = websockets.serve(handle_websocket, host, int(port), ssl=ssl_context)
     asyncio.get_event_loop().run_until_complete(start_server)
     asyncio.get_event_loop().run_until_complete(start_server)
     asyncio.get_event_loop().run_forever()
     asyncio.get_event_loop().run_forever()

+ 1 - 1
examples/signaling-server-rust/Cargo.lock

@@ -348,7 +348,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701"
 checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701"
 
 
 [[package]]
 [[package]]
-name = "libdatachannel_signaling_example"
+name = "libdatachannel_signaling_server_example"
 version = "0.1.0"
 version = "0.1.0"
 dependencies = [
 dependencies = [
  "futures-channel",
  "futures-channel",

+ 3 - 1
examples/signaling-server-rust/src/main.rs

@@ -92,7 +92,9 @@ async fn handle(clients: ClientsMap, stream: TcpStream) {
 #[tokio::main]
 #[tokio::main]
 async fn main() -> Result<(), std::io::Error> {
 async fn main() -> Result<(), std::io::Error> {
     let service = env::args().nth(1).unwrap_or("8000".to_string());
     let service = env::args().nth(1).unwrap_or("8000".to_string());
-    let endpoint = format!("127.0.0.1:{}", service);
+    let endpoint = if service.contains(':') { service } else { format!("127.0.0.1:{}", service) };
+
+	println!("Listening on {}", endpoint);
 
 
     let mut listener = TcpListener::bind(endpoint)
     let mut listener = TcpListener::bind(endpoint)
     	.await.expect("Listener binding failed");
     	.await.expect("Listener binding failed");

+ 4 - 2
examples/web/server.js

@@ -98,8 +98,10 @@ wsServer.on('request', (req) => {
   clients[id] = conn;
   clients[id] = conn;
 });
 });
 
 
-const hostname = '127.0.0.1';
-const port = 8000;
+const endpoint = process.env.PORT || '8000';
+const splitted = endpoint.split(':');
+const port = splitted.pop();
+const hostname = splitted.join(':') || '127.0.0.1';
 
 
 httpServer.listen(port, hostname, () => {
 httpServer.listen(port, hostname, () => {
   console.log(`Server listening on ${hostname}:${port}`);
   console.log(`Server listening on ${hostname}:${port}`);

+ 23 - 14
include/rtc/candidate.hpp

@@ -25,38 +25,47 @@
 
 
 namespace rtc {
 namespace rtc {
 
 
-enum class CandidateType { Host = 0, ServerReflexive, PeerReflexive, Relayed };
-enum class CandidateTransportType { Udp = 0, TcpActive, TcpPassive, TcpSo };
-struct CandidateInfo {
-	string address;
-	int port;
-	CandidateType type;
-	CandidateTransportType transportType;
-};
-
 class Candidate {
 class Candidate {
 public:
 public:
-	Candidate(string candidate, string mid = "");
+	enum class Family { Unresolved, Ipv4, Ipv6 };
+	enum class Type { Unknown, Host, ServerReflexive, PeerReflexive, Relayed };
+	enum class TransportType { Unknown, Udp, TcpActive, TcpPassive, TcpSo, TcpUnknown };
+
+	Candidate(string candidate = "", string mid = "");
 
 
 	enum class ResolveMode { Simple, Lookup };
 	enum class ResolveMode { Simple, Lookup };
 	bool resolve(ResolveMode mode = ResolveMode::Simple);
 	bool resolve(ResolveMode mode = ResolveMode::Simple);
-	bool isResolved() const;
 
 
 	string candidate() const;
 	string candidate() const;
 	string mid() const;
 	string mid() const;
 	operator string() const;
 	operator string() const;
 
 
+	bool isResolved() const;
+	Family family() const;
+	Type type() const;
+	TransportType transportType() const;
+	std::optional<string> address() const;
+	std::optional<uint16_t> port() const;
+	std::optional<uint32_t> priority() const;
+
 private:
 private:
 	string mCandidate;
 	string mCandidate;
 	string mMid;
 	string mMid;
-	bool mIsResolved;
+
+	// Extracted on resolution
+	Family mFamily;
+	Type mType;
+	TransportType mTransportType;
+	string mAddress;
+	uint16_t mPort;
+	uint32_t mPriority;
 };
 };
 
 
 } // namespace rtc
 } // namespace rtc
 
 
 std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate);
 std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate);
-std::ostream &operator<<(std::ostream &out, const rtc::CandidateType &type);
-std::ostream &operator<<(std::ostream &out, const rtc::CandidateTransportType &transportType);
+std::ostream &operator<<(std::ostream &out, const rtc::Candidate::Type &type);
+std::ostream &operator<<(std::ostream &out, const rtc::Candidate::TransportType &transportType);
 
 
 #endif
 #endif
 
 

+ 16 - 10
include/rtc/description.hpp

@@ -34,8 +34,8 @@ namespace rtc {
 
 
 class Description {
 class Description {
 public:
 public:
-	enum class Type { Unspec = 0, Offer = 1, Answer = 2 };
-	enum class Role { ActPass = 0, Passive = 1, Active = 2 };
+	enum class Type { Unspec, Offer, Answer, Pranswer, Rollback };
+	enum class Role { ActPass, Passive, Active };
 	enum class Direction { SendOnly, RecvOnly, SendRecv, Inactive, Unknown };
 	enum class Direction { SendOnly, RecvOnly, SendRecv, Inactive, Unknown };
 
 
 	Description(const string &sdp, const string &typeString = "");
 	Description(const string &sdp, const string &typeString = "");
@@ -45,8 +45,9 @@ public:
 	Type type() const;
 	Type type() const;
 	string typeString() const;
 	string typeString() const;
 	Role role() const;
 	Role role() const;
-	string roleString() const;
 	string bundleMid() const;
 	string bundleMid() const;
+	std::optional<string> iceUfrag() const;
+	std::optional<string> icePwd() const;
 	std::optional<string> fingerprint() const;
 	std::optional<string> fingerprint() const;
 	bool ended() const;
 	bool ended() const;
 
 
@@ -54,6 +55,7 @@ public:
 	void setFingerprint(string fingerprint);
 	void setFingerprint(string fingerprint);
 
 
 	void addCandidate(Candidate candidate);
 	void addCandidate(Candidate candidate);
+	void addCandidates(std::vector<Candidate> candidates);
 	void endCandidates();
 	void endCandidates();
 	std::vector<Candidate> extractCandidates();
 	std::vector<Candidate> extractCandidates();
 
 
@@ -72,7 +74,7 @@ public:
 		void setDirection(Direction dir);
 		void setDirection(Direction dir);
 
 
 		operator string() const;
 		operator string() const;
-		string generateSdp(string_view eol) const;
+		string generateSdp(string_view eol, string_view addr, string_view port) const;
 
 
 		virtual void parseSdpLine(string_view line);
 		virtual void parseSdpLine(string_view line);
 
 
@@ -196,6 +198,7 @@ public:
 
 
 	bool hasApplication() const;
 	bool hasApplication() const;
 	bool hasAudioOrVideo() const;
 	bool hasAudioOrVideo() const;
+	bool hasMid(string_view mid) const;
 
 
 	int addMedia(Media media);
 	int addMedia(Media media);
 	int addMedia(Application application);
 	int addMedia(Application application);
@@ -205,11 +208,15 @@ public:
 
 
 	std::variant<Media *, Application *> media(int index);
 	std::variant<Media *, Application *> media(int index);
 	std::variant<const Media *, const Application *> media(int index) const;
 	std::variant<const Media *, const Application *> media(int index) const;
-	unsigned int mediaCount() const;
+	size_t mediaCount() const;
 
 
 	Application *application();
 	Application *application();
 
 
+	static Type stringToType(const string &typeString);
+	static string typeToString(Type type);
+
 private:
 private:
+	std::optional<Candidate> defaultCandidate() const;
 	std::shared_ptr<Entry> createEntry(string mline, string mid, Direction dir);
 	std::shared_ptr<Entry> createEntry(string mline, string mid, Direction dir);
 	void removeApplication();
 	void removeApplication();
 
 
@@ -217,8 +224,9 @@ private:
 
 
 	// Session-level attributes
 	// Session-level attributes
 	Role mRole;
 	Role mRole;
+	string mUsername;
 	string mSessionId;
 	string mSessionId;
-	string mIceUfrag, mIcePwd;
+	std::optional<string> mIceUfrag, mIcePwd;
 	std::optional<string> mFingerprint;
 	std::optional<string> mFingerprint;
 
 
 	// Entries
 	// Entries
@@ -228,14 +236,12 @@ private:
 	// Candidates
 	// Candidates
 	std::vector<Candidate> mCandidates;
 	std::vector<Candidate> mCandidates;
 	bool mEnded = false;
 	bool mEnded = false;
-
-	static Type stringToType(const string &typeString);
-	static string typeToString(Type type);
-	static string roleToString(Role role);
 };
 };
 
 
 } // namespace rtc
 } // namespace rtc
 
 
 std::ostream &operator<<(std::ostream &out, const rtc::Description &description);
 std::ostream &operator<<(std::ostream &out, const rtc::Description &description);
+std::ostream &operator<<(std::ostream &out, rtc::Description::Type type);
+std::ostream &operator<<(std::ostream &out, rtc::Description::Role role);
 
 
 #endif
 #endif

+ 21 - 4
include/rtc/include.hpp

@@ -29,7 +29,7 @@
 
 
 #ifdef _WIN32
 #ifdef _WIN32
 #ifndef _WIN32_WINNT
 #ifndef _WIN32_WINNT
-#define _WIN32_WINNT 0x0602
+#define _WIN32_WINNT 0x0602 // Windows 8
 #endif
 #endif
 #endif
 #endif
 
 
@@ -62,7 +62,7 @@ using std::uint8_t;
 const size_t MAX_NUMERICNODE_LEN = 48; // Max IPv6 string representation length
 const size_t MAX_NUMERICNODE_LEN = 48; // Max IPv6 string representation length
 const size_t MAX_NUMERICSERV_LEN = 6;  // Max port string representation length
 const size_t MAX_NUMERICSERV_LEN = 6;  // Max port string representation length
 
 
-const uint16_t DEFAULT_SCTP_PORT = 5000; // SCTP port to use by default
+const uint16_t DEFAULT_SCTP_PORT = 5000;          // SCTP port to use by default
 const size_t DEFAULT_MAX_MESSAGE_SIZE = 65536;    // Remote max message size if not specified in SDP
 const size_t DEFAULT_MAX_MESSAGE_SIZE = 65536;    // Remote max message size if not specified in SDP
 const size_t LOCAL_MAX_MESSAGE_SIZE = 256 * 1024; // Local max message size
 const size_t LOCAL_MAX_MESSAGE_SIZE = 256 * 1024; // Local max message size
 
 
@@ -72,7 +72,7 @@ const int THREADPOOL_SIZE = 4; // Number of threads in the global thread pool
 
 
 // overloaded helper
 // overloaded helper
 template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
 template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
-template <class... Ts> overloaded(Ts...)->overloaded<Ts...>;
+template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
 
 
 // weak_ptr bind helper
 // weak_ptr bind helper
 template <typename F, typename T, typename... Args> auto weak_bind(F &&f, T *t, Args &&... _args) {
 template <typename F, typename T, typename... Args> auto weak_bind(F &&f, T *t, Args &&... _args) {
@@ -85,6 +85,23 @@ template <typename F, typename T, typename... Args> auto weak_bind(F &&f, T *t,
 	};
 	};
 }
 }
 
 
+// scope_guard helper
+class scope_guard {
+public:
+	scope_guard(std::function<void()> func) : function(std::move(func)) {}
+	scope_guard(scope_guard &&other) = delete;
+	scope_guard(const scope_guard &) = delete;
+	void operator=(const scope_guard &) = delete;
+
+	~scope_guard() {
+		if (function)
+			function();
+	}
+
+private:
+	std::function<void()> function;
+};
+
 template <typename... P> class synchronized_callback {
 template <typename... P> class synchronized_callback {
 public:
 public:
 	synchronized_callback() = default;
 	synchronized_callback() = default;
@@ -127,6 +144,6 @@ private:
 	std::function<void(P...)> callback;
 	std::function<void(P...)> callback;
 	mutable std::recursive_mutex mutex;
 	mutable std::recursive_mutex mutex;
 };
 };
-}
+} // namespace rtc
 
 
 #endif
 #endif

+ 29 - 8
include/rtc/peerconnection.hpp

@@ -67,6 +67,14 @@ public:
 		Complete = RTC_GATHERING_COMPLETE
 		Complete = RTC_GATHERING_COMPLETE
 	};
 	};
 
 
+	enum class SignalingState : int {
+		Stable = RTC_SIGNALING_STABLE,
+		HaveLocalOffer = RTC_SIGNALING_HAVE_LOCAL_OFFER,
+		HaveRemoteOffer = RTC_SIGNALING_HAVE_REMOTE_OFFER,
+		HaveLocalPranswer = RTC_SIGNALING_HAVE_LOCAL_PRANSWER,
+		HaveRemotePranswer = RTC_SIGNALING_HAVE_REMOTE_PRANSWER,
+	} rtcSignalingState;
+
 	PeerConnection(void);
 	PeerConnection(void);
 	PeerConnection(const Configuration &config);
 	PeerConnection(const Configuration &config);
 	~PeerConnection();
 	~PeerConnection();
@@ -76,14 +84,18 @@ public:
 	const Configuration *config() const;
 	const Configuration *config() const;
 	State state() const;
 	State state() const;
 	GatheringState gatheringState() const;
 	GatheringState gatheringState() const;
+	SignalingState signalingState() const;
+	bool hasLocalDescription() const;
+	bool hasRemoteDescription() const;
+	bool hasMedia() const;
 	std::optional<Description> localDescription() const;
 	std::optional<Description> localDescription() const;
 	std::optional<Description> remoteDescription() const;
 	std::optional<Description> remoteDescription() const;
 	std::optional<string> localAddress() const;
 	std::optional<string> localAddress() const;
 	std::optional<string> remoteAddress() const;
 	std::optional<string> remoteAddress() const;
-	bool hasMedia() const;
+	bool getSelectedCandidatePair(Candidate *local, Candidate *remote);
+
+	void setLocalDescription(Description::Type type = Description::Type::Unspec);
 
 
-	void setLocalDescription();
-    void processLocalDescription(Description description);
 	void setRemoteDescription(Description description);
 	void setRemoteDescription(Description description);
 	void addRemoteCandidate(Candidate candidate);
 	void addRemoteCandidate(Candidate candidate);
 
 
@@ -99,6 +111,7 @@ public:
 	void onLocalCandidate(std::function<void(Candidate candidate)> callback);
 	void onLocalCandidate(std::function<void(Candidate candidate)> callback);
 	void onStateChange(std::function<void(State state)> callback);
 	void onStateChange(std::function<void(State state)> callback);
 	void onGatheringStateChange(std::function<void(GatheringState state)> callback);
 	void onGatheringStateChange(std::function<void(GatheringState state)> callback);
+	void onSignalingStateChange(std::function<void(SignalingState state)> callback);
 
 
 	// Stats
 	// Stats
 	void clearStats();
 	void clearStats();
@@ -110,9 +123,6 @@ public:
 	std::shared_ptr<Track> addTrack(Description::Media description);
 	std::shared_ptr<Track> addTrack(Description::Media description);
 	void onTrack(std::function<void(std::shared_ptr<Track> track)> callback);
 	void onTrack(std::function<void(std::shared_ptr<Track> track)> callback);
 
 
-	// libnice only
-	bool getSelectedCandidatePair(CandidateInfo *local, CandidateInfo *remote);
-
 private:
 private:
 	std::shared_ptr<IceTransport> initIceTransport(Description::Role role);
 	std::shared_ptr<IceTransport> initIceTransport(Description::Role role);
 	std::shared_ptr<DtlsTransport> initDtlsTransport();
 	std::shared_ptr<DtlsTransport> initDtlsTransport();
@@ -137,11 +147,17 @@ private:
 	void incomingTrack(Description::Media description);
 	void incomingTrack(Description::Media description);
 	void openTracks();
 	void openTracks();
 
 
+
+	void validateRemoteDescription(const Description &description);
+	void processLocalDescription(Description description);
 	void processLocalCandidate(Candidate candidate);
 	void processLocalCandidate(Candidate candidate);
+	void processRemoteDescription(Description description);
+	void processRemoteCandidate(Candidate candidate);
 	void triggerDataChannel(std::weak_ptr<DataChannel> weakDataChannel);
 	void triggerDataChannel(std::weak_ptr<DataChannel> weakDataChannel);
 	void triggerTrack(std::shared_ptr<Track> track);
 	void triggerTrack(std::shared_ptr<Track> track);
 	bool changeState(State state);
 	bool changeState(State state);
 	bool changeGatheringState(GatheringState state);
 	bool changeGatheringState(GatheringState state);
+	bool changeSignalingState(SignalingState state);
 
 
 	void resetCallbacks();
 	void resetCallbacks();
 
 
@@ -153,6 +169,7 @@ private:
 	const std::unique_ptr<Processor> mProcessor;
 	const std::unique_ptr<Processor> mProcessor;
 
 
 	std::optional<Description> mLocalDescription, mRemoteDescription;
 	std::optional<Description> mLocalDescription, mRemoteDescription;
+	std::optional<Description> mCurrentLocalDescription;
 	mutable std::mutex mLocalDescriptionMutex, mRemoteDescriptionMutex;
 	mutable std::mutex mLocalDescriptionMutex, mRemoteDescriptionMutex;
 
 
 	std::shared_ptr<IceTransport> mIceTransport;
 	std::shared_ptr<IceTransport> mIceTransport;
@@ -168,18 +185,22 @@ private:
 
 
 	std::atomic<State> mState;
 	std::atomic<State> mState;
 	std::atomic<GatheringState> mGatheringState;
 	std::atomic<GatheringState> mGatheringState;
+	std::atomic<SignalingState> mSignalingState;
+	std::atomic<bool> mNegotiationNeeded;
 
 
 	synchronized_callback<std::shared_ptr<DataChannel>> mDataChannelCallback;
 	synchronized_callback<std::shared_ptr<DataChannel>> mDataChannelCallback;
 	synchronized_callback<Description> mLocalDescriptionCallback;
 	synchronized_callback<Description> mLocalDescriptionCallback;
 	synchronized_callback<Candidate> mLocalCandidateCallback;
 	synchronized_callback<Candidate> mLocalCandidateCallback;
 	synchronized_callback<State> mStateChangeCallback;
 	synchronized_callback<State> mStateChangeCallback;
 	synchronized_callback<GatheringState> mGatheringStateChangeCallback;
 	synchronized_callback<GatheringState> mGatheringStateChangeCallback;
+	synchronized_callback<SignalingState> mSignalingStateChangeCallback;
 	synchronized_callback<std::shared_ptr<Track>> mTrackCallback;
 	synchronized_callback<std::shared_ptr<Track>> mTrackCallback;
 };
 };
 
 
 } // namespace rtc
 } // namespace rtc
 
 
-std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::State &state);
-std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::GatheringState &state);
+std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::State state);
+std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::GatheringState state);
+std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::SignalingState state);
 
 
 #endif
 #endif

+ 43 - 24
include/rtc/queue.hpp

@@ -38,17 +38,22 @@ public:
 	~Queue();
 	~Queue();
 
 
 	void stop();
 	void stop();
+	bool running() const;
 	bool empty() const;
 	bool empty() const;
 	bool full() const;
 	bool full() const;
 	size_t size() const;   // elements
 	size_t size() const;   // elements
 	size_t amount() const; // amount
 	size_t amount() const; // amount
 	void push(T element);
 	void push(T element);
 	std::optional<T> pop();
 	std::optional<T> pop();
+	std::optional<T> tryPop();
 	std::optional<T> peek();
 	std::optional<T> peek();
 	std::optional<T> exchange(T element);
 	std::optional<T> exchange(T element);
 	bool wait(const std::optional<std::chrono::milliseconds> &duration = nullopt);
 	bool wait(const std::optional<std::chrono::milliseconds> &duration = nullopt);
 
 
 private:
 private:
+	void pushImpl(T element);
+	std::optional<T> popImpl();
+
 	const size_t mLimit;
 	const size_t mLimit;
 	size_t mAmount;
 	size_t mAmount;
 	std::queue<T> mQueue;
 	std::queue<T> mQueue;
@@ -76,6 +81,11 @@ template <typename T> void Queue<T>::stop() {
 	mPushCondition.notify_all();
 	mPushCondition.notify_all();
 }
 }
 
 
+template <typename T> bool Queue<T>::running() const {
+	std::lock_guard lock(mMutex);
+	return !mQueue.empty() || !mStopping;
+}
+
 template <typename T> bool Queue<T>::empty() const {
 template <typename T> bool Queue<T>::empty() const {
 	std::lock_guard lock(mMutex);
 	std::lock_guard lock(mMutex);
 	return mQueue.empty();
 	return mQueue.empty();
@@ -99,43 +109,32 @@ template <typename T> size_t Queue<T>::amount() const {
 template <typename T> void Queue<T>::push(T element) {
 template <typename T> void Queue<T>::push(T element) {
 	std::unique_lock lock(mMutex);
 	std::unique_lock lock(mMutex);
 	mPushCondition.wait(lock, [this]() { return !mLimit || mQueue.size() < mLimit || mStopping; });
 	mPushCondition.wait(lock, [this]() { return !mLimit || mQueue.size() < mLimit || mStopping; });
-	if (!mStopping) {
-		mAmount += mAmountFunction(element);
-		mQueue.emplace(std::move(element));
-		mPopCondition.notify_one();
-	}
+	pushImpl(std::move(element));
 }
 }
 
 
 template <typename T> std::optional<T> Queue<T>::pop() {
 template <typename T> std::optional<T> Queue<T>::pop() {
 	std::unique_lock lock(mMutex);
 	std::unique_lock lock(mMutex);
 	mPopCondition.wait(lock, [this]() { return !mQueue.empty() || mStopping; });
 	mPopCondition.wait(lock, [this]() { return !mQueue.empty() || mStopping; });
-	if (!mQueue.empty()) {
-		mAmount -= mAmountFunction(mQueue.front());
-		std::optional<T> element{std::move(mQueue.front())};
-		mQueue.pop();
-		return element;
-	} else {
-		return nullopt;
-	}
+	return popImpl();
+}
+
+template <typename T> std::optional<T> Queue<T>::tryPop() {
+	std::unique_lock lock(mMutex);
+	return popImpl();
 }
 }
 
 
 template <typename T> std::optional<T> Queue<T>::peek() {
 template <typename T> std::optional<T> Queue<T>::peek() {
 	std::unique_lock lock(mMutex);
 	std::unique_lock lock(mMutex);
-	if (!mQueue.empty()) {
-		return std::optional<T>{mQueue.front()};
-	} else {
-		return nullopt;
-	}
+	return !mQueue.empty() ? std::make_optional(mQueue.front()) : nullopt;
 }
 }
 
 
 template <typename T> std::optional<T> Queue<T>::exchange(T element) {
 template <typename T> std::optional<T> Queue<T>::exchange(T element) {
 	std::unique_lock lock(mMutex);
 	std::unique_lock lock(mMutex);
-	if (!mQueue.empty()) {
-		std::swap(mQueue.front(), element);
-		return std::optional<T>{element};
-	} else {
+	if (mQueue.empty())
 		return nullopt;
 		return nullopt;
-	}
+
+	std::swap(mQueue.front(), element);
+	return std::make_optional(std::move(element));
 }
 }
 
 
 template <typename T>
 template <typename T>
@@ -145,7 +144,27 @@ bool Queue<T>::wait(const std::optional<std::chrono::milliseconds> &duration) {
 		mPopCondition.wait_for(lock, *duration, [this]() { return !mQueue.empty() || mStopping; });
 		mPopCondition.wait_for(lock, *duration, [this]() { return !mQueue.empty() || mStopping; });
 	else
 	else
 		mPopCondition.wait(lock, [this]() { return !mQueue.empty() || mStopping; });
 		mPopCondition.wait(lock, [this]() { return !mQueue.empty() || mStopping; });
-	return !mStopping;
+
+	return !mQueue.empty();
+}
+
+template <typename T> void Queue<T>::pushImpl(T element) {
+	if (mStopping)
+		return;
+
+	mAmount += mAmountFunction(element);
+	mQueue.emplace(std::move(element));
+	mPopCondition.notify_one();
+}
+
+template <typename T> std::optional<T> Queue<T>::popImpl() {
+	if (mQueue.empty())
+		return nullopt;
+
+	mAmount -= mAmountFunction(mQueue.front());
+	std::optional<T> element{std::move(mQueue.front())};
+	mQueue.pop();
+	return element;
 }
 }
 
 
 } // namespace rtc
 } // namespace rtc

+ 37 - 15
include/rtc/rtc.h

@@ -29,6 +29,12 @@ extern "C" {
 #define RTC_EXPORT
 #define RTC_EXPORT
 #endif
 #endif
 
 
+#ifdef CAPI_STDCALL
+#define RTC_API __stdcall
+#else
+#define RTC_API
+#endif
+
 #ifndef RTC_ENABLE_WEBSOCKET
 #ifndef RTC_ENABLE_WEBSOCKET
 #define RTC_ENABLE_WEBSOCKET 1
 #define RTC_ENABLE_WEBSOCKET 1
 #endif
 #endif
@@ -53,6 +59,14 @@ typedef enum {
 	RTC_GATHERING_COMPLETE = 2
 	RTC_GATHERING_COMPLETE = 2
 } rtcGatheringState;
 } rtcGatheringState;
 
 
+typedef enum {
+	RTC_SIGNALING_STABLE = 0,
+	RTC_SIGNALING_HAVE_LOCAL_OFFER = 1,
+	RTC_SIGNALING_HAVE_REMOTE_OFFER = 2,
+	RTC_SIGNALING_HAVE_LOCAL_PRANSWER = 3,
+	RTC_SIGNALING_HAVE_REMOTE_PRANSWER = 4,
+} rtcSignalingState;
+
 typedef enum { // Don't change, it must match plog severity
 typedef enum { // Don't change, it must match plog severity
 	RTC_LOG_NONE = 0,
 	RTC_LOG_NONE = 0,
 	RTC_LOG_FATAL = 1,
 	RTC_LOG_FATAL = 1,
@@ -81,22 +95,24 @@ typedef struct {
 	unsigned int maxRetransmits;    // ignored if reliable
 	unsigned int maxRetransmits;    // ignored if reliable
 } rtcReliability;
 } rtcReliability;
 
 
-typedef void (*rtcLogCallbackFunc)(rtcLogLevel level, const char *message);
-typedef void (*rtcDescriptionCallbackFunc)(const char *sdp, const char *type, void *ptr);
-typedef void (*rtcCandidateCallbackFunc)(const char *cand, const char *mid, void *ptr);
-typedef void (*rtcStateChangeCallbackFunc)(rtcState state, void *ptr);
-typedef void (*rtcGatheringStateCallbackFunc)(rtcGatheringState state, void *ptr);
-typedef void (*rtcDataChannelCallbackFunc)(int dc, void *ptr);
-typedef void (*rtcTrackCallbackFunc)(int tr, void *ptr);
-typedef void (*rtcOpenCallbackFunc)(void *ptr);
-typedef void (*rtcClosedCallbackFunc)(void *ptr);
-typedef void (*rtcErrorCallbackFunc)(const char *error, void *ptr);
-typedef void (*rtcMessageCallbackFunc)(const char *message, int size, void *ptr);
-typedef void (*rtcBufferedAmountLowCallbackFunc)(void *ptr);
-typedef void (*rtcAvailableCallbackFunc)(void *ptr);
+typedef void (RTC_API *rtcLogCallbackFunc)(rtcLogLevel level, const char *message);
+typedef void (RTC_API *rtcDescriptionCallbackFunc)(int pc, const char *sdp, const char *type, void *ptr);
+typedef void (RTC_API *rtcCandidateCallbackFunc)(int pc, const char *cand, const char *mid, void *ptr);
+typedef void (RTC_API *rtcStateChangeCallbackFunc)(int pc, rtcState state, void *ptr);
+typedef void (RTC_API *rtcGatheringStateCallbackFunc)(int pc, rtcGatheringState state, void *ptr);
+typedef void (RTC_API *rtcSignalingStateCallbackFunc)(int pc, rtcSignalingState state, void *ptr);
+typedef void (RTC_API *rtcDataChannelCallbackFunc)(int pc, int dc, void *ptr);
+typedef void (RTC_API *rtcTrackCallbackFunc)(int pc, int tr, void *ptr);
+typedef void (RTC_API *rtcOpenCallbackFunc)(int id, void *ptr);
+typedef void (RTC_API *rtcClosedCallbackFunc)(int id, void *ptr);
+typedef void (RTC_API *rtcErrorCallbackFunc)(int id, const char *error, void *ptr);
+typedef void (RTC_API *rtcMessageCallbackFunc)(int id, const char *message, int size, void *ptr);
+typedef void (RTC_API *rtcBufferedAmountLowCallbackFunc)(int id, void *ptr);
+typedef void (RTC_API *rtcAvailableCallbackFunc)(int id, void *ptr);
 
 
 // Log
 // Log
-RTC_EXPORT void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb); // NULL cb to log to stdout
+// NULL cb on the first call will log to stdout
+RTC_EXPORT void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb);
 
 
 // User pointer
 // User pointer
 RTC_EXPORT void rtcSetUserPointer(int id, void *ptr);
 RTC_EXPORT void rtcSetUserPointer(int id, void *ptr);
@@ -109,14 +125,20 @@ RTC_EXPORT int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc
 RTC_EXPORT int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb);
 RTC_EXPORT int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb);
 RTC_EXPORT int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb);
 RTC_EXPORT int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb);
 RTC_EXPORT int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb);
 RTC_EXPORT int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb);
+RTC_EXPORT int rtcSetSignalingStateChangeCallback(int pc, rtcSignalingStateCallbackFunc cb);
 
 
-RTC_EXPORT int rtcSetLocalDescription(int pc);
+RTC_EXPORT int rtcSetLocalDescription(int pc, const char *type);
 RTC_EXPORT int rtcSetRemoteDescription(int pc, const char *sdp, const char *type);
 RTC_EXPORT int rtcSetRemoteDescription(int pc, const char *sdp, const char *type);
 RTC_EXPORT int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid);
 RTC_EXPORT int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid);
 
 
+RTC_EXPORT int rtcGetLocalDescription(int pc, char *buffer, int size);
+RTC_EXPORT int rtcGetRemoteDescription(int pc, char *buffer, int size);
+
 RTC_EXPORT int rtcGetLocalAddress(int pc, char *buffer, int size);
 RTC_EXPORT int rtcGetLocalAddress(int pc, char *buffer, int size);
 RTC_EXPORT int rtcGetRemoteAddress(int pc, char *buffer, int size);
 RTC_EXPORT int rtcGetRemoteAddress(int pc, char *buffer, int size);
 
 
+RTC_EXPORT int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote, int remoteSize);
+
 // DataChannel
 // DataChannel
 RTC_EXPORT int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb);
 RTC_EXPORT int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb);
 RTC_EXPORT int rtcAddDataChannel(int pc, const char *label); // returns dc id
 RTC_EXPORT int rtcAddDataChannel(int pc, const char *label); // returns dc id

+ 106 - 36
src/candidate.cpp

@@ -21,6 +21,7 @@
 #include <algorithm>
 #include <algorithm>
 #include <array>
 #include <array>
 #include <sstream>
 #include <sstream>
+#include <unordered_map>
 
 
 #ifdef _WIN32
 #ifdef _WIN32
 #include <winsock2.h>
 #include <winsock2.h>
@@ -47,20 +48,40 @@ inline bool hasprefix(const string &str, const string &prefix) {
 
 
 namespace rtc {
 namespace rtc {
 
 
-Candidate::Candidate(string candidate, string mid) : mIsResolved(false) {
-	const std::array prefixes{"a=", "candidate:"};
-	for (string prefix : prefixes)
-		if (hasprefix(candidate, prefix))
-			candidate.erase(0, prefix.size());
+Candidate::Candidate(string candidate, string mid)
+    : mFamily(Family::Unresolved), mType(Type::Unknown), mTransportType(TransportType::Unknown),
+      mPort(0), mPriority(0) {
+
+	if (!candidate.empty()) {
+		const std::array prefixes{"a=", "candidate:"};
+		for (const string &prefix : prefixes)
+			if (hasprefix(candidate, prefix))
+				candidate.erase(0, prefix.size());
+	}
 
 
 	mCandidate = std::move(candidate);
 	mCandidate = std::move(candidate);
 	mMid = std::move(mid);
 	mMid = std::move(mid);
 }
 }
 
 
 bool Candidate::resolve(ResolveMode mode) {
 bool Candidate::resolve(ResolveMode mode) {
-	if (mIsResolved)
+	using TypeMap_t = std::unordered_map<string, Type>;	
+	using TcpTypeMap_t = std::unordered_map<string, TransportType>;
+
+	static const TypeMap_t TypeMap = {{"host", Type::Host},
+	                                  {"srflx", Type::ServerReflexive},
+	                                  {"prflx", Type::PeerReflexive},
+	                                  {"relay", Type::Relayed}};
+
+	static const TcpTypeMap_t TcpTypeMap = {{"active", TransportType::TcpActive},
+	                                        {"passive", TransportType::TcpPassive},
+	                                        {"so", TransportType::TcpSo}};
+
+	if (mFamily != Family::Unresolved)
 		return true;
 		return true;
 
 
+	if(mCandidate.empty())
+		throw std::logic_error("Candidate is empty");
+
 	PLOG_VERBOSE << "Resolving candidate (mode="
 	PLOG_VERBOSE << "Resolving candidate (mode="
 				 << (mode == ResolveMode::Simple ? "simple" : "lookup")
 				 << (mode == ResolveMode::Simple ? "simple" : "lookup")
 				 << "): " << mCandidate;
 				 << "): " << mCandidate;
@@ -75,16 +96,39 @@ bool Candidate::resolve(ResolveMode mode) {
 		string left;
 		string left;
 		std::getline(iss, left);
 		std::getline(iss, left);
 
 
+		if (auto it = TypeMap.find(type); it != TypeMap.end())
+			mType = it->second;
+		else
+			mType = Type::Unknown;
+		
+		if (transport == "UDP" || transport == "udp") {
+			mTransportType = TransportType::Udp;
+		}
+		else if (transport == "TCP" || transport == "tcp") {
+			std::istringstream iss(left);
+			string tcptype_, tcptype;
+			if(iss >> tcptype_ >> tcptype && tcptype_ == "tcptype") {
+				if (auto it = TcpTypeMap.find(tcptype); it != TcpTypeMap.end())
+					mTransportType = it->second;
+				else 
+					mTransportType = TransportType::TcpUnknown;
+
+			} else {
+				mTransportType = TransportType::TcpUnknown;
+			}
+		} else {
+			mTransportType = TransportType::Unknown;
+		}
+
 		// Try to resolve the node
 		// Try to resolve the node
 		struct addrinfo hints = {};
 		struct addrinfo hints = {};
 		hints.ai_family = AF_UNSPEC;
 		hints.ai_family = AF_UNSPEC;
 		hints.ai_flags = AI_ADDRCONFIG;
 		hints.ai_flags = AI_ADDRCONFIG;
-		if (transport == "UDP" || transport == "udp") {
+		if (mTransportType == TransportType::Udp) {
 			hints.ai_socktype = SOCK_DGRAM;
 			hints.ai_socktype = SOCK_DGRAM;
 			hints.ai_protocol = IPPROTO_UDP;
 			hints.ai_protocol = IPPROTO_UDP;
 		}
 		}
-
-		if (transport == "TCP" || transport == "tcp") {
+		else if (mTransportType != TransportType::Unknown) {
 			hints.ai_socktype = SOCK_STREAM;
 			hints.ai_socktype = SOCK_STREAM;
 			hints.ai_protocol = IPPROTO_TCP;
 			hints.ai_protocol = IPPROTO_TCP;
 		}
 		}
@@ -94,7 +138,7 @@ bool Candidate::resolve(ResolveMode mode) {
 
 
 		struct addrinfo *result = nullptr;
 		struct addrinfo *result = nullptr;
 		if (getaddrinfo(node.c_str(), service.c_str(), &hints, &result) == 0) {
 		if (getaddrinfo(node.c_str(), service.c_str(), &hints, &result) == 0) {
-			for (auto p = result; p; p = p->ai_next)
+			for (auto p = result; p; p = p->ai_next) {
 				if (p->ai_family == AF_INET || p->ai_family == AF_INET6) {
 				if (p->ai_family == AF_INET || p->ai_family == AF_INET6) {
 					// Rewrite the candidate
 					// Rewrite the candidate
 					char nodebuffer[MAX_NUMERICNODE_LEN];
 					char nodebuffer[MAX_NUMERICNODE_LEN];
@@ -102,27 +146,31 @@ bool Candidate::resolve(ResolveMode mode) {
 					if (getnameinfo(p->ai_addr, socklen_t(p->ai_addrlen), nodebuffer,
 					if (getnameinfo(p->ai_addr, socklen_t(p->ai_addrlen), nodebuffer,
 					                MAX_NUMERICNODE_LEN, servbuffer, MAX_NUMERICSERV_LEN,
 					                MAX_NUMERICNODE_LEN, servbuffer, MAX_NUMERICSERV_LEN,
 					                NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
 					                NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
+						
+						mAddress = nodebuffer;
+						mPort = uint16_t(std::stoul(servbuffer));
+						mFamily = p->ai_family == AF_INET6 ? Family::Ipv6 : Family::Ipv4;
+						
 						const char sp{' '};
 						const char sp{' '};
 						std::ostringstream oss;
 						std::ostringstream oss;
 						oss << foundation << sp << component << sp << transport << sp << priority;
 						oss << foundation << sp << component << sp << transport << sp << priority;
 						oss << sp << nodebuffer << sp << servbuffer << sp << "typ" << sp << type;
 						oss << sp << nodebuffer << sp << servbuffer << sp << "typ" << sp << type;
 						oss << left;
 						oss << left;
 						mCandidate = oss.str();
 						mCandidate = oss.str();
-						mIsResolved = true;
+
 						PLOG_VERBOSE << "Resolved candidate: " << mCandidate;
 						PLOG_VERBOSE << "Resolved candidate: " << mCandidate;
 						break;
 						break;
 					}
 					}
 				}
 				}
-		}
+			}
 
 
-		freeaddrinfo(result);
+			freeaddrinfo(result);
+		}
 	}
 	}
 
 
-	return mIsResolved;
+	return mFamily != Family::Unresolved;
 }
 }
 
 
-bool Candidate::isResolved() const { return mIsResolved; }
-
 string Candidate::candidate() const { return "candidate:" + mCandidate; }
 string Candidate::candidate() const { return "candidate:" + mCandidate; }
 
 
 string Candidate::mid() const { return mMid; }
 string Candidate::mid() const { return mMid; }
@@ -133,38 +181,60 @@ Candidate::operator string() const {
 	return line.str();
 	return line.str();
 }
 }
 
 
+bool Candidate::isResolved() const { return mFamily != Family::Unresolved; }
+
+Candidate::Family Candidate::family() const { return mFamily; }
+
+Candidate::Type Candidate::type() const { return mType; }
+
+Candidate::TransportType Candidate::transportType() const { return mTransportType; }
+
+std::optional<string> Candidate::address() const {
+	return isResolved() ? std::make_optional(mAddress) : nullopt;
+}
+
+std::optional<uint16_t> Candidate::port() const {
+	return isResolved() ? std::make_optional(mPort) : nullopt;
+}
+
+std::optional<uint32_t> Candidate::priority() const {
+	return isResolved() ? std::make_optional(mPriority) : nullopt;
+}
+
 } // namespace rtc
 } // namespace rtc
 
 
 std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate) {
 std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate) {
 	return out << std::string(candidate);
 	return out << std::string(candidate);
 }
 }
 
 
-std::ostream &operator<<(std::ostream &out, const rtc::CandidateType &type) {
+std::ostream &operator<<(std::ostream &out, const rtc::Candidate::Type &type) {
 	switch (type) {
 	switch (type) {
-	case rtc::CandidateType::Host:
-		return out << "Host";
-	case rtc::CandidateType::PeerReflexive:
-		return out << "PeerReflexive";
-	case rtc::CandidateType::Relayed:
-		return out << "Relayed";
-	case rtc::CandidateType::ServerReflexive:
-		return out << "ServerReflexive";
+	case rtc::Candidate::Type::Host:
+		return out << "host";
+	case rtc::Candidate::Type::PeerReflexive:
+		return out << "peer_reflexive";
+	case rtc::Candidate::Type::ServerReflexive:
+		return out << "server_reflexive";
+	case rtc::Candidate::Type::Relayed:
+		return out << "relayed";
 	default:
 	default:
-		return out << "Unknown";
+		return out << "unknown";
 	}
 	}
 }
 }
 
 
-std::ostream &operator<<(std::ostream &out, const rtc::CandidateTransportType &transportType) {
+std::ostream &operator<<(std::ostream &out, const rtc::Candidate::TransportType &transportType) {
 	switch (transportType) {
 	switch (transportType) {
-	case rtc::CandidateTransportType::TcpActive:
-		return out << "TcpActive";
-	case rtc::CandidateTransportType::TcpPassive:
-		return out << "TcpPassive";
-	case rtc::CandidateTransportType::TcpSo:
-		return out << "TcpSo";
-	case rtc::CandidateTransportType::Udp:
-		return out << "Udp";
+	case rtc::Candidate::TransportType::Udp:
+		return out << "UDP";
+	case rtc::Candidate::TransportType::TcpActive:
+		return out << "TCP_active";
+	case rtc::Candidate::TransportType::TcpPassive:
+		return out << "TCP_passive";
+	case rtc::Candidate::TransportType::TcpSo:
+		return out << "TCP_so";
+	case rtc::Candidate::TransportType::TcpUnknown:
+		return out << "TCP_unknown";
 	default:
 	default:
-		return out << "Unknown";
+		return out << "unknown";
 	}
 	}
 }
 }

+ 134 - 28
src/capi.cpp

@@ -195,12 +195,17 @@ template <typename F> int wrap(F func) {
 		return RTC_ERR_SUCCESS;                                                                    \
 		return RTC_ERR_SUCCESS;                                                                    \
 	})
 	})
 
 
-class plog_appender : public plog::IAppender {
+class plogAppender : public plog::IAppender {
 public:
 public:
-	plog_appender(rtcLogCallbackFunc cb = nullptr) { set_callback(cb); }
+	plogAppender(rtcLogCallbackFunc cb = nullptr) { setCallback(cb); }
 
 
-	void set_callback(rtcLogCallbackFunc cb) {
-		std::lock_guard lock(mutex);
+	plogAppender(plogAppender &&appender) : callback(nullptr) {
+		std::lock_guard lock(appender.callbackMutex);
+		std::swap(appender.callback, callback);
+	}
+
+	void setCallback(rtcLogCallbackFunc cb) {
+		std::lock_guard lock(callbackMutex);
 		callback = cb;
 		callback = cb;
 	}
 	}
 
 
@@ -215,7 +220,7 @@ public:
 #else
 #else
 		std::string str = formatted;
 		std::string str = formatted;
 #endif
 #endif
-		std::lock_guard lock(mutex);
+		std::lock_guard lock(callbackMutex);
 		if (callback)
 		if (callback)
 			callback(static_cast<rtcLogLevel>(record.getSeverity()), str.c_str());
 			callback(static_cast<rtcLogLevel>(record.getSeverity()), str.c_str());
 		else
 		else
@@ -224,18 +229,24 @@ public:
 
 
 private:
 private:
 	rtcLogCallbackFunc callback;
 	rtcLogCallbackFunc callback;
+	std::mutex callbackMutex;
 };
 };
 
 
 } // namespace
 } // namespace
 
 
 void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb) {
 void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb) {
-	static std::optional<plog_appender> appender;
-	if (appender)
-		appender->set_callback(cb);
-	else if (cb)
-		appender.emplace(plog_appender(cb));
-
-	InitLogger(static_cast<plog::Severity>(level), appender ? &appender.value() : nullptr);
+	static std::optional<plogAppender> appender;
+	const auto severity = static_cast<plog::Severity>(level);
+	std::lock_guard lock(mutex);
+	if (appender) {
+		appender->setCallback(cb);
+		InitLogger(severity, nullptr); // change the severity
+	} else if (cb) {
+		appender.emplace(plogAppender(cb));
+		InitLogger(severity, &appender.value());
+	} else {
+		InitLogger(severity, nullptr); // log to stdout
+	}
 }
 }
 
 
 void rtcSetUserPointer(int i, void *ptr) { setUserPointer(i, ptr); }
 void rtcSetUserPointer(int i, void *ptr) { setUserPointer(i, ptr); }
@@ -306,7 +317,7 @@ int rtcCreateDataChannel(int pc, const char *label) {
 int rtcCreateDataChannelExt(int pc, const char *label, const char *protocol,
 int rtcCreateDataChannelExt(int pc, const char *label, const char *protocol,
                             const rtcReliability *reliability) {
                             const rtcReliability *reliability) {
 	int dc = rtcAddDataChannelExt(pc, label, protocol, reliability);
 	int dc = rtcAddDataChannelExt(pc, label, protocol, reliability);
-	rtcSetLocalDescription(pc);
+	rtcSetLocalDescription(pc, NULL);
 	return dc;
 	return dc;
 }
 }
 
 
@@ -411,7 +422,7 @@ int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc cb) {
 		if (cb)
 		if (cb)
 			peerConnection->onLocalDescription([pc, cb](Description desc) {
 			peerConnection->onLocalDescription([pc, cb](Description desc) {
 				if (auto ptr = getUserPointer(pc))
 				if (auto ptr = getUserPointer(pc))
-					cb(string(desc).c_str(), desc.typeString().c_str(), *ptr);
+					cb(pc, string(desc).c_str(), desc.typeString().c_str(), *ptr);
 			});
 			});
 		else
 		else
 			peerConnection->onLocalDescription(nullptr);
 			peerConnection->onLocalDescription(nullptr);
@@ -424,7 +435,7 @@ int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb) {
 		if (cb)
 		if (cb)
 			peerConnection->onLocalCandidate([pc, cb](Candidate cand) {
 			peerConnection->onLocalCandidate([pc, cb](Candidate cand) {
 				if (auto ptr = getUserPointer(pc))
 				if (auto ptr = getUserPointer(pc))
-					cb(cand.candidate().c_str(), cand.mid().c_str(), *ptr);
+					cb(pc, cand.candidate().c_str(), cand.mid().c_str(), *ptr);
 			});
 			});
 		else
 		else
 			peerConnection->onLocalCandidate(nullptr);
 			peerConnection->onLocalCandidate(nullptr);
@@ -437,7 +448,7 @@ int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb) {
 		if (cb)
 		if (cb)
 			peerConnection->onStateChange([pc, cb](PeerConnection::State state) {
 			peerConnection->onStateChange([pc, cb](PeerConnection::State state) {
 				if (auto ptr = getUserPointer(pc))
 				if (auto ptr = getUserPointer(pc))
-					cb(static_cast<rtcState>(state), *ptr);
+					cb(pc, static_cast<rtcState>(state), *ptr);
 			});
 			});
 		else
 		else
 			peerConnection->onStateChange(nullptr);
 			peerConnection->onStateChange(nullptr);
@@ -450,7 +461,20 @@ int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb)
 		if (cb)
 		if (cb)
 			peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) {
 			peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) {
 				if (auto ptr = getUserPointer(pc))
 				if (auto ptr = getUserPointer(pc))
-					cb(static_cast<rtcGatheringState>(state), *ptr);
+					cb(pc, static_cast<rtcGatheringState>(state), *ptr);
+			});
+		else
+			peerConnection->onGatheringStateChange(nullptr);
+	});
+}
+
+int rtcSetSignalingStateChangeCallback(int pc, rtcSignalingStateCallbackFunc cb) {
+	return WRAP({
+		auto peerConnection = getPeerConnection(pc);
+		if (cb)
+			peerConnection->onSignalingStateChange([pc, cb](PeerConnection::SignalingState state) {
+				if (auto ptr = getUserPointer(pc))
+					cb(pc, static_cast<rtcSignalingState>(state), *ptr);
 			});
 			});
 		else
 		else
 			peerConnection->onGatheringStateChange(nullptr);
 			peerConnection->onGatheringStateChange(nullptr);
@@ -465,7 +489,7 @@ int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb) {
 				int dc = emplaceDataChannel(dataChannel);
 				int dc = emplaceDataChannel(dataChannel);
 				if (auto ptr = getUserPointer(pc)) {
 				if (auto ptr = getUserPointer(pc)) {
 					rtcSetUserPointer(dc, *ptr);
 					rtcSetUserPointer(dc, *ptr);
-					cb(dc, *ptr);
+					cb(pc, dc, *ptr);
 				}
 				}
 			});
 			});
 		else
 		else
@@ -481,7 +505,7 @@ int rtcSetTrackCallback(int pc, rtcTrackCallbackFunc cb) {
 				int tr = emplaceTrack(track);
 				int tr = emplaceTrack(track);
 				if (auto ptr = getUserPointer(pc)) {
 				if (auto ptr = getUserPointer(pc)) {
 					rtcSetUserPointer(tr, *ptr);
 					rtcSetUserPointer(tr, *ptr);
-					cb(tr, *ptr);
+					cb(pc, tr, *ptr);
 				}
 				}
 			});
 			});
 		else
 		else
@@ -489,10 +513,11 @@ int rtcSetTrackCallback(int pc, rtcTrackCallbackFunc cb) {
 	});
 	});
 }
 }
 
 
-int rtcSetLocalDescription(int pc) {
+int rtcSetLocalDescription(int pc, const char *type) {
 	return WRAP({
 	return WRAP({
 		auto peerConnection = getPeerConnection(pc);
 		auto peerConnection = getPeerConnection(pc);
-		peerConnection->setLocalDescription();
+		peerConnection->setLocalDescription(type ? Description::stringToType(type)
+		                                         : Description::Type::Unspec);
 	});
 	});
 }
 }
 
 
@@ -518,6 +543,52 @@ int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid) {
 	});
 	});
 }
 }
 
 
+int rtcGetLocalDescription(int pc, char *buffer, int size) {
+	return WRAP({
+		auto peerConnection = getPeerConnection(pc);
+
+		if (size <= 0)
+			return 0;
+
+		if (!buffer)
+			throw std::invalid_argument("Unexpected null pointer for buffer");
+
+		if (auto desc = peerConnection->localDescription()) {
+			auto sdp = string(*desc);
+			const char *data = sdp.data();
+			size = std::min(size - 1, int(sdp.size()));
+			std::copy(data, data + size, buffer);
+			buffer[size] = '\0';
+			return size + 1;
+		}
+
+		return RTC_ERR_FAILURE;
+	});
+}
+
+int rtcGetRemoteDescription(int pc, char *buffer, int size) {
+	return WRAP({
+		auto peerConnection = getPeerConnection(pc);
+
+		if (size <= 0)
+			return 0;
+
+		if (!buffer)
+			throw std::invalid_argument("Unexpected null pointer for buffer");
+
+		if (auto desc = peerConnection->remoteDescription()) {
+			auto sdp = string(*desc);
+			const char *data = sdp.data();
+			size = std::min(size - 1, int(sdp.size()));
+			std::copy(data, data + size, buffer);
+			buffer[size] = '\0';
+			return size + 1;
+		}
+
+		return RTC_ERR_FAILURE;
+	});
+}
+
 int rtcGetLocalAddress(int pc, char *buffer, int size) {
 int rtcGetLocalAddress(int pc, char *buffer, int size) {
 	return WRAP({
 	return WRAP({
 		auto peerConnection = getPeerConnection(pc);
 		auto peerConnection = getPeerConnection(pc);
@@ -535,6 +606,8 @@ int rtcGetLocalAddress(int pc, char *buffer, int size) {
 			buffer[size] = '\0';
 			buffer[size] = '\0';
 			return size + 1;
 			return size + 1;
 		}
 		}
+
+		return RTC_ERR_FAILURE;
 	});
 	});
 }
 }
 
 
@@ -555,6 +628,39 @@ int rtcGetRemoteAddress(int pc, char *buffer, int size) {
 			buffer[size] = '\0';
 			buffer[size] = '\0';
 			return int(size + 1);
 			return int(size + 1);
 		}
 		}
+
+		return RTC_ERR_FAILURE;
+	});
+}
+
+int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote, int remoteSize) {
+	return WRAP({
+		auto peerConnection = getPeerConnection(pc);
+
+		if (!local)
+			localSize = 0;
+		if (!remote)
+			remoteSize = 0;
+
+		Candidate localCand;
+		Candidate remoteCand;
+		if (peerConnection->getSelectedCandidatePair(&localCand, &remoteCand)) {
+			if (localSize > 0) {
+				string localSdp = string(localCand);
+				localSize = std::min(localSize - 1, int(localSdp.size()));
+				std::copy(localSdp.begin(), localSdp.begin() + localSize, local);
+				local[localSize] = '\0';
+			}
+			if (remoteSize > 0) {
+				string remoteSdp = string(remoteCand);
+				remoteSize = std::min(remoteSize - 1, int(remoteSdp.size()));
+				std::copy(remoteSdp.begin(), remoteSdp.begin() + remoteSize, remote);
+				remote[remoteSize] = '\0';
+			}
+			return localSize + remoteSize;
+		}
+
+		return RTC_ERR_FAILURE;
 	});
 	});
 }
 }
 
 
@@ -625,7 +731,7 @@ int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb) {
 		if (cb)
 		if (cb)
 			channel->onOpen([id, cb]() {
 			channel->onOpen([id, cb]() {
 				if (auto ptr = getUserPointer(id))
 				if (auto ptr = getUserPointer(id))
-					cb(*ptr);
+					cb(id, *ptr);
 			});
 			});
 		else
 		else
 			channel->onOpen(nullptr);
 			channel->onOpen(nullptr);
@@ -638,7 +744,7 @@ int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb) {
 		if (cb)
 		if (cb)
 			channel->onClosed([id, cb]() {
 			channel->onClosed([id, cb]() {
 				if (auto ptr = getUserPointer(id))
 				if (auto ptr = getUserPointer(id))
-					cb(*ptr);
+					cb(id, *ptr);
 			});
 			});
 		else
 		else
 			channel->onClosed(nullptr);
 			channel->onClosed(nullptr);
@@ -651,7 +757,7 @@ int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb) {
 		if (cb)
 		if (cb)
 			channel->onError([id, cb](string error) {
 			channel->onError([id, cb](string error) {
 				if (auto ptr = getUserPointer(id))
 				if (auto ptr = getUserPointer(id))
-					cb(error.c_str(), *ptr);
+					cb(id, error.c_str(), *ptr);
 			});
 			});
 		else
 		else
 			channel->onError(nullptr);
 			channel->onError(nullptr);
@@ -665,11 +771,11 @@ int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb) {
 			channel->onMessage(
 			channel->onMessage(
 			    [id, cb](binary b) {
 			    [id, cb](binary b) {
 				    if (auto ptr = getUserPointer(id))
 				    if (auto ptr = getUserPointer(id))
-					    cb(reinterpret_cast<const char *>(b.data()), int(b.size()), *ptr);
+					    cb(id, reinterpret_cast<const char *>(b.data()), int(b.size()), *ptr);
 			    },
 			    },
 			    [id, cb](string s) {
 			    [id, cb](string s) {
 				    if (auto ptr = getUserPointer(id))
 				    if (auto ptr = getUserPointer(id))
-					    cb(s.c_str(), -int(s.size() + 1), *ptr);
+					    cb(id, s.c_str(), -int(s.size() + 1), *ptr);
 			    });
 			    });
 		else
 		else
 			channel->onMessage(nullptr);
 			channel->onMessage(nullptr);
@@ -716,7 +822,7 @@ int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb)
 		if (cb)
 		if (cb)
 			channel->onBufferedAmountLow([id, cb]() {
 			channel->onBufferedAmountLow([id, cb]() {
 				if (auto ptr = getUserPointer(id))
 				if (auto ptr = getUserPointer(id))
-					cb(*ptr);
+					cb(id, *ptr);
 			});
 			});
 		else
 		else
 			channel->onBufferedAmountLow(nullptr);
 			channel->onBufferedAmountLow(nullptr);
@@ -733,7 +839,7 @@ int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb) {
 		if (cb)
 		if (cb)
 			channel->onOpen([id, cb]() {
 			channel->onOpen([id, cb]() {
 				if (auto ptr = getUserPointer(id))
 				if (auto ptr = getUserPointer(id))
-					cb(*ptr);
+					cb(id, *ptr);
 			});
 			});
 		else
 		else
 			channel->onOpen(nullptr);
 			channel->onOpen(nullptr);

+ 9 - 1
src/certificate.cpp

@@ -99,7 +99,11 @@ certificate_ptr make_certificate_impl(string commonName) {
 	unique_ptr<gnutls_x509_crt_t, decltype(&free_crt)> crt(new_crt(), free_crt);
 	unique_ptr<gnutls_x509_crt_t, decltype(&free_crt)> crt(new_crt(), free_crt);
 	unique_ptr<gnutls_x509_privkey_t, decltype(&free_privkey)> privkey(new_privkey(), free_privkey);
 	unique_ptr<gnutls_x509_privkey_t, decltype(&free_privkey)> privkey(new_privkey(), free_privkey);
 
 
+#ifdef RSA_KEY_BITS_2048
+	const unsigned int bits = 2048;
+#else
 	const unsigned int bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_HIGH);
 	const unsigned int bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_HIGH);
+#endif
 	gnutls::check(gnutls_x509_privkey_generate(*privkey, GNUTLS_PK_RSA, bits, 0),
 	gnutls::check(gnutls_x509_privkey_generate(*privkey, GNUTLS_PK_RSA, bits, 0),
 	              "Unable to generate key pair");
 	              "Unable to generate key pair");
 
 
@@ -190,7 +194,11 @@ certificate_ptr make_certificate_impl(string commonName) {
 	if (!x509 || !pkey || !rsa || !exponent || !serial_number || !name)
 	if (!x509 || !pkey || !rsa || !exponent || !serial_number || !name)
 		throw std::runtime_error("Unable allocate structures for certificate generation");
 		throw std::runtime_error("Unable allocate structures for certificate generation");
 
 
-	const int bits = 4096;
+#ifdef RSA_KEY_BITS_2048
+	const int bits = 2048;
+#else
+	const int bits = 3072;
+#endif
 	const unsigned int e = 65537; // 2^16 + 1
 	const unsigned int e = 65537; // 2^16 + 1
 
 
 	if (!pkey || !rsa || !exponent || !BN_set_word(exponent.get(), e) ||
 	if (!pkey || !rsa || !exponent || !BN_set_word(exponent.get(), e) ||

+ 4 - 2
src/datachannel.cpp

@@ -122,8 +122,8 @@ bool DataChannel::send(const byte *data, size_t size) {
 }
 }
 
 
 std::optional<message_variant> DataChannel::receive() {
 std::optional<message_variant> DataChannel::receive() {
-	while (!mRecvQueue.empty()) {
-		auto message = *mRecvQueue.pop();
+	while (auto next = mRecvQueue.tryPop()) {
+		message_ptr message = std::move(*next);
 		if (message->type == Message::Control) {
 		if (message->type == Message::Control) {
 			auto raw = reinterpret_cast<const uint8_t *>(message->data());
 			auto raw = reinterpret_cast<const uint8_t *>(message->data());
 			if (!message->empty() && raw[0] == MESSAGE_CLOSE)
 			if (!message->empty() && raw[0] == MESSAGE_CLOSE)
@@ -218,6 +218,8 @@ void DataChannel::incoming(message_ptr message) {
 
 
 	switch (message->type) {
 	switch (message->type) {
 	case Message::Control: {
 	case Message::Control: {
+		if (message->size() == 0)
+			break; // Ignore
 		auto raw = reinterpret_cast<const uint8_t *>(message->data());
 		auto raw = reinterpret_cast<const uint8_t *>(message->data());
 		switch (raw[0]) {
 		switch (raw[0]) {
 		case MESSAGE_OPEN:
 		case MESSAGE_OPEN:

+ 137 - 59
src/description.cpp

@@ -25,6 +25,8 @@
 #include <chrono>
 #include <chrono>
 #include <iostream>
 #include <iostream>
 #include <random>
 #include <random>
+//#include <sstream>
+//#include <unordered_map>
 
 
 using std::shared_ptr;
 using std::shared_ptr;
 using std::size_t;
 using std::size_t;
@@ -73,27 +75,24 @@ Description::Description(const string &sdp, Type type, Role role)
     : mType(Type::Unspec), mRole(role) {
     : mType(Type::Unspec), mRole(role) {
 	hintType(type);
 	hintType(type);
 
 
-	auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
-	std::default_random_engine generator(seed);
-	std::uniform_int_distribution<uint32_t> uniform;
-	mSessionId = std::to_string(uniform(generator));
-
-	std::istringstream ss(sdp);
-	std::shared_ptr<Entry> current;
-
 	int index = -1;
 	int index = -1;
-	string line;
-	while (std::getline(ss, line) || !line.empty()) {
+	std::shared_ptr<Entry> current;
+	std::istringstream ss(sdp);
+	while (ss) {
+		string line;
+		std::getline(ss, line);
 		trim_end(line);
 		trim_end(line);
+		if (line.empty())
+			continue;
+
+		if (match_prefix(line, "m=")) { // Media description line (aka m-line)
+			current = createEntry(line.substr(2), std::to_string(++index), Direction::Unknown);
 
 
-		// Media description line (aka m-line)
-		if (match_prefix(line, "m=")) {
-			++index;
-			string mline = line.substr(2);
-			current = createEntry(std::move(mline), std::to_string(index), Direction::Unknown);
+		} else if (match_prefix(line, "o=")) { // Origin line
+			std::istringstream origin(line.substr(2));
+			origin >> mUsername >> mSessionId;
 
 
-			// Attribute line
-		} else if (match_prefix(line, "a=")) {
+		} else if (match_prefix(line, "a=")) { // Attribute line
 			string attr = line.substr(2);
 			string attr = line.substr(2);
 			auto [key, value] = parse_pair(attr);
 			auto [key, value] = parse_pair(attr);
 
 
@@ -129,7 +128,17 @@ Description::Description(const string &sdp, Type type, Role role)
 		} else if (current) {
 		} else if (current) {
 			current->parseSdpLine(std::move(line));
 			current->parseSdpLine(std::move(line));
 		}
 		}
-	};
+	}
+
+	if (mUsername.empty())
+		mUsername = "rtc";
+
+	if (mSessionId.empty()) {
+		auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
+		std::default_random_engine generator(seed);
+		std::uniform_int_distribution<uint32_t> uniform;
+		mSessionId = std::to_string(uniform(generator));
+	}
 }
 }
 
 
 Description::Type Description::type() const { return mType; }
 Description::Type Description::type() const { return mType; }
@@ -138,13 +147,15 @@ string Description::typeString() const { return typeToString(mType); }
 
 
 Description::Role Description::role() const { return mRole; }
 Description::Role Description::role() const { return mRole; }
 
 
-string Description::roleString() const { return roleToString(mRole); }
-
 string Description::bundleMid() const {
 string Description::bundleMid() const {
 	// Get the mid of the first media
 	// Get the mid of the first media
 	return !mEntries.empty() ? mEntries[0]->mid() : "0";
 	return !mEntries.empty() ? mEntries[0]->mid() : "0";
 }
 }
 
 
+std::optional<string> Description::iceUfrag() const { return mIceUfrag; }
+
+std::optional<string> Description::icePwd() const { return mIcePwd; }
+
 std::optional<string> Description::fingerprint() const { return mFingerprint; }
 std::optional<string> Description::fingerprint() const { return mFingerprint; }
 
 
 bool Description::ended() const { return mEnded; }
 bool Description::ended() const { return mEnded; }
@@ -165,6 +176,11 @@ void Description::addCandidate(Candidate candidate) {
 	mCandidates.emplace_back(std::move(candidate));
 	mCandidates.emplace_back(std::move(candidate));
 }
 }
 
 
+void Description::addCandidates(std::vector<Candidate> candidates) {
+	for(auto candidate : candidates)
+		mCandidates.emplace_back(std::move(candidate));
+}
+
 void Description::endCandidates() { mEnded = true; }
 void Description::endCandidates() { mEnded = true; }
 
 
 std::vector<Candidate> Description::extractCandidates() {
 std::vector<Candidate> Description::extractCandidates() {
@@ -181,7 +197,7 @@ string Description::generateSdp(string_view eol) const {
 
 
 	// Header
 	// Header
 	sdp << "v=0" << eol;
 	sdp << "v=0" << eol;
-	sdp << "o=- " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
+	sdp << "o=" << mUsername << " " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
 	sdp << "s=-" << eol;
 	sdp << "s=-" << eol;
 	sdp << "t=0 0" << eol;
 	sdp << "t=0 0" << eol;
 
 
@@ -204,20 +220,29 @@ string Description::generateSdp(string_view eol) const {
 
 
 	// Session-level attributes
 	// Session-level attributes
 	sdp << "a=msid-semantic:WMS *" << eol;
 	sdp << "a=msid-semantic:WMS *" << eol;
-	sdp << "a=setup:" << roleToString(mRole) << eol;
-	sdp << "a=ice-ufrag:" << mIceUfrag << eol;
-	sdp << "a=ice-pwd:" << mIcePwd << eol;
+	sdp << "a=setup:" << mRole << eol;
 
 
+	if (mIceUfrag)
+		sdp << "a=ice-ufrag:" << *mIceUfrag << eol;
+	if (mIcePwd)
+		sdp << "a=ice-pwd:" << *mIcePwd << eol;
 	if (!mEnded)
 	if (!mEnded)
 		sdp << "a=ice-options:trickle" << eol;
 		sdp << "a=ice-options:trickle" << eol;
-
 	if (mFingerprint)
 	if (mFingerprint)
 		sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
 		sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
 
 
+	auto cand = defaultCandidate();
+	const string addr = cand && cand->isResolved()
+	                        ? (string(cand->family() == Candidate::Family::Ipv6 ? "IP6" : "IP4") +
+	                           " " + *cand->address())
+	                        : "IP4 0.0.0.0";
+	const string port = std::to_string(
+	    cand && cand->isResolved() ? *cand->port() : 9); // Port 9 is the discard protocol
+
 	// Entries
 	// Entries
 	bool first = true;
 	bool first = true;
 	for (const auto &entry : mEntries) {
 	for (const auto &entry : mEntries) {
-		sdp << entry->generateSdp(eol);
+		sdp << entry->generateSdp(eol, addr, port);
 
 
 		if (std::exchange(first, false)) {
 		if (std::exchange(first, false)) {
 			// Candidates
 			// Candidates
@@ -237,23 +262,32 @@ string Description::generateApplicationSdp(string_view eol) const {
 
 
 	// Header
 	// Header
 	sdp << "v=0" << eol;
 	sdp << "v=0" << eol;
-	sdp << "o=- " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
+	sdp << "o=" << mUsername << " " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
 	sdp << "s=-" << eol;
 	sdp << "s=-" << eol;
 	sdp << "t=0 0" << eol;
 	sdp << "t=0 0" << eol;
 
 
+	auto cand = defaultCandidate();
+	const string addr = cand && cand->isResolved()
+	                        ? (string(cand->family() == Candidate::Family::Ipv6 ? "IP6" : "IP4") +
+	                           " " + *cand->address())
+	                        : "IP4 0.0.0.0";
+	const string port = std::to_string(
+	    cand && cand->isResolved() ? *cand->port() : 9); // Port 9 is the discard protocol
+
 	// Application
 	// Application
 	auto app = mApplication ? mApplication : std::make_shared<Application>();
 	auto app = mApplication ? mApplication : std::make_shared<Application>();
-	sdp << app->generateSdp(eol);
+	sdp << app->generateSdp(eol, addr, port);
 
 
 	// Session-level attributes
 	// Session-level attributes
 	sdp << "a=msid-semantic:WMS *" << eol;
 	sdp << "a=msid-semantic:WMS *" << eol;
-	sdp << "a=setup:" << roleToString(mRole) << eol;
-	sdp << "a=ice-ufrag:" << mIceUfrag << eol;
-	sdp << "a=ice-pwd:" << mIcePwd << eol;
+	sdp << "a=setup:" << mRole << eol;
 
 
+	if (mIceUfrag)
+		sdp << "a=ice-ufrag:" << *mIceUfrag << eol;
+	if (mIcePwd)
+		sdp << "a=ice-pwd:" << *mIcePwd << eol;
 	if (!mEnded)
 	if (!mEnded)
 		sdp << "a=ice-options:trickle" << eol;
 		sdp << "a=ice-options:trickle" << eol;
-
 	if (mFingerprint)
 	if (mFingerprint)
 		sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
 		sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
 
 
@@ -267,6 +301,21 @@ string Description::generateApplicationSdp(string_view eol) const {
 	return sdp.str();
 	return sdp.str();
 }
 }
 
 
+std::optional<Candidate> Description::defaultCandidate() const {
+	// Return the first host candidate with highest priority, favoring IPv4
+	std::optional<Candidate> result;
+	for (const auto &c : mCandidates) {
+		if (c.type() == Candidate::Type::Host) {
+			if (!result ||
+			    (result->family() == Candidate::Family::Ipv6 &&
+			     c.family() == Candidate::Family::Ipv4) ||
+			    (result->family() == c.family() && result->priority() < c.priority()))
+				result.emplace(c);
+		}
+	}
+	return result;
+}
+
 shared_ptr<Description::Entry> Description::createEntry(string mline, string mid, Direction dir) {
 shared_ptr<Description::Entry> Description::createEntry(string mline, string mid, Direction dir) {
 	string type = mline.substr(0, mline.find(' '));
 	string type = mline.substr(0, mline.find(' '));
 	if (type == "application") {
 	if (type == "application") {
@@ -302,6 +351,14 @@ bool Description::hasAudioOrVideo() const {
 	return false;
 	return false;
 }
 }
 
 
+bool Description::hasMid(string_view mid) const {
+	for (const auto &entry : mEntries)
+		if (entry->mid() == mid)
+			return true;
+
+	return false;
+}
+
 int Description::addMedia(Media media) {
 int Description::addMedia(Media media) {
 	mEntries.emplace_back(std::make_shared<Media>(std::move(media)));
 	mEntries.emplace_back(std::make_shared<Media>(std::move(media)));
 	return int(mEntries.size()) - 1;
 	return int(mEntries.size()) - 1;
@@ -363,7 +420,7 @@ Description::media(int index) const {
 	}
 	}
 }
 }
 
 
-unsigned int Description::mediaCount() const { return mEntries.size(); }
+size_t Description::mediaCount() const { return mEntries.size(); }
 
 
 Description::Entry::Entry(const string &mline, string mid, Direction dir)
 Description::Entry::Entry(const string &mline, string mid, Direction dir)
     : mMid(std::move(mid)), mDirection(dir) {
     : mMid(std::move(mid)), mDirection(dir) {
@@ -378,13 +435,12 @@ Description::Entry::Entry(const string &mline, string mid, Direction dir)
 
 
 void Description::Entry::setDirection(Direction dir) { mDirection = dir; }
 void Description::Entry::setDirection(Direction dir) { mDirection = dir; }
 
 
-Description::Entry::operator string() const { return generateSdp("\r\n"); }
+Description::Entry::operator string() const { return generateSdp("\r\n", "IP4 0.0.0.0", "9"); }
 
 
-string Description::Entry::generateSdp(string_view eol) const {
+string Description::Entry::generateSdp(string_view eol, string_view addr, string_view port) const {
 	std::ostringstream sdp;
 	std::ostringstream sdp;
-	// Port 9 is the discard protocol
-	sdp << "m=" << type() << ' ' << 9 << ' ' << description() << eol;
-	sdp << "c=IN IP4 0.0.0.0" << eol;
+	sdp << "m=" << type() << ' ' << port << ' ' << description() << eol;
+	sdp << "c=IN " << addr << eol;
 	sdp << generateSdpLines(eol);
 	sdp << generateSdpLines(eol);
 
 
 	return sdp.str();
 	return sdp.str();
@@ -515,9 +571,13 @@ void Description::Application::parseSdpLine(string_view line) {
 
 
 Description::Media::Media(const string &sdp) : Entry(sdp, "", Direction::Unknown) {
 Description::Media::Media(const string &sdp) : Entry(sdp, "", Direction::Unknown) {
 	std::istringstream ss(sdp);
 	std::istringstream ss(sdp);
-	string line;
-	while (std::getline(ss, line) || !line.empty()) {
+	while (ss) {
+		string line;
+		std::getline(ss, line);
 		trim_end(line);
 		trim_end(line);
+		if (line.empty())
+			continue;
+
 		parseSdpLine(line);
 		parseSdpLine(line);
 	}
 	}
 
 
@@ -526,8 +586,7 @@ Description::Media::Media(const string &sdp) : Entry(sdp, "", Direction::Unknown
 }
 }
 
 
 Description::Media::Media(const string &mline, string mid, Direction dir)
 Description::Media::Media(const string &mline, string mid, Direction dir)
-    : Entry(mline, std::move(mid), dir) {
-}
+    : Entry(mline, std::move(mid), dir) {}
 
 
 string Description::Media::description() const {
 string Description::Media::description() const {
 	std::ostringstream desc;
 	std::ostringstream desc;
@@ -796,38 +855,57 @@ Description::Video::Video(string mid, Direction dir)
     : Media("video 9 UDP/TLS/RTP/SAVPF", std::move(mid), dir) {}
     : Media("video 9 UDP/TLS/RTP/SAVPF", std::move(mid), dir) {}
 
 
 Description::Type Description::stringToType(const string &typeString) {
 Description::Type Description::stringToType(const string &typeString) {
-	if (typeString == "offer")
-		return Type::Offer;
-	else if (typeString == "answer")
-		return Type::Answer;
-	else
-		return Type::Unspec;
+	using TypeMap_t = std::unordered_map<string, Type>;
+	static const TypeMap_t TypeMap = {{"unspec", Type::Unspec},
+	                                  {"offer", Type::Offer},
+	                                  {"answer", Type::Pranswer},
+	                                  {"pranswer", Type::Pranswer},
+	                                  {"rollback", Type::Rollback}};
+	auto it = TypeMap.find(typeString);
+	return it != TypeMap.end() ? it->second : Type::Unspec;
 }
 }
 
 
 string Description::typeToString(Type type) {
 string Description::typeToString(Type type) {
 	switch (type) {
 	switch (type) {
+	case Type::Unspec:
+		return "unspec";
 	case Type::Offer:
 	case Type::Offer:
 		return "offer";
 		return "offer";
 	case Type::Answer:
 	case Type::Answer:
 		return "answer";
 		return "answer";
+	case Type::Pranswer:
+		return "pranswer";
+	case Type::Rollback:
+		return "rollback";
 	default:
 	default:
-		return "";
+		return "unknown";
 	}
 	}
 }
 }
 
 
-string Description::roleToString(Role role) {
+} // namespace rtc
+
+std::ostream &operator<<(std::ostream &out, const rtc::Description &description) {
+	return out << std::string(description);
+}
+
+std::ostream &operator<<(std::ostream &out, rtc::Description::Type type) {
+	return out << rtc::Description::typeToString(type);
+}
+
+std::ostream &operator<<(std::ostream &out, rtc::Description::Role role) {
+	using Role = rtc::Description::Role;
+	const char *str;
+	// Used for SDP generation, do not change
 	switch (role) {
 	switch (role) {
 	case Role::Active:
 	case Role::Active:
-		return "active";
+		str = "active";
+		break;
 	case Role::Passive:
 	case Role::Passive:
-		return "passive";
+		str = "passive";
+		break;
 	default:
 	default:
-		return "actpass";
+		str = "actpass";
+		break;
 	}
 	}
-}
-
-} // namespace rtc
-
-std::ostream &operator<<(std::ostream &out, const rtc::Description &description) {
-	return out << std::string(description);
+	return out << str;
 }
 }

+ 4 - 1
src/dtlssrtptransport.hpp

@@ -24,8 +24,11 @@
 
 
 #if RTC_ENABLE_MEDIA
 #if RTC_ENABLE_MEDIA
 
 
+#ifdef RTC_SRTP_FROM_SOURCE
 #include "srtp.h"
 #include "srtp.h"
-
+#else
+#include <srtp2/srtp.h>
+#endif
 #include <atomic>
 #include <atomic>
 
 
 namespace rtc {
 namespace rtc {

+ 8 - 9
src/dtlstransport.cpp

@@ -258,7 +258,7 @@ ssize_t DtlsTransport::WriteCallback(gnutls_transport_ptr_t ptr, const void *dat
 ssize_t DtlsTransport::ReadCallback(gnutls_transport_ptr_t ptr, void *data, size_t maxlen) {
 ssize_t DtlsTransport::ReadCallback(gnutls_transport_ptr_t ptr, void *data, size_t maxlen) {
 	DtlsTransport *t = static_cast<DtlsTransport *>(ptr);
 	DtlsTransport *t = static_cast<DtlsTransport *>(ptr);
 	if (auto next = t->mIncomingQueue.pop()) {
 	if (auto next = t->mIncomingQueue.pop()) {
-		auto message = *next;
+		message_ptr message = std::move(*next);
 		ssize_t len = std::min(maxlen, message->size());
 		ssize_t len = std::min(maxlen, message->size());
 		std::memcpy(data, message->data(), len);
 		std::memcpy(data, message->data(), len);
 		gnutls_transport_set_errno(t->mSession, 0);
 		gnutls_transport_set_errno(t->mSession, 0);
@@ -271,9 +271,9 @@ ssize_t DtlsTransport::ReadCallback(gnutls_transport_ptr_t ptr, void *data, size
 
 
 int DtlsTransport::TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms) {
 int DtlsTransport::TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms) {
 	DtlsTransport *t = static_cast<DtlsTransport *>(ptr);
 	DtlsTransport *t = static_cast<DtlsTransport *>(ptr);
-	t->mIncomingQueue.wait(ms != GNUTLS_INDEFINITE_TIMEOUT ? std::make_optional(milliseconds(ms))
-	                                                       : nullopt);
-	return !t->mIncomingQueue.empty() ? 1 : 0;
+	bool notEmpty = t->mIncomingQueue.wait(
+	    ms != GNUTLS_INDEFINITE_TIMEOUT ? std::make_optional(milliseconds(ms)) : nullopt);
+	return notEmpty ? 1 : 0;
 }
 }
 
 
 #else // USE_GNUTLS==0
 #else // USE_GNUTLS==0
@@ -435,10 +435,10 @@ void DtlsTransport::runRecvLoop() {
 
 
 		const size_t bufferSize = maxMtu;
 		const size_t bufferSize = maxMtu;
 		byte buffer[bufferSize];
 		byte buffer[bufferSize];
-		while (true) {
+		while (mIncomingQueue.running()) {
 			// Process pending messages
 			// Process pending messages
-			while (!mIncomingQueue.empty()) {
-				auto message = *mIncomingQueue.pop();
+			while (auto next = mIncomingQueue.tryPop()) {
+				message_ptr message = std::move(*next);
 				BIO_write(mInBio, message->data(), int(message->size()));
 				BIO_write(mInBio, message->data(), int(message->size()));
 
 
 				if (state() == State::Connecting) {
 				if (state() == State::Connecting) {
@@ -492,8 +492,7 @@ void DtlsTransport::runRecvLoop() {
 				}
 				}
 			}
 			}
 
 
-			if (!mIncomingQueue.wait(duration))
-				break; // queue is stopped
+			mIncomingQueue.wait(duration);
 		}
 		}
 	} catch (const std::exception &e) {
 	} catch (const std::exception &e) {
 		PLOG_ERROR << "DTLS recv: " << e.what();
 		PLOG_ERROR << "DTLS recv: " << e.what();

+ 65 - 49
src/icetransport.cpp

@@ -58,8 +58,32 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
 	if (config.enableIceTcp) {
 	if (config.enableIceTcp) {
 		PLOG_WARNING << "ICE-TCP is not supported with libjuice";
 		PLOG_WARNING << "ICE-TCP is not supported with libjuice";
 	}
 	}
+
+	juice_log_level_t level;
+	auto logger = plog::get();
+	switch (logger ? logger->getMaxSeverity() : plog::none) {
+	case plog::none:
+		level = JUICE_LOG_LEVEL_NONE;
+		break;
+	case plog::fatal:
+		level = JUICE_LOG_LEVEL_VERBOSE;
+		break;
+	case plog::error:
+		level = JUICE_LOG_LEVEL_ERROR;
+		break;
+	case plog::warning:
+		level = JUICE_LOG_LEVEL_WARN;
+		break;
+	case plog::info:
+	case plog::debug: // juice debug is output as verbose
+		level = JUICE_LOG_LEVEL_INFO;
+		break;
+	default:
+		level = JUICE_LOG_LEVEL_VERBOSE;
+		break;
+	}
 	juice_set_log_handler(IceTransport::LogCallback);
 	juice_set_log_handler(IceTransport::LogCallback);
-	juice_set_log_level(JUICE_LOG_LEVEL_VERBOSE);
+	juice_set_log_level(level);
 
 
 	juice_config_t jconfig = {};
 	juice_config_t jconfig = {};
 	jconfig.cb_state_changed = IceTransport::StateChangeCallback;
 	jconfig.cb_state_changed = IceTransport::StateChangeCallback;
@@ -163,6 +187,24 @@ std::optional<string> IceTransport::getRemoteAddress() const {
 	return nullopt;
 	return nullopt;
 }
 }
 
 
+bool IceTransport::getSelectedCandidatePair(Candidate *local, Candidate *remote) {
+	char sdpLocal[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
+	char sdpRemote[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
+	if (juice_get_selected_candidates(mAgent.get(), sdpLocal, JUICE_MAX_CANDIDATE_SDP_STRING_LEN,
+	                                 sdpRemote, JUICE_MAX_CANDIDATE_SDP_STRING_LEN) == 0) {
+		if (local) {
+			*local = Candidate(sdpLocal, mMid);
+			local->resolve(Candidate::ResolveMode::Simple);
+		}
+		if (remote) {
+			*remote = Candidate(sdpRemote, mMid);
+			remote->resolve(Candidate::ResolveMode::Simple);
+		}
+		return true;
+	}
+	return false;
+}
+
 bool IceTransport::send(message_ptr message) {
 bool IceTransport::send(message_ptr message) {
 	auto s = state();
 	auto s = state();
 	if (!message || (s != State::Connected && s != State::Completed))
 	if (!message || (s != State::Connected && s != State::Completed))
@@ -357,8 +399,11 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
 		hints.ai_protocol = IPPROTO_UDP;
 		hints.ai_protocol = IPPROTO_UDP;
 		hints.ai_flags = AI_ADDRCONFIG;
 		hints.ai_flags = AI_ADDRCONFIG;
 		struct addrinfo *result = nullptr;
 		struct addrinfo *result = nullptr;
-		if (getaddrinfo(server.hostname.c_str(), server.service.c_str(), &hints, &result) != 0)
+		if (getaddrinfo(server.hostname.c_str(), server.service.c_str(), &hints, &result) != 0) {
+			PLOG_WARNING << "Unable to resolve STUN server address: " << server.hostname << ':'
+			             << server.service;
 			continue;
 			continue;
+		}
 
 
 		for (auto p = result; p; p = p->ai_next) {
 		for (auto p = result; p; p = p->ai_next) {
 			if (p->ai_family == AF_INET) {
 			if (p->ai_family == AF_INET) {
@@ -400,8 +445,11 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
 		    server.relayType == IceServer::RelayType::TurnUdp ? IPPROTO_UDP : IPPROTO_TCP;
 		    server.relayType == IceServer::RelayType::TurnUdp ? IPPROTO_UDP : IPPROTO_TCP;
 		hints.ai_flags = AI_ADDRCONFIG;
 		hints.ai_flags = AI_ADDRCONFIG;
 		struct addrinfo *result = nullptr;
 		struct addrinfo *result = nullptr;
-		if (getaddrinfo(server.hostname.c_str(), server.service.c_str(), &hints, &result) != 0)
+		if (getaddrinfo(server.hostname.c_str(), server.service.c_str(), &hints, &result) != 0) {
+			PLOG_WARNING << "Unable to resolve TURN server address: " << server.hostname << ':'
+			             << server.service;
 			continue;
 			continue;
+		}
 
 
 		for (auto p = result; p; p = p->ai_next) {
 		for (auto p = result; p; p = p->ai_next) {
 			if (p->ai_family == AF_INET || p->ai_family == AF_INET6) {
 			if (p->ai_family == AF_INET || p->ai_family == AF_INET6) {
@@ -686,56 +734,24 @@ void IceTransport::LogCallback(const gchar * /*logDomain*/, GLogLevelFlags logLe
 	PLOG(severity) << "nice: " << message;
 	PLOG(severity) << "nice: " << message;
 }
 }
 
 
-bool IceTransport::getSelectedCandidatePair(CandidateInfo *localInfo, CandidateInfo *remoteInfo) {
-	NiceCandidate *local, *remote;
-	gboolean result = nice_agent_get_selected_pair(mNiceAgent.get(), mStreamId, 1, &local, &remote);
-
-	if (!result)
+bool IceTransport::getSelectedCandidatePair(Candidate *local, Candidate *remote) {
+	NiceCandidate *niceLocal, *niceRemote;
+	if(!nice_agent_get_selected_pair(mNiceAgent.get(), mStreamId, 1, &niceLocal, &niceRemote))
 		return false;
 		return false;
 
 
-	char ipaddr[INET6_ADDRSTRLEN];
-	nice_address_to_string(&local->addr, ipaddr);
-	localInfo->address = std::string(ipaddr);
-	localInfo->port = nice_address_get_port(&local->addr);
-	localInfo->type = IceTransport::NiceTypeToCandidateType(local->type);
-	localInfo->transportType =
-	    IceTransport::NiceTransportTypeToCandidateTransportType(local->transport);
-
-	nice_address_to_string(&remote->addr, ipaddr);
-	remoteInfo->address = std::string(ipaddr);
-	remoteInfo->port = nice_address_get_port(&remote->addr);
-	remoteInfo->type = IceTransport::NiceTypeToCandidateType(remote->type);
-	remoteInfo->transportType =
-	    IceTransport::NiceTransportTypeToCandidateTransportType(remote->transport);
+	gchar *sdpLocal = nice_agent_generate_local_candidate_sdp(mNiceAgent.get(), niceLocal);
+	if(local) *local = Candidate(sdpLocal, mMid);
+	g_free(sdpLocal);
 
 
-	return true;
-}
+	gchar *sdpRemote = nice_agent_generate_local_candidate_sdp(mNiceAgent.get(), niceRemote);
+	if(remote) *remote = Candidate(sdpRemote, mMid);
+	g_free(sdpRemote);
 
 
-CandidateType IceTransport::NiceTypeToCandidateType(NiceCandidateType type) {
-	switch (type) {
-	case NiceCandidateType::NICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
-		return CandidateType::PeerReflexive;
-	case NiceCandidateType::NICE_CANDIDATE_TYPE_RELAYED:
-		return CandidateType::Relayed;
-	case NiceCandidateType::NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
-		return CandidateType::ServerReflexive;
-	default:
-		return CandidateType::Host;
-	}
-}
-
-CandidateTransportType
-IceTransport::NiceTransportTypeToCandidateTransportType(NiceCandidateTransport type) {
-	switch (type) {
-	case NiceCandidateTransport::NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE:
-		return CandidateTransportType::TcpActive;
-	case NiceCandidateTransport::NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE:
-		return CandidateTransportType::TcpPassive;
-	case NiceCandidateTransport::NICE_CANDIDATE_TRANSPORT_TCP_SO:
-		return CandidateTransportType::TcpSo;
-	default:
-		return CandidateTransportType::Udp;
-	}
+	if (local)
+		local->resolve(Candidate::ResolveMode::Simple);
+	if (remote)
+		remote->resolve(Candidate::ResolveMode::Simple);
+	return true;
 }
 }
 
 
 } // namespace rtc
 } // namespace rtc

+ 1 - 6
src/icetransport.hpp

@@ -63,9 +63,7 @@ public:
 	bool stop() override;
 	bool stop() override;
 	bool send(message_ptr message) override; // false if dropped
 	bool send(message_ptr message) override; // false if dropped
 
 
-#if USE_NICE
-	bool getSelectedCandidatePair(CandidateInfo *local, CandidateInfo *remote);
-#endif
+	bool getSelectedCandidatePair(Candidate *local, Candidate *remote);
 
 
 private:
 private:
 	bool outgoing(message_ptr message) override;
 	bool outgoing(message_ptr message) override;
@@ -113,9 +111,6 @@ private:
 	static gboolean TimeoutCallback(gpointer userData);
 	static gboolean TimeoutCallback(gpointer userData);
 	static void LogCallback(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message,
 	static void LogCallback(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message,
 	                        gpointer user_data);
 	                        gpointer user_data);
-	static CandidateType NiceTypeToCandidateType(NiceCandidateType type);
-	static CandidateTransportType
-	NiceTransportTypeToCandidateTransportType(NiceCandidateTransport type);
 #endif
 #endif
 };
 };
 
 

+ 4 - 0
src/log.cpp

@@ -24,6 +24,8 @@
 #include "plog/Log.h"
 #include "plog/Log.h"
 #include "plog/Logger.h"
 #include "plog/Logger.h"
 
 
+#include <mutex>
+
 namespace rtc {
 namespace rtc {
 
 
 void InitLogger(LogLevel level) { InitLogger(static_cast<plog::Severity>(level)); }
 void InitLogger(LogLevel level) { InitLogger(static_cast<plog::Severity>(level)); }
@@ -31,6 +33,8 @@ void InitLogger(LogLevel level) { InitLogger(static_cast<plog::Severity>(level))
 void InitLogger(plog::Severity severity, plog::IAppender *appender) {
 void InitLogger(plog::Severity severity, plog::IAppender *appender) {
 	static plog::ColorConsoleAppender<plog::TxtFormatter> consoleAppender;
 	static plog::ColorConsoleAppender<plog::TxtFormatter> consoleAppender;
 	static plog::Logger<0> *logger = nullptr;
 	static plog::Logger<0> *logger = nullptr;
+	static std::mutex mutex;
+	std::lock_guard lock(mutex);
 	if (!logger) {
 	if (!logger) {
 		logger = &plog::init(severity, appender ? appender : &consoleAppender);
 		logger = &plog::init(severity, appender ? appender : &consoleAppender);
 		PLOG_DEBUG << "Logger initialized";
 		PLOG_DEBUG << "Logger initialized";

+ 388 - 160
src/peerconnection.cpp

@@ -44,7 +44,8 @@ PeerConnection::PeerConnection() : PeerConnection(Configuration()) {}
 
 
 PeerConnection::PeerConnection(const Configuration &config)
 PeerConnection::PeerConnection(const Configuration &config)
     : mConfig(config), mCertificate(make_certificate()), mProcessor(std::make_unique<Processor>()),
     : mConfig(config), mCertificate(make_certificate()), mProcessor(std::make_unique<Processor>()),
-      mState(State::New), mGatheringState(GatheringState::New) {
+      mState(State::New), mGatheringState(GatheringState::New),
+      mSignalingState(SignalingState::Stable), mNegotiationNeeded(false) {
 	PLOG_VERBOSE << "Creating PeerConnection";
 	PLOG_VERBOSE << "Creating PeerConnection";
 
 
 	if (config.portRangeEnd && config.portRangeBegin > config.portRangeEnd)
 	if (config.portRangeEnd && config.portRangeBegin > config.portRangeEnd)
@@ -60,6 +61,8 @@ PeerConnection::~PeerConnection() {
 void PeerConnection::close() {
 void PeerConnection::close() {
 	PLOG_VERBOSE << "Closing PeerConnection";
 	PLOG_VERBOSE << "Closing PeerConnection";
 
 
+	mNegotiationNeeded = false;
+
 	// Close data channels asynchronously
 	// Close data channels asynchronously
 	mProcessor->enqueue(std::bind(&PeerConnection::closeDataChannels, this));
 	mProcessor->enqueue(std::bind(&PeerConnection::closeDataChannels, this));
 
 
@@ -72,6 +75,8 @@ PeerConnection::State PeerConnection::state() const { return mState; }
 
 
 PeerConnection::GatheringState PeerConnection::gatheringState() const { return mGatheringState; }
 PeerConnection::GatheringState PeerConnection::gatheringState() const { return mGatheringState; }
 
 
+PeerConnection::SignalingState PeerConnection::signalingState() const { return mSignalingState; }
+
 std::optional<Description> PeerConnection::localDescription() const {
 std::optional<Description> PeerConnection::localDescription() const {
 	std::lock_guard lock(mLocalDescriptionMutex);
 	std::lock_guard lock(mLocalDescriptionMutex);
 	return mLocalDescription;
 	return mLocalDescription;
@@ -82,66 +87,194 @@ std::optional<Description> PeerConnection::remoteDescription() const {
 	return mRemoteDescription;
 	return mRemoteDescription;
 }
 }
 
 
-void PeerConnection::setLocalDescription() {
-	PLOG_VERBOSE << "Setting local description";
-
-	if (!std::atomic_load(&mIceTransport)) {
-        // RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of
-        // setup:actpass.
-        // See https://tools.ietf.org/html/rfc5763#section-5
-        auto iceTransport = initIceTransport(Description::Role::ActPass);
-        Description localDescription = iceTransport->getLocalDescription(Description::Type::Offer);
-        processLocalDescription(localDescription);
-        iceTransport->gatherLocalCandidates();
-	} else {
-	    auto localDescription = std::atomic_load(&mIceTransport)->getLocalDescription(Description::Type::Offer);
-        processLocalDescription(localDescription);
+bool PeerConnection::hasLocalDescription() const {
+	std::lock_guard lock(mLocalDescriptionMutex);
+	return bool(mLocalDescription);
+}
+
+bool PeerConnection::hasRemoteDescription() const {
+	std::lock_guard lock(mRemoteDescriptionMutex);
+	return bool(mRemoteDescription);
+}
+
+bool PeerConnection::hasMedia() const {
+	auto local = localDescription();
+	return local && local->hasAudioOrVideo();
+}
+
+void PeerConnection::setLocalDescription(Description::Type type) {
+	PLOG_VERBOSE << "Setting local description, type=" << Description::typeToString(type);
+
+
+	SignalingState signalingState = mSignalingState.load();
+	if (type == Description::Type::Rollback) {
+		if (signalingState == SignalingState::HaveLocalOffer ||
+		    signalingState == SignalingState::HaveLocalPranswer) {
+			PLOG_DEBUG << "Rolling back pending local description";
+
+			std::unique_lock lock(mLocalDescriptionMutex);
+			if (mCurrentLocalDescription) {
+				std::vector<Candidate> existingCandidates;
+				if (mLocalDescription)
+					existingCandidates = mLocalDescription->extractCandidates();
+
+				mLocalDescription.emplace(std::move(*mCurrentLocalDescription));
+				mLocalDescription->addCandidates(std::move(existingCandidates));
+				mCurrentLocalDescription.reset();
+			}
+			lock.unlock();
+
+			changeSignalingState(SignalingState::Stable);
+		}
+		return;
+	}
+
+	// Guess the description type if unspecified
+	if (type == Description::Type::Unspec) {
+		if (mSignalingState == SignalingState::HaveRemoteOffer)
+			type = Description::Type::Answer;
+		else
+			type = Description::Type::Offer;
+	}
+
+	// Only a local offer resets the negotiation needed flag
+	if (type == Description::Type::Offer && !mNegotiationNeeded.exchange(false)) {
+		PLOG_DEBUG << "No negotiation needed";
+		return;
 	}
 	}
+
+	// Get the new signaling state
+	SignalingState newSignalingState;
+	switch (signalingState) {
+	case SignalingState::Stable:
+		if (type != Description::Type::Offer) {
+			std::ostringstream oss;
+			oss << "Unexpected local desciption type " << type << " in signaling state "
+			    << signalingState;
+			throw std::logic_error(oss.str());
+		}
+		newSignalingState = SignalingState::HaveLocalOffer;
+		break;
+
+	case SignalingState::HaveRemoteOffer:
+	case SignalingState::HaveLocalPranswer:
+		if (type != Description::Type::Answer && type != Description::Type::Pranswer) {
+			std::ostringstream oss;
+			oss << "Unexpected local description type " << type
+			    << " description in signaling state " << signalingState;
+			throw std::logic_error(oss.str());
+		}
+		newSignalingState = SignalingState::Stable;
+		break;
+
+	default: {
+		std::ostringstream oss;
+		oss << "Unexpected local description in signaling state " << signalingState << ", ignoring";
+		LOG_WARNING << oss.str();
+		return;
+	}
+	}
+
+	auto iceTransport = std::atomic_load(&mIceTransport);
+	if (!iceTransport) {
+		// RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of
+		// setup:actpass.
+		// See https://tools.ietf.org/html/rfc5763#section-5
+		iceTransport = initIceTransport(Description::Role::ActPass);
+	}
+
+	Description localDescription = iceTransport->getLocalDescription(type);
+	processLocalDescription(std::move(localDescription));
+
+	changeSignalingState(newSignalingState);
+
+	if (mGatheringState == GatheringState::New)
+		iceTransport->gatherLocalCandidates();
 }
 }
 
 
 void PeerConnection::setRemoteDescription(Description description) {
 void PeerConnection::setRemoteDescription(Description description) {
 	PLOG_VERBOSE << "Setting remote description: " << string(description);
 	PLOG_VERBOSE << "Setting remote description: " << string(description);
 
 
-	if (description.mediaCount() == 0)
-		throw std::invalid_argument("Remote description has no media line");
+	if (description.type() == Description::Type::Rollback) {
+		// This is mostly useless because we accept any offer
+		PLOG_VERBOSE << "Rolling back pending remote description";
+		changeSignalingState(SignalingState::Stable);
+		return;
+	}
 
 
-	int activeMediaCount = 0;
-	for (unsigned int i = 0; i < description.mediaCount(); ++i)
-		std::visit( // reciprocate each media
-		    rtc::overloaded{[&](Description::Application *) { ++activeMediaCount; },
-		                    [&](Description::Media *media) {
-			                    if (media->direction() != Description::Direction::Inactive)
-				                    ++activeMediaCount;
-		                    }},
-		    description.media(i));
-
-//	if (activeMediaCount == 0)
-//		throw std::invalid_argument("Remote description has no active media");
+	validateRemoteDescription(description);
+
+	// Get the new signaling state
+	SignalingState signalingState = mSignalingState.load();
+	SignalingState newSignalingState;
+	switch (signalingState) {
+	case SignalingState::Stable:
+		description.hintType(Description::Type::Offer);
+		if (description.type() != Description::Type::Offer) {
+			std::ostringstream oss;
+			oss << "Unexpected remote " << description.type() << " description in signaling state "
+			    << signalingState;
+			throw std::logic_error(oss.str());
+		}
+		newSignalingState = SignalingState::HaveRemoteOffer;
+		break;
 
 
-	if (!description.fingerprint())
-		throw std::invalid_argument("Remote description has no fingerprint");
+	case SignalingState::HaveLocalOffer:
+		description.hintType(Description::Type::Answer);
+		if (description.type() == Description::Type::Offer) {
+			// The ICE agent will automatically initiate a rollback when a peer that had previously
+			// created an offer receives an offer from the remote peer
+			setLocalDescription(Description::Type::Rollback);
+			newSignalingState = SignalingState::HaveRemoteOffer;
+			break;
+		}
+		if (description.type() != Description::Type::Answer &&
+		    description.type() != Description::Type::Pranswer) {
+			std::ostringstream oss;
+			oss << "Unexpected remote " << description.type() << " description in signaling state "
+			    << signalingState;
+			throw std::logic_error(oss.str());
+		}
+		newSignalingState = SignalingState::Stable;
+		break;
+
+	case SignalingState::HaveRemotePranswer:
+		description.hintType(Description::Type::Answer);
+		if (description.type() != Description::Type::Answer &&
+		    description.type() != Description::Type::Pranswer) {
+			std::ostringstream oss;
+			oss << "Unexpected remote " << description.type() << " description in signaling state "
+			    << signalingState;
+			throw std::logic_error(oss.str());
+		}
+		newSignalingState = SignalingState::Stable;
+		break;
+
+	default: {
+		std::ostringstream oss;
+		oss << "Unexpected remote description in signaling state " << signalingState;
+		throw std::logic_error(oss.str());
+	}
+	}
 
 
-	description.hintType(localDescription() ? Description::Type::Answer : Description::Type::Offer);
+	// Candidates will be added at the end, extract them for now
+	auto remoteCandidates = description.extractCandidates();
 	auto type = description.type();
 	auto type = description.type();
-	auto remoteCandidates = description.extractCandidates(); // Candidates will be added at the end
 
 
 	auto iceTransport = std::atomic_load(&mIceTransport);
 	auto iceTransport = std::atomic_load(&mIceTransport);
 	if (!iceTransport)
 	if (!iceTransport)
 		iceTransport = initIceTransport(Description::Role::ActPass);
 		iceTransport = initIceTransport(Description::Role::ActPass);
+
 	iceTransport->setRemoteDescription(description);
 	iceTransport->setRemoteDescription(description);
+	processRemoteDescription(std::move(description));
 
 
-	{
-		std::lock_guard lock(mRemoteDescriptionMutex);
-		mRemoteDescription.emplace(std::move(description));
-	}
+	changeSignalingState(newSignalingState);
 
 
 	if (type == Description::Type::Offer) {
 	if (type == Description::Type::Offer) {
-		// This is an offer and we are the answerer.
-		Description localDescription = iceTransport->getLocalDescription(Description::Type::Answer);
-		processLocalDescription(localDescription);
-		iceTransport->gatherLocalCandidates();
+		// This is an offer, we need to answer
+		setLocalDescription(Description::Type::Answer);
 	} else {
 	} else {
-		// This is an answer and we are the offerer.
+		// This is an answer
 		auto sctpTransport = std::atomic_load(&mSctpTransport);
 		auto sctpTransport = std::atomic_load(&mSctpTransport);
 		if (!sctpTransport && iceTransport->role() == Description::Role::Active) {
 		if (!sctpTransport && iceTransport->role() == Description::Role::Active) {
 			// Since we assumed passive role during DataChannel creation, we need to shift the
 			// Since we assumed passive role during DataChannel creation, we need to shift the
@@ -162,34 +295,11 @@ void PeerConnection::setRemoteDescription(Description description) {
 
 
 	for (const auto &candidate : remoteCandidates)
 	for (const auto &candidate : remoteCandidates)
 		addRemoteCandidate(candidate);
 		addRemoteCandidate(candidate);
-	if (auto transport = std::atomic_load(&mDtlsTransport); transport && transport->state() == rtc::DtlsTransport::State::Connected) {
-        openTracks();
-    }
 }
 }
 
 
 void PeerConnection::addRemoteCandidate(Candidate candidate) {
 void PeerConnection::addRemoteCandidate(Candidate candidate) {
 	PLOG_VERBOSE << "Adding remote candidate: " << string(candidate);
 	PLOG_VERBOSE << "Adding remote candidate: " << string(candidate);
-
-	auto iceTransport = std::atomic_load(&mIceTransport);
-	if (!mRemoteDescription || !iceTransport)
-		throw std::logic_error("Remote candidate set without remote description");
-
-	if (candidate.resolve(Candidate::ResolveMode::Simple)) {
-		iceTransport->addRemoteCandidate(candidate);
-	} else {
-		// OK, we might need a lookup, do it asynchronously
-		// We don't use the thread pool because we have no control on the timeout
-		weak_ptr<IceTransport> weakIceTransport{iceTransport};
-		std::thread t([weakIceTransport, candidate]() mutable {
-			if (candidate.resolve(Candidate::ResolveMode::Lookup))
-				if (auto iceTransport = weakIceTransport.lock())
-					iceTransport->addRemoteCandidate(candidate);
-		});
-		t.detach();
-	}
-
-	std::lock_guard lock(mRemoteDescriptionMutex);
-	mRemoteDescription->addCandidate(candidate);
+	processRemoteCandidate(std::move(candidate));
 }
 }
 
 
 std::optional<string> PeerConnection::localAddress() const {
 std::optional<string> PeerConnection::localAddress() const {
@@ -204,11 +314,6 @@ std::optional<string> PeerConnection::remoteAddress() const {
 
 
 shared_ptr<DataChannel> PeerConnection::addDataChannel(string label, string protocol,
 shared_ptr<DataChannel> PeerConnection::addDataChannel(string label, string protocol,
                                                        Reliability reliability) {
                                                        Reliability reliability) {
-	if (auto local = localDescription(); local && !local->hasApplication()) {
-		PLOG_ERROR << "The PeerConnection was negociated without DataChannel support.";
-		throw std::runtime_error("No DataChannel support on the PeerConnection");
-	}
-
 	// RFC 5763: The answerer MUST use either a setup attribute value of setup:active or
 	// RFC 5763: The answerer MUST use either a setup attribute value of setup:active or
 	// setup:passive. [...] Thus, setup:active is RECOMMENDED.
 	// setup:passive. [...] Thus, setup:active is RECOMMENDED.
 	// See https://tools.ietf.org/html/rfc5763#section-5
 	// See https://tools.ietf.org/html/rfc5763#section-5
@@ -223,6 +328,11 @@ shared_ptr<DataChannel> PeerConnection::addDataChannel(string label, string prot
 		if (transport->state() == SctpTransport::State::Connected)
 		if (transport->state() == SctpTransport::State::Connected)
 			channel->open(transport);
 			channel->open(transport);
 
 
+	// Renegotiation is needed iff the current local description does not have application
+	std::lock_guard lock(mLocalDescriptionMutex);
+	if (!mLocalDescription || !mLocalDescription->hasApplication())
+		mNegotiationNeeded = true;
+
 	return channel;
 	return channel;
 }
 }
 
 
@@ -254,14 +364,11 @@ void PeerConnection::onGatheringStateChange(std::function<void(GatheringState st
 	mGatheringStateChangeCallback = callback;
 	mGatheringStateChangeCallback = callback;
 }
 }
 
 
-bool PeerConnection::hasMedia() const {
-	auto local = localDescription();
-	return local && local->hasAudioOrVideo();
+void PeerConnection::onSignalingStateChange(std::function<void(SignalingState state)> callback) {
+	mSignalingStateChangeCallback = callback;
 }
 }
 
 
 std::shared_ptr<Track> PeerConnection::addTrack(Description::Media description) {
 std::shared_ptr<Track> PeerConnection::addTrack(Description::Media description) {
-//	if (localDescription())
-//		throw std::logic_error("Tracks must be created before local description");
 
 
 	if (auto it = mTracks.find(description.mid()); it != mTracks.end())
 	if (auto it = mTracks.find(description.mid()); it != mTracks.end())
 		if (auto track = it->second.lock())
 		if (auto track = it->second.lock())
@@ -274,7 +381,10 @@ std::shared_ptr<Track> PeerConnection::addTrack(Description::Media description)
 #endif
 #endif
 	auto track = std::make_shared<Track>(std::move(description));
 	auto track = std::make_shared<Track>(std::move(description));
 	mTracks.emplace(std::make_pair(track->mid(), track));
 	mTracks.emplace(std::make_pair(track->mid(), track));
-	mTrackLines.emplace_back(track);
+
+	// Renegotiation is needed for the new track
+	mNegotiationNeeded = true;
+
 	return track;
 	return track;
 }
 }
 
 
@@ -283,6 +393,7 @@ void PeerConnection::onTrack(std::function<void(std::shared_ptr<Track>)> callbac
 }
 }
 
 
 shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role) {
 shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role) {
+	PLOG_VERBOSE << "Starting ICE transport";
 	try {
 	try {
 		if (auto transport = std::atomic_load(&mIceTransport))
 		if (auto transport = std::atomic_load(&mIceTransport))
 			return transport;
 			return transport;
@@ -345,6 +456,7 @@ shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role
 }
 }
 
 
 shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
 shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
+	PLOG_VERBOSE << "Starting DTLS transport";
 	try {
 	try {
 		if (auto transport = std::atomic_load(&mDtlsTransport))
 		if (auto transport = std::atomic_load(&mDtlsTransport))
 			return transport;
 			return transport;
@@ -360,12 +472,12 @@ shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
 
 
 			switch (state) {
 			switch (state) {
 			case DtlsTransport::State::Connected:
 			case DtlsTransport::State::Connected:
-				if (auto local = localDescription(); local && local->hasApplication())
+				if (auto remote = remoteDescription(); remote && remote->hasApplication())
 					initSctpTransport();
 					initSctpTransport();
 				else
 				else
 					changeState(State::Connected);
 					changeState(State::Connected);
 
 
-				openTracks();
+				mProcessor->enqueue(std::bind(&PeerConnection::openTracks, this));
 				break;
 				break;
 			case DtlsTransport::State::Failed:
 			case DtlsTransport::State::Failed:
 				changeState(State::Failed);
 				changeState(State::Failed);
@@ -415,13 +527,14 @@ shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
 }
 }
 
 
 shared_ptr<SctpTransport> PeerConnection::initSctpTransport() {
 shared_ptr<SctpTransport> PeerConnection::initSctpTransport() {
+	PLOG_VERBOSE << "Starting SCTP transport";
 	try {
 	try {
 		if (auto transport = std::atomic_load(&mSctpTransport))
 		if (auto transport = std::atomic_load(&mSctpTransport))
 			return transport;
 			return transport;
 
 
 		auto remote = remoteDescription();
 		auto remote = remoteDescription();
 		if (!remote || !remote->application())
 		if (!remote || !remote->application())
-			throw std::logic_error("Initializing SCTP transport without application description");
+			throw std::logic_error("Starting SCTP transport without application description");
 
 
 		uint16_t sctpPort = remote->application()->sctpPort().value_or(DEFAULT_SCTP_PORT);
 		uint16_t sctpPort = remote->application()->sctpPort().value_or(DEFAULT_SCTP_PORT);
 		auto lower = std::atomic_load(&mDtlsTransport);
 		auto lower = std::atomic_load(&mDtlsTransport);
@@ -435,16 +548,16 @@ shared_ptr<SctpTransport> PeerConnection::initSctpTransport() {
 			    switch (state) {
 			    switch (state) {
 			    case SctpTransport::State::Connected:
 			    case SctpTransport::State::Connected:
 				    changeState(State::Connected);
 				    changeState(State::Connected);
-				    openDataChannels();
+				    mProcessor->enqueue(std::bind(&PeerConnection::openDataChannels, this));
 				    break;
 				    break;
 			    case SctpTransport::State::Failed:
 			    case SctpTransport::State::Failed:
 				    LOG_WARNING << "SCTP transport failed";
 				    LOG_WARNING << "SCTP transport failed";
-				    remoteCloseDataChannels();
 				    changeState(State::Failed);
 				    changeState(State::Failed);
+				    mProcessor->enqueue(std::bind(&PeerConnection::remoteCloseDataChannels, this));
 				    break;
 				    break;
 			    case SctpTransport::State::Disconnected:
 			    case SctpTransport::State::Disconnected:
-				    remoteCloseDataChannels();
 				    changeState(State::Disconnected);
 				    changeState(State::Disconnected);
+				    mProcessor->enqueue(std::bind(&PeerConnection::remoteCloseDataChannels, this));
 				    break;
 				    break;
 			    default:
 			    default:
 				    // Ignore
 				    // Ignore
@@ -471,7 +584,8 @@ void PeerConnection::closeTransports() {
 	PLOG_VERBOSE << "Closing transports";
 	PLOG_VERBOSE << "Closing transports";
 
 
 	// Change state to sink state Closed
 	// Change state to sink state Closed
-	changeState(State::Closed);
+	if (!changeState(State::Closed))
+		return; // already closed
 
 
 	// Reset callbacks now that state is changed
 	// Reset callbacks now that state is changed
 	resetCallbacks();
 	resetCallbacks();
@@ -519,17 +633,17 @@ void PeerConnection::forwardMessage(message_ptr message) {
 	}
 	}
 
 
 	auto channel = findDataChannel(uint16_t(message->stream));
 	auto channel = findDataChannel(uint16_t(message->stream));
-
-	auto iceTransport = std::atomic_load(&mIceTransport);
-	auto sctpTransport = std::atomic_load(&mSctpTransport);
-	if (!iceTransport || !sctpTransport)
-		return;
-
 	if (!channel) {
 	if (!channel) {
+		auto iceTransport = std::atomic_load(&mIceTransport);
+		auto sctpTransport = std::atomic_load(&mSctpTransport);
+		if (!iceTransport || !sctpTransport)
+			return;
+
 		const byte dataChannelOpenMessage{0x03};
 		const byte dataChannelOpenMessage{0x03};
 		unsigned int remoteParity = (iceTransport->role() == Description::Role::Active) ? 1 : 0;
 		unsigned int remoteParity = (iceTransport->role() == Description::Role::Active) ? 1 : 0;
 		if (message->type == Message::Control && *message->data() == dataChannelOpenMessage &&
 		if (message->type == Message::Control && *message->data() == dataChannelOpenMessage &&
 		    message->stream % 2 == remoteParity) {
 		    message->stream % 2 == remoteParity) {
+
 			channel =
 			channel =
 			    std::make_shared<DataChannel>(shared_from_this(), sctpTransport, message->stream);
 			    std::make_shared<DataChannel>(shared_from_this(), sctpTransport, message->stream);
 			channel->onOpen(weak_bind(&PeerConnection::triggerDataChannel, this,
 			channel->onOpen(weak_bind(&PeerConnection::triggerDataChannel, this,
@@ -792,34 +906,49 @@ void PeerConnection::openTracks() {
 	if (auto transport = std::atomic_load(&mDtlsTransport)) {
 	if (auto transport = std::atomic_load(&mDtlsTransport)) {
 		auto srtpTransport = std::reinterpret_pointer_cast<DtlsSrtpTransport>(transport);
 		auto srtpTransport = std::reinterpret_pointer_cast<DtlsSrtpTransport>(transport);
 		std::shared_lock lock(mTracksMutex); // read-only
 		std::shared_lock lock(mTracksMutex); // read-only
-            for (unsigned int i = 0; i < mTrackLines.size(); i++) {
-                if (auto track = mTrackLines[i].lock()) {
-//                    srtpTransport->addSSRC(0);
-
-//                    for (auto ssrc : track->description().getSSRCs()) {
-//                        PLOG_DEBUG << "Adding " << ssrc << " to list";
-//                        srtpTransport->addSSRC(ssrc);
-//                    }
-//                    for (auto ssrc : std::get<rtc::Description::Media *>(remoteDescription()->media(i))->getSSRCs()) {
-//                        PLOG_DEBUG << "Adding " << ssrc << " to list";
-//                        srtpTransport->addSSRC(ssrc);
-//                    }
-
-                if (!track->isOpen()) {
-                    track->open(srtpTransport);
-                }
-            }
-        }
+		for (auto it = mTracks.begin(); it != mTracks.end(); ++it)
+			if (auto track = it->second.lock())
+				if (!track->isOpen())
+					track->open(srtpTransport);
 	}
 	}
 #endif
 #endif
 }
 }
 
 
+void PeerConnection::validateRemoteDescription(const Description &description) {
+	if (!description.iceUfrag())
+		throw std::invalid_argument("Remote description has no ICE user fragment");
+
+	if (!description.icePwd())
+		throw std::invalid_argument("Remote description has no ICE password");
+
+	if (!description.fingerprint())
+		throw std::invalid_argument("Remote description has no fingerprint");
+
+	if (description.mediaCount() == 0)
+		throw std::invalid_argument("Remote description has no media line");
 
 
-void PeerConnection::processLocalDescription(Description description) {
 	int activeMediaCount = 0;
 	int activeMediaCount = 0;
+	for (size_t i = 0; i < description.mediaCount(); ++i)
+		std::visit(rtc::overloaded{[&](const Description::Application *) { ++activeMediaCount; },
+		                           [&](const Description::Media *media) {
+			                           if (media->direction() != Description::Direction::Inactive)
+				                           ++activeMediaCount;
+		                           }},
+		           description.media(i));
+
+	if (activeMediaCount == 0)
+		throw std::invalid_argument("Remote description has no active media");
+
+	if (auto local = localDescription(); local && local->iceUfrag() && local->icePwd())
+		if (*description.iceUfrag() == *local->iceUfrag() &&
+		    *description.icePwd() == *local->icePwd())
+			throw std::logic_error("Got the local description as remote description");
+
+	PLOG_VERBOSE << "Remote description looks valid";
+}
 
 
-    auto remote = remoteDescription();
-	if (remote && remote->type() == Description::Type::Offer) {
+void PeerConnection::processLocalDescription(Description description) {
+	if (auto remote = remoteDescription()) {
 		// Reciprocate remote description
 		// Reciprocate remote description
 		for (unsigned int i = 0; i < remote->mediaCount(); ++i)
 		for (unsigned int i = 0; i < remote->mediaCount(); ++i)
 			std::visit( // reciprocate each media
 			std::visit( // reciprocate each media
@@ -828,7 +957,6 @@ void PeerConnection::processLocalDescription(Description description) {
 				        auto reciprocated = app->reciprocate();
 				        auto reciprocated = app->reciprocate();
 				        reciprocated.hintSctpPort(DEFAULT_SCTP_PORT);
 				        reciprocated.hintSctpPort(DEFAULT_SCTP_PORT);
 				        reciprocated.setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE);
 				        reciprocated.setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE);
-				        ++activeMediaCount;
 
 
 				        PLOG_DEBUG << "Reciprocating application in local description, mid=\""
 				        PLOG_DEBUG << "Reciprocating application in local description, mid=\""
 				                   << reciprocated.mid() << "\"";
 				                   << reciprocated.mid() << "\"";
@@ -837,10 +965,7 @@ void PeerConnection::processLocalDescription(Description description) {
 			        },
 			        },
 			        [&](Description::Media *media) {
 			        [&](Description::Media *media) {
 				        auto reciprocated = media->reciprocate();
 				        auto reciprocated = media->reciprocate();
-#if RTC_ENABLE_MEDIA
-				        if (reciprocated.direction() != Description::Direction::Inactive)
-					        ++activeMediaCount;
-#else
+#if !RTC_ENABLE_MEDIA
 				        // No media support, mark as inactive
 				        // No media support, mark as inactive
 				        reciprocated.setDirection(Description::Direction::Inactive);
 				        reciprocated.setDirection(Description::Direction::Inactive);
 #endif
 #endif
@@ -855,15 +980,17 @@ void PeerConnection::processLocalDescription(Description description) {
 			        },
 			        },
 			    },
 			    },
 			    remote->media(i));
 			    remote->media(i));
-	} else {
+	}
+
+	if (description.type() == Description::Type::Offer) {
+		// This is an offer, add locally created data channels and tracks
 		// Add application for data channels
 		// Add application for data channels
-		{
+		if (!description.hasApplication()) {
 			std::shared_lock lock(mDataChannelsMutex);
 			std::shared_lock lock(mDataChannelsMutex);
 			if (!mDataChannels.empty()) {
 			if (!mDataChannels.empty()) {
 				Description::Application app("data");
 				Description::Application app("data");
 				app.setSctpPort(DEFAULT_SCTP_PORT);
 				app.setSctpPort(DEFAULT_SCTP_PORT);
 				app.setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE);
 				app.setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE);
-				++activeMediaCount;
 
 
 				PLOG_DEBUG << "Adding application to local description, mid=\"" << app.mid()
 				PLOG_DEBUG << "Adding application to local description, mid=\"" << app.mid()
 				           << "\"";
 				           << "\"";
@@ -873,43 +1000,53 @@ void PeerConnection::processLocalDescription(Description description) {
 		}
 		}
 
 
 		// Add media for local tracks
 		// Add media for local tracks
-		{
-			std::shared_lock lock(mTracksMutex);
-//			for (auto it = mTracks.begin(); it != mTracks.end(); ++it) {
-            for (auto ptr : mTrackLines) {
-				if (auto track = ptr.lock()) {
-					auto media = track->description();
-#if RTC_ENABLE_MEDIA
-					if (media.direction() != Description::Direction::Inactive)
-						++activeMediaCount;
-#else
-					// No media support, mark as inactive
-					media.setDirection(Description::Direction::Inactive);
+
+		std::shared_lock lock(mTracksMutex);
+		for (auto it = mTracks.begin(); it != mTracks.end(); ++it) {
+			if (description.hasMid(it->first))
+				continue;
+
+			if (auto track = it->second.lock()) {
+				auto media = track->description();
+#if !RTC_ENABLE_MEDIA
+				// No media support, mark as inactive
+				media.setDirection(Description::Direction::Inactive);
 #endif
 #endif
-					PLOG_DEBUG << "Adding media to local description, mid=\"" << media.mid()
-					           << "\", active=" << std::boolalpha
-					           << (media.direction() != Description::Direction::Inactive);
+				PLOG_DEBUG << "Adding media to local description, mid=\"" << media.mid()
+				           << "\", active=" << std::boolalpha
+				           << (media.direction() != Description::Direction::Inactive);
 
 
-					description.addMedia(std::move(media));
-				}
+				description.addMedia(std::move(media));
 			}
 			}
 		}
 		}
 	}
 	}
 
 
-	// There must be at least one active media to negociate
-//	if (activeMediaCount == 0)
-//		throw std::runtime_error("Nothing to negociate");
-
 	// Set local fingerprint (wait for certificate if necessary)
 	// Set local fingerprint (wait for certificate if necessary)
 	description.setFingerprint(mCertificate.get()->fingerprint());
 	description.setFingerprint(mCertificate.get()->fingerprint());
 
 
-	std::lock_guard lock(mLocalDescriptionMutex);
-	mLocalDescription.emplace(std::move(description));
+	{
+		// Set as local description
+		std::lock_guard lock(mLocalDescriptionMutex);
+
+		std::vector<Candidate> existingCandidates;
+		if (mLocalDescription) {
+			existingCandidates = mLocalDescription->extractCandidates();
+			mCurrentLocalDescription.emplace(std::move(*mLocalDescription));
+		}
+
+		mLocalDescription.emplace(std::move(description));
+		mLocalDescription->addCandidates(std::move(existingCandidates));
+	}
 
 
 	mProcessor->enqueue([this, description = *mLocalDescription]() {
 	mProcessor->enqueue([this, description = *mLocalDescription]() {
 		PLOG_VERBOSE << "Issuing local description: " << description;
 		PLOG_VERBOSE << "Issuing local description: " << description;
 		mLocalDescriptionCallback(std::move(description));
 		mLocalDescriptionCallback(std::move(description));
 	});
 	});
+
+	// Reciprocated tracks might need to be open
+	if (auto dtlsTransport = std::atomic_load(&mDtlsTransport);
+	    dtlsTransport && dtlsTransport->state() == Transport::State::Connected)
+		mProcessor->enqueue(std::bind(&PeerConnection::openTracks, this));
 }
 }
 
 
 void PeerConnection::processLocalCandidate(Candidate candidate) {
 void PeerConnection::processLocalCandidate(Candidate candidate) {
@@ -917,6 +1054,7 @@ void PeerConnection::processLocalCandidate(Candidate candidate) {
 	if (!mLocalDescription)
 	if (!mLocalDescription)
 		throw std::logic_error("Got a local candidate without local description");
 		throw std::logic_error("Got a local candidate without local description");
 
 
+	candidate.resolve(Candidate::ResolveMode::Simple); // for proper SDP generation later
 	mLocalDescription->addCandidate(candidate);
 	mLocalDescription->addCandidate(candidate);
 
 
 	mProcessor->enqueue([this, candidate = std::move(candidate)]() {
 	mProcessor->enqueue([this, candidate = std::move(candidate)]() {
@@ -925,6 +1063,56 @@ void PeerConnection::processLocalCandidate(Candidate candidate) {
 	});
 	});
 }
 }
 
 
+void PeerConnection::processRemoteDescription(Description description) {
+	{
+		// Set as remote description
+		std::lock_guard lock(mRemoteDescriptionMutex);
+
+		std::vector<Candidate> existingCandidates;
+		if (mRemoteDescription)
+			existingCandidates = mRemoteDescription->extractCandidates();
+
+		mRemoteDescription.emplace(std::move(description));
+		mRemoteDescription->addCandidates(std::move(existingCandidates));
+	}
+
+	if (description.hasApplication()) {
+		auto dtlsTransport = std::atomic_load(&mDtlsTransport);
+		auto sctpTransport = std::atomic_load(&mSctpTransport);
+		if (!sctpTransport && dtlsTransport &&
+		    dtlsTransport->state() == Transport::State::Connected)
+			initSctpTransport();
+	}
+}
+
+void PeerConnection::processRemoteCandidate(Candidate candidate) {
+	auto iceTransport = std::atomic_load(&mIceTransport);
+	if (!iceTransport)
+		throw std::logic_error("Remote candidate set without remote description");
+
+	if (candidate.resolve(Candidate::ResolveMode::Simple)) {
+		iceTransport->addRemoteCandidate(candidate);
+	} else {
+		// OK, we might need a lookup, do it asynchronously
+		// We don't use the thread pool because we have no control on the timeout
+		weak_ptr<IceTransport> weakIceTransport{iceTransport};
+		std::thread t([weakIceTransport, candidate]() mutable {
+			if (candidate.resolve(Candidate::ResolveMode::Lookup))
+				if (auto iceTransport = weakIceTransport.lock())
+					iceTransport->addRemoteCandidate(candidate);
+		});
+		t.detach();
+	}
+
+	{
+		std::lock_guard lock(mRemoteDescriptionMutex);
+		if (!mRemoteDescription)
+			throw std::logic_error("Got a remote candidate without remote description");
+
+		mRemoteDescription->addCandidate(candidate);
+	}
+}
+
 void PeerConnection::triggerDataChannel(weak_ptr<DataChannel> weakDataChannel) {
 void PeerConnection::triggerDataChannel(weak_ptr<DataChannel> weakDataChannel) {
 	auto dataChannel = weakDataChannel.lock();
 	auto dataChannel = weakDataChannel.lock();
 	if (!dataChannel)
 	if (!dataChannel)
@@ -942,13 +1130,17 @@ bool PeerConnection::changeState(State state) {
 	State current;
 	State current;
 	do {
 	do {
 		current = mState.load();
 		current = mState.load();
-		if (current == state)
-			return true;
 		if (current == State::Closed)
 		if (current == State::Closed)
 			return false;
 			return false;
+		if (current == state)
+			return false;
 
 
 	} while (!mState.compare_exchange_weak(current, state));
 	} while (!mState.compare_exchange_weak(current, state));
 
 
+	std::ostringstream s;
+	s << state;
+	PLOG_INFO << "Changed state to " << s.str();
+
 	if (state == State::Closed)
 	if (state == State::Closed)
 		// This is the last state change, so we may steal the callback
 		// This is the last state change, so we may steal the callback
 		mProcessor->enqueue([cb = std::move(mStateChangeCallback)]() { cb(State::Closed); });
 		mProcessor->enqueue([cb = std::move(mStateChangeCallback)]() { cb(State::Closed); });
@@ -959,8 +1151,24 @@ bool PeerConnection::changeState(State state) {
 }
 }
 
 
 bool PeerConnection::changeGatheringState(GatheringState state) {
 bool PeerConnection::changeGatheringState(GatheringState state) {
-	if (mGatheringState.exchange(state) != state)
-		mProcessor->enqueue([this, state] { mGatheringStateChangeCallback(state); });
+	if (mGatheringState.exchange(state) == state)
+		return false;
+	
+	std::ostringstream s;
+	s << state;
+	PLOG_INFO << "Changed gathering state to " << s.str();
+	mProcessor->enqueue([this, state] { mGatheringStateChangeCallback(state); });
+	return true;
+}
+
+bool PeerConnection::changeSignalingState(SignalingState state) {
+	if (mSignalingState.exchange(state) == state) 
+		return false;
+	
+	std::ostringstream s;
+	s << state;
+	PLOG_INFO << "Changed signaling state to " << s.str();
+	mProcessor->enqueue([this, state] { mSignalingStateChangeCallback(state); });
 	return true;
 	return true;
 }
 }
 
 
@@ -973,15 +1181,10 @@ void PeerConnection::resetCallbacks() {
 	mGatheringStateChangeCallback = nullptr;
 	mGatheringStateChangeCallback = nullptr;
 }
 }
 
 
-bool PeerConnection::getSelectedCandidatePair([[maybe_unused]] CandidateInfo *local,
-                                              [[maybe_unused]] CandidateInfo *remote) {
-#if USE_NICE
+bool PeerConnection::getSelectedCandidatePair([[maybe_unused]] Candidate *local,
+                                              [[maybe_unused]] Candidate *remote) {
 	auto iceTransport = std::atomic_load(&mIceTransport);
 	auto iceTransport = std::atomic_load(&mIceTransport);
-	return iceTransport->getSelectedCandidatePair(local, remote);
-#else
-	PLOG_WARNING << "getSelectedCandidatePair() is only implemented with libnice as ICE backend";
-	return false;
-#endif
+	return iceTransport ? iceTransport->getSelectedCandidatePair(local, remote) : false;
 }
 }
 
 
 void PeerConnection::clearStats() {
 void PeerConnection::clearStats() {
@@ -1008,15 +1211,14 @@ std::optional<std::chrono::milliseconds> PeerConnection::rtt() {
 	auto sctpTransport = std::atomic_load(&mSctpTransport);
 	auto sctpTransport = std::atomic_load(&mSctpTransport);
 	if (sctpTransport)
 	if (sctpTransport)
 		return sctpTransport->rtt();
 		return sctpTransport->rtt();
-	PLOG_WARNING << "Could not load sctpTransport";
 	return std::nullopt;
 	return std::nullopt;
 }
 }
 
 
 } // namespace rtc
 } // namespace rtc
 
 
-std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::State &state) {
+std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::State state) {
 	using State = rtc::PeerConnection::State;
 	using State = rtc::PeerConnection::State;
-	std::string str;
+	const char *str;
 	switch (state) {
 	switch (state) {
 	case State::New:
 	case State::New:
 		str = "new";
 		str = "new";
@@ -1043,15 +1245,15 @@ std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::State &st
 	return out << str;
 	return out << str;
 }
 }
 
 
-std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::GatheringState &state) {
+std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::GatheringState state) {
 	using GatheringState = rtc::PeerConnection::GatheringState;
 	using GatheringState = rtc::PeerConnection::GatheringState;
-	std::string str;
+	const char *str;
 	switch (state) {
 	switch (state) {
 	case GatheringState::New:
 	case GatheringState::New:
 		str = "new";
 		str = "new";
 		break;
 		break;
 	case GatheringState::InProgress:
 	case GatheringState::InProgress:
-		str = "in_progress";
+		str = "in-progress";
 		break;
 		break;
 	case GatheringState::Complete:
 	case GatheringState::Complete:
 		str = "complete";
 		str = "complete";
@@ -1062,3 +1264,29 @@ std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::Gathering
 	}
 	}
 	return out << str;
 	return out << str;
 }
 }
+
+std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::SignalingState state) {
+	using SignalingState = rtc::PeerConnection::SignalingState;
+	const char *str;
+	switch (state) {
+	case SignalingState::Stable:
+		str = "stable";
+		break;
+	case SignalingState::HaveLocalOffer:
+		str = "have-local-offer";
+		break;
+	case SignalingState::HaveRemoteOffer:
+		str = "have-remote-offer";
+		break;
+	case SignalingState::HaveLocalPranswer:
+		str = "have-local-pranswer";
+		break;
+	case SignalingState::HaveRemotePranswer:
+		str = "have-remote-pranswer";
+		break;
+	default:
+		str = "unknown";
+		break;
+	}
+	return out << str;
+}

+ 8 - 19
src/processor.hpp

@@ -45,7 +45,7 @@ public:
 	void join();
 	void join();
 
 
 	template <class F, class... Args>
 	template <class F, class... Args>
-	auto enqueue(F &&f, Args &&... args) -> invoke_future_t<F, Args...>;
+	void enqueue(F &&f, Args &&... args);
 
 
 protected:
 protected:
 	void schedule();
 	void schedule();
@@ -60,31 +60,20 @@ protected:
 	std::condition_variable mCondition;
 	std::condition_variable mCondition;
 };
 };
 
 
-template <class F, class... Args>
-auto Processor::enqueue(F &&f, Args &&... args) -> invoke_future_t<F, Args...> {
+template <class F, class... Args> void Processor::enqueue(F &&f, Args &&... args) {
 	std::unique_lock lock(mMutex);
 	std::unique_lock lock(mMutex);
-	using R = std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>;
-	auto task = std::make_shared<std::packaged_task<R()>>(
-	    std::bind(std::forward<F>(f), std::forward<Args>(args)...));
-	std::future<R> result = task->get_future();
-
-	auto bundle = [this, task = std::move(task)]() {
-		try {
-			(*task)();
-		} catch (const std::exception &e) {
-			PLOG_WARNING << "Unhandled exception in task: " << e.what();
-		}
-		schedule(); // chain the next task
+	auto bound = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
+	auto task = [this, bound = std::move(bound)]() mutable {
+		scope_guard guard(std::bind(&Processor::schedule, this)); // chain the next task
+		return bound();
 	};
 	};
 
 
 	if (!mPending) {
 	if (!mPending) {
-		ThreadPool::Instance().enqueue(std::move(bundle));
+		ThreadPool::Instance().enqueue(std::move(task));
 		mPending = true;
 		mPending = true;
 	} else {
 	} else {
-		mTasks.emplace(std::move(bundle));
+		mTasks.emplace(std::move(task));
 	}
 	}
-
-	return result;
 }
 }
 
 
 } // namespace rtc
 } // namespace rtc

+ 1 - 1
src/rtcp.cpp

@@ -106,7 +106,7 @@ void RtcpReceivingSession::pushRR(unsigned int lastSR_delay) {
 	auto msg = rtc::make_message(RTCP_RR::sizeWithReportBlocks(1), rtc::Message::Type::Control);
 	auto msg = rtc::make_message(RTCP_RR::sizeWithReportBlocks(1), rtc::Message::Type::Control);
 	auto rr = reinterpret_cast<RTCP_RR *>(msg->data());
 	auto rr = reinterpret_cast<RTCP_RR *>(msg->data());
 	rr->preparePacket(mSsrc, 1);
 	rr->preparePacket(mSsrc, 1);
-	rr->getReportBlock(0)->preparePacket(mSsrc, 0, 0, mGreatestSeqNo, 0, 0, mSyncNTPTS,
+	rr->getReportBlock(0)->preparePacket(mSsrc, 0, 0, uint16_t(mGreatestSeqNo), 0, 0, mSyncNTPTS,
 	                                     lastSR_delay);
 	                                     lastSR_delay);
 	rr->log();
 	rr->log();
 
 

+ 7 - 15
src/sctptransport.cpp

@@ -322,7 +322,7 @@ void SctpTransport::incoming(message_ptr message) {
 bool SctpTransport::trySendQueue() {
 bool SctpTransport::trySendQueue() {
 	// Requires mSendMutex to be locked
 	// Requires mSendMutex to be locked
 	while (auto next = mSendQueue.peek()) {
 	while (auto next = mSendQueue.peek()) {
-		auto message = *next;
+		message_ptr message = std::move(*next);
 		if (!trySendMessage(message))
 		if (!trySendMessage(message))
 			return false;
 			return false;
 		mSendQueue.pop();
 		mSendQueue.pop();
@@ -476,8 +476,6 @@ int SctpTransport::handleRecv(struct socket * /*sock*/, union sctp_sockstore /*a
                               const byte *data, size_t len, struct sctp_rcvinfo info, int flags) {
                               const byte *data, size_t len, struct sctp_rcvinfo info, int flags) {
 	try {
 	try {
 		PLOG_VERBOSE << "Handle recv, len=" << len;
 		PLOG_VERBOSE << "Handle recv, len=" << len;
-		if (!len)
-			return 0; // Ignore
 
 
 		// SCTP_FRAGMENT_INTERLEAVE does not seem to work as expected for messages > 64KB,
 		// SCTP_FRAGMENT_INTERLEAVE does not seem to work as expected for messages > 64KB,
 		// therefore partial notifications and messages need to be handled separately.
 		// therefore partial notifications and messages need to be handled separately.
@@ -497,7 +495,7 @@ int SctpTransport::handleRecv(struct socket * /*sock*/, union sctp_sockstore /*a
 			if (flags & MSG_EOR) {
 			if (flags & MSG_EOR) {
 				// Message is complete, process it
 				// Message is complete, process it
 				processData(std::move(mPartialMessage), info.rcv_sid,
 				processData(std::move(mPartialMessage), info.rcv_sid,
-				            PayloadId(htonl(info.rcv_ppid)));
+				            PayloadId(ntohl(info.rcv_ppid)));
 				mPartialMessage.clear();
 				mPartialMessage.clear();
 			}
 			}
 		}
 		}
@@ -702,8 +700,9 @@ std::optional<milliseconds> SctpTransport::rtt() {
 }
 }
 
 
 int SctpTransport::RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data,
 int SctpTransport::RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data,
-                                size_t len, struct sctp_rcvinfo recv_info, int flags, void *ptr) {
-	auto *transport = static_cast<SctpTransport *>(ptr);
+                                size_t len, struct sctp_rcvinfo recv_info, int flags,
+                                void *ulp_info) {
+	auto *transport = static_cast<SctpTransport *>(ulp_info);
 
 
 	std::shared_lock lock(InstancesMutex);
 	std::shared_lock lock(InstancesMutex);
 	if (Instances.find(transport) == Instances.end()) {
 	if (Instances.find(transport) == Instances.end()) {
@@ -717,15 +716,8 @@ int SctpTransport::RecvCallback(struct socket *sock, union sctp_sockstore addr,
 	return ret;
 	return ret;
 }
 }
 
 
-int SctpTransport::SendCallback(struct socket *sock, uint32_t sb_free) {
-	struct sctp_paddrinfo paddrinfo = {};
-	socklen_t len = sizeof(paddrinfo);
-	if (usrsctp_getsockopt(sock, IPPROTO_SCTP, SCTP_GET_PEER_ADDR_INFO, &paddrinfo, &len))
-		return -1;
-
-	auto sconn = reinterpret_cast<struct sockaddr_conn *>(&paddrinfo.spinfo_address);
-	void *ptr = sconn->sconn_addr;
-	auto *transport = static_cast<SctpTransport *>(ptr);
+int SctpTransport::SendCallback(struct socket *, uint32_t sb_free, void *ulp_info) {
+	auto *transport = static_cast<SctpTransport *>(ulp_info);
 
 
 	std::shared_lock lock(InstancesMutex);
 	std::shared_lock lock(InstancesMutex);
 	if (Instances.find(transport) == Instances.end())
 	if (Instances.find(transport) == Instances.end())

+ 2 - 2
src/sctptransport.hpp

@@ -110,8 +110,8 @@ private:
 	std::atomic<size_t> mBytesSent = 0, mBytesReceived = 0;
 	std::atomic<size_t> mBytesSent = 0, mBytesReceived = 0;
 
 
 	static int RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data, size_t len,
 	static int RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data, size_t len,
-	                        struct sctp_rcvinfo recv_info, int flags, void *user_data);
-	static int SendCallback(struct socket *sock, uint32_t sb_free);
+	                        struct sctp_rcvinfo recv_info, int flags, void *ulp_info);
+	static int SendCallback(struct socket *sock, uint32_t sb_free, void *ulp_info);
 	static int WriteCallback(void *sctp_ptr, void *data, size_t len, uint8_t tos, uint8_t set_df);
 	static int WriteCallback(void *sctp_ptr, void *data, size_t len, uint8_t tos, uint8_t set_df);
 
 
 	static std::unordered_set<SctpTransport *> Instances;
 	static std::unordered_set<SctpTransport *> Instances;

+ 1 - 1
src/tcptransport.cpp

@@ -271,7 +271,7 @@ void TcpTransport::close() {
 bool TcpTransport::trySendQueue() {
 bool TcpTransport::trySendQueue() {
 	// mSockMutex must be locked
 	// mSockMutex must be locked
 	while (auto next = mSendQueue.peek()) {
 	while (auto next = mSendQueue.peek()) {
-		auto message = *next;
+		message_ptr message = std::move(*next);
 		if (!trySendMessage(message)) {
 		if (!trySendMessage(message)) {
 			mSendQueue.exchange(message);
 			mSendQueue.exchange(message);
 			return false;
 			return false;

+ 1 - 5
src/threadpool.cpp

@@ -58,11 +58,7 @@ void ThreadPool::run() {
 
 
 bool ThreadPool::runOne() {
 bool ThreadPool::runOne() {
 	if (auto task = dequeue()) {
 	if (auto task = dequeue()) {
-		try {
-			task();
-		} catch (const std::exception &e) {
-			PLOG_WARNING << "Unhandled exception in task: " << e.what();
-		}
+		task();
 		return true;
 		return true;
 	}
 	}
 	return false;
 	return false;

+ 9 - 2
src/threadpool.hpp

@@ -73,8 +73,15 @@ template <class F, class... Args>
 auto ThreadPool::enqueue(F &&f, Args &&... args) -> invoke_future_t<F, Args...> {
 auto ThreadPool::enqueue(F &&f, Args &&... args) -> invoke_future_t<F, Args...> {
 	std::unique_lock lock(mMutex);
 	std::unique_lock lock(mMutex);
 	using R = std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>;
 	using R = std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>;
-	auto task = std::make_shared<std::packaged_task<R()>>(
-	    std::bind(std::forward<F>(f), std::forward<Args>(args)...));
+	auto bound = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
+	auto task = std::make_shared<std::packaged_task<R()>>([bound = std::move(bound)]() mutable {
+        try {
+            return bound();
+        } catch (const std::exception &e) {
+            PLOG_WARNING << e.what();
+            throw;
+        }
+    });
 	std::future<R> result = task->get_future();
 	std::future<R> result = task->get_future();
 
 
 	mTasks.emplace([task = std::move(task), token = Init::Token()]() { return (*task)(); });
 	mTasks.emplace([task = std::move(task), token = Init::Token()]() { return (*task)(); });

+ 4 - 6
src/tlstransport.cpp

@@ -238,11 +238,9 @@ ssize_t TlsTransport::ReadCallback(gnutls_transport_ptr_t ptr, void *data, size_
 
 
 int TlsTransport::TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms) {
 int TlsTransport::TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms) {
 	TlsTransport *t = static_cast<TlsTransport *>(ptr);
 	TlsTransport *t = static_cast<TlsTransport *>(ptr);
-	if (ms != GNUTLS_INDEFINITE_TIMEOUT)
-		t->mIncomingQueue.wait(milliseconds(ms));
-	else
-		t->mIncomingQueue.wait();
-	return !t->mIncomingQueue.empty() ? 1 : 0;
+	bool notEmpty = t->mIncomingQueue.wait(
+	    ms != GNUTLS_INDEFINITE_TIMEOUT ? std::make_optional(milliseconds(ms)) : nullopt);
+	return notEmpty ? 1 : 0;
 }
 }
 
 
 #else // USE_GNUTLS==0
 #else // USE_GNUTLS==0
@@ -413,7 +411,7 @@ void TlsTransport::runRecvLoop() {
 			if (!next)
 			if (!next)
 				break;
 				break;
 
 
-			message_ptr message = *next;
+			message_ptr message = std::move(*next);
 			if (message->size() > 0)
 			if (message->size() > 0)
 				BIO_write(mInBio, message->data(), int(message->size())); // Input
 				BIO_write(mInBio, message->data(), int(message->size())); // Input
 			else
 			else

+ 2 - 2
src/track.cpp

@@ -49,8 +49,8 @@ bool Track::send(const byte *data, size_t size) {
 }
 }
 
 
 std::optional<message_variant> Track::receive() {
 std::optional<message_variant> Track::receive() {
-	if (!mRecvQueue.empty())
-		return to_variant(std::move(**mRecvQueue.pop()));
+	if (auto next = mRecvQueue.tryPop())
+		return to_variant(std::move(**next));
 
 
 	return nullopt;
 	return nullopt;
 }
 }

+ 41 - 19
src/websocket.cpp

@@ -51,31 +51,45 @@ WebSocket::~WebSocket() {
 WebSocket::State WebSocket::readyState() const { return mState; }
 WebSocket::State WebSocket::readyState() const { return mState; }
 
 
 void WebSocket::open(const string &url) {
 void WebSocket::open(const string &url) {
+	PLOG_VERBOSE << "Opening WebSocket to URL: " << url;
+
 	if (mState != State::Closed)
 	if (mState != State::Closed)
-		throw std::runtime_error("WebSocket must be closed before opening");
+		throw std::logic_error("WebSocket must be closed before opening");
+
+	// Modified regex from RFC 3986, see https://tools.ietf.org/html/rfc3986#appendix-B
+	static const char *rs =
+	    R"(^(([^:.@/?#]+):)?(/{0,2}((([^:@]*)(:([^@]*))?)@)?(([^:/?#]*)(:([^/?#]*))?))?([^?#]*)(\?([^#]*))?(#(.*))?)";
 
 
-	static const char *rs = R"(^(([^:\/?#]+):)?(//([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?)";
-	static std::regex regex(rs, std::regex::extended);
+	static const std::regex r(rs, std::regex::extended);
 
 
-	std::smatch match;
-	if (!std::regex_match(url, match, regex))
-		throw std::invalid_argument("Malformed WebSocket URL: " + url);
+	std::smatch m;
+	if (!std::regex_match(url, m, r) || m[10].length() == 0)
+		throw std::invalid_argument("Invalid WebSocket URL: " + url);
 
 
-	mScheme = match[2];
-	if (mScheme != "ws" && mScheme != "wss")
+	mScheme = m[2];
+	if (mScheme.empty())
+		mScheme = "ws";
+	else if (mScheme != "ws" && mScheme != "wss")
 		throw std::invalid_argument("Invalid WebSocket scheme: " + mScheme);
 		throw std::invalid_argument("Invalid WebSocket scheme: " + mScheme);
 
 
-	mHost = match[4];
-	if (auto pos = mHost.find(':'); pos != string::npos) {
-		mHostname = mHost.substr(0, pos);
-		mService = mHost.substr(pos + 1);
-	} else {
-		mHostname = mHost;
+	mHostname = m[10];
+	mService = m[12];
+	if (mService.empty()) {
 		mService = mScheme == "ws" ? "80" : "443";
 		mService = mScheme == "ws" ? "80" : "443";
+		mHost = mHostname;
+	} else {
+		mHost = mHostname + ':' + mService;
 	}
 	}
 
 
-	mPath = match[5];
-	if (string query = match[7]; !query.empty())
+	while (!mHostname.empty() && mHostname.front() == '[')
+		mHostname.erase(mHostname.begin());
+	while (!mHostname.empty() && mHostname.back() == ']')
+		mHostname.pop_back();
+
+	mPath = m[13];
+	if (mPath.empty())
+		mPath += '/';
+	if (string query = m[15]; !query.empty())
 		mPath += "?" + query;
 		mPath += "?" + query;
 
 
 	changeState(State::Connecting);
 	changeState(State::Connecting);
@@ -110,8 +124,8 @@ bool WebSocket::isClosed() const { return mState == State::Closed; }
 size_t WebSocket::maxMessageSize() const { return DEFAULT_MAX_MESSAGE_SIZE; }
 size_t WebSocket::maxMessageSize() const { return DEFAULT_MAX_MESSAGE_SIZE; }
 
 
 std::optional<message_variant> WebSocket::receive() {
 std::optional<message_variant> WebSocket::receive() {
-	while (!mRecvQueue.empty()) {
-		auto message = *mRecvQueue.pop();
+	while (auto next = mRecvQueue.tryPop()) {
+		message_ptr message = std::move(*next);
 		if (message->type != Message::Control)
 		if (message->type != Message::Control)
 			return to_variant(std::move(*message));
 			return to_variant(std::move(*message));
 	}
 	}
@@ -133,6 +147,11 @@ bool WebSocket::outgoing(message_ptr message) {
 }
 }
 
 
 void WebSocket::incoming(message_ptr message) {
 void WebSocket::incoming(message_ptr message) {
+	if (!message) {
+		remoteClose();
+		return;
+	}
+
 	if (message->type == Message::String || message->type == Message::Binary) {
 	if (message->type == Message::String || message->type == Message::Binary) {
 		mRecvQueue.push(message);
 		mRecvQueue.push(message);
 		triggerAvailable(mRecvQueue.size());
 		triggerAvailable(mRecvQueue.size());
@@ -140,6 +159,7 @@ void WebSocket::incoming(message_ptr message) {
 }
 }
 
 
 shared_ptr<TcpTransport> WebSocket::initTcpTransport() {
 shared_ptr<TcpTransport> WebSocket::initTcpTransport() {
+	PLOG_VERBOSE << "Starting TCP transport";
 	using State = TcpTransport::State;
 	using State = TcpTransport::State;
 	try {
 	try {
 		std::lock_guard lock(mInitMutex);
 		std::lock_guard lock(mInitMutex);
@@ -186,6 +206,7 @@ shared_ptr<TcpTransport> WebSocket::initTcpTransport() {
 }
 }
 
 
 shared_ptr<TlsTransport> WebSocket::initTlsTransport() {
 shared_ptr<TlsTransport> WebSocket::initTlsTransport() {
+	PLOG_VERBOSE << "Starting TLS transport";
 	using State = TlsTransport::State;
 	using State = TlsTransport::State;
 	try {
 	try {
 		std::lock_guard lock(mInitMutex);
 		std::lock_guard lock(mInitMutex);
@@ -243,6 +264,7 @@ shared_ptr<TlsTransport> WebSocket::initTlsTransport() {
 }
 }
 
 
 shared_ptr<WsTransport> WebSocket::initWsTransport() {
 shared_ptr<WsTransport> WebSocket::initWsTransport() {
+	PLOG_VERBOSE << "Starting WebSocket transport";
 	using State = WsTransport::State;
 	using State = WsTransport::State;
 	try {
 	try {
 		std::lock_guard lock(mInitMutex);
 		std::lock_guard lock(mInitMutex);
@@ -321,6 +343,6 @@ void WebSocket::closeTransports() {
 	});
 	});
 }
 }
 
 
-	} // namespace rtc
+} // namespace rtc
 
 
 #endif
 #endif

+ 7 - 1
src/wstransport.cpp

@@ -58,6 +58,12 @@ WsTransport::WsTransport(std::shared_ptr<Transport> lower, string host, string p
 	onRecv(recvCallback);
 	onRecv(recvCallback);
 
 
 	PLOG_DEBUG << "Initializing WebSocket transport";
 	PLOG_DEBUG << "Initializing WebSocket transport";
+
+	if (mHost.empty())
+		throw std::invalid_argument("WebSocket HTTP host cannot be empty");
+
+	if (mPath.empty())
+		throw std::invalid_argument("WebSocket HTTP path cannot be empty");
 }
 }
 
 
 WsTransport::~WsTransport() { stop(); }
 WsTransport::~WsTransport() { stop(); }
@@ -147,7 +153,7 @@ void WsTransport::close() {
 }
 }
 
 
 bool WsTransport::sendHttpRequest() {
 bool WsTransport::sendHttpRequest() {
-	PLOG_DEBUG << "Sending WebSocket HTTP request";
+	PLOG_DEBUG << "Sending WebSocket HTTP request for path " << mPath;
 	changeState(State::Connecting);
 	changeState(State::Connecting);
 
 
 	auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
 	auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());

+ 2 - 1
test/benchmark.cpp

@@ -155,7 +155,8 @@ size_t benchmark(milliseconds duration) {
 
 
 	endTime = steady_clock::now();
 	endTime = steady_clock::now();
 
 
-	auto connectDuration = duration_cast<milliseconds>(openTime - startTime);
+	auto connectDuration = duration_cast<milliseconds>(dc1->isOpen() ? openTime - startTime
+	                                                                 : steady_clock::duration(0));
 	auto transferDuration = duration_cast<milliseconds>(endTime - receivedTime);
 	auto transferDuration = duration_cast<milliseconds>(endTime - receivedTime);
 
 
 	cout << "Test duration: " << duration.count() << " ms" << endl;
 	cout << "Test duration: " << duration.count() << " ms" << endl;

+ 87 - 17
test/capi_connectivity.cpp

@@ -29,9 +29,12 @@ static void sleep(unsigned int secs) { Sleep(secs * 1000); }
 #include <unistd.h> // for sleep
 #include <unistd.h> // for sleep
 #endif
 #endif
 
 
+#define BUFFER_SIZE 4096
+
 typedef struct {
 typedef struct {
 	rtcState state;
 	rtcState state;
 	rtcGatheringState gatheringState;
 	rtcGatheringState gatheringState;
+	rtcSignalingState signalingState;
 	int pc;
 	int pc;
 	int dc;
 	int dc;
 	bool connected;
 	bool connected;
@@ -40,33 +43,39 @@ typedef struct {
 static Peer *peer1 = NULL;
 static Peer *peer1 = NULL;
 static Peer *peer2 = NULL;
 static Peer *peer2 = NULL;
 
 
-static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
+static void descriptionCallback(int pc, const char *sdp, const char *type, void *ptr) {
 	Peer *peer = (Peer *)ptr;
 	Peer *peer = (Peer *)ptr;
 	printf("Description %d:\n%s\n", peer == peer1 ? 1 : 2, sdp);
 	printf("Description %d:\n%s\n", peer == peer1 ? 1 : 2, sdp);
 	Peer *other = peer == peer1 ? peer2 : peer1;
 	Peer *other = peer == peer1 ? peer2 : peer1;
 	rtcSetRemoteDescription(other->pc, sdp, type);
 	rtcSetRemoteDescription(other->pc, sdp, type);
 }
 }
 
 
-static void candidateCallback(const char *cand, const char *mid, void *ptr) {
+static void candidateCallback(int pc, const char *cand, const char *mid, void *ptr) {
 	Peer *peer = (Peer *)ptr;
 	Peer *peer = (Peer *)ptr;
 	printf("Candidate %d: %s\n", peer == peer1 ? 1 : 2, cand);
 	printf("Candidate %d: %s\n", peer == peer1 ? 1 : 2, cand);
 	Peer *other = peer == peer1 ? peer2 : peer1;
 	Peer *other = peer == peer1 ? peer2 : peer1;
 	rtcAddRemoteCandidate(other->pc, cand, mid);
 	rtcAddRemoteCandidate(other->pc, cand, mid);
 }
 }
 
 
-static void stateChangeCallback(rtcState state, void *ptr) {
+static void stateChangeCallback(int pc, rtcState state, void *ptr) {
 	Peer *peer = (Peer *)ptr;
 	Peer *peer = (Peer *)ptr;
 	peer->state = state;
 	peer->state = state;
 	printf("State %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
 	printf("State %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
 }
 }
 
 
-static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
+static void gatheringStateCallback(int pc, rtcGatheringState state, void *ptr) {
 	Peer *peer = (Peer *)ptr;
 	Peer *peer = (Peer *)ptr;
 	peer->gatheringState = state;
 	peer->gatheringState = state;
 	printf("Gathering state %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
 	printf("Gathering state %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
 }
 }
 
 
-static void openCallback(void *ptr) {
+static void signalingStateCallback(int pc, rtcSignalingState state, void *ptr) {
+	Peer *peer = (Peer *)ptr;
+	peer->signalingState = state;
+	printf("Signaling state %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
+}
+
+static void openCallback(int id, void *ptr) {
 	Peer *peer = (Peer *)ptr;
 	Peer *peer = (Peer *)ptr;
 	peer->connected = true;
 	peer->connected = true;
 	printf("DataChannel %d: Open\n", peer == peer1 ? 1 : 2);
 	printf("DataChannel %d: Open\n", peer == peer1 ? 1 : 2);
@@ -75,12 +84,12 @@ static void openCallback(void *ptr) {
 	rtcSendMessage(peer->dc, message, -1); // negative size indicates a null-terminated string
 	rtcSendMessage(peer->dc, message, -1); // negative size indicates a null-terminated string
 }
 }
 
 
-static void closedCallback(void *ptr) {
+static void closedCallback(int id, void *ptr) {
 	Peer *peer = (Peer *)ptr;
 	Peer *peer = (Peer *)ptr;
 	peer->connected = false;
 	peer->connected = false;
 }
 }
 
 
-static void messageCallback(const char *message, int size, void *ptr) {
+static void messageCallback(int id, const char *message, int size, void *ptr) {
 	Peer *peer = (Peer *)ptr;
 	Peer *peer = (Peer *)ptr;
 	if (size < 0) { // negative size indicates a null-terminated string
 	if (size < 0) { // negative size indicates a null-terminated string
 		printf("Message %d: %s\n", peer == peer1 ? 1 : 2, message);
 		printf("Message %d: %s\n", peer == peer1 ? 1 : 2, message);
@@ -89,7 +98,7 @@ static void messageCallback(const char *message, int size, void *ptr) {
 	}
 	}
 }
 }
 
 
-static void dataChannelCallback(int dc, void *ptr) {
+static void dataChannelCallback(int pc, int dc, void *ptr) {
 	Peer *peer = (Peer *)ptr;
 	Peer *peer = (Peer *)ptr;
 	peer->dc = dc;
 	peer->dc = dc;
 	peer->connected = true;
 	peer->connected = true;
@@ -178,20 +187,81 @@ int test_capi_connectivity_main() {
 		goto error;
 		goto error;
 	}
 	}
 
 
+	if (peer1->signalingState != RTC_SIGNALING_STABLE ||
+	    peer2->signalingState != RTC_SIGNALING_STABLE) {
+		fprintf(stderr, "Signaling state is not stable\n");
+		goto error;
+	}
+
 	if (!peer1->connected || !peer2->connected) {
 	if (!peer1->connected || !peer2->connected) {
 		fprintf(stderr, "DataChannel is not connected\n");
 		fprintf(stderr, "DataChannel is not connected\n");
 		goto error;
 		goto error;
 	}
 	}
 
 
-	char buffer[256];
-	if (rtcGetLocalAddress(peer1->pc, buffer, 256) >= 0)
-		printf("Local address 1:  %s\n", buffer);
-	if (rtcGetRemoteAddress(peer1->pc, buffer, 256) >= 0)
-		printf("Remote address 1: %s\n", buffer);
-	if (rtcGetLocalAddress(peer2->pc, buffer, 256) >= 0)
-		printf("Local address 2:  %s\n", buffer);
-	if (rtcGetRemoteAddress(peer2->pc, buffer, 256) >= 0)
-		printf("Remote address 2: %s\n", buffer);
+	char buffer[BUFFER_SIZE];
+	char buffer2[BUFFER_SIZE];
+
+	if (rtcGetLocalDescription(peer1->pc, buffer, BUFFER_SIZE) < 0) {
+		fprintf(stderr, "rtcGetLocalDescription failed\n");
+		goto error;
+	}
+	printf("Local description 1:  %s\n", buffer);
+
+	if (rtcGetRemoteDescription(peer1->pc, buffer, BUFFER_SIZE) < 0) {
+		fprintf(stderr, "rtcGetRemoteDescription failed\n");
+		goto error;
+	}
+	printf("Remote description 1:  %s\n", buffer);
+
+	if (rtcGetLocalDescription(peer2->pc, buffer, BUFFER_SIZE) < 0) {
+		fprintf(stderr, "rtcGetLocalDescription failed\n");
+		goto error;
+	}
+	printf("Local description 2:  %s\n", buffer);
+
+	if (rtcGetRemoteDescription(peer2->pc, buffer, BUFFER_SIZE) < 0) {
+		fprintf(stderr, "rtcGetRemoteDescription failed\n");
+		goto error;
+	}
+	printf("Remote description 2:  %s\n", buffer);
+
+	if (rtcGetLocalAddress(peer1->pc, buffer, BUFFER_SIZE) < 0) {
+		fprintf(stderr, "rtcGetLocalAddress failed\n");
+		goto error;
+	}
+	printf("Local address 1:  %s\n", buffer);
+
+	if (rtcGetRemoteAddress(peer1->pc, buffer, BUFFER_SIZE) < 0) {
+		fprintf(stderr, "rtcGetRemoteAddress failed\n");
+		goto error;
+	}
+	printf("Remote address 1: %s\n", buffer);
+
+	if (rtcGetLocalAddress(peer2->pc, buffer, BUFFER_SIZE) < 0) {
+		fprintf(stderr, "rtcGetLocalAddress failed\n");
+		goto error;
+	}
+	printf("Local address 2:  %s\n", buffer);
+
+	if (rtcGetRemoteAddress(peer2->pc, buffer, BUFFER_SIZE) < 0) {
+		fprintf(stderr, "rtcGetRemoteAddress failed\n");
+		goto error;
+	}
+	printf("Remote address 2: %s\n", buffer);
+
+	if (rtcGetSelectedCandidatePair(peer1->pc, buffer, BUFFER_SIZE, buffer2, BUFFER_SIZE) < 0) {
+		fprintf(stderr, "rtcGetSelectedCandidatePair failed\n");
+		goto error;
+	}
+	printf("Local candidate 1:  %s\n", buffer);
+	printf("Remote candidate 1: %s\n", buffer2);
+
+	if (rtcGetSelectedCandidatePair(peer2->pc, buffer, BUFFER_SIZE, buffer2, BUFFER_SIZE) < 0) {
+		fprintf(stderr, "rtcGetSelectedCandidatePair failed\n");
+		goto error;
+	}
+	printf("Local candidate 2:  %s\n", buffer);
+	printf("Remote candidate 2: %s\n", buffer2);
 
 
 	deletePeer(peer1);
 	deletePeer(peer1);
 	sleep(1);
 	sleep(1);

+ 8 - 8
test/capi_track.cpp

@@ -43,44 +43,44 @@ static Peer *peer2 = NULL;
 static const char *mediaDescription = "video 9 UDP/TLS/RTP/SAVPF\r\n"
 static const char *mediaDescription = "video 9 UDP/TLS/RTP/SAVPF\r\n"
                                       "a=mid:video\r\n";
                                       "a=mid:video\r\n";
 
 
-static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
+static void descriptionCallback(int pc, const char *sdp, const char *type, void *ptr) {
 	Peer *peer = (Peer *)ptr;
 	Peer *peer = (Peer *)ptr;
 	printf("Description %d:\n%s\n", peer == peer1 ? 1 : 2, sdp);
 	printf("Description %d:\n%s\n", peer == peer1 ? 1 : 2, sdp);
 	Peer *other = peer == peer1 ? peer2 : peer1;
 	Peer *other = peer == peer1 ? peer2 : peer1;
 	rtcSetRemoteDescription(other->pc, sdp, type);
 	rtcSetRemoteDescription(other->pc, sdp, type);
 }
 }
 
 
-static void candidateCallback(const char *cand, const char *mid, void *ptr) {
+static void candidateCallback(int pc, const char *cand, const char *mid, void *ptr) {
 	Peer *peer = (Peer *)ptr;
 	Peer *peer = (Peer *)ptr;
 	printf("Candidate %d: %s\n", peer == peer1 ? 1 : 2, cand);
 	printf("Candidate %d: %s\n", peer == peer1 ? 1 : 2, cand);
 	Peer *other = peer == peer1 ? peer2 : peer1;
 	Peer *other = peer == peer1 ? peer2 : peer1;
 	rtcAddRemoteCandidate(other->pc, cand, mid);
 	rtcAddRemoteCandidate(other->pc, cand, mid);
 }
 }
 
 
-static void stateChangeCallback(rtcState state, void *ptr) {
+static void stateChangeCallback(int pc, rtcState state, void *ptr) {
 	Peer *peer = (Peer *)ptr;
 	Peer *peer = (Peer *)ptr;
 	peer->state = state;
 	peer->state = state;
 	printf("State %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
 	printf("State %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
 }
 }
 
 
-static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
+static void gatheringStateCallback(int pc, rtcGatheringState state, void *ptr) {
 	Peer *peer = (Peer *)ptr;
 	Peer *peer = (Peer *)ptr;
 	peer->gatheringState = state;
 	peer->gatheringState = state;
 	printf("Gathering state %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
 	printf("Gathering state %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
 }
 }
 
 
-static void openCallback(void *ptr) {
+static void openCallback(int id, void *ptr) {
 	Peer *peer = (Peer *)ptr;
 	Peer *peer = (Peer *)ptr;
 	peer->connected = true;
 	peer->connected = true;
 	printf("Track %d: Open\n", peer == peer1 ? 1 : 2);
 	printf("Track %d: Open\n", peer == peer1 ? 1 : 2);
 }
 }
 
 
-static void closedCallback(void *ptr) {
+static void closedCallback(int id, void *ptr) {
 	Peer *peer = (Peer *)ptr;
 	Peer *peer = (Peer *)ptr;
 	peer->connected = false;
 	peer->connected = false;
 }
 }
 
 
-static void trackCallback(int tr, void *ptr) {
+static void trackCallback(int pc, int tr, void *ptr) {
 	Peer *peer = (Peer *)ptr;
 	Peer *peer = (Peer *)ptr;
 	peer->tr = tr;
 	peer->tr = tr;
 	peer->connected = true;
 	peer->connected = true;
@@ -156,7 +156,7 @@ int test_capi_track_main() {
 	rtcSetClosedCallback(peer1->tr, closedCallback);
 	rtcSetClosedCallback(peer1->tr, closedCallback);
 
 
 	// Initiate the handshake
 	// Initiate the handshake
-	rtcSetLocalDescription(peer1->pc);
+	rtcSetLocalDescription(peer1->pc, NULL);
 
 
 	attempts = 10;
 	attempts = 10;
 	while ((!peer2->connected || !peer1->connected) && attempts--)
 	while ((!peer2->connected || !peer1->connected) && attempts--)

+ 62 - 0
test/connectivity.cpp

@@ -69,6 +69,10 @@ void test_connectivity() {
 		cout << "Gathering state 1: " << state << endl;
 		cout << "Gathering state 1: " << state << endl;
 	});
 	});
 
 
+	pc1->onSignalingStateChange([](PeerConnection::SignalingState state) {
+		cout << "Signaling state 1: " << state << endl;
+	});
+
 	pc2->onLocalDescription([wpc1 = make_weak_ptr(pc1)](Description sdp) {
 	pc2->onLocalDescription([wpc1 = make_weak_ptr(pc1)](Description sdp) {
 		auto pc1 = wpc1.lock();
 		auto pc1 = wpc1.lock();
 		if (!pc1)
 		if (!pc1)
@@ -91,6 +95,10 @@ void test_connectivity() {
 		cout << "Gathering state 2: " << state << endl;
 		cout << "Gathering state 2: " << state << endl;
 	});
 	});
 
 
+	pc2->onSignalingStateChange([](PeerConnection::SignalingState state) {
+		cout << "Signaling state 2: " << state << endl;
+	});
+
 	shared_ptr<DataChannel> dc2;
 	shared_ptr<DataChannel> dc2;
 	pc2->onDataChannel([&dc2](shared_ptr<DataChannel> dc) {
 	pc2->onDataChannel([&dc2](shared_ptr<DataChannel> dc) {
 		cout << "DataChannel 2: Received with label \"" << dc->label() << "\"" << endl;
 		cout << "DataChannel 2: Received with label \"" << dc->label() << "\"" << endl;
@@ -125,6 +133,7 @@ void test_connectivity() {
 		}
 		}
 	});
 	});
 
 
+	// Wait a bit
 	int attempts = 10;
 	int attempts = 10;
 	shared_ptr<DataChannel> adc2;
 	shared_ptr<DataChannel> adc2;
 	while ((!(adc2 = std::atomic_load(&dc2)) || !adc2->isOpen() || !dc1->isOpen()) && attempts--)
 	while ((!(adc2 = std::atomic_load(&dc2)) || !adc2->isOpen() || !dc1->isOpen()) && attempts--)
@@ -146,6 +155,59 @@ void test_connectivity() {
 	if (auto addr = pc2->remoteAddress())
 	if (auto addr = pc2->remoteAddress())
 		cout << "Remote address 2: " << *addr << endl;
 		cout << "Remote address 2: " << *addr << endl;
 
 
+	Candidate local, remote;
+	if(pc1->getSelectedCandidatePair(&local, &remote)) {
+		cout << "Local candidate 1:  " << local << endl;
+		cout << "Remote candidate 1: " << remote << endl;
+	}
+	if(pc2->getSelectedCandidatePair(&local, &remote)) {
+		cout << "Local candidate 2:  " << local << endl;
+		cout << "Remote candidate 2: " << remote << endl;
+	}
+
+	// Try to open a second data channel with another label
+	shared_ptr<DataChannel> second2;
+	pc2->onDataChannel([&second2](shared_ptr<DataChannel> dc) {
+		cout << "Second DataChannel 2: Received with label \"" << dc->label() << "\"" << endl;
+		if (dc->label() != "second") {
+			cerr << "Wrong second DataChannel label" << endl;
+			return;
+		}
+
+		dc->onMessage([](variant<binary, string> message) {
+			if (holds_alternative<string>(message)) {
+				cout << "Second Message 2: " << get<string>(message) << endl;
+			}
+		});
+
+		dc->send("Send hello from 2");
+
+		std::atomic_store(&second2, dc);
+	});
+
+	auto second1 = pc1->createDataChannel("second");
+	second1->onOpen([wsecond1 = make_weak_ptr(dc1)]() {
+		auto second1 = wsecond1.lock();
+		if (!second1)
+			return;
+
+		cout << "Second DataChannel 1: Open" << endl;
+		second1->send("Second hello from 1");
+	});
+	dc1->onMessage([](const variant<binary, string> &message) {
+		if (holds_alternative<string>(message)) {
+			cout << "Second Message 1: " << get<string>(message) << endl;
+		}
+	});
+
+	// Wait a bit
+	attempts = 10;
+	shared_ptr<DataChannel> asecond2;
+	while (
+	    (!(asecond2 = std::atomic_load(&second2)) || !asecond2->isOpen() || !second1->isOpen()) &&
+	    attempts--)
+		this_thread::sleep_for(1s);
+
 	// Delay close of peer 2 to check closing works properly
 	// Delay close of peer 2 to check closing works properly
 	pc1->close();
 	pc1->close();
 	this_thread::sleep_for(1s);
 	this_thread::sleep_for(1s);

+ 20 - 3
test/track.cpp

@@ -92,9 +92,10 @@ void test_track() {
 	});
 	});
 
 
 	shared_ptr<Track> t2;
 	shared_ptr<Track> t2;
-	pc2->onTrack([&t2](shared_ptr<Track> t) {
+	string newTrackMid;
+	pc2->onTrack([&t2, &newTrackMid](shared_ptr<Track> t) {
 		cout << "Track 2: Received with mid \"" << t->mid() << "\"" << endl;
 		cout << "Track 2: Received with mid \"" << t->mid() << "\"" << endl;
-		if (t->mid() != "test") {
+		if (t->mid() != newTrackMid) {
 			cerr << "Wrong track mid" << endl;
 			cerr << "Wrong track mid" << endl;
 			return;
 			return;
 		}
 		}
@@ -102,7 +103,9 @@ void test_track() {
 		std::atomic_store(&t2, t);
 		std::atomic_store(&t2, t);
 	});
 	});
 
 
-	auto t1 = pc1->addTrack(Description::Video("test"));
+	// Test opening a track
+	newTrackMid = "test";
+	auto t1 = pc1->addTrack(Description::Video(newTrackMid));
 
 
 	pc1->setLocalDescription();
 	pc1->setLocalDescription();
 
 
@@ -118,6 +121,20 @@ void test_track() {
 	if (!at2 || !at2->isOpen() || !t1->isOpen())
 	if (!at2 || !at2->isOpen() || !t1->isOpen())
 		throw runtime_error("Track is not open");
 		throw runtime_error("Track is not open");
 
 
+	// Test renegotiation
+	newTrackMid = "added";
+	t1 = pc1->addTrack(Description::Video(newTrackMid));
+
+	pc1->setLocalDescription();
+
+	attempts = 10;
+	t2.reset();
+	while ((!(at2 = std::atomic_load(&t2)) || !at2->isOpen() || !t1->isOpen()) && attempts--)
+		this_thread::sleep_for(1s);
+
+	if (!at2 || !at2->isOpen() || !t1->isOpen())
+		throw runtime_error("Renegociated track is not open");
+
 	// TODO: Test sending RTP packets in track
 	// TODO: Test sending RTP packets in track
 
 
 	// Delay close of peer 2 to check closing works properly
 	// Delay close of peer 2 to check closing works properly