Browse Source

Merge branch 'paullouisageneau:master' into master

WangCat 3 years ago
parent
commit
d76f48dfef
100 changed files with 3719 additions and 1250 deletions
  1. 4 0
      .gitignore
  2. 14 11
      BUILDING.md
  3. 64 21
      CMakeLists.txt
  4. 161 117
      DOC.md
  5. 21 15
      README.md
  6. 5 0
      cmake/LibDataChannelConfig.cmake
  7. 17 0
      cmake/Modules/FindLibJuice.cmake
  8. 1 1
      deps/libjuice
  9. 1 1
      deps/usrsctp
  10. 17 3
      examples/client-benchmark/CMakeLists.txt
  11. 18 4
      examples/client/CMakeLists.txt
  12. 10 3
      examples/copy-paste-capi/CMakeLists.txt
  13. 8 1
      examples/copy-paste/CMakeLists.txt
  14. 7 3
      examples/media/CMakeLists.txt
  15. 6 3
      examples/sfu-media/CMakeLists.txt
  16. 3 5
      examples/streamer/CMakeLists.txt
  17. 11 4
      examples/streamer/README.md
  18. 2 0
      examples/streamer/helpers.hpp
  19. 60 62
      examples/web/script.js
  20. 1 1
      include/rtc/channel.hpp
  21. 5 2
      include/rtc/configuration.hpp
  22. 0 5
      include/rtc/datachannel.hpp
  23. 3 0
      include/rtc/global.hpp
  24. 1 1
      include/rtc/h264packetizationhandler.hpp
  25. 10 6
      include/rtc/h264rtppacketizer.hpp
  26. 4 2
      include/rtc/mediachainablehandler.hpp
  27. 18 8
      include/rtc/mediahandlerelement.hpp
  28. 1 1
      include/rtc/mediahandlerrootelement.hpp
  29. 2 3
      include/rtc/message.hpp
  30. 2 2
      include/rtc/nalunit.hpp
  31. 1 1
      include/rtc/opuspacketizationhandler.hpp
  32. 5 3
      include/rtc/opusrtppacketizer.hpp
  33. 1 1
      include/rtc/peerconnection.hpp
  34. 34 37
      include/rtc/rtc.h
  35. 4 3
      include/rtc/rtcpnackresponder.hpp
  36. 4 3
      include/rtc/rtcpsrreporter.hpp
  37. 1 1
      include/rtc/rtp.hpp
  38. 0 3
      include/rtc/track.hpp
  39. 79 0
      pages/Makefile
  40. 1 0
      pages/content/extra/CNAME
  41. 6 0
      pages/content/hello-world.md
  42. 103 0
      pages/content/images/forkme_left_gray_6d6d6d.svg
  43. 102 0
      pages/content/images/forkme_right_gray_6d6d6d.svg
  44. BIN
      pages/content/images/icon_compatible.png
  45. BIN
      pages/content/images/icon_easy.png
  46. BIN
      pages/content/images/icon_portable.png
  47. 37 0
      pages/content/pages/index.md
  48. 789 0
      pages/content/pages/reference.md
  49. 41 0
      pages/pelicanconf.py
  50. 20 0
      pages/publishconf.py
  51. 136 0
      pages/tasks.py
  52. 259 0
      pages/theme/static/css/main.css
  53. 62 0
      pages/theme/static/css/pygments-highlight.css
  54. 11 0
      pages/theme/templates/analytics.html
  55. 41 0
      pages/theme/templates/archives.html
  56. 76 0
      pages/theme/templates/article.html
  57. 39 0
      pages/theme/templates/article_list.html
  58. 0 0
      pages/theme/templates/author.html
  59. 0 0
      pages/theme/templates/authors.html
  60. 94 0
      pages/theme/templates/base.html
  61. 18 0
      pages/theme/templates/categories.html
  62. 11 0
      pages/theme/templates/category.html
  63. 9 0
      pages/theme/templates/github.html
  64. 30 0
      pages/theme/templates/index.html
  65. 85 0
      pages/theme/templates/page.html
  66. 11 0
      pages/theme/templates/pagination.html
  67. 14 0
      pages/theme/templates/period_archives.html
  68. 11 0
      pages/theme/templates/tag.html
  69. 16 0
      pages/theme/templates/tags.html
  70. 1 3
      src/candidate.cpp
  71. 767 767
      src/capi.cpp
  72. 5 13
      src/channel.cpp
  73. 1 1
      src/description.cpp
  74. 32 18
      src/global.cpp
  75. 2 1
      src/h264packetizationhandler.cpp
  76. 41 36
      src/h264rtppacketizer.cpp
  77. 8 10
      src/impl/certificate.cpp
  78. 2 2
      src/impl/datachannel.hpp
  79. 6 8
      src/impl/dtlssrtptransport.cpp
  80. 2 2
      src/impl/dtlssrtptransport.hpp
  81. 3 4
      src/impl/dtlstransport.hpp
  82. 1 1
      src/impl/icetransport.cpp
  83. 2 2
      src/impl/icetransport.hpp
  84. 1 1
      src/impl/logcounter.cpp
  85. 1 1
      src/impl/logcounter.hpp
  86. 21 5
      src/impl/peerconnection.cpp
  87. 1 2
      src/impl/queue.hpp
  88. 1 1
      src/impl/sctptransport.cpp
  89. 1 1
      src/impl/sctptransport.hpp
  90. 1 3
      src/impl/selectinterrupter.hpp
  91. 15 19
      src/impl/sha.cpp
  92. 140 0
      src/impl/socket.hpp
  93. 1 3
      src/impl/tcpserver.hpp
  94. 1 1
      src/impl/tcptransport.cpp
  95. 2 4
      src/impl/tcptransport.hpp
  96. 1 1
      src/impl/threadpool.cpp
  97. 26 0
      src/impl/tls.cpp
  98. 2 1
      src/impl/tls.hpp
  99. 1 1
      src/impl/track.cpp
  100. 2 1
      src/impl/verifiedtlstransport.hpp

+ 4 - 0
.gitignore

@@ -1,9 +1,13 @@
 build/
 node_modules/
+output/
 *.d
 *.o
 *.a
 *.so
+*.pyc
+*.pyo
+__pycache__
 compile_commands.json
 /tests
 .DS_Store

+ 14 - 11
BUILDING.md

@@ -10,7 +10,9 @@ $ git submodule update --init --recursive
 
 ## Build with CMake
 
-The CMake library targets `libdatachannel` and `libdatachannel-static` respectively correspond to the shared and static libraries. The default target will build tests and examples. The option `USE_GNUTLS` allows to switch between OpenSSL (default) and GnuTLS, and the option `USE_NICE` allows to switch between libjuice as submodule (default) and libnice.
+The CMake library targets `libdatachannel` and `libdatachannel-static` respectively correspond to the shared and static libraries. The default target will build tests and examples.
+
+The option `USE_GNUTLS` allows to switch between OpenSSL (default) and GnuTLS, and the option `USE_NICE` allows to switch between libjuice as submodule (default) and libnice. The options `USE_SYSTEM_SRTP` and `USE_SYSTEM_JUICE` allow to link against the system library rather than building the submodule, for libsrtp and libjuice respectively.
 
 If you only need Data Channels, the option `NO_MEDIA` allows to make the library lighter by removing media support. Similarly, `NO_WEBSOCKET` removes WebSocket support.
 
@@ -22,30 +24,30 @@ $ cd build
 $ make -j2
 ```
 
-### Apple macOS with XCode project
+### Apple macOS with Xcode project
+
+To generate an Xcode project in the `build` directory:
 
 ```bash
-$ cmake -B "$BUILD_DIR" -DUSE_GNUTLS=0 -DUSE_NICE=0 -G Xcode
+$ cmake -B build -DUSE_GNUTLS=0 -DUSE_NICE=0 -G Xcode
 ```
 
-Xcode project is generated in *build/* directory.
-
-#### Solving **Could NOT find OpenSSL** error
+#### Solving "Could NOT find OpenSSL" error
 
-You need to add OpenSSL root directory if your build fails with the following message:
+You need to add OpenSSL root directory if the build fails with the following message:
 
 ```
-Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the
-system variable OPENSSL_ROOT_DIR (missing: OPENSSL_CRYPTO_LIBRARY
-OPENSSL_INCLUDE_DIR)
+Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the system variable OPENSSL_ROOT_DIR (missing: OPENSSL_CRYPTO_LIBRARY OPENSSL_INCLUDE_DIR)
 ```
 
-for example:
+For example:
+
 ```bash
 $ cmake -B build -DUSE_GNUTLS=0 -DUSE_NICE=0 -G Xcode -DOPENSSL_ROOT_DIR=/usr/local/Cellar/openssl\@1.1/1.1.1h/
 ```
 
 ### Microsoft Windows with MinGW cross-compilation
+
 ```bash
 $ cmake -B build -DCMAKE_TOOLCHAIN_FILE=/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake # replace with your toolchain file
 $ cd build
@@ -53,6 +55,7 @@ $ make -j2
 ```
 
 ### Microsoft Windows with Microsoft Visual C++
+
 ```bash
 $ cmake -B build -G "NMake Makefiles"
 $ cd build

+ 64 - 21
CMakeLists.txt

@@ -1,13 +1,14 @@
 cmake_minimum_required(VERSION 3.7)
 project(libdatachannel
-	VERSION 0.13.4
+	VERSION 0.14.3
 	LANGUAGES CXX)
-set(PROJECT_DESCRIPTION "WebRTC Data Channels Library")
+set(PROJECT_DESCRIPTION "C/C++ WebRTC network library featuring Data Channels, Media Transport, and WebSockets")
 
 # Options
 option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
 option(USE_NICE "Use libnice instead of libjuice" OFF)
 option(USE_SYSTEM_SRTP "Use system libSRTP" OFF)
+option(USE_SYSTEM_JUICE "Use system libjuice" OFF)
 option(NO_WEBSOCKET "Disable WebSocket support" OFF)
 option(NO_MEDIA "Disable media transport support" OFF)
 option(NO_EXAMPLES "Disable examples" OFF)
@@ -70,7 +71,6 @@ set(LIBDATACHANNEL_HEADERS
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/candidate.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/channel.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/configuration.hpp
-	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/configuration.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/datachannel.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/description.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/mediahandler.hpp
@@ -85,6 +85,7 @@ set(LIBDATACHANNEL_HEADERS
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtp.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/track.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/websocket.hpp
+	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/websocketserver.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtppacketizationconfig.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcpsrreporter.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtppacketizer.hpp
@@ -223,21 +224,26 @@ add_library(datachannel-static STATIC EXCLUDE_FROM_ALL
 
 set_target_properties(datachannel PROPERTIES
 	VERSION ${PROJECT_VERSION}
-	CXX_STANDARD 17)
+	CXX_STANDARD 17
+	CXX_VISIBILITY_PRESET default)
 set_target_properties(datachannel-static PROPERTIES
 	VERSION ${PROJECT_VERSION}
 	CXX_STANDARD 17)
 
-target_include_directories(datachannel PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include>)
+target_include_directories(datachannel PUBLIC
+    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+    $<INSTALL_INTERFACE:include>)
 target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
 target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
-target_link_libraries(datachannel PUBLIC Threads::Threads)
+target_link_libraries(datachannel PRIVATE Threads::Threads)
 target_link_libraries(datachannel PRIVATE Usrsctp::Usrsctp plog::plog)
 
-target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
+target_include_directories(datachannel-static PUBLIC
+    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+    $<INSTALL_INTERFACE:include>)
 target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
 target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
-target_link_libraries(datachannel-static PUBLIC Threads::Threads)
+target_link_libraries(datachannel-static PRIVATE Threads::Threads)
 target_link_libraries(datachannel-static PRIVATE Usrsctp::Usrsctp plog::plog)
 
 if(WIN32)
@@ -324,11 +330,21 @@ if (USE_NICE)
 	target_link_libraries(datachannel PRIVATE LibNice::LibNice)
 	target_link_libraries(datachannel-static PRIVATE LibNice::LibNice)
 else()
-	add_subdirectory(deps/libjuice EXCLUDE_FROM_ALL)
 	target_compile_definitions(datachannel PRIVATE USE_NICE=0)
 	target_compile_definitions(datachannel-static PRIVATE USE_NICE=0)
-	target_link_libraries(datachannel PRIVATE LibJuice::LibJuiceStatic)
-	target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuiceStatic)
+	if(USE_SYSTEM_JUICE)
+		find_package(LibJuice REQUIRED)
+		target_compile_definitions(datachannel PRIVATE RTC_SYSTEM_JUICE=1)
+		target_compile_definitions(datachannel-static PRIVATE RTC_SYSTEM_JUICE=1)
+		target_link_libraries(datachannel PRIVATE LibJuice::LibJuice)
+		target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuice)
+	else()
+		add_subdirectory(deps/libjuice EXCLUDE_FROM_ALL)
+		target_compile_definitions(datachannel PRIVATE RTC_SYSTEM_JUICE=0)
+		target_compile_definitions(datachannel-static PRIVATE RTC_SYSTEM_JUICE=0)
+		target_link_libraries(datachannel PRIVATE LibJuice::LibJuiceStatic)
+		target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuiceStatic)
+	endif()
 endif()
 
 if(CAPI_STDCALL)
@@ -336,7 +352,10 @@ if(CAPI_STDCALL)
 	target_compile_definitions(datachannel-static PUBLIC CAPI_STDCALL)
 endif()
 
+set_target_properties(datachannel PROPERTIES EXPORT_NAME LibDataChannel)
 add_library(LibDataChannel::LibDataChannel ALIAS datachannel)
+
+set_target_properties(datachannel-static PROPERTIES EXPORT_NAME LibDataChannelStatic)
 add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static)
 
 if(NOT MSVC)
@@ -354,7 +373,7 @@ if(WARNINGS_AS_ERRORS)
 	endif()
 endif()
 
-install(TARGETS datachannel EXPORT libdatachannel-config
+install(TARGETS datachannel EXPORT LibDataChannelTargets
 	RUNTIME DESTINATION bin
 	LIBRARY DESTINATION lib
 	ARCHIVE DESTINATION lib
@@ -364,12 +383,29 @@ install(FILES ${LIBDATACHANNEL_HEADERS}
 	DESTINATION include/rtc
 )
 
+# Export targets
 install(
-  EXPORT libdatachannel-config
-  NAMESPACE LibDatachannel::
-  DESTINATION share/cmake/libdatachannel
+	EXPORT LibDataChannelTargets
+	FILE LibDataChannelTargets.cmake
+	NAMESPACE LibDataChannel::
+	DESTINATION lib/cmake/LibDataChannel
 )
 
+# Export config
+install(
+	FILES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/LibDataChannelConfig.cmake
+	DESTINATION lib/cmake/LibDataChannel
+)
+
+# Export config version
+include(CMakePackageConfigHelpers)
+write_basic_package_version_file(
+	${CMAKE_BINARY_DIR}/LibDataChannelConfigVersion.cmake
+	VERSION ${PROJECT_VERSION}
+	COMPATIBILITY SameMajorVersion)
+install(FILES ${CMAKE_BINARY_DIR}/LibDataChannelConfigVersion.cmake
+	DESTINATION lib/cmake/LibDataChannel)
+
 # Tests
 if(NOT NO_TESTS)
 	if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
@@ -378,14 +414,17 @@ if(NOT NO_TESTS)
 	else()
 		add_executable(datachannel-tests ${TESTS_SOURCES})
 	endif()
+
 	set_target_properties(datachannel-tests PROPERTIES
 		VERSION ${PROJECT_VERSION}
-		CXX_STANDARD 17)
-	set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests)
+		CXX_STANDARD 17
+		OUTPUT_NAME tests)
+
 	set_target_properties(datachannel-tests PROPERTIES
 		XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.github.paullouisageneau.libdatachannel.tests)
+
 	target_include_directories(datachannel-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
-	target_link_libraries(datachannel-tests datachannel)
+	target_link_libraries(datachannel-tests datachannel Threads::Threads)
 
 	# Benchmark
 	if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
@@ -394,21 +433,25 @@ if(NOT NO_TESTS)
 	else()
 		add_executable(datachannel-benchmark test/benchmark.cpp)
 	endif()
+
 	set_target_properties(datachannel-benchmark PROPERTIES
 		VERSION ${PROJECT_VERSION}
-		CXX_STANDARD 17)
-	set_target_properties(datachannel-benchmark PROPERTIES OUTPUT_NAME benchmark)
+		CXX_STANDARD 17
+		OUTPUT_NAME benchmark)
+
 	set_target_properties(datachannel-benchmark PROPERTIES
 		XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.github.paullouisageneau.libdatachannel.benchmark)
+
 	target_compile_definitions(datachannel-benchmark PRIVATE BENCHMARK_MAIN=1)
 	target_include_directories(datachannel-benchmark PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
-	target_link_libraries(datachannel-benchmark datachannel)
+	target_link_libraries(datachannel-benchmark datachannel Threads::Threads)
 endif()
 
 # Examples
 if(NOT NO_EXAMPLES)
 	set(JSON_BuildTests OFF CACHE INTERNAL "")
 	add_subdirectory(deps/json EXCLUDE_FROM_ALL)
+
 	if(NOT NO_WEBSOCKET)
 		add_subdirectory(examples/client)
 		add_subdirectory(examples/client-benchmark)

+ 161 - 117
DOC.md

@@ -1,4 +1,4 @@
-# libdatachannel - C API Documentation
+## libdatachannel - C API Documentation
 
 The following details the C API of libdatachannel. The C API is available by including the `rtc/rtc.h` header.
 
@@ -7,6 +7,7 @@ The following details the C API of libdatachannel. The C API is available by inc
 Unless stated otherwise, functions return `RTC_ERR_SUCCESS`, defined as `0`, on success.
 
 All functions can return the following negative error codes:
+
 - `RTC_ERR_INVALID`: an invalid argument was provided
 - `RTC_ERR_FAILURE`: a runtime error happened
 - `RTC_ERR_NOT_AVAIL`: an element is not available at the moment
@@ -23,6 +24,7 @@ void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb)
 ```
 
 Arguments:
+
 - `level`: the log level. It must be one of the following: `RTC_LOG_NONE`, `RTC_LOG_FATAL`, `RTC_LOG_ERROR`, `RTC_LOG_WARNING`, `RTC_LOG_INFO`, `RTC_LOG_DEBUG`, `RTC_LOG_VERBOSE`.
 - `cb` (optional): the callback to pass the log lines to. If the callback is already set, it is replaced. If NULL after a callback is set, the callback is unset. If NULL on first call, the library will log to stdout instead.
 
@@ -30,6 +32,7 @@ Arguments:
 `void myLogCallback(rtcLogLevel level, const char *message)`
 
 Arguments:
+
 - `level`: the log level for the current message. It will be one of the following: `RTC_LOG_FATAL`, `RTC_LOG_ERROR`, `RTC_LOG_WARNING`, `RTC_LOG_INFO`, `RTC_LOG_DEBUG`, `RTC_LOG_VERBOSE`.
 - `message`: a null-terminated string containing the log message
 
@@ -58,6 +61,7 @@ void rtcSetUserPointer(int id, void *user_ptr)
 Sets a opaque user pointer for a Peer Connection, Data Channel, Track, or WebSocket. The user pointer will be passed as last argument in each corresponding callback. It will never be accessed in any way. The initial user pointer of a Peer Connection or WebSocket is `NULL`, and the initial one of a Data Channel or Track is the one of the Peer Connection at the time of creation.
 
 Arguments:
+
 - `id`: the identifier of Peer Connection, Data Channel, Track, or WebSocket
 - `user_ptr`: an opaque pointer whose meaning is up to the user
 
@@ -71,23 +75,28 @@ int rtcCreatePeerConnection(const rtcConfiguration *config)
 typedef struct {
 	const char **iceServers;
 	int iceServersCount;
+	const char *bindAddress;
 	rtcCertificateType certificateType;
+	rtcTransportPolicy iceTransportPolicy;
 	bool enableIceTcp;
 	bool disableAutoNegotiation;
 	uint16_t portRangeBegin;
 	uint16_t portRangeEnd;
 	int mtu;
+	int maxMessageSize;
 } rtcConfiguration;
 ```
 
 Creates a Peer Connection.
 
 Arguments:
+
 - `config`: the configuration structure, containing:
   - `iceServers` (optional): an array of pointers on null-terminated ice server URIs (NULL if unused)
   - `iceServersCount` (optional): number of URLs in the array pointed by `iceServers` (0 if unused)
   - `bindAddress` (optional): if non-NULL, bind only to the given local address (ignored with libnice as ICE backend)
   - `certificateType` (optional): certificate type, either `RTC_CERTIFICATE_ECDSA` or `RTC_CERTIFICATE_RSA` (0 or `RTC_CERTIFICATE_DEFAULT` if default)
+  - `iceTransportPolicy` (optional): ICE transport policy, if set to `RTC_TRANSPORT_POLICY_RELAY`, the PeerConnection will emit only relayed candidates (0 or `RTC_TRANSPORT_POLICY_ALL` if default)
   - `enableIceTcp`: if true, generate TCP candidates for ICE (ignored with libjuice as ICE backend)
   - `disableAutoNegotiation`: if true, the user is responsible for calling `rtcSetLocalDescription` after creating a Data Channel and after setting the remote description
   - `portRangeBegin` (optional): first port (included) of the allowed local port range (0 if unused)
@@ -110,6 +119,7 @@ int rtcDeletePeerConnection(int pc)
 Deletes the specified Peer Connection.
 
 Arguments:
+
 - `pc`: the Peer Connection identifier
 
 Return value: `RTC_ERR_SUCCESS` or a negative error code
@@ -169,6 +179,7 @@ int rtcSetLocalDescription(int pc, const char *type)
 Initiates the handshake process. Following this call, the local description callback will be called with the local description, which must be sent to the remote peer by the user's method of choice. Note this call is implicit after `rtcSetRemoteDescription` and `rtcCreateDataChannel` if `disableAutoNegotiation` was not set on Peer Connection creation.
 
 Arguments:
+
 - `pc`: the Peer Connection identifier
 - `type` (optional): type of the description ("offer", "answer", "pranswer", or "rollback") or NULL for autodetection.
 
@@ -181,6 +192,7 @@ int rtcSetRemoteDescription(int pc, const char *sdp, const char *type)
 Sets the remote description received from the remote peer by the user's method of choice. The remote description may have candidates or not.
 
 Arguments:
+
 - `pc`: the Peer Connection identifier
 - `type` (optional): type of the description ("offer", "answer", "pranswer", or "rollback") or NULL for autodetection.
 
@@ -195,6 +207,7 @@ int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid)
 Adds a trickled remote candidate received from the remote peer by the user's method of choice.
 
 Arguments:
+
 - `pc`: the Peer Connection identifier
 - `cand`: a null-terminated SDP string representing the candidate (with or without the `"a="` prefix)
 - `mid` (optional): a null-terminated string representing the mid of the candidate in the remote SDP description or NULL for autodetection
@@ -212,6 +225,7 @@ int rtcGetLocalDescription(int pc, char *buffer, int size)
 Retrieves the current local description in SDP format.
 
 Arguments:
+
 - `pc`: the Peer Connection identifier
 - `buffer`: a user-supplied buffer to store the description
 - `size`: the size of `buffer`
@@ -229,6 +243,7 @@ int rtcGetRemoteDescription(int pc, char *buffer, int size)
 Retrieves the current remote description in SDP format.
 
 Arguments:
+
 - `pc`: the Peer Connection identifier
 - `buffer`: a user-supplied buffer to store the description
 - `size`: the size of `buffer`
@@ -246,6 +261,7 @@ int rtcGetLocalDescriptionType(int pc, char *buffer, int size)
 Retrieves the current local description type as string.
 
 Arguments:
+
 - `pc`: the Peer Connection identifier
 - `buffer`: a user-supplied buffer to store the type
 - `size`: the size of `buffer`
@@ -263,6 +279,7 @@ int rtcGetRemoteDescriptionType(int pc, char *buffer, int size)
 Retrieves the current remote description type as string.
 
 Arguments:
+
 - `pc`: the Peer Connection identifier
 - `buffer`: a user-supplied buffer to store the type
 - `size`: the size of `buffer`
@@ -281,6 +298,7 @@ int rtcGetLocalAddress(int pc, char *buffer, int size)
 Retrieves the current local address, i.e. the network address of the currently selected local candidate. The address will have the format `"IP_ADDRESS:PORT"`, where `IP_ADDRESS` may be either IPv4 or IPv6. The call might fail if the PeerConnection is not in state `RTC_CONNECTED`, and the address might change if the state is not `RTC_COMPLETED`.
 
 Arguments:
+
 - `pc`: the Peer Connection identifier
 - `buffer`: a user-supplied buffer to store the address
 - `size`: the size of `buffer`
@@ -298,6 +316,7 @@ int rtcGetRemoteAddress(int pc, char *buffer, int size)
 Retrieves the current remote address, i.e. the network address of the currently selected remote candidate. The address will have the format `"IP_ADDRESS:PORT"`, where `IP_ADDRESS` may be either IPv4 or IPv6. The call may fail if the state is not `RTC_CONNECTED`, and the address might change if the state is not `RTC_COMPLETED`.
 
 Arguments:
+
 - `pc`: the Peer Connection identifier
 - `buffer`: a user-supplied buffer to store the address
 - `size`: the size of `buffer`
@@ -315,6 +334,7 @@ int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote
 Retrieve the currently selected candidate pair. The call may fail if the state is not `RTC_CONNECTED`, and the selected candidate pair might change if the state is not `RTC_COMPLETED`.
 
 Arguments:
+
 - `pc`: the Peer Connection identifier
 - `local`: a user-supplied buffer to store the local candidate
 - `localSize`: the size of `local`
@@ -325,6 +345,126 @@ Return value: the maximun length of strings copied in buffers (including the ter
 
 If `local`, `remote`, or both, are `NULL`, the corresponding candidate is not copied, but the maximum length is still returned.
 
+### Channel (Common API for Data Channel, Track, and WebSocket)
+
+The following common functions might be called with a generic channel identifier. It may be the identifier of either a Data Channel, a Track, or a WebSocket.
+
+#### rtcSetXCallback
+
+These functions set, change, or unset (if `cb` is `NULL`) the different callbacks of a channel.
+
+```
+int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb)
+```
+
+`cb` must have the following signature: `void myOpenCallback(int id, void *user_ptr)`
+
+```
+int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb)
+```
+
+`cb` must have the following signature: `void myClosedCallback(int id, void *user_ptr)`
+
+```
+int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb)
+```
+
+`cb` must have the following signature: `void myErrorCallback(int id, const char *error, void *user_ptr)`
+
+```
+int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb)
+```
+
+`cb` must have the following signature: `void myMessageCallback(int id, const char *message, int size, void *user_ptr)`
+
+```
+int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb)
+```
+
+`cb` must have the following signature: `void myBufferedAmountLowCallback(int id, void *user_ptr)`
+
+```
+int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb)
+```
+
+`cb` must have the following signature: `void myAvailableCallback(int id, void *user_ptr)`
+
+#### rtcSendMessage
+
+```
+int rtcSendMessage(int id, const char *data, int size)
+```
+
+Arguments:
+
+- `id`: the channel identifier
+- `data`: the message data
+- `size`: if size >= 0, `data` is interpreted as a binary message of length `size`, otherwise it is interpreted as a null-terminated UTF-8 string.
+
+Return value: `RTC_ERR_SUCCESS` or a negative error code
+
+Sends a message immediately if possible.
+
+Data Channel and WebSocket: If the message may not be sent immediately due to flow control or congestion control, it is buffered until it can actually be sent. You can retrieve the current buffered data size with `rtcGetBufferedAmount`.
+Tracks are an exception: There is no flow or congestion control, messages are never buffered and `rtcGetBufferedAmount` always returns 0.
+
+#### rtcGetBufferedAmount
+
+```
+int rtcGetBufferedAmount(int id)
+```
+
+Retrieves the current buffered amount, i.e. the total size of currently buffered messages waiting to be actually sent in the channel. This does not account for the data buffered at the transport level.
+
+Return value: the buffered amount or a negative error code
+
+#### rtcGetBufferedAmountLowThreshold
+
+```
+int rtcSetBufferedAmountLowThreshold(int id, int amount)
+```
+
+Changes the buffered amount threshold under which `BufferedAmountLowCallback` is called. The callback is called when the buffered amount was strictly superior and gets equal to or lower than the threshold when a message is sent. The initial threshold is 0, meaning the the callback is called each time the buffered amount goes back to zero after being non-zero.
+
+Arguments:
+
+- `id`: the channel identifier
+- `amount`: the new buffer level threshold
+
+Return value: the identifier of the new WebSocket or a negative error code
+
+#### rtcReceiveMessage
+
+```
+int rtcReceiveMessage(int id, char *buffer, int *size)
+```
+
+Receives a pending message if possible. The function may only be called if `MessageCallback` is not set.
+
+Arguments:
+
+- `id`: the channel identifier
+- `buffer`: a user-supplied buffer where to write the message data
+- `size`: a pointer to a user-supplied int which must be initialized to the size of `buffer`. On success, the function will write the size of the message to it before returning.
+
+Return value: `RTC_ERR_SUCCESS` or a negative error code (In particular, `RTC_ERR_NOT_AVAIL` is returned when there are no pending messages)
+
+If `buffer` is `NULL`, the message is not copied and kept pending but the size is still written to `size`.
+
+#### rtcGetAvailableAmount
+
+```
+int rtcGetAvailableAmount(int id)
+```
+
+Retrieves the available amount, i.e. the total size of messages pending reception with `rtcReceiveMessage`. The function may only be called if `MessageCallback` is not set.
+
+Arguments:
+
+- `id`: the channel identifier
+
+Return value: the available amount or a negative error code
+
 ### Data Channel
 
 #### rtcCreateDataChannel
@@ -352,6 +492,7 @@ typedef struct {
 Adds a Data Channel on a Peer Connection. The Peer Connection does not need to be connected, however, the Data Channel will be open only when the Peer Connection is connected.
 
 Arguments:
+
 - `pc`: identifier of the PeerConnection on which to add a Data Channel
 - `label`: a user-defined UTF-8 string representing the Data Channel name
 - `init`: a structure of initialization settings containing:
@@ -382,6 +523,7 @@ int rtcDeleteDataChannel(int dc)
 Deletes a Data Channel.
 
 Arguments:
+
 - `dc`: the Data Channel identifier
 
 After this function has been called, `dc` must not be used in a function call anymore. This function will block until all scheduled callbacks of `dc` return (except the one this function might be called in) and no other callback will be called for `dc` after it returns.
@@ -395,6 +537,7 @@ int rtcGetDataChannelStream(int dc)
 Retrieves the stream ID of the Data Channel.
 
 Arguments:
+
 - `dc`: the Data Channel identifier
 
 Return value: the stream ID (0-65534) or a negative error code
@@ -408,6 +551,7 @@ int rtcGetDataChannelLabel(int dc, char *buffer, int size)
 Retrieves the label of a Data Channel.
 
 Arguments:
+
 - `dc`: the Data Channel identifier
 - `buffer`: a user-supplied buffer to store the label
 - `size`: the size of `buffer`
@@ -425,6 +569,7 @@ int rtcGetDataChannelProtocol(int dc, char *buffer, int size)
 Retrieves the protocol of a Data Channel.
 
 Arguments:
+
 - `dc`: the Data Channel identifier
 - `buffer`: a user-supplied buffer to store the protocol
 - `size`: the size of `buffer`
@@ -442,6 +587,7 @@ int rtcGetDataChannelReliability(int dc, rtcReliability *reliability)
 Retrieves the reliability settings of a Data Channel. The function may be called irrelevant of how the Data Channel was created.
 
 Arguments:
+
 - `dc`: the Data Channel identifier
 - `reliability` a user-supplied structure to fill
 
@@ -458,6 +604,7 @@ int rtcAddTrack(int pc, const char *mediaDescriptionSdp)
 Adds a new Track on a Peer Connection. The Peer Connection does not need to be connected, however, the Track will be open only when the Peer Connection is connected.
 
 Arguments:
+
 - `pc`: the Peer Connection identifier
 - `mediaDescriptionSdp`: a null-terminated string specifying the corresponding media SDP. It must start with a m-line and include a mid parameter.
 
@@ -476,6 +623,7 @@ int rtcDeleteTrack(int tr)
 Deletes a Track.
 
 Arguments:
+
 - `tr`: the Track identifier
 
 After this function has been called, `tr` must not be used in a function call anymore. This function will block until all scheduled callbacks of `tr` return (except the one this function might be called in) and no other callback will be called for `tr` after it returns.
@@ -489,6 +637,7 @@ int rtcGetTrackDescription(int tr, char *buffer, int size)
 Retrieves the SDP media description of a Track.
 
 Arguments:
+
 - `dc`: the Track identifier
 - `buffer`: a user-supplied buffer to store the description
 - `size`: the size of `buffer`
@@ -497,6 +646,10 @@ Return value: the length of the string copied in buffer (including the terminati
 
 If `buffer` is `NULL`, the description is not copied but the size is still returned.
 
+### Media
+
+TODO
+
 ### WebSocket
 
 #### rtcCreateWebSocket
@@ -513,6 +666,7 @@ typedef struct {
 Creates a new client WebSocket.
 
 Arguments:
+
 - `url`: a null-terminated string representing the fully-qualified URL to open.
 - `config`: a structure with the following parameters:
   - `bool disableTlsVerification`: if true, don't verify the TLS certificate, else try to verify it if possible
@@ -528,6 +682,7 @@ int rtcDeleteWebSocket(int ws)
 ```
 
 Arguments:
+
 - `ws`: the identifier of the WebSocket to delete
 
 After this function has been called, `ws` must not be used in a function call anymore. This function will block until all scheduled callbacks of `ws` return (except the one this function might be called in) and no other callback will be called for `ws` after it returns.
@@ -541,6 +696,7 @@ int rtcGetWebSocketRemoteAddress(int ws, char *buffer, int size)
 Retrieves the remote address, i.e. the network address of the remote endpoint. The address will have the format `"HOST:PORT"`. The call may fail if the underlying TCP transport of the WebSocket is not connected. This function is useful for a client WebSocket received by a WebSocket Server.
 
 Arguments:
+
 - `ws`: the identifier of the WebSocket
 - `buffer`: a user-supplied buffer to store the description
 - `size`: the size of `buffer`
@@ -558,6 +714,7 @@ int rtcGetWebSocketPath(int ws, char *buffer, int size)
 Retrieves the path of the WebSocket, i.e. the HTTP requested path. This function is useful for a client WebSocket received by a WebSocket Server. Warning: The WebSocket must be open for the call to succeed.
 
 Arguments:
+
 - `ws`: the identifier of the WebSocket
 - `buffer`: a user-supplied buffer to store the description
 - `size`: the size of `buffer`
@@ -586,6 +743,7 @@ typedef struct {
 Creates a new WebSocket server.
 
 Arguments:
+
 - `config`: a structure with the following parameters:
   - `uint16_t port`: the port to listen on (if 0, automatically select an available port)
   - `bool enableTls`: if true, enable the TLS layer (WSS)
@@ -607,6 +765,7 @@ int rtcDeleteWebSocketServer(int wsserver)
 ```
 
 Arguments:
+
 - `wsserver`: the identifier of the WebSocket Server to delete
 
 After this function has been called, `wsserver` must not be used in a function call anymore. This function will block until all scheduled callbacks of `wsserver` return (except the one this function might be called in) and no other callback will be called for `wsserver` after it returns.
@@ -619,124 +778,9 @@ int rtcGetWebSocketServerPort(int wsserver);
 Retrieves the port which the WebSocket Server is listening on.
 
 Arguments:
+
 - `wsserver`: the identifier of the WebSocket Server
 
 Return value: The port of the WebSocket Server or a negative error code
 
-### Channel (Common API for Data Channel, Track, and WebSocket)
-
-The following common functions might be called with a generic channel identifier. It may be the identifier of either a Data Channel, a Track, or a WebSocket.
-
-#### rtcSetXCallback
-
-These functions set, change, or unset (if `cb` is `NULL`) the different callbacks of a channel.
-
-```
-int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb)
-```
-
-`cb` must have the following signature: `void myOpenCallback(int id, void *user_ptr)`
-
-```
-int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb)
-```
-
-`cb` must have the following signature: `void myClosedCallback(int id, void *user_ptr)`
-
-```
-int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb)
-```
-
-`cb` must have the following signature: `void myErrorCallback(int id, const char *error, void *user_ptr)`
-
-```
-int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb)
-```
-
-`cb` must have the following signature: `void myMessageCallback(int id, const char *message, int size, void *user_ptr)`
-
-```
-int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb)
-```
-
-`cb` must have the following signature: `void myBufferedAmountLowCallback(int id, void *user_ptr)`
-
-```
-int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb)
-```
-
-`cb` must have the following signature: `void myAvailableCallback(int id, void *user_ptr)`
-
-#### rtcSendMessage
-
-```
-int rtcSendMessage(int id, const char *data, int size)
-```
-
-Arguments:
-- `id`: the channel identifier
-- `data`: the message data
-- `size`: if size >= 0, `data` is interpreted as a binary message of length `size`, otherwise it is interpreted as a null-terminated UTF-8 string.
-
-Return value: `RTC_ERR_SUCCESS` or a negative error code
-
-Sends a message immediately if possible.
-
-Data Channel and WebSocket: If the message may not be sent immediately due to flow control or congestion control, it is buffered until it can actually be sent. You can retrieve the current buffered data size with `rtcGetBufferedAmount`.
-Tracks are an exception: There is no flow or congestion control, messages are never buffered and `rtcGetBufferedAmount` always returns 0.
-
-#### rtcGetBufferedAmount
-
-```
-int rtcGetBufferedAmount(int id)
-```
-
-Retrieves the current buffered amount, i.e. the total size of currently buffered messages waiting to be actually sent in the channel. This does not account for the data buffered at the transport level.
-
-Return value: the buffered amount or a negative error code
-
-#### rtcGetBufferedAmountLowThreshold
-
-```
-int rtcSetBufferedAmountLowThreshold(int id, int amount)
-```
-
-Changes the buffered amount threshold under which `BufferedAmountLowCallback` is called. The callback is called when the buffered amount was strictly superior and gets equal to or lower than the threshold when a message is sent. The initial threshold is 0, meaning the the callback is called each time the buffered amount goes back to zero after being non-zero.
-
-Arguments:
-- `id`: the channel identifier
-- `amount`: the new buffer level threshold
-
-Return value: the identifier of the new WebSocket or a negative error code
-
-#### rtcReceiveMessage
-
-```
-int rtcReceiveMessage(int id, char *buffer, int *size)
-```
-
-Receives a pending message if possible. The function may only be called if `MessageCallback` is not set.
-
-Arguments:
-- `id`: the channel identifier
-- `buffer`: a user-supplied buffer where to write the message data
-- `size`: a pointer to a user-supplied int which must be initialized to the size of `buffer`. On success, the function will write the size of the message to it before returning.
-
-Return value: `RTC_ERR_SUCCESS` or a negative error code (In particular, `RTC_ERR_NOT_AVAIL` is returned when there are no pending messages)
-
-If `buffer` is `NULL`, the message is not copied and kept pending but the size is still written to `size`.
-
-#### rtcGetAvailableAmount
-
-```
-int rtcGetAvailableAmount(int id)
-```
-
-Retrieves the available amount, i.e. the total size of messages pending reception with `rtcReceiveMessage`. The function may only be called if `MessageCallback` is not set.
-
-Arguments:
-- `id`: the channel identifier
-
-Return value: the available amount or a negative error code
-
 

+ 21 - 15
README.md

@@ -1,6 +1,8 @@
-# libdatachannel - C/C++ WebRTC lightweight library
+# libdatachannel - C/C++ WebRTC network library
 
-libdatachannel is a standalone implementation of WebRTC Data Channels, WebRTC Media Transport, and WebSockets in C++17 with C bindings for POSIX platforms (including GNU/Linux, Android, and Apple macOS) and Microsoft Windows.
+[![Join the chat at https://gitter.im/libdatachannel/community](https://badges.gitter.im/libdatachannel/community.svg)](https://gitter.im/libdatachannel/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+libdatachannel is a standalone implementation of WebRTC Data Channels, WebRTC Media Transport, and WebSockets in C++17 with C bindings for POSIX platforms (including GNU/Linux, Android, Apple macOS and iOS) and Microsoft Windows.
 
 The library aims at being both straightforward and lightweight with minimal external dependencies, to enable direct connectivity between native applications and web browsers without the pain of importing Google's bloated [reference library](https://webrtc.googlesource.com/src/). The interface consists of somewhat simplified versions of the JavaScript WebRTC and WebSocket APIs present in browsers, in order to ease the design of cross-environment applications.
 
@@ -8,17 +10,20 @@ It can be compiled with multiple backends:
 - The security layer can be provided through [OpenSSL](https://www.openssl.org/) or [GnuTLS](https://www.gnutls.org/).
 - The connectivity for WebRTC can be provided through my ad-hoc ICE library [libjuice](https://github.com/paullouisageneau/libjuice) as submodule or through [libnice](https://github.com/libnice/libnice).
 
-The WebRTC stack is fully compatible with Firefox and Chromium, see [Compatibility](#Compatibility) below.
+The WebRTC stack is fully compatible with browsers like Firefox and Chromium, see [Compatibility](#Compatibility) below. Additionally, code using Data Channels and WebSockets from the library may be compiled as is to WebAssembly for browsers with [datachannel-wasm](https://github.com/paullouisageneau/datachannel-wasm).
+
+libdatachannel is licensed under LGPLv2, see [LICENSE](https://github.com/paullouisageneau/libdatachannel/blob/master/LICENSE).
 
-Licensed under LGPLv2, see [LICENSE](https://github.com/paullouisageneau/libdatachannel/blob/master/LICENSE).
+libdatachannel is available on [AUR](https://aur.archlinux.org/packages/libdatachannel/) and [vcpkg](https://vcpkg.info/port/libdatachannel).
 
 ## Dependencies
 
 Only [GnuTLS](https://www.gnutls.org/) or [OpenSSL](https://www.openssl.org/) are necessary. Optionally, [libnice](https://nice.freedesktop.org/) can be selected as an alternative ICE backend instead of libjuice.
 
 Submodules:
-- libjuice: https://github.com/paullouisageneau/libjuice
 - usrsctp: https://github.com/sctplab/usrsctp
+- plog: https://github.com/SergiusTheBest/plog
+- libjuice: https://github.com/paullouisageneau/libjuice (if not compiled with libnice backend)
 - libsrtp: https://github.com/cisco/libsrtp (if compiled with media support)
 
 ## Building
@@ -45,7 +50,7 @@ rtc::PeerConection pc(config);
 
 pc.onLocalDescription([](rtc::Description sdp) {
     // Send the SDP to the remote peer
-    MY_SEND_DESCRIPTION_TO_REMOTE(string(sdp));
+    MY_SEND_DESCRIPTION_TO_REMOTE(std::string(sdp));
 });
 
 pc.onLocalCandidate([](rtc::Candidate candidate) {
@@ -53,11 +58,11 @@ pc.onLocalCandidate([](rtc::Candidate candidate) {
     MY_SEND_CANDIDATE_TO_REMOTE(candidate.candidate(), candidate.mid());
 });
 
-MY_ON_RECV_DESCRIPTION_FROM_REMOTE([&pc](string sdp) {
+MY_ON_RECV_DESCRIPTION_FROM_REMOTE([&pc](std::string sdp) {
     pc.setRemoteDescription(rtc::Description(sdp));
 });
 
-MY_ON_RECV_CANDIDATE_FROM_REMOTE([&pc](string candidate, string mid) {
+MY_ON_RECV_CANDIDATE_FROM_REMOTE([&pc](std::string candidate, std::string mid) {
     pc.addRemoteCandidate(rtc::Candidate(candidate, mid));
 });
 ```
@@ -83,9 +88,9 @@ dc->onOpen([]() {
     std::cout << "Open" << std::endl;
 });
 
-dc->onMessage([](std::variant<binary, string> message) {
-    if (std::holds_alternative<string>(message)) {
-        std::cout << "Received: " << get<string>(message) << std::endl;
+dc->onMessage([](std::variant<rtc::binary, rtc::string> message) {
+    if (std::holds_alternative<rtc::string>(message)) {
+        std::cout << "Received: " << get<rtc::string>(message) << std::endl;
     }
 });
 ```
@@ -109,9 +114,9 @@ ws.onOpen([]() {
     std::cout << "WebSocket open" << std::endl;
 });
 
-ws.onMessage([](std::variant<binary, string> message) {
-    if (std::holds_alternative<string>(message)) {
-        std::cout << "WebSocket received: " << std::get<string>(message) << endl;
+ws.onMessage([](std::variant<rtc::binary, rtc::string> message) {
+    if (std::holds_alternative<rtc::string>(message)) {
+        std::cout << "WebSocket received: " << std::get<rtc::string>(message) << endl;
     }
 });
 
@@ -139,8 +144,8 @@ Features:
 - SCTP over DTLS with SDP offer/answer ([RFC8841](https://tools.ietf.org/html/rfc8841))
 - DTLS with ECDSA or RSA keys ([RFC8824](https://tools.ietf.org/html/rfc8827))
 - SRTP and SRTCP key derivation from DTLS ([RFC5764](https://tools.ietf.org/html/rfc5764))
+- Differentiated Services QoS ([RFC8837](https://tools.ietf.org/html/rfc8837)) where possible
 - Multicast DNS candidates ([draft-ietf-rtcweb-mdns-ice-candidates-04](https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-04))
-- Differentiated Services QoS ([draft-ietf-tsvwg-rtcweb-qos-18](https://tools.ietf.org/html/draft-ietf-tsvwg-rtcweb-qos-18))
 
 Note only SDP BUNDLE mode is supported for media multiplexing ([RFC8843](https://tools.ietf.org/html/rfc8843)). The behavior is equivalent to the JSEP bundle-only policy: the library always negociates one unique network component, where SRTP media streams are multiplexed with SRTCP control packets ([RFC5761](https://tools.ietf.org/html/rfc5761)) and SCTP/DTLS data traffic ([RFC8261](https://tools.ietf.org/html/rfc8261)).
 
@@ -161,6 +166,7 @@ Features:
 - 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)
+- Lightweight STUN/TURN server: [Violet](https://github.com/paullouisageneau/violet)
 
 ## Thanks
 

+ 5 - 0
cmake/LibDataChannelConfig.cmake

@@ -0,0 +1,5 @@
+include("${CMAKE_CURRENT_LIST_DIR}/LibDataChannelTargets.cmake")
+
+# For backward compatibility
+add_library(LibDataChannel::datachannel ALIAS LibDataChannel::LibDataChannel)
+

+ 17 - 0
cmake/Modules/FindLibJuice.cmake

@@ -0,0 +1,17 @@
+if (NOT TARGET LibJuice::LibJuice)
+	find_path(JUICE_INCLUDE_DIR juice/juice.h)
+	find_library(JUICE_LIBRARY NAMES juice)
+
+	include(FindPackageHandleStandardArgs)
+	find_package_handle_standard_args(LibJuice DEFAULT_MSG JUICE_LIBRARY JUICE_INCLUDE_DIR)
+
+    if (LibJuice_FOUND)
+        add_library(LibJuice::LibJuice UNKNOWN IMPORTED)
+        set_target_properties(LibJuice::LibJuice PROPERTIES
+            IMPORTED_LOCATION "${JUICE_LIBRARY}"
+            INTERFACE_INCLUDE_DIRECTORIES "${JUICE_INCLUDE_DIRS}"
+            INTERFACE_LINK_LIBRARIES "${JUICE_LIBRARIES}"
+                IMPORTED_LINK_INTERFACE_LANGUAGES "C")
+    endif ()
+endif ()
+

+ 1 - 1
deps/libjuice

@@ -1 +1 @@
-Subproject commit 700b786d0455111776b57851f29a38f2e4757ad7
+Subproject commit 53c139e97bba973ce475d9eeb3230a6cc1f4ee39

+ 1 - 1
deps/usrsctp

@@ -1 +1 @@
-Subproject commit 07f871bda23943c43c9e74cc54f25130459de830
+Subproject commit b56b4300b9ad1c0eb447b7b76a0a3f40b30716be

+ 17 - 3
examples/client-benchmark/CMakeLists.txt

@@ -3,19 +3,33 @@ if(POLICY CMP0079)
 	cmake_policy(SET CMP0079 NEW)
 endif()
 
+set(CLIENT_SOURCES
+	main.cpp
+	parse_cl.cpp
+	parse_cl.h
+)
+
+set(GETOPT_SOURCES
+	getopt.cpp
+	getopt.h
+)
+
 if(WIN32)
-	add_executable(datachannel-client-benchmark main.cpp parse_cl.cpp parse_cl.h getopt.cpp getopt.h)
+	add_executable(datachannel-client-benchmark ${CLIENT_SOURCES} ${GETOPT_SOURCES})
 	target_compile_definitions(datachannel-client-benchmark PUBLIC STATIC_GETOPT)
 else()
-	add_executable(datachannel-client-benchmark main.cpp parse_cl.cpp parse_cl.h)
+	add_executable(datachannel-client-benchmark ${CLIENT_SOURCES})
 endif()
 
 set_target_properties(datachannel-client-benchmark PROPERTIES
 	CXX_STANDARD 17
 	OUTPUT_NAME client-benchmark)
+
 set_target_properties(datachannel-client-benchmark PROPERTIES
 	XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.github.paullouisageneau.libdatachannel.examples.client.benchmark)
-target_link_libraries(datachannel-client-benchmark datachannel nlohmann_json)
+
+find_package(Threads REQUIRED)
+target_link_libraries(datachannel-client-benchmark LibDataChannel::LibDataChannel Threads::Threads nlohmann_json)
 
 if(WIN32)
 	add_custom_command(TARGET datachannel-client-benchmark POST_BUILD

+ 18 - 4
examples/client/CMakeLists.txt

@@ -3,6 +3,17 @@ if(POLICY CMP0079)
 	cmake_policy(SET CMP0079 NEW)
 endif()
 
+set(CLIENT_SOURCES
+	main.cpp
+	parse_cl.cpp
+	parse_cl.h
+)
+
+set(GETOPT_SOURCES
+	getopt.cpp
+	getopt.h
+)
+
 set(CLIENT_UWP_RESOURCES
 	uwp/Logo.png
 	uwp/package.appxManifest
@@ -15,21 +26,24 @@ set(CLIENT_UWP_RESOURCES
 
 if(WIN32)
 	if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
-	add_executable(datachannel-client main.cpp parse_cl.cpp parse_cl.h getopt.cpp getopt.h ${CLIENT_UWP_RESOURCES})
+		add_executable(datachannel-client ${CLIENT_SOURCES} ${GETOPT_SOURCES} ${CLIENT_UWP_RESOURCES})
 	else()
-	    add_executable(datachannel-client main.cpp parse_cl.cpp parse_cl.h getopt.cpp getopt.h)
+	    add_executable(datachannel-client ${CLIENT_SOURCES} ${GETOPT_SOURCES})
 	endif()
 	target_compile_definitions(datachannel-client PUBLIC STATIC_GETOPT)
 else()
-	add_executable(datachannel-client main.cpp parse_cl.cpp parse_cl.h)
+	add_executable(datachannel-client ${CLIENT_SOURCES})
 endif()
 
 set_target_properties(datachannel-client PROPERTIES
 	CXX_STANDARD 17
 	OUTPUT_NAME client)
+
 set_target_properties(datachannel-client PROPERTIES
 	XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.github.paullouisageneau.libdatachannel.examples.client)
-target_link_libraries(datachannel-client datachannel nlohmann_json)
+
+find_package(Threads REQUIRED)
+target_link_libraries(datachannel-client LibDataChannel::LibDataChannel Threads::Threads nlohmann_json)
 
 if(WIN32)
 	add_custom_command(TARGET datachannel-client POST_BUILD

+ 10 - 3
examples/copy-paste-capi/CMakeLists.txt

@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.5.1)
+cmake_minimum_required(VERSION 3.7)
 project(offerer C)
 
 set(CMAKE_C_STANDARD 11)
@@ -28,22 +28,29 @@ if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
 else()
 	add_executable(datachannel-copy-paste-capi-offerer offerer.c)
 endif()
+
 set_target_properties(datachannel-copy-paste-capi-offerer PROPERTIES
 	OUTPUT_NAME offerer)
+
 set_target_properties(datachannel-copy-paste-capi-offerer PROPERTIES
 	XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.github.paullouisageneau.libdatachannel.examples.copypaste.capi.offerer)
-target_link_libraries(datachannel-copy-paste-capi-offerer datachannel)
+
+target_link_libraries(datachannel-copy-paste-capi-offerer datachannel Threads::Threads)
 
 if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
 	add_executable(datachannel-copy-paste-capi-answerer answerer.c ${ANSWERER_UWP_RESOURCES})
 else()
 	add_executable(datachannel-copy-paste-capi-answerer answerer.c)
 endif()
+
 set_target_properties(datachannel-copy-paste-capi-answerer PROPERTIES
 	OUTPUT_NAME answerer)
+
 set_target_properties(datachannel-copy-paste-capi-answerer PROPERTIES
 	XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.github.paullouisageneau.libdatachannel.examples.copypaste.capi.answerer)
-target_link_libraries(datachannel-copy-paste-capi-answerer datachannel)
+
+find_package(Threads REQUIRED)
+target_link_libraries(datachannel-copy-paste-capi-answerer LibDataChannel::LibDataChannel Threads::Threads)
 
 if(WIN32)
 	add_custom_command(TARGET datachannel-copy-paste-capi-offerer POST_BUILD

+ 8 - 1
examples/copy-paste/CMakeLists.txt

@@ -25,11 +25,14 @@ if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
 else()
 	add_executable(datachannel-copy-paste-offerer offerer.cpp)
 endif()
+
 set_target_properties(datachannel-copy-paste-offerer PROPERTIES
 	CXX_STANDARD 17
 	OUTPUT_NAME offerer)
+
 set_target_properties(datachannel-copy-paste-offerer PROPERTIES
 	XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.github.paullouisageneau.libdatachannel.examples.copypaste.offerer)
+
 target_link_libraries(datachannel-copy-paste-offerer datachannel)
 
 if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
@@ -37,12 +40,16 @@ if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
 else()
 	add_executable(datachannel-copy-paste-answerer answerer.cpp)
 endif()
+
 set_target_properties(datachannel-copy-paste-answerer PROPERTIES
 	CXX_STANDARD 17
 	OUTPUT_NAME answerer)
+
 set_target_properties(datachannel-copy-paste-answerer PROPERTIES
 	XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.github.paullouisageneau.libdatachannel.examples.copypaste.answerer)
-target_link_libraries(datachannel-copy-paste-answerer datachannel)
+
+find_package(Threads REQUIRED)
+target_link_libraries(datachannel-copy-paste-answerer LibDataChannel::LibDataChannel Threads::Threads)
 
 if(WIN32)
 	add_custom_command(TARGET datachannel-copy-paste-offerer POST_BUILD

+ 7 - 3
examples/media/CMakeLists.txt

@@ -15,12 +15,16 @@ if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
 else()
 	add_executable(datachannel-media main.cpp)
 endif()
+
 set_target_properties(datachannel-media PROPERTIES
-        CXX_STANDARD 17
-        OUTPUT_NAME media)
+    CXX_STANDARD 17
+    OUTPUT_NAME media)
+
 set_target_properties(datachannel-media PROPERTIES
 	XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.github.paullouisageneau.libdatachannel.examples.media)
-target_link_libraries(datachannel-media datachannel nlohmann_json)
+
+find_package(Threads REQUIRED)
+target_link_libraries(datachannel-media LibDataChannel::LibDataChannel Threads::Threads nlohmann_json)
 
 if(WIN32)
 	add_custom_command(TARGET datachannel-media POST_BUILD

+ 6 - 3
examples/sfu-media/CMakeLists.txt

@@ -15,12 +15,15 @@ if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
 else()
 	add_executable(datachannel-sfu-media main.cpp)
 endif()
+
 set_target_properties(datachannel-sfu-media PROPERTIES
-		CXX_STANDARD 17
-		OUTPUT_NAME sfu-media)
+	CXX_STANDARD 17
+	OUTPUT_NAME sfu-media)
+
 set_target_properties(datachannel-sfu-media PROPERTIES
 	XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.github.paullouisageneau.libdatachannel.examples.sfumedia)
-target_link_libraries(datachannel-sfu-media datachannel nlohmann_json)
+
+target_link_libraries(datachannel-sfu-media LibDataChannel::LibDataChannel nlohmann_json)
 
 if(WIN32)
 	add_custom_command(TARGET datachannel-sfu-media POST_BUILD

+ 3 - 5
examples/streamer/CMakeLists.txt

@@ -37,17 +37,15 @@ else()
 	add_executable(streamer ${STREAMER_SOURCES})
 endif()
 
-if(WIN32)
-	target_compile_definitions(streamer PUBLIC STATIC_GETOPT)
-endif()
-
 set_target_properties(streamer PROPERTIES
 	CXX_STANDARD 17
 	OUTPUT_NAME streamer)
+
 set_target_properties(streamer PROPERTIES
 	XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.github.paullouisageneau.libdatachannel.examples.streamer)
 
-target_link_libraries(streamer datachannel nlohmann_json)
+find_package(Threads REQUIRED)
+target_link_libraries(streamer LibDataChannel::LibDataChannel Threads::Threads nlohmann_json)
 
 if(WIN32)
 	add_custom_command(TARGET streamer POST_BUILD

+ 11 - 4
examples/streamer/README.md

@@ -2,21 +2,26 @@
 
 This example streams H264 and opus<sup id="a1">[1](#f1)</sup> samples to the connected browser client.
 
-## Start a signaling server
+## Start the example signaling server
 
 ```sh
-$ python3 ../signaling-server-python/signaling-server.py
+$ python3 examples/signaling-server-python/signaling-server.py
 ```
 
 ## Start a web server
 
 ```sh
+$ cd examples/streamer
 $ python3 -m http.server --bind 127.0.0.1 8080
 ```
 
-Now you can open the demo at [http://127.0.0.1:8080](http://127.0.0.1:8080).
+## Start the streamer
 
-## Arguments
+```sh
+$ cd build/examples/streamer
+$ ./streamer
+```
+Arguments:
 
 - `-a` Directory with OPUS samples (default: *../../../../examples/streamer/samples/opus/*).
 - `-b` Directory with H264 samples (default: *../../../../examples/streamer/samples/h264/*).
@@ -25,6 +30,8 @@ Now you can open the demo at [http://127.0.0.1:8080](http://127.0.0.1:8080).
 - `-v` Enable debug logs.
 - `-h` Print this help and exit.
 
+You can now open the example at the web server URL [http://127.0.0.1:8080](http://127.0.0.1:8080).
+
 ## Generating H264 and Opus samples
 
 You can generate H264 and Opus sample with *samples/generate_h264.py* and *samples/generate_opus.py* respectively. This require ffmpeg, python3 and kaitaistruct library to be installed. Use `-h`/`--help` to learn more about arguments.

+ 2 - 0
examples/streamer/helpers.hpp

@@ -21,6 +21,8 @@
 
 #include "rtc/rtc.hpp"
 
+#include <shared_mutex>
+
 struct ClientTrackData {
     std::shared_ptr<rtc::Track> track;
 	std::shared_ptr<rtc::RtcpSrReporter> sender;

+ 60 - 62
examples/web/script.js

@@ -20,9 +20,9 @@
 window.addEventListener('load', () => {
 
 const config = {
-  iceServers: [{
-    urls: 'stun:stun.l.google.com:19302', // change to your STUN server
-  }],
+  iceServers : [ {
+    urls : 'stun:stun.l.google.com:19302', // change to your STUN server
+  } ],
 };
 
 const localId = randomId(4);
@@ -41,14 +41,13 @@ _localId.textContent = localId;
 
 console.log('Connecting to signaling...');
 openSignaling(url)
-  .then((ws) => {
-    console.log('WebSocket connected, signaling ready');
-    offerId.disabled = false;
-    offerBtn.disabled = false;
-    offerBtn.onclick = () => offerPeerConnection(ws, offerId.value);
-  })
-  .catch((err) => console.error(err));
-
+    .then((ws) => {
+      console.log('WebSocket connected, signaling ready');
+      offerId.disabled = false;
+      offerBtn.disabled = false;
+      offerBtn.onclick = () => offerPeerConnection(ws, offerId.value);
+    })
+    .catch((err) => console.error(err));
 
 function openSignaling(url) {
   return new Promise((resolve, reject) => {
@@ -57,50 +56,51 @@ function openSignaling(url) {
     ws.onerror = () => reject(new Error('WebSocket error'));
     ws.onclose = () => console.error('WebSocket disconnected');
     ws.onmessage = (e) => {
-      if(typeof(e.data) != 'string') return;
+      if (typeof (e.data) != 'string')
+        return;
       const message = JSON.parse(e.data);
       console.log(message);
-      const { id, type } = message;
+      const {id, type} = message;
 
       let pc = peerConnectionMap[id];
-      if(!pc) {
-		    if(type != 'offer') return;
+      if (!pc) {
+        if (type != 'offer')
+          return;
 
-			  // Create PeerConnection for answer
-			  console.log(`Answering to ${id}`);
-			  pc = createPeerConnection(ws, id);
-		  }
+        // Create PeerConnection for answer
+        console.log(`Answering to ${id}`);
+        pc = createPeerConnection(ws, id);
+      }
 
-      switch(type) {
+      switch (type) {
       case 'offer':
       case 'answer':
-			  pc.setRemoteDescription({
-			    sdp: message.description,
-			    type: message.type,
-			  })
-			  .then(() => {
-          if(type == 'offer') {
-			      // Send answer
+        pc.setRemoteDescription({
+            sdp : message.description,
+            type : message.type,
+          }).then(() => {
+          if (type == 'offer') {
+            // Send answer
             sendLocalDescription(ws, id, pc, 'answer');
           }
-			  });
-			  break;
-
-			case 'candidate':
-		    pc.addIceCandidate({
-          candidate: message.candidate,
-          sdpMid: message.mid,
-		    });
-		    break;
-		  }
+        });
+        break;
+
+      case 'candidate':
+        pc.addIceCandidate({
+          candidate : message.candidate,
+          sdpMid : message.mid,
+        });
+        break;
+      }
     }
   });
 }
 
 function offerPeerConnection(ws, id) {
-	// Create PeerConnection
-	console.log(`Offering to ${id}`);
-	pc = createPeerConnection(ws, id);
+  // Create PeerConnection
+  console.log(`Offering to ${id}`);
+  pc = createPeerConnection(ws, id);
 
   // Create DataChannel
   const label = "test";
@@ -115,7 +115,7 @@ function offerPeerConnection(ws, id) {
 // Create and setup a PeerConnection
 function createPeerConnection(ws, id) {
   const pc = new RTCPeerConnection(config);
-  pc.onconnectionstatechange = () => console.log(`Connection state: ${pc.connectionState}`);
+  pc.oniceconnectionstatechange = () => console.log(`Connection state: ${pc.iceConnectionState}`);
   pc.onicegatheringstatechange = () => console.log(`Gathering state: ${pc.iceGatheringState}`);
   pc.onicecandidate = (e) => {
     if (e.candidate && e.candidate.candidate) {
@@ -128,7 +128,7 @@ function createPeerConnection(ws, id) {
     console.log(`"DataChannel from ${id} received with label "${dc.label}"`);
     setupDataChannel(dc, id);
 
-		dc.send(`Hello from ${localId}`);
+    dc.send(`Hello from ${localId}`);
 
     sendMsg.disabled = false;
     sendBtn.disabled = false;
@@ -148,14 +148,13 @@ function setupDataChannel(dc, id) {
     sendBtn.disabled = false;
     sendBtn.onclick = () => dc.send(sendMsg.value);
   };
-  dc.onclose = () => {
-    console.log(`DataChannel from ${id} closed`);
-  };
-	dc.onmessage = (e) => {
-    if(typeof(e.data) != 'string') return;
-		console.log(`Message from ${id} received: ${e.data}`);
+  dc.onclose = () => { console.log(`DataChannel from ${id} closed`); };
+  dc.onmessage = (e) => {
+    if (typeof (e.data) != 'string')
+      return;
+    console.log(`Message from ${id} received: ${e.data}`);
     document.body.appendChild(document.createTextNode(e.data));
-	};
+  };
 
   dataChannelMap[id] = dc;
   return dc;
@@ -163,24 +162,24 @@ function setupDataChannel(dc, id) {
 
 function sendLocalDescription(ws, id, pc, type) {
   (type == 'offer' ? pc.createOffer() : pc.createAnswer())
-    .then((desc) => pc.setLocalDescription(desc))
-    .then(() => {
-      const { sdp, type } = pc.localDescription;
-      ws.send(JSON.stringify({
-        id,
-        type,
-        description: sdp,
-      }));
-    });
+      .then((desc) => pc.setLocalDescription(desc))
+      .then(() => {
+        const {sdp, type} = pc.localDescription;
+        ws.send(JSON.stringify({
+          id,
+          type,
+          description : sdp,
+        }));
+      });
 }
 
 function sendLocalCandidate(ws, id, cand) {
   const {candidate, sdpMid} = cand;
   ws.send(JSON.stringify({
     id,
-    type: 'candidate',
+    type : 'candidate',
     candidate,
-    mid: sdpMid,
+    mid : sdpMid,
   }));
 }
 
@@ -188,8 +187,7 @@ function sendLocalCandidate(ws, id, cand) {
 function randomId(length) {
   const characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
   const pickRandom = () => characters.charAt(Math.floor(Math.random() * characters.length));
-  return [...Array(length)].map(pickRandom).join('');
+  return [...Array(length) ].map(pickRandom).join('');
 }
 
 });
-

+ 1 - 1
include/rtc/channel.hpp

@@ -58,7 +58,7 @@ public:
 	// Extended API
 	optional<message_variant> receive(); // only if onMessage unset
 	optional<message_variant> peek();    // only if onMessage unset
-	size_t availableAmount() const;           // total size available to receive
+	size_t availableAmount() const;      // total size available to receive
 	void onAvailable(std::function<void()> callback);
 
 protected:

+ 5 - 2
include/rtc/configuration.hpp

@@ -70,6 +70,8 @@ enum class CertificateType {
 	Rsa = RTC_CERTIFICATE_RSA
 };
 
+enum class TransportPolicy { All = RTC_TRANSPORT_POLICY_ALL, Relay = RTC_TRANSPORT_POLICY_RELAY };
+
 struct RTC_CPP_EXPORT Configuration {
 	// ICE settings
 	std::vector<IceServer> iceServers;
@@ -78,6 +80,7 @@ struct RTC_CPP_EXPORT Configuration {
 
 	// Options
 	CertificateType certificateType = CertificateType::Default;
+	TransportPolicy iceTransportPolicy = TransportPolicy::All;
 	bool enableIceTcp = false;
 	bool disableAutoNegotiation = false;
 
@@ -85,10 +88,10 @@ struct RTC_CPP_EXPORT Configuration {
 	uint16_t portRangeBegin = 1024;
 	uint16_t portRangeEnd = 65535;
 
-	// MTU
+	// Network MTU
 	optional<size_t> mtu;
 
-	// Local max message size at reception
+	// Local maximum message size for Data Channels
 	optional<size_t> maxMessageSize;
 };
 

+ 0 - 5
include/rtc/datachannel.hpp

@@ -24,12 +24,7 @@
 #include "message.hpp"
 #include "reliability.hpp"
 
-#include <atomic>
-#include <chrono>
-#include <functional>
-#include <shared_mutex>
 #include <type_traits>
-#include <shared_mutex>
 
 namespace rtc {
 

+ 3 - 0
include/rtc/global.hpp

@@ -39,7 +39,10 @@ enum class LogLevel { // Don't change, it must match plog severity
 typedef std::function<void(LogLevel level, string message)> LogCallback;
 
 RTC_CPP_EXPORT void InitLogger(LogLevel level, LogCallback callback = nullptr);
+
 #ifdef PLOG_DEFAULT_INSTANCE_ID
+// Deprecated, kept for retro-compatibility
+[[deprecated]]
 RTC_CPP_EXPORT void InitLogger(plog::Severity severity, plog::IAppender *appender = nullptr);
 #endif
 

+ 1 - 1
include/rtc/h264packetizationhandler.hpp

@@ -22,8 +22,8 @@
 #if RTC_ENABLE_MEDIA
 
 #include "h264rtppacketizer.hpp"
-#include "nalunit.hpp"
 #include "mediachainablehandler.hpp"
+#include "nalunit.hpp"
 
 namespace rtc {
 

+ 10 - 6
include/rtc/h264rtppacketizer.hpp

@@ -21,14 +21,15 @@
 
 #if RTC_ENABLE_MEDIA
 
+#include "mediahandlerrootelement.hpp"
 #include "nalunit.hpp"
 #include "rtppacketizer.hpp"
-#include "mediahandlerrootelement.hpp"
 
 namespace rtc {
 
 /// RTP packetization of h264 payload
-class RTC_CPP_EXPORT H264RtpPacketizer final : public RtpPacketizer, public MediaHandlerRootElement {
+class RTC_CPP_EXPORT H264RtpPacketizer final : public RtpPacketizer,
+                                               public MediaHandlerRootElement {
 	shared_ptr<NalUnits> splitMessage(binary_ptr message);
 	const uint16_t maximumFragmentSize;
 
@@ -44,8 +45,9 @@ public:
 		Length              // first 4 bytes is nal unit length
 	};
 
-	H264RtpPacketizer(H264RtpPacketizer::Separator separator, shared_ptr<RtpPacketizationConfig> rtpConfig,
-					  uint16_t maximumFragmentSize = NalUnits::defaultMaximumFragmentSize);
+	H264RtpPacketizer(H264RtpPacketizer::Separator separator,
+	                  shared_ptr<RtpPacketizationConfig> rtpConfig,
+	                  uint16_t maximumFragmentSize = NalUnits::defaultMaximumFragmentSize);
 
 	/// Constructs h264 payload packetizer with given RTP configuration.
 	/// @note RTP configuration is used in packetization process which may change some configuration
@@ -53,9 +55,11 @@ public:
 	/// @param rtpConfig  RTP configuration
 	/// @param maximumFragmentSize maximum size of one NALU fragment
 	H264RtpPacketizer(shared_ptr<RtpPacketizationConfig> rtpConfig,
-					  uint16_t maximumFragmentSize = NalUnits::defaultMaximumFragmentSize);
+	                  uint16_t maximumFragmentSize = NalUnits::defaultMaximumFragmentSize);
+
+	ChainedOutgoingProduct processOutgoingBinaryMessage(ChainedMessagesProduct messages,
+	                                                    message_ptr control) override;
 
-	ChainedOutgoingProduct processOutgoingBinaryMessage(ChainedMessagesProduct messages, message_ptr control) override;
 private:
 	const Separator separator;
 };

+ 4 - 2
include/rtc/mediachainablehandler.hpp

@@ -29,13 +29,15 @@ namespace rtc {
 class RTC_CPP_EXPORT MediaChainableHandler : public MediaHandler {
 	const shared_ptr<MediaHandlerRootElement> root;
 	shared_ptr<MediaHandlerElement> leaf;
-	std::mutex inoutMutex;
+	mutable std::mutex mutex;
 
 	message_ptr handleIncomingBinary(message_ptr);
 	message_ptr handleIncomingControl(message_ptr);
 	message_ptr handleOutgoingBinary(message_ptr);
 	message_ptr handleOutgoingControl(message_ptr);
 	bool sendProduct(ChainedOutgoingProduct product);
+	shared_ptr<MediaHandlerElement> getLeaf() const;
+
 public:
 	MediaChainableHandler(shared_ptr<MediaHandlerRootElement> root);
 	~MediaChainableHandler();
@@ -46,7 +48,7 @@ public:
 
 	/// Adds element to chain
 	/// @param chainable Chainable element
-    void addToChain(shared_ptr<MediaHandlerElement> chainable);
+	void addToChain(shared_ptr<MediaHandlerElement> chainable);
 };
 
 } // namespace rtc

+ 18 - 8
include/rtc/mediahandlerelement.hpp

@@ -34,33 +34,39 @@ RTC_CPP_EXPORT ChainedMessagesProduct make_chained_messages_product(message_ptr
 
 /// Ougoing messages
 struct RTC_CPP_EXPORT ChainedOutgoingProduct {
-	ChainedOutgoingProduct(ChainedMessagesProduct messages = nullptr, message_ptr control = nullptr);
+	ChainedOutgoingProduct(ChainedMessagesProduct messages = nullptr,
+	                       message_ptr control = nullptr);
 	const ChainedMessagesProduct messages;
 	const message_ptr control;
 };
 
 /// Incoming messages with response
 struct RTC_CPP_EXPORT ChainedIncomingProduct {
-	ChainedIncomingProduct(ChainedMessagesProduct incoming = nullptr, ChainedMessagesProduct outgoing = nullptr);
+	ChainedIncomingProduct(ChainedMessagesProduct incoming = nullptr,
+	                       ChainedMessagesProduct outgoing = nullptr);
 	const ChainedMessagesProduct incoming;
 	const ChainedOutgoingProduct outgoing;
 };
 
 /// Incoming control messages with response
 struct RTC_CPP_EXPORT ChainedIncomingControlProduct {
-	ChainedIncomingControlProduct(message_ptr incoming, optional<ChainedOutgoingProduct> outgoing = nullopt);
+	ChainedIncomingControlProduct(message_ptr incoming,
+	                              optional<ChainedOutgoingProduct> outgoing = nullopt);
 	const message_ptr incoming;
 	const optional<ChainedOutgoingProduct> outgoing;
 };
 
 /// Chainable handler
-class RTC_CPP_EXPORT MediaHandlerElement: public std::enable_shared_from_this<MediaHandlerElement> {
+class RTC_CPP_EXPORT MediaHandlerElement
+    : public std::enable_shared_from_this<MediaHandlerElement> {
 	shared_ptr<MediaHandlerElement> upstream = nullptr;
 	shared_ptr<MediaHandlerElement> downstream = nullptr;
 
-	void prepareAndSendResponse(optional<ChainedOutgoingProduct> outgoing, std::function<bool (ChainedOutgoingProduct)> send);
+	void prepareAndSendResponse(optional<ChainedOutgoingProduct> outgoing,
+	                            std::function<bool(ChainedOutgoingProduct)> send);
 
 	void removeFromChain();
+
 public:
 	MediaHandlerElement();
 
@@ -70,8 +76,11 @@ public:
 	optional<ChainedOutgoingProduct> processOutgoingResponse(ChainedOutgoingProduct messages);
 
 	// Process incoming and ougoing messages
-	message_ptr formIncomingControlMessage(message_ptr message, std::function<bool (ChainedOutgoingProduct)> send);
-	ChainedMessagesProduct formIncomingBinaryMessage(ChainedMessagesProduct messages, std::function<bool (ChainedOutgoingProduct)> send);
+	message_ptr formIncomingControlMessage(message_ptr message,
+	                                       std::function<bool(ChainedOutgoingProduct)> send);
+	ChainedMessagesProduct
+	formIncomingBinaryMessage(ChainedMessagesProduct messages,
+	                          std::function<bool(ChainedOutgoingProduct)> send);
 	message_ptr formOutgoingControlMessage(message_ptr message);
 	optional<ChainedOutgoingProduct> formOutgoingBinaryMessage(ChainedOutgoingProduct product);
 
@@ -94,7 +103,8 @@ public:
 	/// @param messages current message
 	/// @param control current control message
 	/// @returns Modified binary message and control message
-	virtual ChainedOutgoingProduct processOutgoingBinaryMessage(ChainedMessagesProduct messages, message_ptr control);
+	virtual ChainedOutgoingProduct processOutgoingBinaryMessage(ChainedMessagesProduct messages,
+	                                                            message_ptr control);
 
 	/// Set given element as upstream to this
 	/// @param upstream Upstream element

+ 1 - 1
include/rtc/mediahandlerrootelement.hpp

@@ -28,7 +28,7 @@ namespace rtc {
 /// Chainable message handler
 class RTC_CPP_EXPORT MediaHandlerRootElement : public MediaHandlerElement {
 public:
-	MediaHandlerRootElement() { }
+	MediaHandlerRootElement() {}
 
 	/// Reduce multiple messages into one message
 	/// @param messages Messages to reduce

+ 2 - 3
include/rtc/message.hpp

@@ -50,12 +50,11 @@ using message_variant = variant<binary, string>;
 
 inline size_t message_size_func(const message_ptr &m) {
 	return m->type == Message::Binary || m->type == Message::String ? m->size() : 0;
-};
+}
 
 template <typename Iterator>
 message_ptr make_message(Iterator begin, Iterator end, Message::Type type = Message::Binary,
-                         unsigned int stream = 0,
-                         shared_ptr<Reliability> reliability = nullptr) {
+                         unsigned int stream = 0, shared_ptr<Reliability> reliability = nullptr) {
 	auto message = std::make_shared<Message>(begin, end, type);
 	message->stream = stream;
 	message->reliability = reliability;

+ 2 - 2
include/rtc/nalunit.hpp

@@ -101,8 +101,8 @@ struct RTC_CPP_EXPORT NalUnitFragmentA : NalUnit {
 	NalUnitFragmentA(FragmentType type, bool forbiddenBit, uint8_t nri, uint8_t unitType,
 	                 binary data);
 
-	static std::vector<shared_ptr<NalUnitFragmentA>>
-	fragmentsFrom(shared_ptr<NalUnit> nalu, uint16_t maximumFragmentSize);
+	static std::vector<shared_ptr<NalUnitFragmentA>> fragmentsFrom(shared_ptr<NalUnit> nalu,
+	                                                               uint16_t maximumFragmentSize);
 
 	uint8_t unitType() { return fragmentHeader()->unitType(); }
 

+ 1 - 1
include/rtc/opuspacketizationhandler.hpp

@@ -21,8 +21,8 @@
 
 #if RTC_ENABLE_MEDIA
 
-#include "opusrtppacketizer.hpp"
 #include "mediachainablehandler.hpp"
+#include "opusrtppacketizer.hpp"
 
 namespace rtc {
 

+ 5 - 3
include/rtc/opusrtppacketizer.hpp

@@ -21,13 +21,14 @@
 
 #if RTC_ENABLE_MEDIA
 
-#include "rtppacketizer.hpp"
 #include "mediahandlerrootelement.hpp"
+#include "rtppacketizer.hpp"
 
 namespace rtc {
 
 /// RTP packetizer for opus
-class RTC_CPP_EXPORT OpusRtpPacketizer final : public RtpPacketizer, public MediaHandlerRootElement {
+class RTC_CPP_EXPORT OpusRtpPacketizer final : public RtpPacketizer,
+                                               public MediaHandlerRootElement {
 public:
 	/// default clock rate used in opus RTP communication
 	inline static const uint32_t defaultClockRate = 48 * 1000;
@@ -48,7 +49,8 @@ public:
 	/// @param messages opus samples
 	/// @param control RTCP
 	/// @returns RTP packets and unchanged `control`
-	ChainedOutgoingProduct processOutgoingBinaryMessage(ChainedMessagesProduct messages, message_ptr control) override;
+	ChainedOutgoingProduct processOutgoingBinaryMessage(ChainedMessagesProduct messages,
+	                                                    message_ptr control) override;
 };
 
 } // namespace rtc

+ 1 - 1
include/rtc/peerconnection.hpp

@@ -20,10 +20,10 @@
 #define RTC_PEER_CONNECTION_H
 
 #include "candidate.hpp"
+#include "common.hpp"
 #include "configuration.hpp"
 #include "datachannel.hpp"
 #include "description.hpp"
-#include "common.hpp"
 #include "message.hpp"
 #include "reliability.hpp"
 #include "track.hpp"

+ 34 - 37
include/rtc/rtc.h

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019 Paul-Louis Ageneau
+ * Copyright (c) 2019-2021 Paul-Louis Ageneau
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -113,6 +113,8 @@ typedef enum {
 	RTC_DIRECTION_INACTIVE = 4
 } rtcDirection;
 
+typedef enum { RTC_TRANSPORT_POLICY_ALL = 0, RTC_TRANSPORT_POLICY_RELAY = 1 } rtcTransportPolicy;
+
 #define RTC_ERR_SUCCESS 0
 #define RTC_ERR_INVALID -1   // invalid argument
 #define RTC_ERR_FAILURE -2   // runtime error
@@ -152,6 +154,7 @@ typedef struct {
 	int iceServersCount;
 	const char *bindAddress; // libjuice only, NULL means any
 	rtcCertificateType certificateType;
+	rtcTransportPolicy iceTransportPolicy;
 	bool enableIceTcp;
 	bool disableAutoNegotiation;
 	uint16_t portRangeBegin; // 0 means automatic
@@ -185,6 +188,25 @@ 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, Track, and WebSocket common API
+
+RTC_EXPORT int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb);
+RTC_EXPORT int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb);
+RTC_EXPORT int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb);
+RTC_EXPORT int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb);
+RTC_EXPORT int rtcSendMessage(int id, const char *data, int size);
+RTC_EXPORT bool rtcIsOpen(int id);
+
+RTC_EXPORT int rtcGetBufferedAmount(int id); // total size buffered to send
+RTC_EXPORT int rtcSetBufferedAmountLowThreshold(int id, int amount);
+RTC_EXPORT int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb);
+
+// DataChannel, Track, and WebSocket common extended API
+
+RTC_EXPORT int rtcGetAvailableAmount(int id); // total size available to receive
+RTC_EXPORT int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb);
+RTC_EXPORT int rtcReceiveMessage(int id, char *buffer, int *size);
+
 // DataChannel
 
 typedef struct {
@@ -206,7 +228,6 @@ RTC_EXPORT int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb);
 RTC_EXPORT int rtcCreateDataChannel(int pc, const char *label); // returns dc id
 RTC_EXPORT int rtcCreateDataChannelEx(int pc, const char *label,
                                       const rtcDataChannelInit *init); // returns dc id
-RTC_EXPORT int rtcIsOpen(int dc);
 RTC_EXPORT int rtcDeleteDataChannel(int dc);
 
 RTC_EXPORT int rtcGetDataChannelStream(int dc);
@@ -280,47 +301,41 @@ RTC_EXPORT int rtcSetRtpConfigurationStartTime(int id, const rtcStartTime *start
 // Start stats recording for RTCP Sender Reporter
 RTC_EXPORT int rtcStartRtcpSenderReporterRecording(int id);
 
-// Transform seconds to timestamp using track's clock rate
-// Result is written to timestamp
+// Transform seconds to timestamp using track's clock rate, result is written to timestamp
 RTC_EXPORT int rtcTransformSecondsToTimestamp(int id, double seconds, uint32_t *timestamp);
 
-// Transform timestamp to seconds using track's clock rate
-// Result is written to seconds
+// Transform timestamp to seconds using track's clock rate, result is written to seconds
 RTC_EXPORT int rtcTransformTimestampToSeconds(int id, uint32_t timestamp, double *seconds);
 
-// Get current timestamp
-// Result is written to timestamp
+// Get current timestamp, result is written to timestamp
 RTC_EXPORT int rtcGetCurrentTrackTimestamp(int id, uint32_t *timestamp);
 
-// Get start timestamp for track identified by given id
-// Result is written to timestamp
+// Get start timestamp for track identified by given id, result is written to timestamp
 RTC_EXPORT int rtcGetTrackStartTimestamp(int id, uint32_t *timestamp);
 
 // Set RTP timestamp for track identified by given id
 RTC_EXPORT int rtcSetTrackRtpTimestamp(int id, uint32_t timestamp);
 
-// Get timestamp of previous RTCP SR
-// Result is written to timestamp
+// Get timestamp of previous RTCP SR, result is written to timestamp
 RTC_EXPORT int rtcGetPreviousTrackSenderReportTimestamp(int id, uint32_t *timestamp);
 
 // Set NeedsToReport flag in RtcpSrReporter handler identified by given track id
 RTC_EXPORT int rtcSetNeedsToSendRtcpSr(int id);
 
-/// Get all available payload types for given codec and stores them in buffer, does nothing if
-/// buffer is NULL
+// Get all available payload types for given codec and stores them in buffer, does nothing if
+// buffer is NULL
 int rtcGetTrackPayloadTypesForCodec(int tr, const char *ccodec, int *buffer, int size);
 
-/// Get all SSRCs for given track
+// Get all SSRCs for given track
 int rtcGetSsrcsForTrack(int tr, uint32_t *buffer, int count);
 
-/// Get CName for SSRC
+// Get CName for SSRC
 int rtcGetCNameForSsrc(int tr, uint32_t ssrc, char *cname, int cnameSize);
 
-/// Get all SSRCs for given media type in given SDP
-/// @param mediaType Media type (audio/video)
+// Get all SSRCs for given media type in given SDP
 int rtcGetSsrcsForType(const char *mediaType, const char *sdp, uint32_t *buffer, int bufferSize);
 
-/// Set SSRC for given media type in given SDP
+// Set SSRC for given media type in given SDP
 int rtcSetSsrcForType(const char *mediaType, const char *sdp, char *buffer, const int bufferSize,
                       rtcSsrcForTypeInit *init);
 
@@ -361,24 +376,6 @@ RTC_EXPORT int rtcGetWebSocketServerPort(int wsserver);
 
 #endif
 
-// DataChannel, Track, and WebSocket common API
-
-RTC_EXPORT int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb);
-RTC_EXPORT int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb);
-RTC_EXPORT int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb);
-RTC_EXPORT int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb);
-RTC_EXPORT int rtcSendMessage(int id, const char *data, int size);
-
-RTC_EXPORT int rtcGetBufferedAmount(int id); // total size buffered to send
-RTC_EXPORT int rtcSetBufferedAmountLowThreshold(int id, int amount);
-RTC_EXPORT int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb);
-
-// DataChannel, Track, and WebSocket common extended API
-
-RTC_EXPORT int rtcGetAvailableAmount(int id); // total size available to receive
-RTC_EXPORT int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb);
-RTC_EXPORT int rtcReceiveMessage(int id, char *buffer, int *size);
-
 // Optional global preload and cleanup
 
 RTC_EXPORT void rtcPreload(void);

+ 4 - 3
include/rtc/rtcpnackresponder.hpp

@@ -23,12 +23,12 @@
 
 #include "mediahandlerelement.hpp"
 
-#include <unordered_map>
 #include <queue>
+#include <unordered_map>
 
 namespace rtc {
 
-class RTC_CPP_EXPORT RtcpNackResponder final: public MediaHandlerElement {
+class RTC_CPP_EXPORT RtcpNackResponder final : public MediaHandlerElement {
 
 	/// Packet storage
 	class RTC_CPP_EXPORT Storage {
@@ -85,7 +85,8 @@ public:
 	/// @param messages RTP packets
 	/// @param control RTCP
 	/// @returns Unchanged RTP and RTCP
-	ChainedOutgoingProduct processOutgoingBinaryMessage(ChainedMessagesProduct messages, message_ptr control) override;
+	ChainedOutgoingProduct processOutgoingBinaryMessage(ChainedMessagesProduct messages,
+	                                                    message_ptr control) override;
 };
 
 } // namespace rtc

+ 4 - 3
include/rtc/rtcpsrreporter.hpp

@@ -21,13 +21,13 @@
 
 #if RTC_ENABLE_MEDIA
 
+#include "mediahandlerelement.hpp"
 #include "message.hpp"
 #include "rtppacketizationconfig.hpp"
-#include "mediahandlerelement.hpp"
 
 namespace rtc {
 
-class RTC_CPP_EXPORT RtcpSrReporter final: public MediaHandlerElement {
+class RTC_CPP_EXPORT RtcpSrReporter final : public MediaHandlerElement {
 
 	bool needsToReport = false;
 
@@ -51,7 +51,8 @@ public:
 
 	RtcpSrReporter(shared_ptr<RtpPacketizationConfig> rtpConfig);
 
-	ChainedOutgoingProduct processOutgoingBinaryMessage(ChainedMessagesProduct messages, message_ptr control) override;
+	ChainedOutgoingProduct processOutgoingBinaryMessage(ChainedMessagesProduct messages,
+	                                                    message_ptr control) override;
 
 	/// Set `needsToReport` flag. Sender report will be sent before next RTP packet with same
 	/// timestamp.

+ 1 - 1
include/rtc/rtp.hpp

@@ -340,6 +340,6 @@ struct RTC_CPP_EXPORT RTP_RTX {
 
 #pragma pack(pop)
 
-}; // namespace rtc
+} // namespace rtc
 
 #endif

+ 0 - 3
include/rtc/track.hpp

@@ -25,9 +25,6 @@
 #include "mediahandler.hpp"
 #include "message.hpp"
 
-#include <atomic>
-#include <shared_mutex>
-
 namespace rtc {
 
 namespace impl {

+ 79 - 0
pages/Makefile

@@ -0,0 +1,79 @@
+PY?=python3
+PELICAN?=pelican
+PELICANOPTS=
+
+BASEDIR=$(CURDIR)
+INPUTDIR=$(BASEDIR)/content
+OUTPUTDIR=$(BASEDIR)/output
+CONFFILE=$(BASEDIR)/pelicanconf.py
+PUBLISHCONF=$(BASEDIR)/publishconf.py
+
+GITHUB_PAGES_BRANCH=gh-pages
+
+
+DEBUG ?= 0
+ifeq ($(DEBUG), 1)
+	PELICANOPTS += -D
+endif
+
+RELATIVE ?= 0
+ifeq ($(RELATIVE), 1)
+	PELICANOPTS += --relative-urls
+endif
+
+SERVER ?= "0.0.0.0"
+
+PORT ?= 0
+ifneq ($(PORT), 0)
+	PELICANOPTS += -p $(PORT)
+endif
+
+
+help:
+	@echo 'Makefile for a pelican Web site                                           '
+	@echo '                                                                          '
+	@echo 'Usage:                                                                    '
+	@echo '   make html                           (re)generate the web site          '
+	@echo '   make clean                          remove the generated files         '
+	@echo '   make regenerate                     regenerate files upon modification '
+	@echo '   make publish                        generate using production settings '
+	@echo '   make serve [PORT=8000]              serve site at http://localhost:8000'
+	@echo '   make serve-global [SERVER=0.0.0.0]  serve (as root) to $(SERVER):80    '
+	@echo '   make devserver [PORT=8000]          serve and regenerate together      '
+	@echo '   make devserver-global               regenerate and serve on 0.0.0.0    '
+	@echo '   make github                         upload the web site via gh-pages   '
+	@echo '                                                                          '
+	@echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html   '
+	@echo 'Set the RELATIVE variable to 1 to enable relative urls                    '
+	@echo '                                                                          '
+
+html:
+	"$(PELICAN)" "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS)
+
+clean:
+	[ ! -d "$(OUTPUTDIR)" ] || rm -rf "$(OUTPUTDIR)"
+
+regenerate:
+	"$(PELICAN)" -r "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS)
+
+serve:
+	"$(PELICAN)" -l "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS)
+
+serve-global:
+	"$(PELICAN)" -l "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS) -b $(SERVER)
+
+devserver:
+	"$(PELICAN)" -lr "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS)
+
+devserver-global:
+	$(PELICAN) -lr $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) -b 0.0.0.0
+
+publish:
+	"$(PELICAN)" "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(PUBLISHCONF)" $(PELICANOPTS)
+
+github: publish
+	ghp-import -m "Generate Pelican site" -b $(GITHUB_PAGES_BRANCH) "$(OUTPUTDIR)"
+	git push origin $(GITHUB_PAGES_BRANCH)
+
+
+.PHONY: html help clean regenerate serve serve-global devserver publish github

+ 1 - 0
pages/content/extra/CNAME

@@ -0,0 +1 @@
+libdatachannel.org

+ 6 - 0
pages/content/hello-world.md

@@ -0,0 +1,6 @@
+Title: Hello World!
+Slug: hello-world
+Date: 2021-07-21 21:00
+
+Hello world!
+

+ 103 - 0
pages/content/images/forkme_left_gray_6d6d6d.svg

@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+	<!ENTITY ns_svg "http://www.w3.org/2000/svg">
+	<!ENTITY ns_xlink "http://www.w3.org/1999/xlink">
+]>
+<svg  version="1.1" id="Layer_1" xmlns="&ns_svg;" xmlns:xlink="&ns_xlink;" width="147" height="147"
+	 viewBox="28.386 1.317 147 147" overflow="visible" enable-background="new 28.386 1.317 147 147" xml:space="preserve" style="-webkit-filter: drop-shadow( 0px 0px 5px rgba(0, 0, 0, .7)); filter: drop-shadow( 0px 0px 5px rgba(0, 0, 0, .7));">
+<g>
+	<g>
+		<g>
+			
+			<linearGradient id="balk_3_" gradientUnits="userSpaceOnUse" x1="125.5657" y1="634.8547" x2="105.6487" y2="654.7717" gradientTransform="matrix(-4.371139e-008 -1 1 -4.371139e-008 -557.2045 174.0939)">
+				<stop offset="0" style="stop-color:#6d6d6d"/>
+				<stop offset="1" style="stop-color:#5d5d5d"/>
+			</linearGradient>
+			<polygon id="balk" fill="url(#balk_3_)" points="167.538,-1.528 27.589,138.418 7.674,118.503 147.621,-21.445"/>
+			<g id="letters">
+				<path fill="#FFFFFF" d="M47.906,91.107c-0.516-0.518-0.888-0.858-1.115-1.026l3.66-3.66l1.272,1.271
+					c-0.19,0.145-0.482,0.414-0.879,0.811l-1.38,1.38l1.778,1.779l1.152-1.152c0.349-0.35,0.597-0.619,0.741-0.809l1.25,1.25
+					c-0.176,0.129-0.41,0.342-0.706,0.638l-1.256,1.255l2.388,2.388c0.448,0.448,0.803,0.772,1.061,0.973l-1.48,1.479
+					c-0.19-0.25-0.532-0.623-1.026-1.116L47.906,91.107z"/>
+				<path fill="#FFFFFF" d="M60.76,91.469c-0.729,0.73-1.531,1.024-2.406,0.88c-0.76-0.137-1.522-0.588-2.29-1.354
+					c-1.716-1.716-1.871-3.278-0.463-4.687c1.392-1.392,2.944-1.229,4.661,0.487c0.76,0.759,1.207,1.52,1.345,2.278
+					C61.765,89.949,61.482,90.747,60.76,91.469z M56.704,87.437c-0.349,0.351-0.413,0.747-0.19,1.195
+					c0.121,0.258,0.405,0.611,0.854,1.06c0.448,0.447,0.801,0.732,1.061,0.854c0.447,0.22,0.847,0.157,1.193-0.192
+					c0.349-0.349,0.416-0.741,0.204-1.183c-0.122-0.258-0.411-0.615-0.866-1.071c-0.447-0.446-0.801-0.731-1.06-0.854
+					C57.45,87.026,57.054,87.088,56.704,87.437z"/>
+				<path fill="#FFFFFF" d="M63.537,88.537c-0.13-0.174-0.376-0.444-0.741-0.811l-3.544-3.544c-0.355-0.355-0.613-0.593-0.772-0.708
+					l1.249-1.248l0.341,0.346c-0.038-0.492,0.102-0.898,0.42-1.217c0.136-0.137,0.313-0.254,0.531-0.354l1.29,1.29
+					c-0.335,0.059-0.583,0.167-0.741,0.327c-0.212,0.212-0.333,0.553-0.362,1.021l2.771,2.771c0.388,0.388,0.68,0.658,0.876,0.811
+					L63.537,88.537z"/>
+				<path fill="#FFFFFF" d="M66.82,85.254c-0.13-0.174-0.376-0.444-0.741-0.809l-6.086-6.086c-0.356-0.357-0.615-0.595-0.773-0.708
+					l1.293-1.293c0.122,0.167,0.351,0.417,0.685,0.752l3.202,3.201l-0.249-1.801c-0.03-0.258-0.08-0.475-0.148-0.647l1.472-1.474
+					l0.432,3.488l4.822,1.469l-1.334,1.334c-0.146-0.083-0.427-0.188-0.846-0.317l-3.055-0.957l1.744,1.744
+					c0.388,0.388,0.68,0.656,0.876,0.81L66.82,85.254z"/>
+				<path fill="#FFFFFF" d="M72.192,70.631c0.068-0.691,0.365-1.301,0.89-1.825c0.595-0.594,1.311-0.775,2.146-0.549
+					c0.684,0.196,1.367,0.637,2.052,1.32l1.561,1.562c0.519,0.519,0.882,0.854,1.094,1.004l-1.359,1.359
+					c-0.207-0.28-0.545-0.656-1.017-1.128l-1.54-1.54c-0.836-0.836-1.482-1.027-1.938-0.573c-0.31,0.312-0.479,0.603-0.51,0.874
+					c0.388,0.252,0.78,0.575,1.175,0.971l1.563,1.563c0.517,0.518,0.881,0.853,1.093,1.005l-1.37,1.37
+					c-0.207-0.28-0.545-0.656-1.017-1.127l-1.529-1.551c-0.479-0.479-0.852-0.766-1.117-0.856c-0.266-0.092-0.516-0.021-0.75,0.214
+					c-0.227,0.229-0.343,0.55-0.35,0.967l2.84,2.841c0.129,0.129,0.281,0.271,0.456,0.423c0.174,0.15,0.271,0.24,0.296,0.263
+					l-1.326,1.326c-0.105-0.152-0.335-0.402-0.684-0.752l-3.558-3.555c-0.409-0.411-0.687-0.666-0.831-0.767l1.271-1.249
+					l0.356,0.371c0.069-0.6,0.253-1.048,0.55-1.344C71.088,70.799,71.607,70.593,72.192,70.631z"/>
+				<path fill="#FFFFFF" d="M82.379,65.408l-2.528,2.528c0.79,0.669,1.516,0.674,2.177,0.013c0.433-0.435,0.702-0.923,0.808-1.473
+					l1.219,1.221c-0.281,0.631-0.602,1.125-0.957,1.48c-0.754,0.753-1.592,1.081-2.511,0.982c-0.874-0.083-1.713-0.526-2.518-1.332
+					c-0.684-0.685-1.096-1.424-1.24-2.221c-0.15-0.884,0.092-1.643,0.731-2.279c0.624-0.624,1.313-0.893,2.074-0.8
+					c0.715,0.091,1.404,0.471,2.072,1.139C81.964,64.926,82.19,65.173,82.379,65.408z M78.646,65.341
+					c-0.441,0.441-0.392,0.964,0.154,1.569l1.402-1.403C79.614,64.948,79.095,64.893,78.646,65.341z"/>
+				<path fill="#FFFFFF" d="M89.562,62.667c-0.73,0.729-1.532,1.022-2.407,0.88c-0.759-0.137-1.522-0.588-2.29-1.354
+					c-1.716-1.719-1.871-3.278-0.463-4.688c1.392-1.392,2.946-1.229,4.662,0.486c0.759,0.76,1.207,1.52,1.344,2.277
+					C90.567,61.146,90.284,61.946,89.562,62.667z M85.507,58.635c-0.35,0.351-0.412,0.746-0.191,1.194
+					c0.122,0.259,0.407,0.61,0.854,1.06c0.448,0.449,0.803,0.733,1.061,0.854c0.447,0.221,0.846,0.157,1.194-0.191
+					c0.35-0.35,0.415-0.743,0.203-1.184c-0.122-0.259-0.41-0.615-0.866-1.07c-0.447-0.448-0.802-0.732-1.06-0.854
+					C86.252,58.223,85.856,58.286,85.507,58.635z"/>
+				<path fill="#FFFFFF" d="M89.599,52.268c0.606-0.624,1.349-0.833,2.222-0.628c0.737,0.176,1.443,0.602,2.12,1.275l1.562,1.562
+					c0.518,0.518,0.881,0.853,1.093,1.005l-1.369,1.369c-0.152-0.195-0.495-0.567-1.028-1.114l-1.527-1.553
+					c-0.852-0.865-1.555-1.021-2.105-0.471c-0.25,0.25-0.382,0.633-0.396,1.149l2.807,2.805c0.13,0.13,0.279,0.271,0.457,0.423
+					c0.174,0.152,0.271,0.24,0.295,0.263l-1.325,1.326c-0.107-0.149-0.335-0.399-0.687-0.751l-3.5-3.499
+					c-0.438-0.438-0.731-0.711-0.876-0.811l1.262-1.261l0.408,0.411C89.042,53.126,89.24,52.624,89.599,52.268z"/>
+				<path fill="#FFFFFF" d="M103.262,47.535c0.082,0.28,0.062,0.627-0.063,1.037c-0.152,0.501-0.44,0.965-0.866,1.39
+					c-1.886,1.886-4.146,1.509-6.783-1.13c-1.255-1.254-1.945-2.479-2.074-3.671c-0.129-1.194,0.321-2.31,1.356-3.343
+					c0.594-0.595,1.296-0.916,2.109-0.971l1.299,1.301c-0.908,0.064-1.632,0.366-2.172,0.906c-1.119,1.119-0.791,2.565,0.983,4.34
+					c1.798,1.8,3.147,2.244,4.055,1.341c0.736-0.737,0.649-1.563-0.263-2.476c-0.084-0.099-0.389-0.388-0.91-0.867L101.325,44
+					c0.298,0.354,0.645,0.732,1.039,1.127l1.321,1.322c0.213,0.212,0.416,0.4,0.613,0.567c0.196,0.168,0.329,0.286,0.397,0.354
+					c-0.038,0.066-0.118,0.146-0.239,0.239L103.262,47.535z"/>
+				<path fill="#FFFFFF" d="M105.659,46.415c-0.138-0.167-0.388-0.434-0.753-0.797l-3.545-3.545
+					c-0.356-0.357-0.611-0.599-0.763-0.721l1.28-1.279c0.13,0.157,0.361,0.403,0.695,0.738l3.499,3.498
+					c0.388,0.39,0.677,0.661,0.865,0.821L105.659,46.415z M100.367,39.821c-0.22,0.219-0.49,0.32-0.813,0.312
+					c-0.321-0.013-0.599-0.133-0.825-0.361c-0.236-0.234-0.359-0.516-0.373-0.838c-0.013-0.322,0.092-0.595,0.313-0.813
+					c0.211-0.212,0.479-0.313,0.805-0.301c0.32,0.014,0.602,0.137,0.838,0.372c0.229,0.229,0.348,0.505,0.359,0.825
+					C100.679,39.34,100.578,39.607,100.367,39.821z"/>
+				<path fill="#FFFFFF" d="M104.313,37.641l0.982-0.981l1.104,1.104c-0.037,0.037-0.111,0.104-0.222,0.198
+					c-0.109,0.094-0.209,0.187-0.301,0.276l-0.483,0.484l2.12,2.12c0.51,0.509,0.931,0.598,1.265,0.265
+					c0.235-0.235,0.382-0.514,0.44-0.834l1.141,1.141c-0.13,0.434-0.394,0.848-0.788,1.243c-0.556,0.557-1.138,0.742-1.746,0.562
+					c-0.455-0.137-1.003-0.522-1.641-1.162l-2.039-2.039l0.011-0.011l-0.021-0.022l-0.184,0.161c-0.1,0.102-0.219,0.241-0.356,0.427
+					l-1.104-1.105l0.563-0.563l-0.445-0.446c-0.214-0.214-0.396-0.374-0.549-0.482l1.326-1.326c0.122,0.169,0.273,0.345,0.458,0.526
+					L104.313,37.641z"/>
+				<path fill="#FFFFFF" d="M111.484,31.287l0.377-0.376l1.248,1.248l-0.376,0.377l2.604,2.604c0.356,0.357,0.639,0.608,0.843,0.753
+					l-1.492,1.491c-0.168-0.226-0.441-0.529-0.82-0.909l-2.538-2.538l-2.077,2.077l2.526,2.527c0.41,0.409,0.717,0.688,0.921,0.832
+					l-1.479,1.479c-0.152-0.212-0.403-0.49-0.754-0.842l-6.007-6.006c-0.35-0.35-0.631-0.601-0.842-0.754l1.48-1.48
+					c0.159,0.222,0.411,0.5,0.753,0.844l2.151,2.15l2.076-2.077l-1.993-1.991c-0.463-0.463-0.797-0.769-1.001-0.912l1.492-1.492
+					c0.139,0.197,0.354,0.443,0.65,0.739L111.484,31.287z"/>
+				<path fill="#FFFFFF" d="M119.115,33.048c-0.599,0.6-1.309,0.775-2.128,0.532c-0.66-0.206-1.34-0.658-2.039-1.356l-1.562-1.562
+					c-0.518-0.519-0.881-0.854-1.094-1.004l1.37-1.37c0.146,0.188,0.488,0.562,1.027,1.116l1.528,1.551
+					c0.435,0.434,0.795,0.696,1.082,0.788c0.35,0.123,0.675,0.033,0.976-0.27c0.212-0.212,0.323-0.572,0.336-1.089l-2.806-2.805
+					c-0.13-0.13-0.285-0.271-0.468-0.423c-0.183-0.153-0.279-0.236-0.297-0.252l1.326-1.326c0.114,0.144,0.348,0.391,0.696,0.74
+					l3.486,3.485c0.447,0.449,0.739,0.728,0.877,0.833l-1.249,1.249l-0.407-0.412C119.73,32.13,119.509,32.655,119.115,33.048z"/>
+				<path fill="#FFFFFF" d="M124.74,27.401c-0.411,0.41-0.902,0.605-1.472,0.586l0.409,0.408l-1.203,1.204
+					c-0.107-0.149-0.336-0.399-0.688-0.751l-6.027-6.027c-0.447-0.447-0.743-0.724-0.889-0.821l1.293-1.293
+					c0.121,0.152,0.399,0.444,0.832,0.877l1.99,1.976c0.131-0.567,0.363-1.021,0.697-1.354c0.602-0.604,1.339-0.81,2.213-0.618
+					c0.752,0.175,1.46,0.592,2.12,1.253c0.676,0.676,1.11,1.417,1.309,2.223C125.545,26.01,125.351,26.79,124.74,27.401z
+					 M120.635,23.639c-0.252,0.252-0.391,0.643-0.412,1.167l1.976,1.979c0.479-0.01,0.839-0.131,1.075-0.367
+					c0.299-0.299,0.357-0.688,0.185-1.167c-0.136-0.381-0.384-0.75-0.741-1.108C121.855,23.281,121.161,23.114,120.635,23.639z"/>
+			</g>
+			<g opacity="0.5">
+				<line fill="none" stroke="#FFFFFF" stroke-dasharray="3 1.5" x1="19.705" y1="143.389" x2="174.08" y2="-10.986"/>
+				<line fill="none" stroke="#FFFFFF" stroke-dasharray="3 1.5" x1="1.58" y1="127.514" x2="155.955" y2="-26.861"/>
+			</g>
+		</g>
+	</g>
+</g>
+</svg>

+ 102 - 0
pages/content/images/forkme_right_gray_6d6d6d.svg

@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+	<!ENTITY ns_svg "http://www.w3.org/2000/svg">
+	<!ENTITY ns_xlink "http://www.w3.org/1999/xlink">
+]>
+<svg  version="1.1" id="Layer_1" xmlns="&ns_svg;" xmlns:xlink="&ns_xlink;" width="147" height="147"
+	 viewBox="28.386 1.317 147 147" overflow="visible" enable-background="new 28.386 1.317 147 147" xml:space="preserve" style="-webkit-filter: drop-shadow( 0px 0px 5px rgba(0, 0, 0, .7)); filter: drop-shadow( 0px 0px 5px rgba(0, 0, 0, .7));">
+<g>
+	<g>
+		<g>
+			
+			<linearGradient id="balk_5_" gradientUnits="userSpaceOnUse" x1="125.5657" y1="662.8528" x2="105.6487" y2="682.7698" gradientTransform="matrix(1 0 0 1 0 -614.7715)">
+				<stop offset="0" style="stop-color:#6d6d6d"/>
+				<stop offset="1" style="stop-color:#5d5d5d"/>
+			</linearGradient>
+			<polygon id="balk" fill="url(#balk_5_)" points="175.622,137.972 35.676,-1.977 55.591,-21.892 195.539,118.055" />
+			<g id="letters">
+				<path fill="#FFFFFF" d="M82.988,18.34c0.518-0.516,0.858-0.888,1.026-1.115l3.66,3.66l-1.271,1.272
+					c-0.145-0.19-0.414-0.482-0.811-0.879l-1.38-1.38l-1.779,1.778l1.152,1.152c0.35,0.349,0.619,0.597,0.809,0.741l-1.25,1.25
+					c-0.129-0.176-0.342-0.41-0.638-0.706l-1.255-1.256l-2.388,2.388c-0.448,0.448-0.772,0.803-0.973,1.061l-1.479-1.48
+					c0.25-0.19,0.623-0.532,1.116-1.026L82.988,18.34z"/>
+				<path fill="#FFFFFF" d="M82.625,31.194c-0.73-0.729-1.024-1.531-0.88-2.406c0.137-0.76,0.588-1.522,1.354-2.29
+					c1.716-1.716,3.278-1.871,4.687-0.463c1.392,1.392,1.229,2.944-0.487,4.661c-0.759,0.76-1.52,1.207-2.278,1.345
+					C84.145,32.198,83.347,31.915,82.625,31.194z M86.657,27.138c-0.351-0.349-0.747-0.413-1.195-0.19
+					c-0.258,0.121-0.611,0.405-1.06,0.854c-0.447,0.448-0.732,0.801-0.854,1.061c-0.22,0.447-0.157,0.847,0.192,1.193
+					c0.349,0.349,0.741,0.416,1.183,0.204c0.258-0.122,0.615-0.411,1.071-0.866c0.446-0.447,0.731-0.801,0.854-1.06
+					C87.069,27.884,87.006,27.488,86.657,27.138z"/>
+				<path fill="#FFFFFF" d="M85.557,33.971c0.174-0.13,0.444-0.376,0.811-0.741l3.544-3.544c0.355-0.355,0.593-0.613,0.708-0.772
+					l1.248,1.249l-0.346,0.341c0.492-0.038,0.898,0.102,1.217,0.42c0.137,0.136,0.254,0.313,0.354,0.531l-1.29,1.29
+					c-0.059-0.335-0.167-0.583-0.327-0.741c-0.212-0.212-0.553-0.333-1.021-0.362l-2.771,2.771c-0.388,0.388-0.658,0.68-0.811,0.876
+					L85.557,33.971z"/>
+				<path fill="#FFFFFF" d="M88.84,37.253c0.174-0.13,0.444-0.376,0.809-0.741l6.086-6.086c0.357-0.356,0.595-0.615,0.708-0.773
+					l1.293,1.293c-0.167,0.122-0.417,0.351-0.752,0.685l-3.201,3.202l1.801-0.249c0.258-0.03,0.475-0.08,0.647-0.148l1.474,1.472
+					l-3.488,0.432l-1.469,4.822l-1.334-1.334c0.083-0.146,0.188-0.427,0.317-0.846l0.957-3.055l-1.744,1.744
+					c-0.388,0.388-0.656,0.68-0.81,0.876L88.84,37.253z"/>
+				<path fill="#FFFFFF" d="M103.463,42.625c0.691,0.068,1.301,0.365,1.825,0.89c0.594,0.595,0.775,1.311,0.549,2.146
+					c-0.196,0.684-0.637,1.367-1.32,2.052l-1.562,1.561c-0.519,0.519-0.854,0.882-1.004,1.094l-1.359-1.359
+					c0.28-0.207,0.656-0.545,1.128-1.017l1.54-1.54c0.836-0.836,1.027-1.482,0.573-1.938c-0.312-0.31-0.603-0.479-0.874-0.51
+					c-0.252,0.388-0.575,0.78-0.971,1.175l-1.563,1.563c-0.518,0.517-0.853,0.881-1.005,1.093l-1.37-1.37
+					c0.28-0.207,0.656-0.545,1.127-1.017l1.551-1.529c0.479-0.479,0.766-0.852,0.856-1.117c0.092-0.266,0.021-0.516-0.214-0.75
+					c-0.229-0.227-0.55-0.343-0.967-0.35l-2.841,2.84c-0.129,0.129-0.271,0.281-0.423,0.456c-0.15,0.174-0.24,0.271-0.263,0.296
+					l-1.326-1.326c0.152-0.105,0.402-0.335,0.752-0.684l3.555-3.558c0.411-0.409,0.666-0.687,0.767-0.831l1.249,1.271l-0.371,0.356
+					c0.6,0.069,1.048,0.253,1.344,0.55C103.295,41.522,103.501,42.04,103.463,42.625z"/>
+				<path fill="#FFFFFF" d="M108.686,52.813l-2.528-2.528c-0.669,0.79-0.674,1.516-0.013,2.177c0.435,0.433,0.923,0.702,1.473,0.808
+					l-1.221,1.219c-0.631-0.281-1.125-0.602-1.48-0.957c-0.753-0.754-1.081-1.592-0.982-2.511c0.083-0.874,0.526-1.713,1.332-2.518
+					c0.685-0.684,1.424-1.096,2.221-1.24c0.884-0.15,1.643,0.092,2.279,0.731c0.624,0.624,0.893,1.313,0.8,2.074
+					c-0.091,0.715-0.471,1.404-1.139,2.072C109.168,52.398,108.921,52.623,108.686,52.813z M108.753,49.079
+					c-0.441-0.441-0.964-0.392-1.569,0.154l1.403,1.402C109.146,50.047,109.201,49.529,108.753,49.079z"/>
+				<path fill="#FFFFFF" d="M111.427,59.995c-0.729-0.73-1.022-1.532-0.88-2.407c0.137-0.759,0.588-1.522,1.354-2.29
+					c1.719-1.716,3.278-1.871,4.688-0.463c1.392,1.392,1.229,2.946-0.486,4.662c-0.76,0.759-1.52,1.207-2.277,1.344
+					C112.948,61,112.149,60.718,111.427,59.995z M115.459,55.941c-0.351-0.35-0.746-0.412-1.194-0.191
+					c-0.259,0.122-0.61,0.407-1.06,0.854c-0.449,0.448-0.733,0.803-0.854,1.061c-0.221,0.447-0.157,0.846,0.191,1.194
+					c0.35,0.35,0.743,0.415,1.184,0.203c0.259-0.122,0.615-0.41,1.07-0.866c0.448-0.447,0.732-0.802,0.854-1.06
+					C115.871,56.686,115.808,56.289,115.459,55.941z"/>
+				<path fill="#FFFFFF" d="M121.826,60.032c0.624,0.606,0.833,1.349,0.628,2.222c-0.176,0.737-0.602,1.443-1.275,2.12l-1.562,1.562
+					c-0.518,0.518-0.853,0.881-1.005,1.093l-1.369-1.369c0.195-0.152,0.567-0.495,1.114-1.028l1.553-1.527
+					c0.865-0.852,1.021-1.555,0.471-2.105c-0.25-0.25-0.633-0.382-1.149-0.396l-2.805,2.807c-0.13,0.13-0.271,0.279-0.423,0.457
+					c-0.152,0.174-0.24,0.271-0.263,0.295l-1.326-1.325c0.149-0.107,0.399-0.335,0.751-0.687l3.499-3.5
+					c0.438-0.438,0.711-0.731,0.811-0.876l1.261,1.262l-0.411,0.408C120.968,59.476,121.47,59.674,121.826,60.032z"/>
+				<path fill="#FFFFFF" d="M126.559,73.696c-0.28,0.082-0.627,0.062-1.037-0.063c-0.501-0.152-0.965-0.44-1.39-0.866
+					c-1.886-1.886-1.509-4.146,1.13-6.783c1.254-1.255,2.479-1.945,3.671-2.074c1.194-0.129,2.31,0.321,3.343,1.356
+					c0.595,0.594,0.916,1.296,0.971,2.109l-1.301,1.299c-0.064-0.908-0.366-1.632-0.906-2.172c-1.119-1.119-2.565-0.791-4.34,0.983
+					c-1.8,1.798-2.244,3.147-1.341,4.055c0.737,0.736,1.563,0.649,2.476-0.263c0.099-0.084,0.388-0.389,0.867-0.91l1.393,1.392
+					c-0.354,0.298-0.732,0.645-1.127,1.039l-1.322,1.321c-0.212,0.213-0.4,0.416-0.567,0.613c-0.168,0.196-0.286,0.329-0.354,0.397
+					c-0.066-0.038-0.146-0.118-0.239-0.239L126.559,73.696z"/>
+				<path fill="#FFFFFF" d="M127.679,76.093c0.167-0.138,0.434-0.388,0.797-0.753l3.545-3.545c0.357-0.356,0.599-0.611,0.721-0.763
+					l1.279,1.28c-0.157,0.13-0.403,0.361-0.738,0.695l-3.498,3.499c-0.39,0.388-0.661,0.677-0.821,0.865L127.679,76.093z
+					 M134.274,70.801c-0.219-0.22-0.32-0.49-0.312-0.813c0.013-0.321,0.133-0.599,0.361-0.825c0.234-0.236,0.516-0.359,0.838-0.373
+					c0.322-0.013,0.595,0.092,0.813,0.313c0.212,0.211,0.313,0.479,0.301,0.805c-0.014,0.32-0.137,0.602-0.372,0.838
+					c-0.229,0.229-0.505,0.348-0.825,0.359C134.754,71.113,134.488,71.012,134.274,70.801z"/>
+				<path fill="#FFFFFF" d="M136.453,74.746l0.981,0.982l-1.104,1.104c-0.037-0.037-0.104-0.111-0.198-0.222
+					c-0.094-0.109-0.187-0.209-0.276-0.301l-0.484-0.483l-2.12,2.12c-0.509,0.51-0.598,0.931-0.265,1.265
+					c0.235,0.235,0.514,0.382,0.834,0.44l-1.141,1.141c-0.434-0.13-0.848-0.394-1.243-0.788c-0.557-0.556-0.742-1.138-0.562-1.746
+					c0.137-0.455,0.522-1.003,1.162-1.641l2.039-2.039l0.011,0.011l0.022-0.021l-0.161-0.184c-0.102-0.1-0.241-0.219-0.427-0.356
+					l1.105-1.104l0.563,0.563l0.446-0.445c0.214-0.214,0.374-0.396,0.482-0.549l1.326,1.326c-0.169,0.122-0.345,0.273-0.526,0.458
+					L136.453,74.746z"/>
+				<path fill="#FFFFFF" d="M142.807,81.917l0.376,0.377l-1.248,1.248l-0.377-0.376l-2.604,2.604
+					c-0.357,0.356-0.608,0.639-0.753,0.843l-1.491-1.492c0.226-0.168,0.529-0.441,0.909-0.82l2.538-2.538l-2.077-2.077l-2.527,2.526
+					c-0.409,0.41-0.688,0.717-0.832,0.921l-1.479-1.479c0.212-0.152,0.49-0.403,0.842-0.754l6.006-6.007
+					c0.35-0.35,0.601-0.631,0.754-0.842l1.48,1.48c-0.222,0.159-0.5,0.411-0.844,0.753l-2.15,2.151l2.077,2.076l1.991-1.993
+					c0.463-0.463,0.769-0.797,0.912-1.001l1.492,1.492c-0.197,0.139-0.443,0.354-0.739,0.65L142.807,81.917z"/>
+				<path fill="#FFFFFF" d="M141.046,89.549c-0.6-0.599-0.775-1.309-0.532-2.128c0.206-0.66,0.658-1.34,1.356-2.039l1.562-1.562
+					c0.519-0.518,0.854-0.881,1.004-1.094l1.37,1.37c-0.188,0.146-0.562,0.488-1.116,1.027l-1.551,1.528
+					c-0.434,0.435-0.696,0.795-0.788,1.082c-0.123,0.35-0.033,0.675,0.27,0.976c0.212,0.212,0.572,0.323,1.089,0.336l2.805-2.806
+					c0.13-0.13,0.271-0.285,0.423-0.468c0.153-0.183,0.236-0.279,0.252-0.297l1.326,1.326c-0.144,0.114-0.391,0.348-0.74,0.696
+					l-3.485,3.486c-0.449,0.447-0.728,0.739-0.833,0.877l-1.249-1.249l0.412-0.407C141.964,90.163,141.44,89.943,141.046,89.549z"/>
+				<path fill="#FFFFFF" d="M146.694,95.174c-0.41-0.411-0.605-0.902-0.586-1.472l-0.408,0.409l-1.204-1.203
+					c0.149-0.107,0.399-0.336,0.751-0.688l6.027-6.027c0.447-0.447,0.724-0.743,0.821-0.889l1.293,1.293
+					c-0.152,0.121-0.444,0.399-0.877,0.832l-1.976,1.99c0.567,0.131,1.021,0.363,1.354,0.697c0.604,0.602,0.81,1.339,0.618,2.213
+					c-0.175,0.752-0.592,1.46-1.253,2.12c-0.676,0.676-1.417,1.11-2.223,1.309C148.084,95.979,147.304,95.784,146.694,95.174z
+					 M150.455,91.069c-0.252-0.252-0.643-0.391-1.167-0.412l-1.979,1.976c0.01,0.479,0.131,0.839,0.367,1.075
+					c0.299,0.299,0.688,0.357,1.167,0.185c0.381-0.136,0.75-0.384,1.108-0.741C150.814,92.288,150.981,91.595,150.455,91.069z"/>
+			</g>
+			<g opacity="0.5">
+				<line fill="none" stroke="#FFFFFF" stroke-dasharray="3 1.5" x1="30.705" y1="-9.861" x2="185.08" y2="144.514"/>
+				<line fill="none" stroke="#FFFFFF" stroke-dasharray="3 1.5" x1="46.58" y1="-27.986" x2="200.955" y2="126.389"/>
+			</g>
+		</g>
+	</g>
+</g>
+</svg>

BIN
pages/content/images/icon_compatible.png


BIN
pages/content/images/icon_easy.png


BIN
pages/content/images/icon_portable.png


+ 37 - 0
pages/content/pages/index.md

@@ -0,0 +1,37 @@
+title: libdatachannel
+url:
+save_as: index.html
+
+<div id="home">
+	<p>
+	libdatachannel is an open-source software library implementing WebRTC Data Channels, WebRTC Media Transport, and WebSockets. It is written in C++17 and offers C bindings. The <a href="https://github.com/paullouisageneau/libdatachannel">code source</a> is available under LGPLv2.
+	</p>
+	<section>
+		<img src="/images/icon_easy.png">
+		<h3>Easy</h3>
+		<ul>
+			<li>Simple API inspired by the JavaScript API including WebSocket for signaling</li>
+			<li>Minimal external dependencies (only <a href="https://www.openssl.org/">OpenSSL</a> or <a href="https://www.openssl.org/">GnuTLS</a>)
+			<li>Lightweight and way easier to compile and use than Google's <a href="https://webrtc.googlesource.com/src/">reference library</a>
+		</ul>
+	</section>
+	<section>
+		<img src="/images/icon_compatible.png">
+		<h3>Compatible</h3>
+		<ul>
+			<li>Compatible with browsers Firefox, Chromium, and Safari, and other WebRTC libraries (see <a href="https://github.com/sipsorcery/webrtc-echoes">webrtc-echoes</a>)</li>
+			<li>Licensed under <a href="https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html">LGPLv2</a>, meaning software with any license may link against the library</li>
+			<li>Community-maintained bindings available for <a href="https://github.com/lerouxrgd/datachannel-rs">Rust</a>, <a href="https://github.com/murat-dogan/node-datachannel">Node.js</a>, and <a href="https://github.com/hanseuljun/datachannel-unity">Unity</a></li>
+		</ul>
+	</section>
+	<section>
+		<img src="/images/icon_portable.png">
+		<h3>Portable</h3>
+		<ul>
+			<li>Support for POSIX platforms (including GNU/Linux, Android, Apple macOS, and FreeBSD) and Microsoft Windows</li>
+			<li>Support for both <a href="https://www.openssl.org/">OpenSSL</a> and <a href="https://www.gnutls.org/">GnuTLS</a> as TLS backend
+			<li>Code using Data Channels and WebSockets may be compiled as-is to WebAssembly for browsers with <a href="https://github.com/paullouisageneau/datachannel-wasm">datachannel-wasm</a></li>
+		</ul>
+	</section>
+</div>
+

+ 789 - 0
pages/content/pages/reference.md

@@ -0,0 +1,789 @@
+Title: Reference
+url: pages/reference.html
+
+## libdatachannel - C API Documentation
+
+The following details the C API of libdatachannel. The C API is available by including the `rtc/rtc.h` header.
+
+### General considerations
+
+Unless stated otherwise, functions return `RTC_ERR_SUCCESS`, defined as `0`, on success.
+
+All functions can return the following negative error codes:
+
+- `RTC_ERR_INVALID`: an invalid argument was provided
+- `RTC_ERR_FAILURE`: a runtime error happened
+- `RTC_ERR_NOT_AVAIL`: an element is not available at the moment
+- `RTC_ERR_TOO_SMALL`: a user-provided buffer is too small
+
+All functions taking pointers as arguments (excepted the opaque user pointer) need the memory to be accessible until the call returns, but it can be safely discarded afterwards.
+
+### Common
+
+#### rtcInitLogger
+
+```
+void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb)
+```
+
+Arguments:
+
+- `level`: the log level. It must be one of the following: `RTC_LOG_NONE`, `RTC_LOG_FATAL`, `RTC_LOG_ERROR`, `RTC_LOG_WARNING`, `RTC_LOG_INFO`, `RTC_LOG_DEBUG`, `RTC_LOG_VERBOSE`.
+- `cb` (optional): the callback to pass the log lines to. If the callback is already set, it is replaced. If NULL after a callback is set, the callback is unset. If NULL on first call, the library will log to stdout instead.
+
+`cb` must have the following signature:
+`void myLogCallback(rtcLogLevel level, const char *message)`
+
+Arguments:
+
+- `level`: the log level for the current message. It will be one of the following: `RTC_LOG_FATAL`, `RTC_LOG_ERROR`, `RTC_LOG_WARNING`, `RTC_LOG_INFO`, `RTC_LOG_DEBUG`, `RTC_LOG_VERBOSE`.
+- `message`: a null-terminated string containing the log message
+
+#### rtcPreload
+
+```
+void rtcPreload(void)
+```
+
+An optional call to `rtcPreload` preloads the global resources used by the library. If it is not called, resources are lazy-loaded when they are required for the first time by a PeerConnection, which for instance prevents from properly timing connection establishment (as the first one will take way more time). The call blocks until preloading is finished. If resources are already loaded, the call has no effect.
+
+#### rtcCleanup
+
+```
+void rtcCleanup(void)
+```
+
+An optional call to `rtcCleanup` requests unloading of the global resources used by the library. If all created PeerConnections are deleted, unloading will happen immediately and the call will block until unloading is done, otherwise unloading will happen as soon as the last PeerConnection is deleted. If resources are already unloaded, the call has no effect.
+
+#### rtcSetUserPointer
+
+```
+void rtcSetUserPointer(int id, void *user_ptr)
+```
+
+Sets a opaque user pointer for a Peer Connection, Data Channel, Track, or WebSocket. The user pointer will be passed as last argument in each corresponding callback. It will never be accessed in any way. The initial user pointer of a Peer Connection or WebSocket is `NULL`, and the initial one of a Data Channel or Track is the one of the Peer Connection at the time of creation.
+
+Arguments:
+
+- `id`: the identifier of Peer Connection, Data Channel, Track, or WebSocket
+- `user_ptr`: an opaque pointer whose meaning is up to the user
+
+### PeerConnection
+
+#### rtcCreatePeerConnection
+
+```
+int rtcCreatePeerConnection(const rtcConfiguration *config)
+
+typedef struct {
+	const char **iceServers;
+	int iceServersCount;
+	const char *bindAddress;
+	rtcCertificateType certificateType;
+	rtcTransportPolicy iceTransportPolicy;
+	bool enableIceTcp;
+	bool disableAutoNegotiation;
+	uint16_t portRangeBegin;
+	uint16_t portRangeEnd;
+	int mtu;
+	int maxMessageSize;
+} rtcConfiguration;
+```
+
+Creates a Peer Connection.
+
+Arguments:
+
+- `config`: the configuration structure, containing:
+  - `iceServers` (optional): an array of pointers on null-terminated ice server URIs (NULL if unused)
+  - `iceServersCount` (optional): number of URLs in the array pointed by `iceServers` (0 if unused)
+  - `bindAddress` (optional): if non-NULL, bind only to the given local address (ignored with libnice as ICE backend)
+  - `certificateType` (optional): certificate type, either `RTC_CERTIFICATE_ECDSA` or `RTC_CERTIFICATE_RSA` (0 or `RTC_CERTIFICATE_DEFAULT` if default)
+  - `iceTransportPolicy` (optional): ICE transport policy, if set to `RTC_TRANSPORT_POLICY_RELAY`, the PeerConnection will emit only relayed candidates (0 or `RTC_TRANSPORT_POLICY_ALL` if default)
+  - `enableIceTcp`: if true, generate TCP candidates for ICE (ignored with libjuice as ICE backend)
+  - `disableAutoNegotiation`: if true, the user is responsible for calling `rtcSetLocalDescription` after creating a Data Channel and after setting the remote description
+  - `portRangeBegin` (optional): first port (included) of the allowed local port range (0 if unused)
+  - `portRangeEnd` (optional): last port (included) of the allowed local port (0 if unused)
+  - `mtu` (optional): manually set the Maximum Transfer Unit (MTU) for the connection (0 if automatic)
+  - `maxMessageSize` (optional): manually set the local maximum message size for Data Channels (0 if default)
+
+Return value: the identifier of the new Peer Connection or a negative error code.
+
+The Peer Connection must be deleted with `rtcDeletePeerConnection`.
+
+The format of each entry in `iceServers` must match the format `[("stun"|"turn"|"turns") ":"][login ":" password "@"]hostname[":" port]["?transport=" ("udp"|"tcp"|"tls")]`. The default scheme is STUN, the default port is 3478 (5349 over TLS), and the default transport is UDP.  For instance, a STUN server URI could be `mystunserver.org`, and a TURN server URI could be `turn:myuser:[email protected]`. Note transports TCP and TLS are only available for a TURN server with libnice as ICE backend and govern only the TURN control connection, meaning relaying is always performed over UDP.
+
+#### rtcDeletePeerConnection
+
+```
+int rtcDeletePeerConnection(int pc)
+```
+
+Deletes the specified Peer Connection.
+
+Arguments:
+
+- `pc`: the Peer Connection identifier
+
+Return value: `RTC_ERR_SUCCESS` or a negative error code
+
+After this function has been called, `pc` must not be used in a function call anymore. This function will block until all scheduled callbacks of `pc` return (except the one this function might be called in) and no other callback will be called for `pc` after it returns.
+
+#### rtcSetXCallback
+
+These functions set, change, or unset (if `cb` is `NULL`) the different callbacks of a Peer Connection.
+
+```
+int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc cb)*
+```
+
+`cb` must have the following signature: `void myDescriptionCallback(int pc, const char *sdp, const char *type, void *user_ptr)`
+
+```
+int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb)
+```
+
+`cb` must have the following signature: `void myCandidateCallback(int pc, const char *cand, const char *mid, void *user_ptr)`
+
+```
+int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb)
+```
+
+`cb` must have the following signature: `void myStateChangeCallback(int pc, rtcState state, void *user_ptr)`
+
+`state` will be one of the following: `RTC_CONNECTING`, `RTC_CONNECTED`, `RTC_DISCONNECTED`, `RTC_FAILED`, or `RTC_CLOSED`.
+
+```
+int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb)
+```
+
+`void myGatheringStateCallback(int pc, rtcGatheringState state, void *user_ptr)`
+
+`state` will be `RTC_GATHERING_INPROGRESS` or `RTC_GATHERING_COMPLETE`.
+
+```
+int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb)
+```
+
+`cb` must have the following signature: `void myDataChannelCallback(int pc, int dc, void *user_ptr)`
+
+```
+int rtcSetTrackCallback(int pc, rtcTrackCallbackFunc cb)
+```
+
+`cb` must have the following signature: `void myTrackCallback(int pc, int tr, void *user_ptr)`
+
+#### rtcSetLocalDescription
+
+```
+int rtcSetLocalDescription(int pc, const char *type)
+```
+
+Initiates the handshake process. Following this call, the local description callback will be called with the local description, which must be sent to the remote peer by the user's method of choice. Note this call is implicit after `rtcSetRemoteDescription` and `rtcCreateDataChannel` if `disableAutoNegotiation` was not set on Peer Connection creation.
+
+Arguments:
+
+- `pc`: the Peer Connection identifier
+- `type` (optional): type of the description ("offer", "answer", "pranswer", or "rollback") or NULL for autodetection.
+
+#### rtcSetRemoteDescription
+
+```
+int rtcSetRemoteDescription(int pc, const char *sdp, const char *type)
+```
+
+Sets the remote description received from the remote peer by the user's method of choice. The remote description may have candidates or not.
+
+Arguments:
+
+- `pc`: the Peer Connection identifier
+- `type` (optional): type of the description ("offer", "answer", "pranswer", or "rollback") or NULL for autodetection.
+
+If the remote description is an offer and `disableAutoNegotiation` was not set in `rtcConfiguration`, the library will automatically answer by calling `rtcSetLocalDescription` internally. Otherwise, the user must call it to answer the remote description.
+
+#### rtcAddRemoteCandidate
+
+```
+int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid)
+```
+
+Adds a trickled remote candidate received from the remote peer by the user's method of choice.
+
+Arguments:
+
+- `pc`: the Peer Connection identifier
+- `cand`: a null-terminated SDP string representing the candidate (with or without the `"a="` prefix)
+- `mid` (optional): a null-terminated string representing the mid of the candidate in the remote SDP description or NULL for autodetection
+
+The Peer Connection must have a remote description set.
+
+Return value: `RTC_ERR_SUCCESS` or a negative error code
+
+#### rtcGetLocalDescription
+
+```
+int rtcGetLocalDescription(int pc, char *buffer, int size)
+```
+
+Retrieves the current local description in SDP format.
+
+Arguments:
+
+- `pc`: the Peer Connection identifier
+- `buffer`: a user-supplied buffer to store the description
+- `size`: the size of `buffer`
+
+Return value: the length of the string copied in buffer (including the terminating null character) or a negative error code
+
+If `buffer` is `NULL`, the description is not copied but the size is still returned.
+
+#### rtcGetRemoteDescription
+
+```
+int rtcGetRemoteDescription(int pc, char *buffer, int size)
+```
+
+Retrieves the current remote description in SDP format.
+
+Arguments:
+
+- `pc`: the Peer Connection identifier
+- `buffer`: a user-supplied buffer to store the description
+- `size`: the size of `buffer`
+
+Return value: the length of the string copied in buffer (including the terminating null character) or a negative error code
+
+If `buffer` is `NULL`, the description is not copied but the size is still returned.
+
+#### rtcGetLocalDescriptionType
+
+```
+int rtcGetLocalDescriptionType(int pc, char *buffer, int size)
+```
+
+Retrieves the current local description type as string.
+
+Arguments:
+
+- `pc`: the Peer Connection identifier
+- `buffer`: a user-supplied buffer to store the type
+- `size`: the size of `buffer`
+
+Return value: the length of the string copied in buffer (including the terminating null character) or a negative error code
+
+If `buffer` is `NULL`, the description is not copied but the size is still returned.
+
+#### rtcGetRemoteDescription
+
+```
+int rtcGetRemoteDescriptionType(int pc, char *buffer, int size)
+```
+
+Retrieves the current remote description type as string.
+
+Arguments:
+
+- `pc`: the Peer Connection identifier
+- `buffer`: a user-supplied buffer to store the type
+- `size`: the size of `buffer`
+
+Return value: the length of the string copied in buffer (including the terminating null character) or a negative error code
+
+If `buffer` is `NULL`, the description is not copied but the size is still returned.
+
+
+#### rtcGetLocalAddress
+
+```
+int rtcGetLocalAddress(int pc, char *buffer, int size)
+```
+
+Retrieves the current local address, i.e. the network address of the currently selected local candidate. The address will have the format `"IP_ADDRESS:PORT"`, where `IP_ADDRESS` may be either IPv4 or IPv6. The call might fail if the PeerConnection is not in state `RTC_CONNECTED`, and the address might change if the state is not `RTC_COMPLETED`.
+
+Arguments:
+
+- `pc`: the Peer Connection identifier
+- `buffer`: a user-supplied buffer to store the address
+- `size`: the size of `buffer`
+
+Return value: the length of the string copied in buffer (including the terminating null character) or a negative error code
+
+If `buffer` is `NULL`, the address is not copied but the size is still returned.
+
+#### rtcGetRemoteAddress
+
+```
+int rtcGetRemoteAddress(int pc, char *buffer, int size)
+```
+
+Retrieves the current remote address, i.e. the network address of the currently selected remote candidate. The address will have the format `"IP_ADDRESS:PORT"`, where `IP_ADDRESS` may be either IPv4 or IPv6. The call may fail if the state is not `RTC_CONNECTED`, and the address might change if the state is not `RTC_COMPLETED`.
+
+Arguments:
+
+- `pc`: the Peer Connection identifier
+- `buffer`: a user-supplied buffer to store the address
+- `size`: the size of `buffer`
+
+Return value: the length of the string copied in buffer (including the terminating null character) or a negative error code
+
+If `buffer` is `NULL`, the address is not copied but the size is still returned.
+
+#### rtcGetSelectedCandidatePair
+
+```
+int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote, int remoteSize)
+```
+
+Retrieve the currently selected candidate pair. The call may fail if the state is not `RTC_CONNECTED`, and the selected candidate pair might change if the state is not `RTC_COMPLETED`.
+
+Arguments:
+
+- `pc`: the Peer Connection identifier
+- `local`: a user-supplied buffer to store the local candidate
+- `localSize`: the size of `local`
+- `remote`: a user-supplied buffer to store the remote candidate
+- `remoteSize`: the size of `remote`
+
+Return value: the maximun length of strings copied in buffers (including the terminating null character) or a negative error code
+
+If `local`, `remote`, or both, are `NULL`, the corresponding candidate is not copied, but the maximum length is still returned.
+
+### Channel (Common API for Data Channel, Track, and WebSocket)
+
+The following common functions might be called with a generic channel identifier. It may be the identifier of either a Data Channel, a Track, or a WebSocket.
+
+#### rtcSetXCallback
+
+These functions set, change, or unset (if `cb` is `NULL`) the different callbacks of a channel.
+
+```
+int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb)
+```
+
+`cb` must have the following signature: `void myOpenCallback(int id, void *user_ptr)`
+
+```
+int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb)
+```
+
+`cb` must have the following signature: `void myClosedCallback(int id, void *user_ptr)`
+
+```
+int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb)
+```
+
+`cb` must have the following signature: `void myErrorCallback(int id, const char *error, void *user_ptr)`
+
+```
+int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb)
+```
+
+`cb` must have the following signature: `void myMessageCallback(int id, const char *message, int size, void *user_ptr)`
+
+```
+int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb)
+```
+
+`cb` must have the following signature: `void myBufferedAmountLowCallback(int id, void *user_ptr)`
+
+```
+int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb)
+```
+
+`cb` must have the following signature: `void myAvailableCallback(int id, void *user_ptr)`
+
+#### rtcSendMessage
+
+```
+int rtcSendMessage(int id, const char *data, int size)
+```
+
+Arguments:
+
+- `id`: the channel identifier
+- `data`: the message data
+- `size`: if size >= 0, `data` is interpreted as a binary message of length `size`, otherwise it is interpreted as a null-terminated UTF-8 string.
+
+Return value: `RTC_ERR_SUCCESS` or a negative error code
+
+Sends a message immediately if possible.
+
+Data Channel and WebSocket: If the message may not be sent immediately due to flow control or congestion control, it is buffered until it can actually be sent. You can retrieve the current buffered data size with `rtcGetBufferedAmount`.
+Tracks are an exception: There is no flow or congestion control, messages are never buffered and `rtcGetBufferedAmount` always returns 0.
+
+#### rtcGetBufferedAmount
+
+```
+int rtcGetBufferedAmount(int id)
+```
+
+Retrieves the current buffered amount, i.e. the total size of currently buffered messages waiting to be actually sent in the channel. This does not account for the data buffered at the transport level.
+
+Return value: the buffered amount or a negative error code
+
+#### rtcGetBufferedAmountLowThreshold
+
+```
+int rtcSetBufferedAmountLowThreshold(int id, int amount)
+```
+
+Changes the buffered amount threshold under which `BufferedAmountLowCallback` is called. The callback is called when the buffered amount was strictly superior and gets equal to or lower than the threshold when a message is sent. The initial threshold is 0, meaning the the callback is called each time the buffered amount goes back to zero after being non-zero.
+
+Arguments:
+
+- `id`: the channel identifier
+- `amount`: the new buffer level threshold
+
+Return value: the identifier of the new WebSocket or a negative error code
+
+#### rtcReceiveMessage
+
+```
+int rtcReceiveMessage(int id, char *buffer, int *size)
+```
+
+Receives a pending message if possible. The function may only be called if `MessageCallback` is not set.
+
+Arguments:
+
+- `id`: the channel identifier
+- `buffer`: a user-supplied buffer where to write the message data
+- `size`: a pointer to a user-supplied int which must be initialized to the size of `buffer`. On success, the function will write the size of the message to it before returning.
+
+Return value: `RTC_ERR_SUCCESS` or a negative error code (In particular, `RTC_ERR_NOT_AVAIL` is returned when there are no pending messages)
+
+If `buffer` is `NULL`, the message is not copied and kept pending but the size is still written to `size`.
+
+#### rtcGetAvailableAmount
+
+```
+int rtcGetAvailableAmount(int id)
+```
+
+Retrieves the available amount, i.e. the total size of messages pending reception with `rtcReceiveMessage`. The function may only be called if `MessageCallback` is not set.
+
+Arguments:
+
+- `id`: the channel identifier
+
+Return value: the available amount or a negative error code
+
+### Data Channel
+
+#### rtcCreateDataChannel
+
+```
+int rtcCreateDataChannel(int pc, const char *label)
+int rtcCreateDataChannelEx(int pc, const char *label, const rtcDataChannelInit *init)
+
+typedef struct {
+	bool unordered;
+	bool unreliable;
+	unsigned int maxPacketLifeTime;
+	unsigned int maxRetransmits;
+} rtcReliability;
+
+typedef struct {
+	rtcReliability reliability;
+	const char *protocol;
+	bool negotiated;
+	bool manualStream;
+	uint16_t stream;
+} rtcDataChannelInit;
+```
+
+Adds a Data Channel on a Peer Connection. The Peer Connection does not need to be connected, however, the Data Channel will be open only when the Peer Connection is connected.
+
+Arguments:
+
+- `pc`: identifier of the PeerConnection on which to add a Data Channel
+- `label`: a user-defined UTF-8 string representing the Data Channel name
+- `init`: a structure of initialization settings containing:
+  - `reliability`: a structure of reliability settings containing:
+    - `bool unordered`: if `true`, the Data Channel will not enforce message ordering, else it will be ordered
+    - `bool unreliable`: if `true`, the Data Channel will not enforce strict reliability, else it will be reliable
+    - `unsigned int maxPacketLifeTime`: if unreliable, maximum packet life time in milliseconds
+    - `unsigned int maxRetransmits`: if unreliable and maxPacketLifeTime is 0, maximum number of retransmissions (0 means no retransmission)
+  - `protocol` (optional): a user-defined UTF-8 string representing the Data Channel protocol, empty if NULL
+  - `negotiated`: if `true`, the Data Channel is assumed to be negotiated by the user and won't be negotiated by the WebRTC layer
+  - `manualStream`: if `true`, the Data Channel will use `stream` as stream ID, else an available id is automatically selected
+  - `stream` (0-65534): if `manualStream` is `true`, the Data Channel will use it as stream ID, else it is ignored
+
+`rtcDataChannel()` is equivalent to `rtcDataChannelEx()` with settings set to ordered, reliable, non-negotiated, with automatic stream ID selection (all flags set to `false`), and `protocol` set to an empty string.
+
+Return value: the identifier of the new Data Channel or a negative error code.
+
+The Data Channel must be deleted with `rtcDeleteDataChannel`.
+
+If `disableAutoNegotiation` was not set in `rtcConfiguration`, the library will automatically initiate the negotiation by calling `rtcSetLocalDescription` internally. Otherwise, the user must call `rtcSetLocalDescription` to initiate the negotiation after creating the first Data Channel.
+
+#### rtcDeleteDataChannel
+
+```
+int rtcDeleteDataChannel(int dc)
+```
+
+Deletes a Data Channel.
+
+Arguments:
+
+- `dc`: the Data Channel identifier
+
+After this function has been called, `dc` must not be used in a function call anymore. This function will block until all scheduled callbacks of `dc` return (except the one this function might be called in) and no other callback will be called for `dc` after it returns.
+
+#### rtcGetDataChannelStream
+
+```
+int rtcGetDataChannelStream(int dc)
+```
+
+Retrieves the stream ID of the Data Channel.
+
+Arguments:
+
+- `dc`: the Data Channel identifier
+
+Return value: the stream ID (0-65534) or a negative error code
+
+#### rtcGetDataChannelLabel
+
+```
+int rtcGetDataChannelLabel(int dc, char *buffer, int size)
+```
+
+Retrieves the label of a Data Channel.
+
+Arguments:
+
+- `dc`: the Data Channel identifier
+- `buffer`: a user-supplied buffer to store the label
+- `size`: the size of `buffer`
+
+Return value: the length of the string copied in buffer (including the terminating null character) or a negative error code
+
+If `buffer` is `NULL`, the label is not copied but the size is still returned.
+
+#### rtcGetDataChannelProtocol
+
+```
+int rtcGetDataChannelProtocol(int dc, char *buffer, int size)
+```
+
+Retrieves the protocol of a Data Channel.
+
+Arguments:
+
+- `dc`: the Data Channel identifier
+- `buffer`: a user-supplied buffer to store the protocol
+- `size`: the size of `buffer`
+
+Return value: the length of the string copied in buffer (including the terminating null character) or a negative error code
+
+If `buffer` is `NULL`, the protocol is not copied but the size is still returned.
+
+#### rtcGetDataChannelReliability
+
+```
+int rtcGetDataChannelReliability(int dc, rtcReliability *reliability)
+```
+
+Retrieves the reliability settings of a Data Channel. The function may be called irrelevant of how the Data Channel was created.
+
+Arguments:
+
+- `dc`: the Data Channel identifier
+- `reliability` a user-supplied structure to fill
+
+Return value: `RTC_ERR_SUCCESS` or a negative error code
+
+### Track
+
+#### rtcAddTrack
+
+```
+int rtcAddTrack(int pc, const char *mediaDescriptionSdp)
+```
+
+Adds a new Track on a Peer Connection. The Peer Connection does not need to be connected, however, the Track will be open only when the Peer Connection is connected.
+
+Arguments:
+
+- `pc`: the Peer Connection identifier
+- `mediaDescriptionSdp`: a null-terminated string specifying the corresponding media SDP. It must start with a m-line and include a mid parameter.
+
+Return value: the identifier of the new Track or a negative error code
+
+The new track must be deleted with `rtcDeleteTrack`.
+
+The user must call `rtcSetLocalDescription` to negotiate the track.
+
+#### rtcDeleteTrack
+
+```
+int rtcDeleteTrack(int tr)
+```
+
+Deletes a Track.
+
+Arguments:
+
+- `tr`: the Track identifier
+
+After this function has been called, `tr` must not be used in a function call anymore. This function will block until all scheduled callbacks of `tr` return (except the one this function might be called in) and no other callback will be called for `tr` after it returns.
+
+#### rtcGetTrackDescription
+
+```
+int rtcGetTrackDescription(int tr, char *buffer, int size)
+```
+
+Retrieves the SDP media description of a Track.
+
+Arguments:
+
+- `dc`: the Track identifier
+- `buffer`: a user-supplied buffer to store the description
+- `size`: the size of `buffer`
+
+Return value: the length of the string copied in buffer (including the terminating null character) or a negative error code
+
+If `buffer` is `NULL`, the description is not copied but the size is still returned.
+
+### Media
+
+TODO
+
+### WebSocket
+
+#### rtcCreateWebSocket
+
+```
+int rtcCreateWebSocket(const char *url)
+int rtcCreateWebSocketEx(const char *url, const rtcWsConfiguration *config)
+
+typedef struct {
+	bool disableTlsVerification;    // if true, disable TLS certificate verification
+} rtcWsConfiguration;
+```
+
+Creates a new client WebSocket.
+
+Arguments:
+
+- `url`: a null-terminated string representing the fully-qualified URL to open.
+- `config`: a structure with the following parameters:
+  - `bool disableTlsVerification`: if true, don't verify the TLS certificate, else try to verify it if possible
+
+Return value: the identifier of the new WebSocket or a negative error code
+
+The new WebSocket must be deleted with `rtcDeleteWebSocket`. The scheme of the URL must be either `ws` or `wss`.
+
+#### rtcDeleteWebSocket
+
+```
+int rtcDeleteWebSocket(int ws)
+```
+
+Arguments:
+
+- `ws`: the identifier of the WebSocket to delete
+
+After this function has been called, `ws` must not be used in a function call anymore. This function will block until all scheduled callbacks of `ws` return (except the one this function might be called in) and no other callback will be called for `ws` after it returns.
+
+#### rtcGetWebSocketRemoteAddress
+
+```
+int rtcGetWebSocketRemoteAddress(int ws, char *buffer, int size)
+```
+
+Retrieves the remote address, i.e. the network address of the remote endpoint. The address will have the format `"HOST:PORT"`. The call may fail if the underlying TCP transport of the WebSocket is not connected. This function is useful for a client WebSocket received by a WebSocket Server.
+
+Arguments:
+
+- `ws`: the identifier of the WebSocket
+- `buffer`: a user-supplied buffer to store the description
+- `size`: the size of `buffer`
+
+Return value: the length of the string copied in buffer (including the terminating null character) or a negative error code
+
+If `buffer` is `NULL`, the address is not copied but the size is still returned.
+
+#### rtcGetWebSocketPath
+
+```
+int rtcGetWebSocketPath(int ws, char *buffer, int size)
+```
+
+Retrieves the path of the WebSocket, i.e. the HTTP requested path. This function is useful for a client WebSocket received by a WebSocket Server. Warning: The WebSocket must be open for the call to succeed.
+
+Arguments:
+
+- `ws`: the identifier of the WebSocket
+- `buffer`: a user-supplied buffer to store the description
+- `size`: the size of `buffer`
+
+Return value: the length of the string copied in buffer (including the terminating null character) or a negative error code
+
+If `buffer` is `NULL`, the path is not copied but the size is still returned.
+
+### WebSocket Server
+
+#### rtcCreateWebSocketServer
+
+```
+int rtcCreateWebSocketServer(const rtcWsServerConfiguration *config, rtcWebSocketClientCallbackFunc cb);
+
+typedef struct {
+	uint16_t port;
+	bool enableTls;
+	const char *certificatePemFile; // NULL for autogenerated certificate
+	const char *keyPemFile;         // NULL for autogenerated certificate
+	const char *keyPemPass;         // NULL if no pass
+} rtcWsServerConfiguration;
+
+```
+
+Creates a new WebSocket server.
+
+Arguments:
+
+- `config`: a structure with the following parameters:
+  - `uint16_t port`: the port to listen on (if 0, automatically select an available port)
+  - `bool enableTls`: if true, enable the TLS layer (WSS)
+  - `const char *certificatePemFile`: path of the file containing the TLS PEM certificate (`NULL` for an autogenerated certificate)
+  - `const char *keyPemFile`: path of the file containing the TLS PEM key (`NULL` for an autogenerated certificate)
+  - `const char *keyPemPass`: the TLS PEM key passphrase (NULL if no passphrase)
+- `cb`: the callback for incoming client WebSocket connections (must not be `NULL`)
+
+`cb` must have the following signature: `void rtcWebSocketClientCallbackFunc(int wsserver, int ws, void *user_ptr)`
+
+Return value: the identifier of the new WebSocket Server or a negative error code
+
+The new WebSocket Server must be deleted with `rtcDeleteWebSocketServer`.
+
+#### rtcDeleteWebSocketServer
+
+```
+int rtcDeleteWebSocketServer(int wsserver)
+```
+
+Arguments:
+
+- `wsserver`: the identifier of the WebSocket Server to delete
+
+After this function has been called, `wsserver` must not be used in a function call anymore. This function will block until all scheduled callbacks of `wsserver` return (except the one this function might be called in) and no other callback will be called for `wsserver` after it returns.
+
+#### rtcGetWebSocketServerPort
+```
+int rtcGetWebSocketServerPort(int wsserver);
+```
+
+Retrieves the port which the WebSocket Server is listening on.
+
+Arguments:
+
+- `wsserver`: the identifier of the WebSocket Server
+
+Return value: The port of the WebSocket Server or a negative error code
+
+

+ 41 - 0
pages/pelicanconf.py

@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*- #
+
+AUTHOR = 'Paul-Louis Ageneau'
+COPYRIGHT_YEAR = 2021
+SITENAME = 'libdatachannel'
+SITEURL = ''
+
+PATH = 'content'
+STATIC_PATHS = ['images', 'extra/CNAME']
+EXTRA_PATH_METADATA = {'extra/CNAME': {'path': 'CNAME'}}
+
+THEME = 'theme'
+
+TIMEZONE = 'Europe/Paris'
+DEFAULT_LANG = 'en'
+
+GITHUB_URL = "https://github.com/paullouisageneau/libdatachannel"
+
+# Feed generation is usually not desired when developing
+FEED_ALL_ATOM = None
+CATEGORY_FEED_ATOM = None
+TRANSLATION_FEED_ATOM = None
+AUTHOR_FEED_ATOM = None
+AUTHOR_FEED_RSS = None
+
+# Blogroll
+#
+#LINKS = (('Pelican', 'https://getpelican.com/'),
+#         ('Python.org', 'https://www.python.org/'),
+#         ('Jinja2', 'https://palletsprojects.com/p/jinja/'),
+#         ('You can modify those links in your config file', '#'),)
+
+# Social widget
+#SOCIAL = (('You can add links in your config file', '#'),
+#          ('Another social link', '#'),)
+
+DEFAULT_PAGINATION = False
+
+# Uncomment following line if you want document-relative URLs when developing
+#RELATIVE_URLS = True

+ 20 - 0
pages/publishconf.py

@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*- #
+
+# This file is only used if you use `make publish` or
+# explicitly specify it as your config file.
+
+import os
+import sys
+sys.path.append(os.curdir)
+from pelicanconf import *
+
+# If your site is available via HTTPS, make sure SITEURL begins with https://
+SITEURL = 'https://libdatachannel.org'
+RELATIVE_URLS = False
+
+FEED_ALL_ATOM = 'feeds/all.atom.xml'
+CATEGORY_FEED_ATOM = 'feeds/{slug}.atom.xml'
+
+DELETE_OUTPUT_DIRECTORY = True
+

+ 136 - 0
pages/tasks.py

@@ -0,0 +1,136 @@
+# -*- coding: utf-8 -*-
+
+import os
+import shlex
+import shutil
+import sys
+import datetime
+
+from invoke import task
+from invoke.main import program
+from invoke.util import cd
+from pelican import main as pelican_main
+from pelican.server import ComplexHTTPRequestHandler, RootedHTTPServer
+from pelican.settings import DEFAULT_CONFIG, get_settings_from_file
+
+SETTINGS_FILE_BASE = 'pelicanconf.py'
+SETTINGS = {}
+SETTINGS.update(DEFAULT_CONFIG)
+LOCAL_SETTINGS = get_settings_from_file(SETTINGS_FILE_BASE)
+SETTINGS.update(LOCAL_SETTINGS)
+
+CONFIG = {
+    'settings_base': SETTINGS_FILE_BASE,
+    'settings_publish': 'publishconf.py',
+    # Output path. Can be absolute or relative to tasks.py. Default: 'output'
+    'deploy_path': SETTINGS['OUTPUT_PATH'],
+    # Github Pages configuration
+    'github_pages_branch': 'gh-pages',
+    'commit_message': "'Publish site on {}'".format(datetime.date.today().isoformat()),
+    # Host and port for `serve`
+    'host': 'localhost',
+    'port': 8000,
+}
+
+@task
+def clean(c):
+    """Remove generated files"""
+    if os.path.isdir(CONFIG['deploy_path']):
+        shutil.rmtree(CONFIG['deploy_path'])
+        os.makedirs(CONFIG['deploy_path'])
+
+@task
+def build(c):
+    """Build local version of site"""
+    pelican_run('-s {settings_base}'.format(**CONFIG))
+
+@task
+def rebuild(c):
+    """`build` with the delete switch"""
+    pelican_run('-d -s {settings_base}'.format(**CONFIG))
+
+@task
+def regenerate(c):
+    """Automatically regenerate site upon file modification"""
+    pelican_run('-r -s {settings_base}'.format(**CONFIG))
+
+@task
+def serve(c):
+    """Serve site at http://$HOST:$PORT/ (default is localhost:8000)"""
+
+    class AddressReuseTCPServer(RootedHTTPServer):
+        allow_reuse_address = True
+
+    server = AddressReuseTCPServer(
+        CONFIG['deploy_path'],
+        (CONFIG['host'], CONFIG['port']),
+        ComplexHTTPRequestHandler)
+
+    sys.stderr.write('Serving at {host}:{port} ...\n'.format(**CONFIG))
+    server.serve_forever()
+
+@task
+def reserve(c):
+    """`build`, then `serve`"""
+    build(c)
+    serve(c)
+
+@task
+def preview(c):
+    """Build production version of site"""
+    pelican_run('-s {settings_publish}'.format(**CONFIG))
+
+@task
+def livereload(c):
+    """Automatically reload browser tab upon file modification."""
+    from livereload import Server
+
+    def cached_build():
+        cmd = '-s {settings_base} -e CACHE_CONTENT=True LOAD_CONTENT_CACHE=True'
+        pelican_run(cmd.format(**CONFIG))
+
+    cached_build()
+    server = Server()
+    theme_path = SETTINGS['THEME']
+    watched_globs = [
+        CONFIG['settings_base'],
+        '{}/templates/**/*.html'.format(theme_path),
+    ]
+
+    content_file_extensions = ['.md', '.rst']
+    for extension in content_file_extensions:
+        content_glob = '{0}/**/*{1}'.format(SETTINGS['PATH'], extension)
+        watched_globs.append(content_glob)
+
+    static_file_extensions = ['.css', '.js']
+    for extension in static_file_extensions:
+        static_file_glob = '{0}/static/**/*{1}'.format(theme_path, extension)
+        watched_globs.append(static_file_glob)
+
+    for glob in watched_globs:
+        server.watch(glob, cached_build)
+    server.serve(host=CONFIG['host'], port=CONFIG['port'], root=CONFIG['deploy_path'])
+
+
+@task
+def publish(c):
+    """Publish to production via rsync"""
+    pelican_run('-s {settings_publish}'.format(**CONFIG))
+    c.run(
+        'rsync --delete --exclude ".DS_Store" -pthrvz -c '
+        '-e "ssh -p {ssh_port}" '
+        '{} {ssh_user}@{ssh_host}:{ssh_path}'.format(
+            CONFIG['deploy_path'].rstrip('/') + '/',
+            **CONFIG))
+
+@task
+def gh_pages(c):
+    """Publish to GitHub Pages"""
+    preview(c)
+    c.run('ghp-import -b {github_pages_branch} '
+          '-m {commit_message} '
+          '{deploy_path} -p'.format(**CONFIG))
+
+def pelican_run(cmd):
+    cmd += ' ' + program.core.remainder  # allows to pass-through args to pelican
+    pelican_main(shlex.split(cmd))

+ 259 - 0
pages/theme/static/css/main.css

@@ -0,0 +1,259 @@
+@charset "UTF-8";
+
+@viewport {
+  width: device-width;
+}
+
+/* html5doctor.com/html-5-reset-stylesheet/ */
+/* http://meyerweb.com/eric/tools/css/reset/ */
+html, body, div, span, object, iframe, h1, h2,
+h3, h4, h5, h6, p, blockquote, pre, abbr, address,
+cite, code, del, dfn, em, img, ins, kbd, q, samp, small,
+strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, fieldset,
+form, label, legend, table, caption, tbody, tfoot, thead, tr,
+th, td, time, mark, audio, video {
+  margin: 0;
+  padding: 0;
+  border: 0;
+  outline: 0;
+  font-size: 100%;
+  vertical-align: baseline;
+  background: transparent;
+}
+
+* {
+  box-sizing: border-box;
+}
+
+body {
+  font-family: Helvetica, sans-serif;
+  font-size: 11pt;
+  line-height: 1.5em;
+  font-smooth: antialiased;
+  text-rendering: optimizeLegibility;
+  color: #000000;
+}
+div, section, article, header, footer, nav, aside, hgroup {
+  display: block;
+}
+ol, ul {
+  list-style: none;
+}
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+footer {
+  margin-bottom: 2em;
+  margin-top: 1em;
+  color: #888888;
+}
+a {
+  text-decoration: underline;
+}
+
+#menu {
+	margin: 1.5em;
+	font-size: 1.2em;
+	font-weight: bold;
+}
+#menu ul {
+	display: block;
+	text-align: center;
+}
+#menu li {
+	display: inline-block;
+	margin-left: 0.5em;
+	margin-right: 0.5em;
+}
+.page {
+  max-width: 64em;
+  margin: auto;
+  text-align: center;
+  line-height: 1.5;
+}
+.articles {
+  text-align: left;
+  padding: 1.5em;
+}
+.articles ol li {
+  padding-bottom: 4em;
+  padding-top: 1em;
+}
+.article article {
+  display: inline-block;
+  width: 100%;
+  padding: 1em;
+  padding-top: 1em;
+  text-align: left;
+  line-height: 1.5;
+}
+.articles article footer {
+  padding-bottom: 0.25em;
+  margin-bottom: 0;
+  margin-top: 0;
+}
+.article article footer p {
+  text-align: center;
+}
+.articles article header {
+  padding-bottom: 1em;
+}
+.articles article header a {
+  text-decoration: none;
+}
+.articles article .summary {
+  padding-bottom: 1em;
+  font-size: 1.125em;
+}
+.articles article .readmore {
+  color: #39739d;
+  float: right;
+  text-decoration: underline;
+}
+.articles article .tags {
+  list-style: none;
+  margin: 0;
+  overflow: hidden;
+  padding: 0;
+}
+.articles article .tags li {
+  float: left;
+  padding: 0;
+}
+.article img {
+  max-width: 100%;
+  display: block;
+  margin-left: auto;
+  margin-right: auto;
+}
+.article article header h1 {
+  font-size: 2em;
+  padding-bottom: 1em;
+  font-weight: bold;
+  text-align: center;
+}
+.article article .content p {
+  padding-bottom: 0.5em;
+}
+.article article .content h1 {
+  font-size: 2em;
+  padding-bottom: 1em;
+  padding-top: 2em;
+  font-weight: bold;
+}
+.article article .content h2 {
+  font-size: 1.4em;
+  padding-bottom: 0.75em;
+  padding-top: 1.5em;
+  font-weight: bold;
+}
+.article article .content h3 {
+  font-size: 1.2em;
+  padding-bottom: 0.75em;
+  padding-top: 1.5em;
+  font-weight: bold;
+}
+.article article .content h4 {
+  font-size: 1em;
+  padding-bottom: 1.25em;
+  padding-top: 1em;
+  font-weight: bold;
+}
+.article article .content ul, .article article .content ol {
+  margin: 0 auto;
+  line-height: 1.5;
+  padding-left: 2em;
+  padding-bottom: 1em;
+}
+.article article .content ul {
+  list-style-type: square;
+}
+.article article .content ol {
+  list-style-type: decimal;
+}
+.article article .content em {
+  font-style: italic;
+  font-size: 0.8em;
+}
+.article article .content .highlight {
+  margin-bottom: 1.5em;
+}
+.article article .content pre {
+  padding-bottom: 1.75em;
+  padding-top: 1em;
+  display: block;
+  background-color: #f5f5f5;
+  border: 1px solid #000000;
+  border-radius: 0.25em;
+  padding: 0.5em;
+  white-space: pre-wrap;
+  word-wrap: break-word;
+}
+.article article .content span.nv {
+  color: #c65d09;
+  font-weight: bold;
+}
+.article article .content .embedded-tweet {
+  margin-top: 1em;
+  margin-bottom: 2em;
+}
+.back-to-top {
+  text-align: center;
+  margin-top: 4em;
+}
+.tag {
+  background: #E1ECF4;
+  border-radius: 3px;
+  color: #39739d;
+  display: inline-block;
+  height: 2em;
+  line-height: 2em;
+  padding: 0 1em;
+  position: relative;
+  margin: 0 1em 1em 0;
+  text-decoration: none;
+}
+.tag:hover {
+  background-color: crimson;
+  color: white;
+}
+.tag:hover::after {
+  border-left-color: crimson;
+}
+.category-tag {
+  font-style: italic;
+  color: #39739d;
+  text-decoration: underline;
+}
+.paginator {
+  font-size: 3em;
+  text-align: center;
+}
+.paginator span {
+  font-size: 0.5em;
+}
+.paginator a.previous {
+  float: lefdantont;
+}
+.paginator a.next {
+  float: right;
+}
+img.emoji {
+  margin: 0;
+  vertical-align: -0.3em;
+  display: inline;
+  padding-right: 0.5em;
+}
+
+#home section {
+	clear: both;
+}
+#home section img {
+  float: left;
+  margin: 1em;
+  margin-right: 2em;
+  width: 10em;
+  max-width: 30%;
+}
+

+ 62 - 0
pages/theme/static/css/pygments-highlight.css

@@ -0,0 +1,62 @@
+.highlight .hll { background-color: #f4f4cc }
+.highlight  { background: #f4f4f4; }
+.highlight .c { color: #808080 } /* Comment */
+.highlight .err { color: #F00000; background-color: #F0A0A0 } /* Error */
+.highlight .k { color: #008000; font-weight: bold } /* Keyword */
+.highlight .o { color: #303030 } /* Operator */
+.highlight .cm { color: #808080 } /* Comment.Multiline */
+.highlight .cp { color: #507090 } /* Comment.Preproc */
+.highlight .c1 { color: #808080 } /* Comment.Single */
+.highlight .cs { color: #cc0000; font-weight: bold } /* Comment.Special */
+.highlight .gd { color: #A00000 } /* Generic.Deleted */
+.highlight .ge { font-style: italic } /* Generic.Emph */
+.highlight .gr { color: #FF0000 } /* Generic.Error */
+.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
+.highlight .gi { color: #00A000 } /* Generic.Inserted */
+.highlight .go { color: #808080 } /* Generic.Output */
+.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
+.highlight .gs { font-weight: bold } /* Generic.Strong */
+.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
+.highlight .gt { color: #0040D0 } /* Generic.Traceback */
+.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
+.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
+.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
+.highlight .kp { color: #003080; font-weight: bold } /* Keyword.Pseudo */
+.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
+.highlight .kt { color: #303090; font-weight: bold } /* Keyword.Type */
+.highlight .m { color: #6000E0; font-weight: bold } /* Literal.Number */
+.highlight .s { background-color: #fff0f0 } /* Literal.String */
+.highlight .na { color: #0000C0 } /* Name.Attribute */
+.highlight .nb { color: #007020 } /* Name.Builtin */
+.highlight .nc { color: #B00060; font-weight: bold } /* Name.Class */
+.highlight .no { color: #003060; font-weight: bold } /* Name.Constant */
+.highlight .nd { color: #505050; font-weight: bold } /* Name.Decorator */
+.highlight .ni { color: #800000; font-weight: bold } /* Name.Entity */
+.highlight .ne { color: #F00000; font-weight: bold } /* Name.Exception */
+.highlight .nf { color: #0060B0; font-weight: bold } /* Name.Function */
+.highlight .nl { color: #907000; font-weight: bold } /* Name.Label */
+.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
+.highlight .nt { color: #007000 } /* Name.Tag */
+.highlight .nv { color: #906030 } /* Name.Variable */
+.highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */
+.highlight .w { color: #bbbbbb } /* Text.Whitespace */
+.highlight .mf { color: #6000E0; font-weight: bold } /* Literal.Number.Float */
+.highlight .mh { color: #005080; font-weight: bold } /* Literal.Number.Hex */
+.highlight .mi { color: #0000D0; font-weight: bold } /* Literal.Number.Integer */
+.highlight .mo { color: #4000E0; font-weight: bold } /* Literal.Number.Oct */
+.highlight .sb { background-color: #fff0f0 } /* Literal.String.Backtick */
+.highlight .sc { color: #0040D0 } /* Literal.String.Char */
+.highlight .sd { color: #D04020 } /* Literal.String.Doc */
+.highlight .s2 { background-color: #fff0f0 } /* Literal.String.Double */
+.highlight .se { color: #606060; font-weight: bold; background-color: #fff0f0 } /* Literal.String.Escape */
+.highlight .sh { background-color: #fff0f0 } /* Literal.String.Heredoc */
+.highlight .si { background-color: #e0e0e0 } /* Literal.String.Interpol */
+.highlight .sx { color: #D02000; background-color: #fff0f0 } /* Literal.String.Other */
+.highlight .sr { color: #000000; background-color: #fff0ff } /* Literal.String.Regex */
+.highlight .s1 { background-color: #fff0f0 } /* Literal.String.Single */
+.highlight .ss { color: #A06000 } /* Literal.String.Symbol */
+.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */
+.highlight .vc { color: #306090 } /* Name.Variable.Class */
+.highlight .vg { color: #d07000; font-weight: bold } /* Name.Variable.Global */
+.highlight .vi { color: #3030B0 } /* Name.Variable.Instance */
+.highlight .il { color: #0000D0; font-weight: bold } /* Literal.Number.Integer.Long */

+ 11 - 0
pages/theme/templates/analytics.html

@@ -0,0 +1,11 @@
+{% if GOOGLE_ANALYTICS %}
+<!-- Global site tag (gtag.js) - Google Analytics -->
+<script async src="https://www.googletagmanager.com/gtag/js?id={{GOOGLE_ANALYTICS}}"></script>
+<script>
+  window.dataLayer = window.dataLayer || [];
+  function gtag(){dataLayer.push(arguments);}
+  gtag('js', new Date());
+
+  gtag('config', '{{GOOGLE_ANALYTICS}}');
+</script>
+{% endif %}

+ 41 - 0
pages/theme/templates/archives.html

@@ -0,0 +1,41 @@
+{% extends "base.html" %}
+{% block content %}
+<div class="page-title">
+        <h1>archives</h1>
+    </div>
+<div class="articles">
+    <ol>
+        {% for article in dates %}
+        <li>
+            <article>
+                <footer>
+                        <p>
+                        {% if article.category %}
+                            {% if article.category != "misc" %}
+                                in <a href="{{ SITEURL }}/{{ article.category.url }}"><span class="category-tag">{{ article.category }}</span></a>
+                            {% endif %}
+                        {% endif %}
+                    </p>
+                </footer>
+                <header>
+                    <a href="{{ SITEURL }}/{{ article.url }}">
+                        <h2>{{ article.title }}</h2>
+                    </a>
+                </header>
+                <div class="summary">{{ article.summary }} </div>
+                <div class="tags">
+                    <ul class="tags">
+                    {% for tag in article.tags %}
+                        <li><a href="{{ SITEURL }}/{{ tag.url }}" class="tag">{{ tag.name }}</a></li>
+                    {% endfor %}
+                    </ul>
+                </div>
+                <a href="{{ SITEURL }}/{{ article.url }}">
+                    <p class="readmore">Read more...</p>
+                </a>
+            </article>
+        </li>
+        {% endfor %}
+    </ol>
+</div>
+{% endblock content %}

+ 76 - 0
pages/theme/templates/article.html

@@ -0,0 +1,76 @@
+{% extends "base.html" %}
+{% block head %}
+  {{ super() }}
+  {% if article.description %}
+    <meta name="description" content="{{ article.description }}" />
+  {% endif %}
+
+  {% for tag in article.tags %}
+    <meta name="tags" content="{{tag}}" />
+  {% endfor %}
+
+{% endblock %}
+{% block twittercard %}
+{% if article.cover_image %}
+<meta name="twitter:card" content="summary_large_image">
+<meta name="twitter:image" content="{{ SITEURL }}/images/{{ article.cover_image }}">
+<meta name="twitter:creator" content="{{ TWITTER_USERNAME }}">
+{% elif GRAVATAR %}
+<meta name="twitter:card" content="summary">
+<meta name="twitter:image" content="{{ GRAVATAR }}">
+{% elif LOGO %}
+<meta name="twitter:card" content="summary">
+<meta name="twitter:image" content="{{ SITEURL }}/images/{{ LOGO }}">
+{% endif %}
+<meta name="twitter:site" content="{{ TWITTER_USERNAME }}">
+<meta name="twitter:title" content="{{ article.title }}">
+<meta name="twitter:description" content="{{ article.description }}">
+{% endblock twittercard %}
+<!-- OG Tags -->
+{% block ogtags %}
+<meta property="og:url" content="{{ SITEURL }}/{{ article.url }}"/>
+<meta property="og:title" content="{{ SITENAME }} | {{ article.title }}" />
+<meta property="og:description" content="{{ article.description }}" />
+{% if article.cover_image %}
+<meta property="og:image" content="{{ SITEURL }}/images/{{ article.cover_image }}" />
+{% endif %}
+{% endblock ogtags %}
+
+{% block content %}
+  <div class="article" role="article">
+    <article>
+        <header>
+          <h1>
+            {{ article.title }}
+          </h1>
+        </header>
+      <div class="content">
+         {{ article.content|safe }}
+      </div>
+      <div class="back-to-top">
+          <a href="#top">back to top</a>
+      </div>
+      {% if DISQUS_SITENAME %}
+      <div id="disqus_thread"></div>
+        {% if SITEURL %}
+        <script>
+          var disqus_config = function () {
+          this.page.url = "{{ SITEURL }}/{{ article.url }}";  // Replace PAGE_URL with your page's canonical URL variable
+          this.page.identifier = "{{ article.slug }}"; // Replace PAGE_IDENTIFIER with your page's unique identifier variable
+          };
+        </script>
+        {% endif %}
+        <script>
+        (function() {
+              var d = document, s = d.createElement('script');
+              s.src = 'https://{{ DISQUS_SITENAME }}.disqus.com/embed.js';
+              s.setAttribute('data-timestamp', +new Date());
+              (d.head || d.body).appendChild(s);
+        })();
+      </script>
+      <noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
+      {% endif %}
+    </article>
+  </div>
+<!-- end article -->
+{% endblock content %}

+ 39 - 0
pages/theme/templates/article_list.html

@@ -0,0 +1,39 @@
+<div class="articles">
+        <ol>
+            {% for article in articles_page.object_list %}
+            <li>
+                <article>
+                    <footer>
+                         <p>
+                            {% if article.category %}
+                                {% if article.category != "misc" %}
+                                    in <a href="{{ SITEURL }}/{{ article.category.url }}"><span class="category-tag">{{ article.category }}</span></a>
+                                {% endif %}
+                            {% endif %}
+                        </p>
+                    </footer>
+                    <header>
+                        <a href="{{ SITEURL }}/{{ article.url }}">
+                            <h2>{{ article.title }}</h2>
+                        </a>
+                    </header>
+                    <div class="summary">{{ article.description }} </div>
+                    <div class="tags">
+                        <ul class="tags">
+                        {% for tag in article.tags %}
+                            <li><a href="{{ SITEURL }}/{{ tag.url }}" class="tag">{{ tag.name }}</a></li>
+                        {% endfor %}
+                        </ul>
+                    </div>
+                    <a href="{{ SITEURL }}/{{ article.url }}">
+                        <p class="readmore">Read more...</p>
+                    </a>
+                </article>
+            </li>
+            {% endfor %}
+        </ol>
+        {% if articles_page.has_other_pages() %}
+            {% include 'pagination.html' %}
+        {% endif %}
+
+</div>

+ 0 - 0
pages/theme/templates/author.html


+ 0 - 0
pages/theme/templates/authors.html


+ 94 - 0
pages/theme/templates/base.html

@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<html lang="{{ DEFAULT_LANG }}">
+<head>
+        {% block head %}
+        {% include 'analytics.html' %}
+        <title>{% block title %}{{ SITENAME }}{% endblock title %}</title>
+        <meta name="viewport" content="width=device-width, initial-scale=1">
+        <meta charset="utf-8">
+        {% if FEED_ALL_ATOM %}
+        <link href="{{ FEED_DOMAIN }}/{% if FEED_ALL_ATOM_URL %}{{ FEED_ALL_ATOM_URL }}{% else %}{{ FEED_ALL_ATOM }}{% endif %}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Full Atom Feed">
+        {% endif %}
+        {% if FEED_ALL_RSS %}
+        <link href="{{ FEED_DOMAIN }}/{% if FEED_ALL_RSS_URL %}{{ FEED_ALL_RSS_URL }}{% else %}{{ FEED_ALL_RSS }}{% endif %}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} Full RSS Feed">
+        {% endif %}
+        {% if FEED_ATOM %}
+        <link href="{{ FEED_DOMAIN }}/{%if FEED_ATOM_URL %}{{ FEED_ATOM_URL }}{% else %}{{ FEED_ATOM }}{% endif %}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Atom Feed">
+        {% endif %}
+        {% if FEED_RSS %}
+        <link href="{{ FEED_DOMAIN }}/{% if FEED_RSS_URL %}{{ FEED_RSS_URL }}{% else %}{{ FEED_RSS }}{% endif %}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} RSS Feed">
+        {% endif %}
+        {% if CATEGORY_FEED_ATOM and category %}
+        <link href="{{ FEED_DOMAIN }}/{% if CATEGORY_FEED_ATOM_URL %}{{ CATEGORY_FEED_ATOM_URL|format(slug=category.slug) }}{% else %}{{ CATEGORY_FEED_ATOM|format(slug=category.slug) }}{% endif %}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Categories Atom Feed">
+        {% endif %}
+        {% if CATEGORY_FEED_RSS and category %}
+        <link href="{{ FEED_DOMAIN }}/{% if CATEGORY_FEED_RSS_URL %}{{ CATEGORY_FEED_RSS_URL|format(slug=category.slug) }}{% else %}{{ CATEGORY_FEED_RSS|format(slug=category.slug) }}{% endif %}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} Categories RSS Feed">
+        {% endif %}
+        {% if TAG_FEED_ATOM and tag %}
+        <link href="{{ FEED_DOMAIN }}/{% if TAG_FEED_ATOM_URL %}{{ TAG_FEED_ATOM_URL|format(slug=tag.slug) }}{% else %}{{ TAG_FEED_ATOM|format(slug=tag.slug) }}{% endif %}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Tags Atom Feed">
+        {% endif %}
+        {% if TAG_FEED_RSS and tag %}
+        <link href="{{ FEED_DOMAIN }}/{% if TAG_FEED_RSS_URL %}{{ TAG_FEED_RSS_URL|format(slug=tag.slug) }}{% else %}{{ TAG_FEED_RSS|format(slug=tag.slug) }}{% endif %}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} Tags RSS Feed">
+        {% endif %}
+        <!-- twitter card metadata -->
+        {% block twittercard %}
+        {% endblock twittercard %}
+        <!-- OG Tags -->
+        {% block ogtags %}
+        {% endblock ogtags %}
+        <!-- favicon -->
+        {% if FAVICON %}
+        <link rel="icon" type="image/png" href="{{ SITEURL }}/images/{{ FAVICON }}">
+        {% endif %}
+        <!-- css -->
+        <link rel="stylesheet" type="text/css" href="{{ SITEURL }}/theme/css/main.css">
+        <link rel="stylesheet" type="text/css" href="{{ SITEURL }}/theme/css/pygments-highlight.css">
+        {% endblock head %}
+</head>
+<body>
+    {% include 'github.html' %}
+    {% block header %}
+    <div role="banner" id="banner">
+        <header>
+            {% if GRAVATAR %}
+            <a href="/"><img src="{{ GRAVATAR }}" alt="Avatar" class="gravatar"></a>
+            {% elif LOGO %}
+            <a href="/"><img src="{{ SITEURL }}/images/{{ LOGO }}" alt="{{ SITENAME }}"></a>
+            {% endif %}
+            <nav id="menu">
+                <ul>
+                    {% if DISPLAY_PAGES_ON_MENU %}
+                      {% for p in pages %}
+                        <li{% if p == page %} class="active"{% endif %}><a href="{{ SITEURL }}/{{ p.url }}">{{ p.slug }}</a></li>
+                      {% endfor %}
+                    {% endif %}
+                    {% for title, link in MENUITEMS %}
+                        <li><a href="{{ link }}">{{ title }}</a></li>
+                    {% endfor %}
+                    {% if DISPLAY_CATEGORIES_ON_MENU %}
+                        {% for cat, null in categories %}
+                            {% if cat != "misc" %}
+                            <li{% if cat == category %} class="active"{% endif %}><a href="{{ SITEURL }}/{{ cat.url }}">{{ cat }}</a></li>
+                            {% endif %}
+                        {% endfor %}
+                    {% endif %}
+                </ul>
+            </nav>
+        </header>
+    </div>
+    {% endblock header %}
+        <div class="page" role="main">
+            {% block content %}
+            {% endblock content %}
+            {% block footer %}
+                <footer>
+                    <p>© {{ COPYRIGHT_YEAR }} {{ AUTHOR }}</p>
+                    {% if ATTRIBUTION %}
+                    <p><i>"Brutal"</i> Pelican Theme</p>
+                    <p>Designed and built by <a href="http://twitter.com/mcman_s">@mcman_s</a> in Denver</p>
+                    {% endif %}
+                </footer>
+        {% endblock footer %}
+        </div>
+</body>
+</html>

+ 18 - 0
pages/theme/templates/categories.html

@@ -0,0 +1,18 @@
+{% extends "base.html" %}
+
+{% block title %}{{ SITENAME }} - Categories{% endblock %}
+
+{% block content %}
+<div class="page-title">
+        <h1>categories</h1>
+</div>
+<div class="articles">
+    <ul>
+    {% for category, articles in categories%}
+        {% if category != "misc" %}
+        <li><a href="{{ SITEURL }}/{{ category.url }}">{{ category }}</a> <span class="count">({{ articles|count }})</span></li>
+        {% endif %}
+    {% endfor %}
+    </ul>
+</div>
+{% endblock %}

+ 11 - 0
pages/theme/templates/category.html

@@ -0,0 +1,11 @@
+{% extends "base.html" %}
+{% block ogtags %}
+<meta property="og:url" content="{{ SITEURL }}/category/{{ category }}"/>
+<meta property="og:title" content="{{ SITENAME }} | {{ category }}" />
+{% endblock ogtags %}
+{% block content %}
+<div class="page-title">
+    <h1>{{ category }}</h1>
+</div>
+{% include 'article_list.html' %}
+{% endblock content %}

+ 9 - 0
pages/theme/templates/github.html

@@ -0,0 +1,9 @@
+{% if GITHUB_URL %}
+<a href="{{ GITHUB_URL }}">
+{% if GITHUB_POSITION != "left" %}
+<img style="position: absolute; top: 0; right: 0; border: 0;" src="{{ SITEURL }}/images/forkme_right_gray_6d6d6d.svg" alt="Fork me on GitHub" />
+{% else %}
+<img style="position: fixed; top: 0; left: 0; border: 0; z-index: 1;" src="{{ SITEURL }}/images/forkme_left_gray_6d6d6d.svg" alt="Fork me on GitHub" />
+{% endif %}
+</a>
+{% endif %}

+ 30 - 0
pages/theme/templates/index.html

@@ -0,0 +1,30 @@
+{% extends 'base.html' %}
+<!-- twitter card metadata -->
+{% block twittercard %}
+{% if SITEIMAGE %}
+<meta name="twitter:card" content="summary_large_image">
+<meta name="twitter:image" content="{{ SITEURL }}/images/{{ SITEIMAGE }}">
+<meta name="twitter:creator" content="{{ TWITTER_USERNAME }}">
+{% elif GRAVATAR %}
+<meta name="twitter:card" content="summary">
+<meta name="twitter:image" content="{{ GRAVATAR }}">
+{% elif LOGO %}
+<meta name="twitter:card" content="summary">
+<meta name="twitter:image" content="{{ SITEURL }}/images/{{ LOGO }}">
+{% endif %}
+<meta name="twitter:site" content="{{ TWITTER_USERNAME }}">
+<meta name="twitter:title" content="{{ SITENAME }}">
+<meta name="twitter:description" content="{{ SITEDESCRIPTION }}">
+{% endblock twittercard %}
+<!-- OG Tags -->
+{% block ogtags %}
+<meta property="og:url" content="{{ SITEURL }}"/>
+<meta property="og:title" content="{{ SITENAME }}" />
+<meta property="og:description" content="{{ SITEDESCRIPTION }}" />
+{% if SITEIMAGE %}
+<meta property="og:image" content="{{ SITEURL }}/images/{{ SITEIMAGE }}" />
+{% endif %}
+{% endblock ogtags %}
+{% block content %}
+{% include 'article_list.html' %}
+{% endblock content %}

+ 85 - 0
pages/theme/templates/page.html

@@ -0,0 +1,85 @@
+{% extends "base.html" %}
+{% block head %}
+  {{ super() }}
+  {% if page.description %}
+    <meta name="description" content="{{ page.description }}" />
+  {% endif %}
+
+  {% for tag in page.tags %}
+    <meta name="tags" content="{{tag}}" />
+  {% endfor %}
+
+{% endblock %}
+{% block twittercard %}
+{% if page.cover_image %}
+<meta name="twitter:card" content="summary_large_image">
+<meta name="twitter:image" content="{{ SITEURL }}/images/{{ page.cover_image }}">
+<meta name="twitter:creator" content="{{ TWITTER_USERNAME }}">
+{% elif GRAVATAR %}
+<meta name="twitter:card" content="summary">
+<meta name="twitter:image" content="{{ GRAVATAR }}">
+{% elif LOGO %}
+<meta name="twitter:card" content="summary">
+<meta name="twitter:image" content="{{ SITEURL }}/images/{{ LOGO }}">
+{% endif %}
+<meta name="twitter:site" content="{{ TWITTER_USERNAME }}">
+<meta name="twitter:title" content="{{ page.title }}">
+<meta name="twitter:description" content="{{ page.description }}">
+{% endblock twittercard %}
+<!-- OG Tags -->
+{% block ogtags %}
+<meta property="og:url" content="{{ SITEURL }}/{{ page.url }}"/>
+<meta property="og:title" content="{{ SITENAME }} | {{ page.title }}" />
+<meta property="og:description" content="{{ page.description }}" />
+{% if page.cover_image %}
+<meta property="og:image" content="{{ SITEURL }}/images/{{ page.cover_image }}" />
+{% endif %}
+{% endblock ogtags %}
+{% block content %}
+  <div class="article" role="article">
+    <article>
+      {% if page.date %}
+        <footer>
+            <a name="top"></a>
+            <p>
+              <time datetime=" {{ page.date }}">
+                <script>document.write(moment('{{ page.date }}').format('LL'));</script>
+              </time>
+            </p>
+        </footer>
+      {% endif %}
+        <header>
+          <h1>
+            {{ page.title }}
+          </h1>
+        </header>
+      <div class="content">
+         {{ page.content|safe }}
+      </div>
+      <div class="back-to-top">
+          <a href="#top">back to top</a>
+      </div>
+      {% if DISQUS_SITENAME %}
+      <div id="disqus_thread"></div>
+        {% if SITEURL %}
+        <script>
+          var disqus_config = function () {
+          this.page.url = "{{ SITEURL }}/{{ page.url }}";  // Replace PAGE_URL with your page's canonical URL variable
+          this.page.identifier = "{{ page.slug }}"; // Replace PAGE_IDENTIFIER with your page's unique identifier variable
+          };
+        </script>
+        {% endif %}
+        <script>
+        (function() {
+              var d = document, s = d.createElement('script');
+              s.src = 'https://{{ DISQUS_SITENAME }}.disqus.com/embed.js';
+              s.setAttribute('data-timestamp', +new Date());
+              (d.head || d.body).appendChild(s);
+        })();
+      </script>
+      <noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
+      {% endif %}
+    </article>
+  </div>
+<!-- end article -->
+{% endblock content %}

+ 11 - 0
pages/theme/templates/pagination.html

@@ -0,0 +1,11 @@
+{% if DEFAULT_PAGINATION %}
+<p class="paginator">
+    {% if articles_page.has_previous() %}
+        <a href="{{ SITEURL }}/{{ articles_previous_page.url }}" class="previous">&laquo;</a>
+    {% endif %}
+    <span>{{ articles_page.number }} / {{ articles_paginator.num_pages }}</span>
+    {% if articles_page.has_next() %}
+        <a href="{{ SITEURL }}/{{ articles_next_page.url }}" class="next">&raquo;</a>
+    {% endif %}
+</p>
+{% endif %}

+ 14 - 0
pages/theme/templates/period_archives.html

@@ -0,0 +1,14 @@
+{% extends "base.html" %}
+
+{% block title %}{{ SITENAME }} - {{ period | reverse | join(' ') }} archives{% endblock %}
+
+{% block content %}
+<h1>Archives for {{ period | reverse | join(' ') }}</h1>
+
+<dl>
+{% for article in dates %}
+    <dt>{{ article.locale_date }}</dt>
+    <dd><a href="{{ SITEURL }}/{{ article.url }}">{{ article.title }}</a></dd>
+{% endfor %}
+</dl>
+{% endblock %}

+ 11 - 0
pages/theme/templates/tag.html

@@ -0,0 +1,11 @@
+{% extends "base.html" %}
+{% block ogtags %}
+<meta property="og:url" content="{{ SITEURL }}/tag/{{ tag }}"/>
+<meta property="og:title" content="{{ SITENAME }} | {{ tag }}" />
+{% endblock ogtags %}
+{% block content %}
+<div class="page-title">
+    <h1>{{ tag }}</h1>
+</div>
+{% include 'article_list.html' %}
+{% endblock content %}

+ 16 - 0
pages/theme/templates/tags.html

@@ -0,0 +1,16 @@
+{% extends "base.html" %}
+
+{% block title %}{{ SITENAME }} - Tags{% endblock %}
+
+{% block content %}
+<div class="page-title">
+        <h1>tags</h1>
+</div>
+<div class="articles">
+    <ul>
+    {% for tag, articles in tags %}
+        <li><a href="{{ SITEURL }}/{{ tag.url }}">{{ tag }}</a> <span class="count">({{ articles|count }})</span></li>
+    {% endfor %}
+    </ul>
+</div>
+{% endblock %}

+ 1 - 3
src/candidate.cpp

@@ -221,9 +221,7 @@ Candidate::operator string() const {
 }
 
 bool Candidate::operator==(const Candidate &other) const {
-	return (mFoundation == other.mFoundation &&
-	        mService == other.mService &&
-	        mNode == other.mNode);
+	return (mFoundation == other.mFoundation && mService == other.mService && mNode == other.mNode);
 }
 
 bool Candidate::operator!=(const Candidate &other) const {

+ 767 - 767
src/capi.cpp

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2020 Paul-Louis Ageneau
+ * Copyright (c) 2019-2021 Paul-Louis Ageneau
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -134,8 +134,75 @@ void eraseTrack(int tr) {
 	userPointerMap.erase(tr);
 }
 
+shared_ptr<Channel> getChannel(int id) {
+	std::lock_guard lock(mutex);
+	if (auto it = dataChannelMap.find(id); it != dataChannelMap.end())
+		return it->second;
+	if (auto it = trackMap.find(id); it != trackMap.end())
+		return it->second;
+#if RTC_ENABLE_WEBSOCKET
+	if (auto it = webSocketMap.find(id); it != webSocketMap.end())
+		return it->second;
+#endif
+	throw std::invalid_argument("DataChannel, Track, or WebSocket ID does not exist");
+}
+
+int copyAndReturn(string s, char *buffer, int size) {
+	if (!buffer)
+		return int(s.size() + 1);
+
+	if (size < int(s.size()))
+		return RTC_ERR_TOO_SMALL;
+
+	std::copy(s.begin(), s.end(), buffer);
+	buffer[s.size()] = '\0';
+	return int(s.size() + 1);
+}
+
+int copyAndReturn(binary b, char *buffer, int size) {
+	if (!buffer)
+		return int(b.size());
+
+	if (size < int(b.size()))
+		return RTC_ERR_TOO_SMALL;
+
+	auto data = reinterpret_cast<const char *>(b.data());
+	std::copy(data, data + b.size(), buffer);
+	buffer[b.size()] = '\0';
+	return int(b.size());
+}
+
+template <typename T> int copyAndReturn(std::vector<T> b, T *buffer, int size) {
+	if (!buffer)
+		return int(b.size());
+
+	if (size < int(b.size()))
+		return RTC_ERR_TOO_SMALL;
+	std::copy(b.begin(), b.end(), buffer);
+	return int(b.size());
+}
+
+template <typename F> int wrap(F func) {
+	try {
+		return int(func());
+
+	} catch (const std::invalid_argument &e) {
+		PLOG_ERROR << e.what();
+		return RTC_ERR_INVALID;
+	} catch (const std::exception &e) {
+		PLOG_ERROR << e.what();
+		return RTC_ERR_FAILURE;
+	}
+}
+
 #if RTC_ENABLE_MEDIA
 
+string lowercased(string str) {
+	std::transform(str.begin(), str.end(), str.begin(),
+	               [](unsigned char c) { return std::tolower(c); });
+	return str;
+}
+
 shared_ptr<RtcpSrReporter> getRtcpSrReporter(int id) {
 	std::lock_guard lock(mutex);
 	if (auto it = rtcpSrReporterMap.find(id); it != rtcpSrReporterMap.end()) {
@@ -243,75 +310,6 @@ void eraseWebSocketServer(int wsserver) {
 
 #endif
 
-shared_ptr<Channel> getChannel(int id) {
-	std::lock_guard lock(mutex);
-	if (auto it = dataChannelMap.find(id); it != dataChannelMap.end())
-		return it->second;
-	if (auto it = trackMap.find(id); it != trackMap.end())
-		return it->second;
-#if RTC_ENABLE_WEBSOCKET
-	if (auto it = webSocketMap.find(id); it != webSocketMap.end())
-		return it->second;
-#endif
-	throw std::invalid_argument("DataChannel, Track, or WebSocket ID does not exist");
-}
-
-template <typename F> int wrap(F func) {
-	try {
-		return int(func());
-
-	} catch (const std::invalid_argument &e) {
-		PLOG_ERROR << e.what();
-		return RTC_ERR_INVALID;
-	} catch (const std::exception &e) {
-		PLOG_ERROR << e.what();
-		return RTC_ERR_FAILURE;
-	}
-}
-
-int copyAndReturn(string s, char *buffer, int size) {
-	if (!buffer)
-		return int(s.size() + 1);
-
-	if (size < int(s.size()))
-		return RTC_ERR_TOO_SMALL;
-
-	std::copy(s.begin(), s.end(), buffer);
-	buffer[s.size()] = '\0';
-	return int(s.size() + 1);
-}
-
-int copyAndReturn(binary b, char *buffer, int size) {
-	if (!buffer)
-		return int(b.size());
-
-	if (size < int(b.size()))
-		return RTC_ERR_TOO_SMALL;
-
-	auto data = reinterpret_cast<const char *>(b.data());
-	std::copy(data, data + b.size(), buffer);
-	buffer[b.size()] = '\0';
-	return int(b.size());
-}
-
-template <typename T> int copyAndReturn(std::vector<T> b, T *buffer, int size) {
-	if (!buffer)
-		return int(b.size());
-
-	if (size < int(b.size()))
-		return RTC_ERR_TOO_SMALL;
-	std::copy(b.begin(), b.end(), buffer);
-	return int(b.size());
-}
-
-#if RTC_ENABLE_MEDIA
-// function is used in RTC_ENABLE_MEDIA only
-string lowercased(string str) {
-	std::transform(str.begin(), str.end(), str.begin(),
-	               [](unsigned char c) { return std::tolower(c); });
-	return str;
-}
-#endif // RTC_ENABLE_MEDIA
 } // namespace
 
 void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb) {
@@ -343,6 +341,7 @@ int rtcCreatePeerConnection(const rtcConfiguration *config) {
 		}
 
 		c.certificateType = static_cast<CertificateType>(config->certificateType);
+		c.iceTransportPolicy = static_cast<TransportPolicy>(config->iceTransportPolicy);
 		c.enableIceTcp = config->enableIceTcp;
 		c.disableAutoNegotiation = config->disableAutoNegotiation;
 
@@ -371,968 +370,969 @@ int rtcDeletePeerConnection(int pc) {
 	});
 }
 
-int rtcCreateDataChannel(int pc, const char *label) {
-	return rtcCreateDataChannelEx(pc, label, nullptr);
+int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc cb) {
+	return wrap([&] {
+		auto peerConnection = getPeerConnection(pc);
+		if (cb)
+			peerConnection->onLocalDescription([pc, cb](Description desc) {
+				if (auto ptr = getUserPointer(pc))
+					cb(pc, string(desc).c_str(), desc.typeString().c_str(), *ptr);
+			});
+		else
+			peerConnection->onLocalDescription(nullptr);
+		return RTC_ERR_SUCCESS;
+	});
 }
 
-int rtcCreateDataChannelEx(int pc, const char *label, const rtcDataChannelInit *init) {
+int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb) {
 	return wrap([&] {
-		DataChannelInit dci = {};
-		if (init) {
-			auto *reliability = &init->reliability;
-			dci.reliability.unordered = reliability->unordered;
-			if (reliability->unreliable) {
-				if (reliability->maxPacketLifeTime > 0) {
-					dci.reliability.type = Reliability::Type::Timed;
-					dci.reliability.rexmit = milliseconds(reliability->maxPacketLifeTime);
-				} else {
-					dci.reliability.type = Reliability::Type::Rexmit;
-					dci.reliability.rexmit = reliability->maxRetransmits;
-				}
-			} else {
-				dci.reliability.type = Reliability::Type::Reliable;
-			}
-
-			dci.negotiated = init->negotiated;
-			dci.id = init->manualStream ? std::make_optional(init->stream) : nullopt;
-			dci.protocol = init->protocol ? init->protocol : "";
-		}
-
 		auto peerConnection = getPeerConnection(pc);
-		int dc = emplaceDataChannel(
-		    peerConnection->createDataChannel(string(label ? label : ""), std::move(dci)));
-
-		if (auto ptr = getUserPointer(pc))
-			rtcSetUserPointer(dc, *ptr);
-
-		return dc;
+		if (cb)
+			peerConnection->onLocalCandidate([pc, cb](Candidate cand) {
+				if (auto ptr = getUserPointer(pc))
+					cb(pc, cand.candidate().c_str(), cand.mid().c_str(), *ptr);
+			});
+		else
+			peerConnection->onLocalCandidate(nullptr);
+		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcIsOpen(int cid) {
-	return wrap([cid] { return getChannel(cid)->isOpen(); });
+int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb) {
+	return wrap([&] {
+		auto peerConnection = getPeerConnection(pc);
+		if (cb)
+			peerConnection->onStateChange([pc, cb](PeerConnection::State state) {
+				if (auto ptr = getUserPointer(pc))
+					cb(pc, static_cast<rtcState>(state), *ptr);
+			});
+		else
+			peerConnection->onStateChange(nullptr);
+		return RTC_ERR_SUCCESS;
+	});
 }
 
-int rtcDeleteDataChannel(int dc) {
-	return wrap([dc] {
-		auto dataChannel = getDataChannel(dc);
-		dataChannel->onOpen(nullptr);
-		dataChannel->onClosed(nullptr);
-		dataChannel->onError(nullptr);
-		dataChannel->onMessage(nullptr);
-		dataChannel->onBufferedAmountLow(nullptr);
-		dataChannel->onAvailable(nullptr);
-
-		eraseDataChannel(dc);
+int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb) {
+	return wrap([&] {
+		auto peerConnection = getPeerConnection(pc);
+		if (cb)
+			peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) {
+				if (auto ptr = getUserPointer(pc))
+					cb(pc, static_cast<rtcGatheringState>(state), *ptr);
+			});
+		else
+			peerConnection->onGatheringStateChange(nullptr);
 		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcAddTrack(int pc, const char *mediaDescriptionSdp) {
+int rtcSetSignalingStateChangeCallback(int pc, rtcSignalingStateCallbackFunc cb) {
 	return wrap([&] {
-		if (!mediaDescriptionSdp)
-			throw std::invalid_argument("Unexpected null pointer for track media description");
-
 		auto peerConnection = getPeerConnection(pc);
-		Description::Media media{string(mediaDescriptionSdp)};
-		int tr = emplaceTrack(peerConnection->addTrack(std::move(media)));
-		if (auto ptr = getUserPointer(pc))
-			rtcSetUserPointer(tr, *ptr);
-
-		return tr;
+		if (cb)
+			peerConnection->onSignalingStateChange([pc, cb](PeerConnection::SignalingState state) {
+				if (auto ptr = getUserPointer(pc))
+					cb(pc, static_cast<rtcSignalingState>(state), *ptr);
+			});
+		else
+			peerConnection->onGatheringStateChange(nullptr);
+		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcAddTrackEx(int pc, const rtcTrackInit *init) {
+int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb) {
 	return wrap([&] {
 		auto peerConnection = getPeerConnection(pc);
+		if (cb)
+			peerConnection->onDataChannel([pc, cb](shared_ptr<DataChannel> dataChannel) {
+				int dc = emplaceDataChannel(dataChannel);
+				if (auto ptr = getUserPointer(pc)) {
+					rtcSetUserPointer(dc, *ptr);
+					cb(pc, dc, *ptr);
+				}
+			});
+		else
+			peerConnection->onDataChannel(nullptr);
+		return RTC_ERR_SUCCESS;
+	});
+}
 
-		if (!init)
-			throw std::invalid_argument("Unexpected null pointer for track init");
-
-		auto direction = static_cast<Description::Direction>(init->direction);
-
-		string mid;
-		if (init->mid) {
-			mid = string(init->mid);
-		} else {
-			switch (init->codec) {
-			case RTC_CODEC_H264:
-			case RTC_CODEC_VP8:
-			case RTC_CODEC_VP9:
-				mid = "video";
-				break;
-			case RTC_CODEC_OPUS:
-				mid = "audio";
-				break;
-			default:
-				mid = "video";
-				break;
-			}
-		}
-
-		optional<Description::Media> optDescription = nullopt;
-
-		switch (init->codec) {
-		case RTC_CODEC_H264:
-		case RTC_CODEC_VP8:
-		case RTC_CODEC_VP9: {
-			auto desc = Description::Video(mid, direction);
-			switch (init->codec) {
-			case RTC_CODEC_H264:
-				desc.addH264Codec(init->payloadType);
-				break;
-			case RTC_CODEC_VP8:
-				desc.addVP8Codec(init->payloadType);
-				break;
-			case RTC_CODEC_VP9:
-				desc.addVP8Codec(init->payloadType);
-				break;
-			default:
-				break;
-			}
-			optDescription = desc;
-			break;
-		}
-		case RTC_CODEC_OPUS: {
-			auto desc = Description::Audio(mid, direction);
-			switch (init->codec) {
-			case RTC_CODEC_OPUS:
-				desc.addOpusCodec(init->payloadType);
-				break;
-			default:
-				break;
-			}
-			optDescription = desc;
-			break;
-		}
-		default:
-			break;
-		}
-
-		if (!optDescription)
-			throw std::invalid_argument("Unexpected codec");
-
-		auto desc = std::move(*optDescription);
-		desc.addSSRC(init->ssrc, init->name ? std::make_optional(string(init->name)) : nullopt,
-		             init->msid ? std::make_optional(string(init->msid)) : nullopt,
-		             init->trackId ? std::make_optional(string(init->trackId)) : nullopt);
-
-		int tr = emplaceTrack(peerConnection->addTrack(std::move(desc)));
-
-		if (auto ptr = getUserPointer(pc))
-			rtcSetUserPointer(tr, *ptr);
-
-		return tr;
-	});
-}
-
-int rtcDeleteTrack(int tr) {
+int rtcSetTrackCallback(int pc, rtcTrackCallbackFunc cb) {
 	return wrap([&] {
-		auto track = getTrack(tr);
-		track->onOpen(nullptr);
-		track->onClosed(nullptr);
-		track->onError(nullptr);
-		track->onMessage(nullptr);
-		track->onBufferedAmountLow(nullptr);
-		track->onAvailable(nullptr);
-
-		eraseTrack(tr);
+		auto peerConnection = getPeerConnection(pc);
+		if (cb)
+			peerConnection->onTrack([pc, cb](shared_ptr<Track> track) {
+				int tr = emplaceTrack(track);
+				if (auto ptr = getUserPointer(pc)) {
+					rtcSetUserPointer(tr, *ptr);
+					cb(pc, tr, *ptr);
+				}
+			});
+		else
+			peerConnection->onTrack(nullptr);
 		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcGetTrackDescription(int tr, char *buffer, int size) {
+int rtcSetLocalDescription(int pc, const char *type) {
 	return wrap([&] {
-		auto track = getTrack(tr);
-		return copyAndReturn(track->description(), buffer, size);
+		auto peerConnection = getPeerConnection(pc);
+		peerConnection->setLocalDescription(type ? Description::stringToType(type)
+		                                         : Description::Type::Unspec);
+		return RTC_ERR_SUCCESS;
 	});
 }
 
-#if RTC_ENABLE_MEDIA
-
-void setSSRC(Description::Media *description, uint32_t ssrc, const char *_name, const char *_msid,
-             const char *_trackID) {
-
-	optional<string> name = nullopt;
-	if (_name) {
-		name = string(_name);
-	}
-
-	optional<string> msid = nullopt;
-	if (_msid) {
-		msid = string(_msid);
-	}
-
-	optional<string> trackID = nullopt;
-	if (_trackID) {
-		trackID = string(_trackID);
-	}
+int rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
+	return wrap([&] {
+		auto peerConnection = getPeerConnection(pc);
 
-	description->addSSRC(ssrc, name, msid, trackID);
-}
+		if (!sdp)
+			throw std::invalid_argument("Unexpected null pointer for remote description");
 
-int rtcSetH264PacketizationHandler(int tr, const rtcPacketizationHandlerInit *init) {
-	return wrap([&] {
-		auto track = getTrack(tr);
-		// create RTP configuration
-		auto rtpConfig = createRtpPacketizationConfig(init);
-		// create packetizer
-		auto maxFragmentSize = init && init->maxFragmentSize ? init->maxFragmentSize
-		                                                     : RTC_DEFAULT_MAXIMUM_FRAGMENT_SIZE;
-		auto packetizer = std::make_shared<H264RtpPacketizer>(rtpConfig, maxFragmentSize);
-		// create H264 handler
-		auto h264Handler = std::make_shared<H264PacketizationHandler>(packetizer);
-		emplaceMediaChainableHandler(h264Handler, tr);
-		emplaceRtpConfig(rtpConfig, tr);
-		// set handler
-		track->setMediaHandler(h264Handler);
+		peerConnection->setRemoteDescription({string(sdp), type ? string(type) : ""});
 		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcSetOpusPacketizationHandler(int tr, const rtcPacketizationHandlerInit *init) {
+int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid) {
 	return wrap([&] {
-		auto track = getTrack(tr);
-		// create RTP configuration
-		auto rtpConfig = createRtpPacketizationConfig(init);
-		// create packetizer
-		auto packetizer = std::make_shared<OpusRtpPacketizer>(rtpConfig);
-		// create Opus handler
-		auto opusHandler = std::make_shared<OpusPacketizationHandler>(packetizer);
-		emplaceMediaChainableHandler(opusHandler, tr);
-		emplaceRtpConfig(rtpConfig, tr);
-		// set handler
-		track->setMediaHandler(opusHandler);
-		return RTC_ERR_SUCCESS;
-	});
-}
+		auto peerConnection = getPeerConnection(pc);
 
-int rtcChainRtcpSrReporter(int tr) {
-	return wrap([tr] {
-		auto config = getRtpConfig(tr);
-		auto reporter = std::make_shared<RtcpSrReporter>(config);
-		emplaceRtcpSrReporter(reporter, tr);
-		auto chainableHandler = getMediaChainableHandler(tr);
-		chainableHandler->addToChain(reporter);
-		return RTC_ERR_SUCCESS;
-	});
-}
+		if (!cand)
+			throw std::invalid_argument("Unexpected null pointer for remote candidate");
 
-int rtcChainRtcpNackResponder(int tr, unsigned int maxStoredPacketsCount) {
-	return wrap([tr, maxStoredPacketsCount] {
-		auto responder = std::make_shared<RtcpNackResponder>(maxStoredPacketsCount);
-		auto chainableHandler = getMediaChainableHandler(tr);
-		chainableHandler->addToChain(responder);
+		peerConnection->addRemoteCandidate({string(cand), mid ? string(mid) : ""});
 		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcSetRtpConfigurationStartTime(int id, const rtcStartTime *startTime) {
+int rtcGetLocalDescription(int pc, char *buffer, int size) {
 	return wrap([&] {
-		auto config = getRtpConfig(id);
-		auto epoch = startTime->since1970 ? RtpPacketizationConfig::EpochStart::T1970
-		                                  : RtpPacketizationConfig::EpochStart::T1900;
-		config->setStartTime(startTime->seconds, epoch, startTime->timestamp);
-		return RTC_ERR_SUCCESS;
-	});
-}
+		auto peerConnection = getPeerConnection(pc);
 
-int rtcStartRtcpSenderReporterRecording(int id) {
-	return wrap([id] {
-		auto sender = getRtcpSrReporter(id);
-		sender->startRecording();
-		return RTC_ERR_SUCCESS;
+		if (auto desc = peerConnection->localDescription())
+			return copyAndReturn(string(*desc), buffer, size);
+		else
+			return RTC_ERR_NOT_AVAIL;
 	});
 }
 
-int rtcTransformSecondsToTimestamp(int id, double seconds, uint32_t *timestamp) {
+int rtcGetRemoteDescription(int pc, char *buffer, int size) {
 	return wrap([&] {
-		auto config = getRtpConfig(id);
-		*timestamp = config->secondsToTimestamp(seconds);
-		return RTC_ERR_SUCCESS;
-	});
-}
+		auto peerConnection = getPeerConnection(pc);
 
-int rtcTransformTimestampToSeconds(int id, uint32_t timestamp, double *seconds) {
-	return wrap([&] {
-		auto config = getRtpConfig(id);
-		*seconds = config->timestampToSeconds(timestamp);
-		return RTC_ERR_SUCCESS;
+		if (auto desc = peerConnection->remoteDescription())
+			return copyAndReturn(string(*desc), buffer, size);
+		else
+			return RTC_ERR_NOT_AVAIL;
 	});
 }
 
-int rtcGetCurrentTrackTimestamp(int id, uint32_t *timestamp) {
+int rtcGetLocalDescriptionType(int pc, char *buffer, int size) {
 	return wrap([&] {
-		auto config = getRtpConfig(id);
-		*timestamp = config->timestamp;
-		return RTC_ERR_SUCCESS;
+		auto peerConnection = getPeerConnection(pc);
+
+		if (auto desc = peerConnection->localDescription())
+			return copyAndReturn(desc->typeString(), buffer, size);
+		else
+			return RTC_ERR_NOT_AVAIL;
 	});
 }
 
-int rtcGetTrackStartTimestamp(int id, uint32_t *timestamp) {
+int rtcGetRemoteDescriptionType(int pc, char *buffer, int size) {
 	return wrap([&] {
-		auto config = getRtpConfig(id);
-		*timestamp = config->startTimestamp;
-		return RTC_ERR_SUCCESS;
+		auto peerConnection = getPeerConnection(pc);
+
+		if (auto desc = peerConnection->remoteDescription())
+			return copyAndReturn(desc->typeString(), buffer, size);
+		else
+			return RTC_ERR_NOT_AVAIL;
 	});
 }
 
-int rtcSetTrackRtpTimestamp(int id, uint32_t timestamp) {
+int rtcGetLocalAddress(int pc, char *buffer, int size) {
 	return wrap([&] {
-		auto config = getRtpConfig(id);
-		config->timestamp = timestamp;
-		return RTC_ERR_SUCCESS;
+		auto peerConnection = getPeerConnection(pc);
+
+		if (auto addr = peerConnection->localAddress())
+			return copyAndReturn(std::move(*addr), buffer, size);
+		else
+			return RTC_ERR_NOT_AVAIL;
 	});
 }
 
-int rtcGetPreviousTrackSenderReportTimestamp(int id, uint32_t *timestamp) {
+int rtcGetRemoteAddress(int pc, char *buffer, int size) {
 	return wrap([&] {
-		auto sender = getRtcpSrReporter(id);
-		*timestamp = sender->previousReportedTimestamp;
-		return RTC_ERR_SUCCESS;
+		auto peerConnection = getPeerConnection(pc);
+
+		if (auto addr = peerConnection->remoteAddress())
+			return copyAndReturn(std::move(*addr), buffer, size);
+		else
+			return RTC_ERR_NOT_AVAIL;
 	});
 }
 
-int rtcSetNeedsToSendRtcpSr(int id) {
-	return wrap([id] {
-		auto sender = getRtcpSrReporter(id);
-		sender->setNeedsToReport();
-		return RTC_ERR_SUCCESS;
+int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote, int remoteSize) {
+	return wrap([&] {
+		auto peerConnection = getPeerConnection(pc);
+
+		Candidate localCand;
+		Candidate remoteCand;
+		if (!peerConnection->getSelectedCandidatePair(&localCand, &remoteCand))
+			return RTC_ERR_NOT_AVAIL;
+
+		int localRet = copyAndReturn(string(localCand), local, localSize);
+		if (localRet < 0)
+			return localRet;
+
+		int remoteRet = copyAndReturn(string(remoteCand), remote, remoteSize);
+		if (remoteRet < 0)
+			return remoteRet;
+
+		return std::max(localRet, remoteRet);
 	});
 }
 
-int rtcGetTrackPayloadTypesForCodec(int tr, const char *ccodec, int *buffer, int size) {
+int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb) {
 	return wrap([&] {
-		auto track = getTrack(tr);
-		auto codec = lowercased(string(ccodec));
-		auto description = track->description();
-		std::vector<int> payloadTypes{};
-		payloadTypes.reserve(std::max(size, 0));
-		for (auto it = description.beginMaps(); it != description.endMaps(); it++) {
-			auto element = *it;
-			if (lowercased(element.second.format) == codec) {
-				payloadTypes.push_back(element.first);
-			}
-		}
-		return copyAndReturn(payloadTypes, buffer, size);
+		auto channel = getChannel(id);
+		if (cb)
+			channel->onOpen([id, cb]() {
+				if (auto ptr = getUserPointer(id))
+					cb(id, *ptr);
+			});
+		else
+			channel->onOpen(nullptr);
+		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcGetSsrcsForTrack(int tr, uint32_t *buffer, int count) {
+int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb) {
 	return wrap([&] {
-		auto track = getTrack(tr);
-		auto ssrcs = track->description().getSSRCs();
-		return copyAndReturn(ssrcs, buffer, count);
+		auto channel = getChannel(id);
+		if (cb)
+			channel->onClosed([id, cb]() {
+				if (auto ptr = getUserPointer(id))
+					cb(id, *ptr);
+			});
+		else
+			channel->onClosed(nullptr);
+		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcGetCNameForSsrc(int tr, uint32_t ssrc, char *cname, int cnameSize) {
+int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb) {
 	return wrap([&] {
-		auto track = getTrack(tr);
-		auto description = track->description();
-		auto optCName = description.getCNameForSsrc(ssrc);
-		if (optCName.has_value()) {
-			return copyAndReturn(optCName.value(), cname, cnameSize);
-		} else {
-			return 0;
-		}
+		auto channel = getChannel(id);
+		if (cb)
+			channel->onError([id, cb](string error) {
+				if (auto ptr = getUserPointer(id))
+					cb(id, error.c_str(), *ptr);
+			});
+		else
+			channel->onError(nullptr);
+		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcGetSsrcsForType(const char *mediaType, const char *sdp, uint32_t *buffer, int bufferSize) {
+int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb) {
 	return wrap([&] {
-		auto type = lowercased(string(mediaType));
-		auto oldSDP = string(sdp);
-		auto description = Description(oldSDP, "unspec");
-		auto mediaCount = description.mediaCount();
-		for (unsigned int i = 0; i < mediaCount; i++) {
-			if (std::holds_alternative<Description::Media *>(description.media(i))) {
-				auto media = std::get<Description::Media *>(description.media(i));
-				auto currentMediaType = lowercased(media->type());
-				if (currentMediaType == type) {
-					auto ssrcs = media->getSSRCs();
-					return copyAndReturn(ssrcs, buffer, bufferSize);
-				}
-			}
-		}
-		return 0;
+		auto channel = getChannel(id);
+		if (cb)
+			channel->onMessage(
+			    [id, cb](binary b) {
+				    if (auto ptr = getUserPointer(id))
+					    cb(id, reinterpret_cast<const char *>(b.data()), int(b.size()), *ptr);
+			    },
+			    [id, cb](string s) {
+				    if (auto ptr = getUserPointer(id))
+					    cb(id, s.c_str(), -int(s.size() + 1), *ptr);
+			    });
+		else
+			channel->onMessage(nullptr);
+		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcSetSsrcForType(const char *mediaType, const char *sdp, char *buffer, const int bufferSize,
-                      rtcSsrcForTypeInit *init) {
+int rtcSendMessage(int id, const char *data, int size) {
 	return wrap([&] {
-		auto type = lowercased(string(mediaType));
-		auto prevSDP = string(sdp);
-		auto description = Description(prevSDP, "unspec");
-		auto mediaCount = description.mediaCount();
-		for (unsigned int i = 0; i < mediaCount; i++) {
-			if (std::holds_alternative<Description::Media *>(description.media(i))) {
-				auto media = std::get<Description::Media *>(description.media(i));
-				auto currentMediaType = lowercased(media->type());
-				if (currentMediaType == type) {
-					setSSRC(media, init->ssrc, init->name, init->msid, init->trackId);
-					break;
-				}
-			}
+		auto channel = getChannel(id);
+
+		if (!data && size != 0)
+			throw std::invalid_argument("Unexpected null pointer for data");
+
+		if (size >= 0) {
+			auto b = reinterpret_cast<const byte *>(data);
+			channel->send(binary(b, b + size));
+			return size;
+		} else {
+			string str(data);
+			int len = int(str.size());
+			channel->send(std::move(str));
+			return len;
 		}
-		return copyAndReturn(string(description), buffer, bufferSize);
 	});
 }
 
-#endif // RTC_ENABLE_MEDIA
-#if RTC_ENABLE_WEBSOCKET
+bool rtcIsOpen(int id) {
+	return wrap([id] { return getChannel(id)->isOpen(); });
+}
 
-int rtcCreateWebSocket(const char *url) {
-	return wrap([&] {
-		auto webSocket = std::make_shared<WebSocket>();
-		webSocket->open(url);
-		return emplaceWebSocket(webSocket);
+int rtcGetBufferedAmount(int id) {
+	return wrap([id] {
+		auto channel = getChannel(id);
+		return int(channel->bufferedAmount());
 	});
 }
 
-int rtcCreateWebSocketEx(const char *url, const rtcWsConfiguration *config) {
+int rtcSetBufferedAmountLowThreshold(int id, int amount) {
 	return wrap([&] {
-		if (!url)
-			throw std::invalid_argument("Unexpected null pointer for URL");
-
-		if (!config)
-			throw std::invalid_argument("Unexpected null pointer for config");
-
-		WebSocket::Configuration c;
-		c.disableTlsVerification = config->disableTlsVerification;
-		auto webSocket = std::make_shared<WebSocket>(std::move(c));
-		webSocket->open(url);
-		return emplaceWebSocket(webSocket);
+		auto channel = getChannel(id);
+		channel->setBufferedAmountLowThreshold(size_t(amount));
+		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcDeleteWebSocket(int ws) {
+int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb) {
 	return wrap([&] {
-		auto webSocket = getWebSocket(ws);
-		webSocket->onOpen(nullptr);
-		webSocket->onClosed(nullptr);
-		webSocket->onError(nullptr);
-		webSocket->onMessage(nullptr);
-		webSocket->onBufferedAmountLow(nullptr);
-		webSocket->onAvailable(nullptr);
-
-		eraseWebSocket(ws);
+		auto channel = getChannel(id);
+		if (cb)
+			channel->onBufferedAmountLow([id, cb]() {
+				if (auto ptr = getUserPointer(id))
+					cb(id, *ptr);
+			});
+		else
+			channel->onBufferedAmountLow(nullptr);
 		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcGetWebSocketRemoteAddress(int ws, char *buffer, int size) {
+int rtcGetAvailableAmount(int id) {
+	return wrap([id] { return int(getChannel(id)->availableAmount()); });
+}
+
+int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb) {
 	return wrap([&] {
-		auto webSocket = getWebSocket(ws);
-		if (auto remoteAddress = webSocket->remoteAddress())
-			return copyAndReturn(*remoteAddress, buffer, size);
+		auto channel = getChannel(id);
+		if (cb)
+			channel->onAvailable([id, cb]() {
+				if (auto ptr = getUserPointer(id))
+					cb(id, *ptr);
+			});
 		else
-			return RTC_ERR_NOT_AVAIL;
+			channel->onAvailable(nullptr);
+		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcGetWebSocketPath(int ws, char *buffer, int size) {
+int rtcReceiveMessage(int id, char *buffer, int *size) {
 	return wrap([&] {
-		auto webSocket = getWebSocket(ws);
-		if (auto path = webSocket->path())
-			return copyAndReturn(*path, buffer, size);
-		else
+		auto channel = getChannel(id);
+
+		if (!size)
+			throw std::invalid_argument("Unexpected null pointer for size");
+
+		*size = std::abs(*size);
+
+		auto message = channel->peek();
+		if (!message)
 			return RTC_ERR_NOT_AVAIL;
+
+		return std::visit( //
+		    overloaded{
+		        [&](binary b) {
+			        int ret = copyAndReturn(std::move(b), buffer, *size);
+			        if (ret >= 0) {
+				        channel->receive(); // discard
+				        *size = ret;
+				        return RTC_ERR_SUCCESS;
+			        } else {
+				        *size = int(b.size());
+				        return ret;
+			        }
+		        },
+		        [&](string s) {
+			        int ret = copyAndReturn(std::move(s), buffer, *size);
+			        if (ret >= 0) {
+				        channel->receive(); // discard
+				        *size = -ret;
+				        return RTC_ERR_SUCCESS;
+			        } else {
+				        *size = -int(s.size() + 1);
+				        return ret;
+			        }
+		        },
+		    },
+		    *message);
 	});
 }
 
-RTC_EXPORT int rtcCreateWebSocketServer(const rtcWsServerConfiguration *config,
-                                        rtcWebSocketClientCallbackFunc cb) {
+int rtcCreateDataChannel(int pc, const char *label) {
+	return rtcCreateDataChannelEx(pc, label, nullptr);
+}
+
+int rtcCreateDataChannelEx(int pc, const char *label, const rtcDataChannelInit *init) {
 	return wrap([&] {
-		if (!config)
-			throw std::invalid_argument("Unexpected null pointer for config");
+		DataChannelInit dci = {};
+		if (init) {
+			auto *reliability = &init->reliability;
+			dci.reliability.unordered = reliability->unordered;
+			if (reliability->unreliable) {
+				if (reliability->maxPacketLifeTime > 0) {
+					dci.reliability.type = Reliability::Type::Timed;
+					dci.reliability.rexmit = milliseconds(reliability->maxPacketLifeTime);
+				} else {
+					dci.reliability.type = Reliability::Type::Rexmit;
+					dci.reliability.rexmit = reliability->maxRetransmits;
+				}
+			} else {
+				dci.reliability.type = Reliability::Type::Reliable;
+			}
 
-		if (!cb)
-			throw std::invalid_argument("Unexpected null pointer for client callback");
+			dci.negotiated = init->negotiated;
+			dci.id = init->manualStream ? std::make_optional(init->stream) : nullopt;
+			dci.protocol = init->protocol ? init->protocol : "";
+		}
 
-		WebSocketServer::Configuration c;
-		c.port = config->port;
-		c.enableTls = config->enableTls;
-		c.certificatePemFile = config->certificatePemFile
-		                           ? make_optional(string(config->certificatePemFile))
-		                           : nullopt;
-		c.keyPemFile = config->keyPemFile ? make_optional(string(config->keyPemFile)) : nullopt;
-		c.keyPemPass = config->keyPemPass ? make_optional(string(config->keyPemPass)) : nullopt;
-		auto webSocketServer = std::make_shared<WebSocketServer>(std::move(c));
-		int wsserver = emplaceWebSocketServer(webSocketServer);
+		auto peerConnection = getPeerConnection(pc);
+		int dc = emplaceDataChannel(
+		    peerConnection->createDataChannel(string(label ? label : ""), std::move(dci)));
 
-		webSocketServer->onClient([wsserver, cb](shared_ptr<WebSocket> webSocket) {
-			int ws = emplaceWebSocket(webSocket);
-			if (auto ptr = getUserPointer(wsserver)) {
-				rtcSetUserPointer(wsserver, *ptr);
-				cb(wsserver, ws, *ptr);
-			}
-		});
+		if (auto ptr = getUserPointer(pc))
+			rtcSetUserPointer(dc, *ptr);
 
-		return wsserver;
+		return dc;
 	});
 }
 
-RTC_EXPORT int rtcDeleteWebSocketServer(int wsserver) {
-	return wrap([&] {
-		auto webSocketServer = getWebSocketServer(wsserver);
-		webSocketServer->onClient(nullptr);
-		webSocketServer->stop();
+int rtcDeleteDataChannel(int dc) {
+	return wrap([dc] {
+		auto dataChannel = getDataChannel(dc);
+		dataChannel->onOpen(nullptr);
+		dataChannel->onClosed(nullptr);
+		dataChannel->onError(nullptr);
+		dataChannel->onMessage(nullptr);
+		dataChannel->onBufferedAmountLow(nullptr);
+		dataChannel->onAvailable(nullptr);
 
-		eraseWebSocketServer(wsserver);
+		eraseDataChannel(dc);
 		return RTC_ERR_SUCCESS;
 	});
 }
 
-RTC_EXPORT int rtcGetWebSocketServerPort(int wsserver) {
-	return wrap([&] {
-		auto webSocketServer = getWebSocketServer(wsserver);
-		return int(webSocketServer->port());
+int rtcGetDataChannelStream(int dc) {
+	return wrap([dc] {
+		auto dataChannel = getDataChannel(dc);
+		return int(dataChannel->id());
 	});
 }
 
-#endif
-
-int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc cb) {
+int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
 	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
-		if (cb)
-			peerConnection->onLocalDescription([pc, cb](Description desc) {
-				if (auto ptr = getUserPointer(pc))
-					cb(pc, string(desc).c_str(), desc.typeString().c_str(), *ptr);
-			});
-		else
-			peerConnection->onLocalDescription(nullptr);
-		return RTC_ERR_SUCCESS;
+		auto dataChannel = getDataChannel(dc);
+		return copyAndReturn(dataChannel->label(), buffer, size);
 	});
 }
 
-int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb) {
+int rtcGetDataChannelProtocol(int dc, char *buffer, int size) {
 	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
-		if (cb)
-			peerConnection->onLocalCandidate([pc, cb](Candidate cand) {
-				if (auto ptr = getUserPointer(pc))
-					cb(pc, cand.candidate().c_str(), cand.mid().c_str(), *ptr);
-			});
-		else
-			peerConnection->onLocalCandidate(nullptr);
-		return RTC_ERR_SUCCESS;
+		auto dataChannel = getDataChannel(dc);
+		return copyAndReturn(dataChannel->protocol(), buffer, size);
 	});
 }
 
-int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb) {
+int rtcGetDataChannelReliability(int dc, rtcReliability *reliability) {
 	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
-		if (cb)
-			peerConnection->onStateChange([pc, cb](PeerConnection::State state) {
-				if (auto ptr = getUserPointer(pc))
-					cb(pc, static_cast<rtcState>(state), *ptr);
-			});
-		else
-			peerConnection->onStateChange(nullptr);
-		return RTC_ERR_SUCCESS;
-	});
-}
+		auto dataChannel = getDataChannel(dc);
 
-int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb) {
-	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
-		if (cb)
-			peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) {
-				if (auto ptr = getUserPointer(pc))
-					cb(pc, static_cast<rtcGatheringState>(state), *ptr);
-			});
-		else
-			peerConnection->onGatheringStateChange(nullptr);
+		if (!reliability)
+			throw std::invalid_argument("Unexpected null pointer for reliability");
+
+		Reliability dcr = dataChannel->reliability();
+		std::memset(reliability, 0, sizeof(*reliability));
+		reliability->unordered = dcr.unordered;
+		if (dcr.type == Reliability::Type::Timed) {
+			reliability->unreliable = true;
+			reliability->maxPacketLifeTime = int(std::get<milliseconds>(dcr.rexmit).count());
+		} else if (dcr.type == Reliability::Type::Rexmit) {
+			reliability->unreliable = true;
+			reliability->maxRetransmits = std::get<int>(dcr.rexmit);
+		} else {
+			reliability->unreliable = false;
+		}
 		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcSetSignalingStateChangeCallback(int pc, rtcSignalingStateCallbackFunc cb) {
+int rtcAddTrack(int pc, const char *mediaDescriptionSdp) {
 	return wrap([&] {
+		if (!mediaDescriptionSdp)
+			throw std::invalid_argument("Unexpected null pointer for track media description");
+
 		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
-			peerConnection->onGatheringStateChange(nullptr);
-		return RTC_ERR_SUCCESS;
+		Description::Media media{string(mediaDescriptionSdp)};
+		int tr = emplaceTrack(peerConnection->addTrack(std::move(media)));
+		if (auto ptr = getUserPointer(pc))
+			rtcSetUserPointer(tr, *ptr);
+
+		return tr;
 	});
 }
 
-int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb) {
+int rtcAddTrackEx(int pc, const rtcTrackInit *init) {
 	return wrap([&] {
 		auto peerConnection = getPeerConnection(pc);
-		if (cb)
-			peerConnection->onDataChannel([pc, cb](shared_ptr<DataChannel> dataChannel) {
-				int dc = emplaceDataChannel(dataChannel);
-				if (auto ptr = getUserPointer(pc)) {
-					rtcSetUserPointer(dc, *ptr);
-					cb(pc, dc, *ptr);
-				}
-			});
-		else
-			peerConnection->onDataChannel(nullptr);
-		return RTC_ERR_SUCCESS;
+
+		if (!init)
+			throw std::invalid_argument("Unexpected null pointer for track init");
+
+		auto direction = static_cast<Description::Direction>(init->direction);
+
+		string mid;
+		if (init->mid) {
+			mid = string(init->mid);
+		} else {
+			switch (init->codec) {
+			case RTC_CODEC_H264:
+			case RTC_CODEC_VP8:
+			case RTC_CODEC_VP9:
+				mid = "video";
+				break;
+			case RTC_CODEC_OPUS:
+				mid = "audio";
+				break;
+			default:
+				mid = "video";
+				break;
+			}
+		}
+
+		optional<Description::Media> optDescription = nullopt;
+
+		switch (init->codec) {
+		case RTC_CODEC_H264:
+		case RTC_CODEC_VP8:
+		case RTC_CODEC_VP9: {
+			auto desc = Description::Video(mid, direction);
+			switch (init->codec) {
+			case RTC_CODEC_H264:
+				desc.addH264Codec(init->payloadType);
+				break;
+			case RTC_CODEC_VP8:
+				desc.addVP8Codec(init->payloadType);
+				break;
+			case RTC_CODEC_VP9:
+				desc.addVP8Codec(init->payloadType);
+				break;
+			default:
+				break;
+			}
+			optDescription = desc;
+			break;
+		}
+		case RTC_CODEC_OPUS: {
+			auto desc = Description::Audio(mid, direction);
+			switch (init->codec) {
+			case RTC_CODEC_OPUS:
+				desc.addOpusCodec(init->payloadType);
+				break;
+			default:
+				break;
+			}
+			optDescription = desc;
+			break;
+		}
+		default:
+			break;
+		}
+
+		if (!optDescription)
+			throw std::invalid_argument("Unexpected codec");
+
+		auto desc = std::move(*optDescription);
+		desc.addSSRC(init->ssrc, init->name ? std::make_optional(string(init->name)) : nullopt,
+		             init->msid ? std::make_optional(string(init->msid)) : nullopt,
+		             init->trackId ? std::make_optional(string(init->trackId)) : nullopt);
+
+		int tr = emplaceTrack(peerConnection->addTrack(std::move(desc)));
+
+		if (auto ptr = getUserPointer(pc))
+			rtcSetUserPointer(tr, *ptr);
+
+		return tr;
 	});
 }
 
-int rtcSetTrackCallback(int pc, rtcTrackCallbackFunc cb) {
+int rtcDeleteTrack(int tr) {
 	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
-		if (cb)
-			peerConnection->onTrack([pc, cb](shared_ptr<Track> track) {
-				int tr = emplaceTrack(track);
-				if (auto ptr = getUserPointer(pc)) {
-					rtcSetUserPointer(tr, *ptr);
-					cb(pc, tr, *ptr);
-				}
-			});
-		else
-			peerConnection->onTrack(nullptr);
+		auto track = getTrack(tr);
+		track->onOpen(nullptr);
+		track->onClosed(nullptr);
+		track->onError(nullptr);
+		track->onMessage(nullptr);
+		track->onBufferedAmountLow(nullptr);
+		track->onAvailable(nullptr);
+
+		eraseTrack(tr);
 		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcSetLocalDescription(int pc, const char *type) {
+int rtcGetTrackDescription(int tr, char *buffer, int size) {
 	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
-		peerConnection->setLocalDescription(type ? Description::stringToType(type)
-		                                         : Description::Type::Unspec);
-		return RTC_ERR_SUCCESS;
+		auto track = getTrack(tr);
+		return copyAndReturn(track->description(), buffer, size);
 	});
 }
 
-int rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
-	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
+#if RTC_ENABLE_MEDIA
 
-		if (!sdp)
-			throw std::invalid_argument("Unexpected null pointer for remote description");
+void setSSRC(Description::Media *description, uint32_t ssrc, const char *_name, const char *_msid,
+             const char *_trackID) {
 
-		peerConnection->setRemoteDescription({string(sdp), type ? string(type) : ""});
-		return RTC_ERR_SUCCESS;
-	});
-}
+	optional<string> name = nullopt;
+	if (_name) {
+		name = string(_name);
+	}
 
-int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid) {
-	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
+	optional<string> msid = nullopt;
+	if (_msid) {
+		msid = string(_msid);
+	}
 
-		if (!cand)
-			throw std::invalid_argument("Unexpected null pointer for remote candidate");
+	optional<string> trackID = nullopt;
+	if (_trackID) {
+		trackID = string(_trackID);
+	}
 
-		peerConnection->addRemoteCandidate({string(cand), mid ? string(mid) : ""});
+	description->addSSRC(ssrc, name, msid, trackID);
+}
+
+int rtcSetH264PacketizationHandler(int tr, const rtcPacketizationHandlerInit *init) {
+	return wrap([&] {
+		auto track = getTrack(tr);
+		// create RTP configuration
+		auto rtpConfig = createRtpPacketizationConfig(init);
+		// create packetizer
+		auto maxFragmentSize = init && init->maxFragmentSize ? init->maxFragmentSize
+		                                                     : RTC_DEFAULT_MAXIMUM_FRAGMENT_SIZE;
+		auto packetizer = std::make_shared<H264RtpPacketizer>(rtpConfig, maxFragmentSize);
+		// create H264 handler
+		auto h264Handler = std::make_shared<H264PacketizationHandler>(packetizer);
+		emplaceMediaChainableHandler(h264Handler, tr);
+		emplaceRtpConfig(rtpConfig, tr);
+		// set handler
+		track->setMediaHandler(h264Handler);
 		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcGetLocalDescription(int pc, char *buffer, int size) {
+int rtcSetOpusPacketizationHandler(int tr, const rtcPacketizationHandlerInit *init) {
 	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
-
-		if (auto desc = peerConnection->localDescription())
-			return copyAndReturn(string(*desc), buffer, size);
-		else
-			return RTC_ERR_NOT_AVAIL;
+		auto track = getTrack(tr);
+		// create RTP configuration
+		auto rtpConfig = createRtpPacketizationConfig(init);
+		// create packetizer
+		auto packetizer = std::make_shared<OpusRtpPacketizer>(rtpConfig);
+		// create Opus handler
+		auto opusHandler = std::make_shared<OpusPacketizationHandler>(packetizer);
+		emplaceMediaChainableHandler(opusHandler, tr);
+		emplaceRtpConfig(rtpConfig, tr);
+		// set handler
+		track->setMediaHandler(opusHandler);
+		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcGetRemoteDescription(int pc, char *buffer, int size) {
-	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
+int rtcChainRtcpSrReporter(int tr) {
+	return wrap([tr] {
+		auto config = getRtpConfig(tr);
+		auto reporter = std::make_shared<RtcpSrReporter>(config);
+		emplaceRtcpSrReporter(reporter, tr);
+		auto chainableHandler = getMediaChainableHandler(tr);
+		chainableHandler->addToChain(reporter);
+		return RTC_ERR_SUCCESS;
+	});
+}
 
-		if (auto desc = peerConnection->remoteDescription())
-			return copyAndReturn(string(*desc), buffer, size);
-		else
-			return RTC_ERR_NOT_AVAIL;
+int rtcChainRtcpNackResponder(int tr, unsigned int maxStoredPacketsCount) {
+	return wrap([tr, maxStoredPacketsCount] {
+		auto responder = std::make_shared<RtcpNackResponder>(maxStoredPacketsCount);
+		auto chainableHandler = getMediaChainableHandler(tr);
+		chainableHandler->addToChain(responder);
+		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcGetLocalDescriptionType(int pc, char *buffer, int size) {
+int rtcSetRtpConfigurationStartTime(int id, const rtcStartTime *startTime) {
 	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
+		auto config = getRtpConfig(id);
+		auto epoch = startTime->since1970 ? RtpPacketizationConfig::EpochStart::T1970
+		                                  : RtpPacketizationConfig::EpochStart::T1900;
+		config->setStartTime(startTime->seconds, epoch, startTime->timestamp);
+		return RTC_ERR_SUCCESS;
+	});
+}
 
-		if (auto desc = peerConnection->localDescription())
-			return copyAndReturn(desc->typeString(), buffer, size);
-		else
-			return RTC_ERR_NOT_AVAIL;
+int rtcStartRtcpSenderReporterRecording(int id) {
+	return wrap([id] {
+		auto sender = getRtcpSrReporter(id);
+		sender->startRecording();
+		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcGetRemoteDescriptionType(int pc, char *buffer, int size) {
+int rtcTransformSecondsToTimestamp(int id, double seconds, uint32_t *timestamp) {
 	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
-
-		if (auto desc = peerConnection->remoteDescription())
-			return copyAndReturn(desc->typeString(), buffer, size);
-		else
-			return RTC_ERR_NOT_AVAIL;
+		auto config = getRtpConfig(id);
+		*timestamp = config->secondsToTimestamp(seconds);
+		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcGetLocalAddress(int pc, char *buffer, int size) {
+int rtcTransformTimestampToSeconds(int id, uint32_t timestamp, double *seconds) {
 	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
-
-		if (auto addr = peerConnection->localAddress())
-			return copyAndReturn(std::move(*addr), buffer, size);
-		else
-			return RTC_ERR_NOT_AVAIL;
+		auto config = getRtpConfig(id);
+		*seconds = config->timestampToSeconds(timestamp);
+		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcGetRemoteAddress(int pc, char *buffer, int size) {
+int rtcGetCurrentTrackTimestamp(int id, uint32_t *timestamp) {
 	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
-
-		if (auto addr = peerConnection->remoteAddress())
-			return copyAndReturn(std::move(*addr), buffer, size);
-		else
-			return RTC_ERR_NOT_AVAIL;
+		auto config = getRtpConfig(id);
+		*timestamp = config->timestamp;
+		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote, int remoteSize) {
+int rtcGetTrackStartTimestamp(int id, uint32_t *timestamp) {
 	return wrap([&] {
-		auto peerConnection = getPeerConnection(pc);
-
-		Candidate localCand;
-		Candidate remoteCand;
-		if (!peerConnection->getSelectedCandidatePair(&localCand, &remoteCand))
-			return RTC_ERR_NOT_AVAIL;
-
-		int localRet = copyAndReturn(string(localCand), local, localSize);
-		if (localRet < 0)
-			return localRet;
-
-		int remoteRet = copyAndReturn(string(remoteCand), remote, remoteSize);
-		if (remoteRet < 0)
-			return remoteRet;
-
-		return std::max(localRet, remoteRet);
+		auto config = getRtpConfig(id);
+		*timestamp = config->startTimestamp;
+		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcGetDataChannelStream(int dc) {
-	return wrap([dc] {
-		auto dataChannel = getDataChannel(dc);
-		return int(dataChannel->id());
+int rtcSetTrackRtpTimestamp(int id, uint32_t timestamp) {
+	return wrap([&] {
+		auto config = getRtpConfig(id);
+		config->timestamp = timestamp;
+		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
+int rtcGetPreviousTrackSenderReportTimestamp(int id, uint32_t *timestamp) {
 	return wrap([&] {
-		auto dataChannel = getDataChannel(dc);
-		return copyAndReturn(dataChannel->label(), buffer, size);
+		auto sender = getRtcpSrReporter(id);
+		*timestamp = sender->previousReportedTimestamp;
+		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcGetDataChannelProtocol(int dc, char *buffer, int size) {
-	return wrap([&] {
-		auto dataChannel = getDataChannel(dc);
-		return copyAndReturn(dataChannel->protocol(), buffer, size);
+int rtcSetNeedsToSendRtcpSr(int id) {
+	return wrap([id] {
+		auto sender = getRtcpSrReporter(id);
+		sender->setNeedsToReport();
+		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcGetDataChannelReliability(int dc, rtcReliability *reliability) {
+int rtcGetTrackPayloadTypesForCodec(int tr, const char *ccodec, int *buffer, int size) {
 	return wrap([&] {
-		auto dataChannel = getDataChannel(dc);
-
-		if (!reliability)
-			throw std::invalid_argument("Unexpected null pointer for reliability");
-
-		Reliability dcr = dataChannel->reliability();
-		std::memset(reliability, 0, sizeof(*reliability));
-		reliability->unordered = dcr.unordered;
-		if (dcr.type == Reliability::Type::Timed) {
-			reliability->unreliable = true;
-			reliability->maxPacketLifeTime = int(std::get<milliseconds>(dcr.rexmit).count());
-		} else if (dcr.type == Reliability::Type::Rexmit) {
-			reliability->unreliable = true;
-			reliability->maxRetransmits = std::get<int>(dcr.rexmit);
-		} else {
-			reliability->unreliable = false;
+		auto track = getTrack(tr);
+		auto codec = lowercased(string(ccodec));
+		auto description = track->description();
+		std::vector<int> payloadTypes{};
+		payloadTypes.reserve(std::max(size, 0));
+		for (auto it = description.beginMaps(); it != description.endMaps(); it++) {
+			auto element = *it;
+			if (lowercased(element.second.format) == codec) {
+				payloadTypes.push_back(element.first);
+			}
 		}
-		return RTC_ERR_SUCCESS;
+		return copyAndReturn(payloadTypes, buffer, size);
 	});
 }
 
-int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb) {
+int rtcGetSsrcsForTrack(int tr, uint32_t *buffer, int count) {
 	return wrap([&] {
-		auto channel = getChannel(id);
-		if (cb)
-			channel->onOpen([id, cb]() {
-				if (auto ptr = getUserPointer(id))
-					cb(id, *ptr);
-			});
-		else
-			channel->onOpen(nullptr);
-		return RTC_ERR_SUCCESS;
+		auto track = getTrack(tr);
+		auto ssrcs = track->description().getSSRCs();
+		return copyAndReturn(ssrcs, buffer, count);
 	});
 }
 
-int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb) {
+int rtcGetCNameForSsrc(int tr, uint32_t ssrc, char *cname, int cnameSize) {
 	return wrap([&] {
-		auto channel = getChannel(id);
-		if (cb)
-			channel->onClosed([id, cb]() {
-				if (auto ptr = getUserPointer(id))
-					cb(id, *ptr);
-			});
-		else
-			channel->onClosed(nullptr);
-		return RTC_ERR_SUCCESS;
+		auto track = getTrack(tr);
+		auto description = track->description();
+		auto optCName = description.getCNameForSsrc(ssrc);
+		if (optCName.has_value()) {
+			return copyAndReturn(optCName.value(), cname, cnameSize);
+		} else {
+			return 0;
+		}
 	});
 }
 
-int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb) {
+int rtcGetSsrcsForType(const char *mediaType, const char *sdp, uint32_t *buffer, int bufferSize) {
 	return wrap([&] {
-		auto channel = getChannel(id);
-		if (cb)
-			channel->onError([id, cb](string error) {
-				if (auto ptr = getUserPointer(id))
-					cb(id, error.c_str(), *ptr);
-			});
-		else
-			channel->onError(nullptr);
-		return RTC_ERR_SUCCESS;
+		auto type = lowercased(string(mediaType));
+		auto oldSDP = string(sdp);
+		auto description = Description(oldSDP, "unspec");
+		auto mediaCount = description.mediaCount();
+		for (unsigned int i = 0; i < mediaCount; i++) {
+			if (std::holds_alternative<Description::Media *>(description.media(i))) {
+				auto media = std::get<Description::Media *>(description.media(i));
+				auto currentMediaType = lowercased(media->type());
+				if (currentMediaType == type) {
+					auto ssrcs = media->getSSRCs();
+					return copyAndReturn(ssrcs, buffer, bufferSize);
+				}
+			}
+		}
+		return 0;
 	});
 }
 
-int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb) {
+int rtcSetSsrcForType(const char *mediaType, const char *sdp, char *buffer, const int bufferSize,
+                      rtcSsrcForTypeInit *init) {
 	return wrap([&] {
-		auto channel = getChannel(id);
-		if (cb)
-			channel->onMessage(
-			    [id, cb](binary b) {
-				    if (auto ptr = getUserPointer(id))
-					    cb(id, reinterpret_cast<const char *>(b.data()), int(b.size()), *ptr);
-			    },
-			    [id, cb](string s) {
-				    if (auto ptr = getUserPointer(id))
-					    cb(id, s.c_str(), -int(s.size() + 1), *ptr);
-			    });
-		else
-			channel->onMessage(nullptr);
-		return RTC_ERR_SUCCESS;
+		auto type = lowercased(string(mediaType));
+		auto prevSDP = string(sdp);
+		auto description = Description(prevSDP, "unspec");
+		auto mediaCount = description.mediaCount();
+		for (unsigned int i = 0; i < mediaCount; i++) {
+			if (std::holds_alternative<Description::Media *>(description.media(i))) {
+				auto media = std::get<Description::Media *>(description.media(i));
+				auto currentMediaType = lowercased(media->type());
+				if (currentMediaType == type) {
+					setSSRC(media, init->ssrc, init->name, init->msid, init->trackId);
+					break;
+				}
+			}
+		}
+		return copyAndReturn(string(description), buffer, bufferSize);
 	});
 }
 
-int rtcSendMessage(int id, const char *data, int size) {
-	return wrap([&] {
-		auto channel = getChannel(id);
+#endif // RTC_ENABLE_MEDIA
 
-		if (!data && size != 0)
-			throw std::invalid_argument("Unexpected null pointer for data");
+#if RTC_ENABLE_WEBSOCKET
 
-		if (size >= 0) {
-			auto b = reinterpret_cast<const byte *>(data);
-			channel->send(binary(b, b + size));
-			return size;
-		} else {
-			string str(data);
-			int len = int(str.size());
-			channel->send(std::move(str));
-			return len;
-		}
+int rtcCreateWebSocket(const char *url) {
+	return wrap([&] {
+		auto webSocket = std::make_shared<WebSocket>();
+		webSocket->open(url);
+		return emplaceWebSocket(webSocket);
 	});
 }
 
-int rtcGetBufferedAmount(int id) {
-	return wrap([id] {
-		auto channel = getChannel(id);
-		return int(channel->bufferedAmount());
+int rtcCreateWebSocketEx(const char *url, const rtcWsConfiguration *config) {
+	return wrap([&] {
+		if (!url)
+			throw std::invalid_argument("Unexpected null pointer for URL");
+
+		if (!config)
+			throw std::invalid_argument("Unexpected null pointer for config");
+
+		WebSocket::Configuration c;
+		c.disableTlsVerification = config->disableTlsVerification;
+		auto webSocket = std::make_shared<WebSocket>(std::move(c));
+		webSocket->open(url);
+		return emplaceWebSocket(webSocket);
 	});
 }
 
-int rtcSetBufferedAmountLowThreshold(int id, int amount) {
+int rtcDeleteWebSocket(int ws) {
 	return wrap([&] {
-		auto channel = getChannel(id);
-		channel->setBufferedAmountLowThreshold(size_t(amount));
+		auto webSocket = getWebSocket(ws);
+		webSocket->onOpen(nullptr);
+		webSocket->onClosed(nullptr);
+		webSocket->onError(nullptr);
+		webSocket->onMessage(nullptr);
+		webSocket->onBufferedAmountLow(nullptr);
+		webSocket->onAvailable(nullptr);
+
+		eraseWebSocket(ws);
 		return RTC_ERR_SUCCESS;
 	});
 }
 
-int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb) {
+int rtcGetWebSocketRemoteAddress(int ws, char *buffer, int size) {
 	return wrap([&] {
-		auto channel = getChannel(id);
-		if (cb)
-			channel->onBufferedAmountLow([id, cb]() {
-				if (auto ptr = getUserPointer(id))
-					cb(id, *ptr);
-			});
+		auto webSocket = getWebSocket(ws);
+		if (auto remoteAddress = webSocket->remoteAddress())
+			return copyAndReturn(*remoteAddress, buffer, size);
 		else
-			channel->onBufferedAmountLow(nullptr);
-		return RTC_ERR_SUCCESS;
+			return RTC_ERR_NOT_AVAIL;
 	});
 }
 
-int rtcGetAvailableAmount(int id) {
-	return wrap([id] { return int(getChannel(id)->availableAmount()); });
-}
-
-int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb) {
+int rtcGetWebSocketPath(int ws, char *buffer, int size) {
 	return wrap([&] {
-		auto channel = getChannel(id);
-		if (cb)
-			channel->onAvailable([id, cb]() {
-				if (auto ptr = getUserPointer(id))
-					cb(id, *ptr);
-			});
+		auto webSocket = getWebSocket(ws);
+		if (auto path = webSocket->path())
+			return copyAndReturn(*path, buffer, size);
 		else
-			channel->onAvailable(nullptr);
-		return RTC_ERR_SUCCESS;
+			return RTC_ERR_NOT_AVAIL;
 	});
 }
 
-int rtcReceiveMessage(int id, char *buffer, int *size) {
+RTC_EXPORT int rtcCreateWebSocketServer(const rtcWsServerConfiguration *config,
+                                        rtcWebSocketClientCallbackFunc cb) {
 	return wrap([&] {
-		auto channel = getChannel(id);
+		if (!config)
+			throw std::invalid_argument("Unexpected null pointer for config");
 
-		if (!size)
-			throw std::invalid_argument("Unexpected null pointer for size");
+		if (!cb)
+			throw std::invalid_argument("Unexpected null pointer for client callback");
 
-		*size = std::abs(*size);
+		WebSocketServer::Configuration c;
+		c.port = config->port;
+		c.enableTls = config->enableTls;
+		c.certificatePemFile = config->certificatePemFile
+		                           ? make_optional(string(config->certificatePemFile))
+		                           : nullopt;
+		c.keyPemFile = config->keyPemFile ? make_optional(string(config->keyPemFile)) : nullopt;
+		c.keyPemPass = config->keyPemPass ? make_optional(string(config->keyPemPass)) : nullopt;
+		auto webSocketServer = std::make_shared<WebSocketServer>(std::move(c));
+		int wsserver = emplaceWebSocketServer(webSocketServer);
 
-		auto message = channel->peek();
-		if (!message)
-			return RTC_ERR_NOT_AVAIL;
+		webSocketServer->onClient([wsserver, cb](shared_ptr<WebSocket> webSocket) {
+			int ws = emplaceWebSocket(webSocket);
+			if (auto ptr = getUserPointer(wsserver)) {
+				rtcSetUserPointer(wsserver, *ptr);
+				cb(wsserver, ws, *ptr);
+			}
+		});
 
-		return std::visit( //
-		    overloaded{
-		        [&](binary b) {
-			        int ret = copyAndReturn(std::move(b), buffer, *size);
-			        if (ret >= 0) {
-				        channel->receive(); // discard
-				        *size = ret;
-				        return RTC_ERR_SUCCESS;
-			        } else {
-				        *size = int(b.size());
-				        return ret;
-			        }
-		        },
-		        [&](string s) {
-			        int ret = copyAndReturn(std::move(s), buffer, *size);
-			        if (ret >= 0) {
-				        channel->receive(); // discard
-				        *size = -ret;
-				        return RTC_ERR_SUCCESS;
-			        } else {
-				        *size = -int(s.size() + 1);
-				        return ret;
-			        }
-		        },
-		    },
-		    *message);
+		return wsserver;
+	});
+}
+
+RTC_EXPORT int rtcDeleteWebSocketServer(int wsserver) {
+	return wrap([&] {
+		auto webSocketServer = getWebSocketServer(wsserver);
+		webSocketServer->onClient(nullptr);
+		webSocketServer->stop();
+
+		eraseWebSocketServer(wsserver);
+		return RTC_ERR_SUCCESS;
+	});
+}
+
+RTC_EXPORT int rtcGetWebSocketServerPort(int wsserver) {
+	return wrap([&] {
+		auto webSocketServer = getWebSocketServer(wsserver);
+		return int(webSocketServer->port());
 	});
 }
 
+#endif
+
 void rtcPreload() { rtc::Preload(); }
 
 void rtcCleanup() { rtc::Cleanup(); }

+ 5 - 13
src/channel.cpp

@@ -18,14 +18,12 @@
 
 #include "channel.hpp"
 
-#include "impl/internals.hpp"
 #include "impl/channel.hpp"
+#include "impl/internals.hpp"
 
 namespace rtc {
 
-Channel::~Channel() {
-	impl()->resetCallbacks();
-}
+Channel::~Channel() { impl()->resetCallbacks(); }
 
 Channel::Channel(impl_ptr<impl::Channel> impl) : CheshireCat<impl::Channel>(std::move(impl)) {}
 
@@ -61,17 +59,11 @@ void Channel::setBufferedAmountLowThreshold(size_t amount) {
 	impl()->bufferedAmountLowThreshold = amount;
 }
 
-optional<message_variant> Channel::receive() {
-	return impl()->receive();
-}
+optional<message_variant> Channel::receive() { return impl()->receive(); }
 
-optional<message_variant> Channel::peek() {
-	return impl()->peek();
-}
+optional<message_variant> Channel::peek() { return impl()->peek(); }
 
-size_t Channel::availableAmount() const {
-	return impl()->availableAmount();
-}
+size_t Channel::availableAmount() const { return impl()->availableAmount(); }
 
 void Channel::onAvailable(std::function<void()> callback) { impl()->availableCallback = callback; }
 

+ 1 - 1
src/description.cpp

@@ -1000,7 +1000,7 @@ string Description::typeToString(Type type) {
 } // namespace rtc
 
 std::ostream &operator<<(std::ostream &out, const rtc::Description &description) {
-	return out << string(description);
+	return out << std::string(description);
 }
 
 std::ostream &operator<<(std::ostream &out, rtc::Description::Type type) {

+ 32 - 18
src/global.cpp

@@ -34,6 +34,30 @@
 #include <locale>
 #endif
 
+namespace {
+
+void plogInit(plog::Severity severity, plog::IAppender *appender) {
+	using Logger = plog::Logger<PLOG_DEFAULT_INSTANCE_ID>;
+	static Logger *logger = nullptr;
+	if (!logger) {
+		PLOG_DEBUG << "Initializing logger";
+		logger = new Logger(severity);
+		if (appender) {
+			logger->addAppender(appender);
+		} else {
+			using ConsoleAppender = plog::ColorConsoleAppender<plog::TxtFormatter>;
+			static ConsoleAppender *consoleAppender = new ConsoleAppender();
+			logger->addAppender(consoleAppender);
+		}
+	} else {
+		logger->setMaxSeverity(severity);
+		if (appender)
+			logger->addAppender(appender);
+	}
+}
+
+}
+
 namespace rtc {
 
 struct LogAppender : public plog::IAppender {
@@ -58,33 +82,24 @@ struct LogAppender : public plog::IAppender {
 };
 
 void InitLogger(LogLevel level, LogCallback callback) {
-	static unique_ptr<LogAppender> appender;
 	const auto severity = static_cast<plog::Severity>(level);
+	static LogAppender *appender = nullptr;
+	static std::mutex mutex;
+	std::lock_guard lock(mutex);
 	if (appender) {
 		appender->callback = std::move(callback);
-		InitLogger(severity, nullptr); // change the severity
+		plogInit(severity, nullptr); // change the severity
 	} else if (callback) {
-		appender = std::make_unique<LogAppender>();
+		appender = new LogAppender();
 		appender->callback = std::move(callback);
-		InitLogger(severity, appender.get());
+		plogInit(severity, appender);
 	} else {
-		InitLogger(severity, nullptr); // log to cout
+		plogInit(severity, nullptr); // log to cout
 	}
 }
 
 void InitLogger(plog::Severity severity, plog::IAppender *appender) {
-	static plog::ColorConsoleAppender<plog::TxtFormatter> consoleAppender;
-	static plog::Logger<0> *logger = nullptr;
-	static std::mutex mutex;
-	std::lock_guard lock(mutex);
-	if (!logger) {
-		logger = &plog::init(severity, appender ? appender : &consoleAppender);
-		PLOG_DEBUG << "Logger initialized";
-	} else {
-		logger->setMaxSeverity(severity);
-		if (appender)
-			logger->addAppender(appender);
-	}
+	plogInit(severity, appender);
 }
 
 void Preload() { Init::Preload(); }
@@ -120,4 +135,3 @@ RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, rtc::LogLevel level)
 	}
 	return out;
 }
-

+ 2 - 1
src/h264packetizationhandler.cpp

@@ -22,7 +22,8 @@
 
 namespace rtc {
 
-H264PacketizationHandler::H264PacketizationHandler(shared_ptr<H264RtpPacketizer> packetizer): MediaChainableHandler(packetizer) { }
+H264PacketizationHandler::H264PacketizationHandler(shared_ptr<H264RtpPacketizer> packetizer)
+    : MediaChainableHandler(packetizer) {}
 
 } // namespace rtc
 

+ 41 - 36
src/h264rtppacketizer.cpp

@@ -42,40 +42,40 @@ typedef enum {
 } NalUnitStartSequenceMatch;
 
 NalUnitStartSequenceMatch StartSequenceMatchSucc(NalUnitStartSequenceMatch match, byte _byte,
-												 H264RtpPacketizer::Separator separator) {
+                                                 H264RtpPacketizer::Separator separator) {
 	assert(separator != H264RtpPacketizer::Separator::Length);
 	auto byte = (uint8_t)_byte;
 	auto detectShort = separator == H264RtpPacketizer::Separator::ShortStartSequence ||
-	separator == H264RtpPacketizer::Separator::StartSequence;
+	                   separator == H264RtpPacketizer::Separator::StartSequence;
 	auto detectLong = separator == H264RtpPacketizer::Separator::LongStartSequence ||
-	separator == H264RtpPacketizer::Separator::StartSequence;
+	                  separator == H264RtpPacketizer::Separator::StartSequence;
 	switch (match) {
-		case NUSM_noMatch:
-			if (byte == 0x00) {
-				return NUSM_firstZero;
-			}
-			break;
-		case NUSM_firstZero:
-			if (byte == 0x00) {
-				return NUSM_secondZero;
-			}
-			break;
-		case NUSM_secondZero:
-			if (byte == 0x00 && detectLong) {
-				return NUSM_thirdZero;
-			} else if (byte == 0x01 && detectShort) {
-				return NUSM_shortMatch;
-			}
-			break;
-		case NUSM_thirdZero:
-			if (byte == 0x01 && detectLong) {
-				return NUSM_longMatch;
-			}
-			break;
-		case NUSM_shortMatch:
+	case NUSM_noMatch:
+		if (byte == 0x00) {
+			return NUSM_firstZero;
+		}
+		break;
+	case NUSM_firstZero:
+		if (byte == 0x00) {
+			return NUSM_secondZero;
+		}
+		break;
+	case NUSM_secondZero:
+		if (byte == 0x00 && detectLong) {
+			return NUSM_thirdZero;
+		} else if (byte == 0x01 && detectShort) {
 			return NUSM_shortMatch;
-		case NUSM_longMatch:
+		}
+		break;
+	case NUSM_thirdZero:
+		if (byte == 0x01 && detectLong) {
 			return NUSM_longMatch;
+		}
+		break;
+	case NUSM_shortMatch:
+		return NUSM_shortMatch;
+	case NUSM_longMatch:
+		return NUSM_longMatch;
 	}
 	return NUSM_noMatch;
 }
@@ -139,16 +139,21 @@ shared_ptr<NalUnits> H264RtpPacketizer::splitMessage(binary_ptr message) {
 }
 
 H264RtpPacketizer::H264RtpPacketizer(shared_ptr<RtpPacketizationConfig> rtpConfig,
-									 uint16_t maximumFragmentSize)
-: RtpPacketizer(rtpConfig), MediaHandlerRootElement(), maximumFragmentSize(maximumFragmentSize), separator(Separator::Length) {}
-
-H264RtpPacketizer::H264RtpPacketizer(H264RtpPacketizer::Separator separator, shared_ptr<RtpPacketizationConfig> rtpConfig,
-									 uint16_t maximumFragmentSize)
-: RtpPacketizer(rtpConfig), MediaHandlerRootElement(), maximumFragmentSize(maximumFragmentSize), separator(separator) {}
-
-ChainedOutgoingProduct H264RtpPacketizer::processOutgoingBinaryMessage(ChainedMessagesProduct messages, message_ptr control) {
+                                     uint16_t maximumFragmentSize)
+    : RtpPacketizer(rtpConfig), MediaHandlerRootElement(), maximumFragmentSize(maximumFragmentSize),
+      separator(Separator::Length) {}
+
+H264RtpPacketizer::H264RtpPacketizer(H264RtpPacketizer::Separator separator,
+                                     shared_ptr<RtpPacketizationConfig> rtpConfig,
+                                     uint16_t maximumFragmentSize)
+    : RtpPacketizer(rtpConfig), MediaHandlerRootElement(), maximumFragmentSize(maximumFragmentSize),
+      separator(separator) {}
+
+ChainedOutgoingProduct
+H264RtpPacketizer::processOutgoingBinaryMessage(ChainedMessagesProduct messages,
+                                                message_ptr control) {
 	ChainedMessagesProduct packets = std::make_shared<std::vector<binary_ptr>>();
-	for (auto message: *messages) {
+	for (auto message : *messages) {
 		auto nalus = splitMessage(message);
 		auto fragments = nalus->generateFragments(maximumFragmentSize);
 		if (fragments.size() == 0) {

+ 8 - 10
src/impl/certificate.cpp

@@ -161,8 +161,6 @@ string make_fingerprint(gnutls_x509_crt_t crt) {
 
 #else // USE_GNUTLS==0
 
-#include <cstdio>
-
 namespace {
 
 // Dummy password callback that copies the password from user data
@@ -198,23 +196,23 @@ Certificate Certificate::FromFile(const string &crt_pem_file, const string &key_
                                   const string &pass) {
 	PLOG_DEBUG << "Importing certificate from PEM file (OpenSSL): " << crt_pem_file;
 
-	FILE *file = fopen(crt_pem_file.c_str(), "r");
-	if (!file)
+	BIO *bio = openssl::BIO_new_from_file(crt_pem_file);
+	if (!bio)
 		throw std::invalid_argument("Unable to open PEM certificate file");
 
-	auto x509 = shared_ptr<X509>(PEM_read_X509(file, nullptr, nullptr, nullptr), X509_free);
-	fclose(file);
+	auto x509 = shared_ptr<X509>(PEM_read_bio_X509(bio, nullptr, nullptr, nullptr), X509_free);
+	BIO_free(bio);
 	if (!x509)
 		throw std::invalid_argument("Unable to import PEM certificate from file");
 
-	file = fopen(key_pem_file.c_str(), "r");
-	if (!file)
+	bio = openssl::BIO_new_from_file(key_pem_file);
+	if (!bio)
 		throw std::invalid_argument("Unable to open PEM key file");
 
 	auto pkey = shared_ptr<EVP_PKEY>(
-	    PEM_read_PrivateKey(file, nullptr, dummy_pass_cb, const_cast<char *>(pass.c_str())),
+	    PEM_read_bio_PrivateKey(bio, nullptr, dummy_pass_cb, const_cast<char *>(pass.c_str())),
 	    EVP_PKEY_free);
-	fclose(file);
+	BIO_free(bio);
 	if (!pkey)
 		throw std::invalid_argument("Unable to import PEM key from file");
 

+ 2 - 2
src/impl/datachannel.hpp

@@ -82,8 +82,8 @@ protected:
 struct NegotiatedDataChannel final : public DataChannel {
 	NegotiatedDataChannel(weak_ptr<PeerConnection> pc, uint16_t stream, string label,
 	                      string protocol, Reliability reliability);
-	NegotiatedDataChannel(weak_ptr<PeerConnection> pc,
-	                      weak_ptr<SctpTransport> transport, uint16_t stream);
+	NegotiatedDataChannel(weak_ptr<PeerConnection> pc, weak_ptr<SctpTransport> transport,
+	                      uint16_t stream);
 	~NegotiatedDataChannel();
 
 	void open(impl_ptr<SctpTransport> transport) override;

+ 6 - 8
src/impl/dtlssrtptransport.cpp

@@ -18,8 +18,8 @@
 
 #include "dtlssrtptransport.hpp"
 #include "logcounter.hpp"
-#include "tls.hpp"
 #include "rtp.hpp"
+#include "tls.hpp"
 
 #if RTC_ENABLE_MEDIA
 
@@ -32,12 +32,11 @@ using std::to_string;
 namespace rtc::impl {
 
 static LogCounter COUNTER_MEDIA_TRUNCATED(plog::warning,
-                                               "Number of truncated SRT(C)P packets received");
+                                          "Number of truncated SRT(C)P packets received");
 static LogCounter
     COUNTER_UNKNOWN_PACKET_TYPE(plog::warning,
                                 "Number of RTP packets received with an unknown packet type");
-static LogCounter COUNTER_SRTCP_REPLAY(plog::warning,
-                                            "Number of SRTCP replay packets received");
+static LogCounter COUNTER_SRTCP_REPLAY(plog::warning, "Number of SRTCP replay packets received");
 static LogCounter
     COUNTER_SRTCP_AUTH_FAIL(plog::warning,
                             "Number of SRTCP packets received that failed authentication checks");
@@ -57,8 +56,7 @@ void DtlsSrtpTransport::Init() { srtp_init(); }
 void DtlsSrtpTransport::Cleanup() { srtp_shutdown(); }
 
 DtlsSrtpTransport::DtlsSrtpTransport(shared_ptr<IceTransport> lower,
-                                     shared_ptr<Certificate> certificate,
-                                     optional<size_t> mtu,
+                                     shared_ptr<Certificate> certificate, optional<size_t> mtu,
                                      verifier_callback verifierCallback,
                                      message_callback srtpRecvCallback,
                                      state_callback stateChangeCallback)
@@ -141,7 +139,7 @@ bool DtlsSrtpTransport::sendMedia(message_ptr message) {
 
 	if (message->dscp == 0) { // Track might override the value
 		// Set recommended medium-priority DSCP value
-		// See https://tools.ietf.org/html/draft-ietf-tsvwg-rtcweb-qos-18
+		// See https://datatracker.ietf.org/doc/html/rfc8837#section-5
 		message->dscp = 36; // AF42: Assured Forwarding class 4, medium drop probability
 	}
 
@@ -324,6 +322,6 @@ void DtlsSrtpTransport::postHandshake() {
 	mInitDone = true;
 }
 
-} // namespace rtc
+} // namespace rtc::impl
 
 #endif

+ 2 - 2
src/impl/dtlssrtptransport.hpp

@@ -19,8 +19,8 @@
 #ifndef RTC_IMPL_DTLS_SRTP_TRANSPORT_H
 #define RTC_IMPL_DTLS_SRTP_TRANSPORT_H
 
-#include "dtlstransport.hpp"
 #include "common.hpp"
+#include "dtlstransport.hpp"
 
 #if RTC_ENABLE_MEDIA
 
@@ -60,7 +60,7 @@ private:
 	std::mutex sendMutex;
 };
 
-} // namespace rtc
+} // namespace rtc::impl
 
 #endif
 

+ 3 - 4
src/impl/dtlstransport.hpp

@@ -42,9 +42,8 @@ public:
 
 	using verifier_callback = std::function<bool(const std::string &fingerprint)>;
 
-	DtlsTransport(shared_ptr<IceTransport> lower, certificate_ptr certificate,
-	              optional<size_t> mtu, verifier_callback verifierCallback,
-	              state_callback stateChangeCallback);
+	DtlsTransport(shared_ptr<IceTransport> lower, certificate_ptr certificate, optional<size_t> mtu,
+	              verifier_callback verifierCallback, state_callback stateChangeCallback);
 	~DtlsTransport();
 
 	virtual void start() override;
@@ -95,6 +94,6 @@ protected:
 #endif
 };
 
-} // namespace rtc
+} // namespace rtc::impl
 
 #endif

+ 1 - 1
src/impl/icetransport.cpp

@@ -559,7 +559,7 @@ Description IceTransport::getLocalDescription(Description::Type type) const {
 	             type == Description::Type::Offer ? TRUE : FALSE, nullptr);
 
 	unique_ptr<gchar[], void (*)(void *)> sdp(nice_agent_generate_local_sdp(mNiceAgent.get()),
-	                                               g_free);
+	                                          g_free);
 
 	// RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of
 	// setup:actpass.

+ 2 - 2
src/impl/icetransport.hpp

@@ -20,9 +20,9 @@
 #define RTC_IMPL_ICE_TRANSPORT_H
 
 #include "candidate.hpp"
+#include "common.hpp"
 #include "configuration.hpp"
 #include "description.hpp"
-#include "common.hpp"
 #include "peerconnection.hpp"
 #include "transport.hpp"
 
@@ -115,6 +115,6 @@ private:
 #endif
 };
 
-} // namespace rtc
+} // namespace rtc::impl
 
 #endif

+ 1 - 1
src/impl/logcounter.cpp

@@ -47,4 +47,4 @@ LogCounter &LogCounter::operator++(int) {
 	return *this;
 }
 
-} // namespace rtc
+} // namespace rtc::impl

+ 1 - 1
src/impl/logcounter.hpp

@@ -46,6 +46,6 @@ public:
 	LogCounter &operator++(int);
 };
 
-} // namespace rtc
+} // namespace rtc::impl
 
 #endif // RTC_SERVER_LOGCOUNTER_HPP

+ 21 - 5
src/impl/peerconnection.cpp

@@ -21,8 +21,8 @@
 #include "certificate.hpp"
 #include "common.hpp"
 #include "dtlstransport.hpp"
-#include "internals.hpp"
 #include "icetransport.hpp"
+#include "internals.hpp"
 #include "logcounter.hpp"
 #include "peerconnection.hpp"
 #include "processor.hpp"
@@ -187,7 +187,7 @@ shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
 		PLOG_VERBOSE << "Starting DTLS transport";
 
 		auto lower = std::atomic_load(&mIceTransport);
-		if(!lower)
+		if (!lower)
 			throw std::logic_error("No underlying ICE transport for DTLS transport");
 
 		auto certificate = mCertificate.get();
@@ -262,7 +262,7 @@ shared_ptr<SctpTransport> PeerConnection::initSctpTransport() {
 		PLOG_VERBOSE << "Starting SCTP transport";
 
 		auto lower = std::atomic_load(&mDtlsTransport);
-		if(!lower)
+		if (!lower)
 			throw std::logic_error("No underlying DTLS transport for SCTP transport");
 
 		auto remote = remoteDescription();
@@ -867,11 +867,21 @@ void PeerConnection::processLocalDescription(Description description) {
 				description.addMedia(std::move(media));
 			}
 		}
+
+		// There might be no media at this point if the user created a Track, deleted it,
+		// then called setLocalDescription().
+		if (description.mediaCount() == 0)
+			throw std::runtime_error("No DataChannel or Track to negotiate");
 	}
 
 	// Set local fingerprint (wait for certificate if necessary)
 	description.setFingerprint(mCertificate.get()->fingerprint());
 
+	PLOG_VERBOSE << "Issuing local description: " << description;
+
+	if (description.mediaCount() == 0)
+		throw std::logic_error("Local description has no media line");
+
 	{
 		// Set as local description
 		std::lock_guard lock(mLocalDescriptionMutex);
@@ -886,7 +896,6 @@ void PeerConnection::processLocalDescription(Description description) {
 		mLocalDescription->addCandidates(std::move(existingCandidates));
 	}
 
-	PLOG_VERBOSE << "Issuing local description: " << description;
 	mProcessor->enqueue(localDescriptionCallback.wrap(), std::move(description));
 
 	// Reciprocated tracks might need to be open
@@ -900,10 +909,17 @@ void PeerConnection::processLocalCandidate(Candidate candidate) {
 	if (!mLocalDescription)
 		throw std::logic_error("Got a local candidate without local description");
 
+	if (config.iceTransportPolicy == TransportPolicy::Relay &&
+	    candidate.type() != Candidate::Type::Relayed) {
+		PLOG_VERBOSE << "Not issuing local candidate because of transport policy: " << candidate;
+		return;
+	}
+
+	PLOG_VERBOSE << "Issuing local candidate: " << candidate;
+
 	candidate.resolve(Candidate::ResolveMode::Simple);
 	mLocalDescription->addCandidate(candidate);
 
-	PLOG_VERBOSE << "Issuing local candidate: " << candidate;
 	mProcessor->enqueue(localCandidateCallback.wrap(), std::move(candidate));
 }
 

+ 1 - 2
src/impl/queue.hpp

@@ -136,8 +136,7 @@ template <typename T> optional<T> Queue<T>::exchange(T element) {
 	return std::make_optional(std::move(element));
 }
 
-template <typename T>
-bool Queue<T>::wait(const optional<std::chrono::milliseconds> &duration) {
+template <typename T> bool Queue<T>::wait(const optional<std::chrono::milliseconds> &duration) {
 	std::unique_lock lock(mMutex);
 	if (duration)
 		mPopCondition.wait_for(lock, *duration, [this]() { return !mQueue.empty() || mStopping; });

+ 1 - 1
src/impl/sctptransport.cpp

@@ -455,7 +455,7 @@ void SctpTransport::incoming(message_ptr message) {
 
 bool SctpTransport::outgoing(message_ptr message) {
 	// Set recommended medium-priority DSCP value
-	// See https://tools.ietf.org/html/draft-ietf-tsvwg-rtcweb-qos-18
+	// See https://datatracker.ietf.org/doc/html/rfc8837#section-5
 	message->dscp = 10; // AF11: Assured Forwarding class 1, low drop probability
 	return Transport::outgoing(std::move(message));
 }

+ 1 - 1
src/impl/sctptransport.hpp

@@ -20,10 +20,10 @@
 #define RTC_IMPL_SCTP_TRANSPORT_H
 
 #include "common.hpp"
+#include "configuration.hpp"
 #include "processor.hpp"
 #include "queue.hpp"
 #include "transport.hpp"
-#include "configuration.hpp"
 
 #include <condition_variable>
 #include <functional>

+ 1 - 3
src/impl/selectinterrupter.hpp

@@ -20,14 +20,12 @@
 #define RTC_IMPL_SELECT_INTERRUPTER_H
 
 #include "common.hpp"
+#include "socket.hpp"
 
 #if RTC_ENABLE_WEBSOCKET
 
 #include <mutex>
 
-// Use the socket defines from libjuice
-#include "../deps/libjuice/src/socket.h"
-
 namespace rtc::impl {
 
 // Utility class to interrupt select()

+ 15 - 19
src/impl/sha.cpp

@@ -33,37 +33,33 @@ namespace {
 binary Sha1(const byte *data, size_t size) {
 #if USE_GNUTLS
 
-binary output(SHA1_DIGEST_SIZE);
-struct sha1_ctx ctx;
-sha1_init(&ctx);
-sha1_update(&ctx, size, reinterpret_cast<const uint8_t*>(data));
-sha1_digest(&ctx, SHA1_DIGEST_SIZE, reinterpret_cast<uint8_t*>(output.data()));
-return output;
+	binary output(SHA1_DIGEST_SIZE);
+	struct sha1_ctx ctx;
+	sha1_init(&ctx);
+	sha1_update(&ctx, size, reinterpret_cast<const uint8_t *>(data));
+	sha1_digest(&ctx, SHA1_DIGEST_SIZE, reinterpret_cast<uint8_t *>(output.data()));
+	return output;
 
 #else // USE_GNUTLS==0
 
-binary output(SHA_DIGEST_LENGTH);
-SHA_CTX ctx;
-SHA1_Init(&ctx);
-SHA1_Update(&ctx, data, size);
-SHA1_Final(reinterpret_cast<unsigned char*>(output.data()), &ctx);
-return output;
+	binary output(SHA_DIGEST_LENGTH);
+	SHA_CTX ctx;
+	SHA1_Init(&ctx);
+	SHA1_Update(&ctx, data, size);
+	SHA1_Final(reinterpret_cast<unsigned char *>(output.data()), &ctx);
+	return output;
 
 #endif
 }
 
-}
-
-binary Sha1(const binary &input) {
-	return Sha1(input.data(), input.size());
-}
+} // namespace
 
+binary Sha1(const binary &input) { return Sha1(input.data(), input.size()); }
 
 binary Sha1(const string &input) {
-	return Sha1(reinterpret_cast<const byte*>(input.data()), input.size());
+	return Sha1(reinterpret_cast<const byte *>(input.data()), input.size());
 }
 
 } // namespace rtc::impl
 
 #endif
-

+ 140 - 0
src/impl/socket.hpp

@@ -0,0 +1,140 @@
+/**
+ * Copyright (c) 2020 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+// This header defines types to allow cross-platform socket API usage.
+
+#ifndef RTC_SOCKET_H
+#define RTC_SOCKET_H
+
+#ifdef _WIN32
+
+#ifndef _WIN32_WINNT
+#define _WIN32_WINNT 0x0601 // Windows 7
+#endif
+#ifndef __MSVCRT_VERSION__
+#define __MSVCRT_VERSION__ 0x0601
+#endif
+
+#include <winsock2.h>
+#include <ws2tcpip.h>
+//
+#include <iphlpapi.h>
+#include <windows.h>
+
+#ifdef __MINGW32__
+#include <sys/stat.h>
+#include <sys/time.h>
+#ifndef IPV6_V6ONLY
+#define IPV6_V6ONLY 27
+#endif
+#endif
+
+#define NO_IFADDRS
+#define NO_PMTUDISC
+
+typedef SOCKET socket_t;
+typedef SOCKADDR sockaddr;
+typedef u_long ctl_t;
+typedef DWORD sockopt_t;
+#define sockerrno ((int)WSAGetLastError())
+#define IP_DONTFRAG IP_DONTFRAGMENT
+#define SOCKET_TO_INT(x) 0
+#define HOST_NAME_MAX 256
+
+#define SEADDRINUSE WSAEADDRINUSE
+#define SEINTR WSAEINTR
+#define SEAGAIN WSAEWOULDBLOCK
+#define SEACCES WSAEACCES
+#define SEWOULDBLOCK WSAEWOULDBLOCK
+#define SEINPROGRESS WSAEINPROGRESS
+#define SECONNREFUSED WSAECONNREFUSED
+#define SECONNRESET WSAECONNRESET
+#define SENETRESET WSAENETRESET
+
+#else // assume POSIX
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#ifndef __linux__
+#define NO_PMTUDISC
+#endif
+
+#ifdef __ANDROID__
+#define NO_IFADDRS
+#else
+#include <ifaddrs.h>
+#endif
+
+typedef int socket_t;
+typedef int ctl_t;
+typedef int sockopt_t;
+#define sockerrno errno
+#define INVALID_SOCKET -1
+#define SOCKET_TO_INT(x) (x)
+#define ioctlsocket ioctl
+#define closesocket close
+
+#define SEADDRINUSE EADDRINUSE
+#define SEINTR EINTR
+#define SEAGAIN EAGAIN
+#define SEACCES EACCES
+#define SEWOULDBLOCK EWOULDBLOCK
+#define SEINPROGRESS EINPROGRESS
+#define SECONNREFUSED ECONNREFUSED
+#define SECONNRESET ECONNRESET
+#define SENETRESET ENETRESET
+
+#endif // _WIN32
+
+#ifndef IN6_IS_ADDR_LOOPBACK
+#define IN6_IS_ADDR_LOOPBACK(a)                                                                    \
+	(((const uint32_t *)(a))[0] == 0 && ((const uint32_t *)(a))[1] == 0 &&                         \
+	 ((const uint32_t *)(a))[2] == 0 && ((const uint32_t *)(a))[3] == htonl(1))
+#endif
+
+#ifndef IN6_IS_ADDR_LINKLOCAL
+#define IN6_IS_ADDR_LINKLOCAL(a)                                                                   \
+	((((const uint32_t *)(a))[0] & htonl(0xffc00000)) == htonl(0xfe800000))
+#endif
+
+#ifndef IN6_IS_ADDR_SITELOCAL
+#define IN6_IS_ADDR_SITELOCAL(a)                                                                   \
+	((((const uint32_t *)(a))[0] & htonl(0xffc00000)) == htonl(0xfec00000))
+#endif
+
+#ifndef IN6_IS_ADDR_V4MAPPED
+#define IN6_IS_ADDR_V4MAPPED(a)                                                                    \
+	((((const uint32_t *)(a))[0] == 0) && (((const uint32_t *)(a))[1] == 0) &&                     \
+	 (((const uint32_t *)(a))[2] == htonl(0xFFFF)))
+#endif
+
+#endif // JUICE_SOCKET_H

+ 1 - 3
src/impl/tcpserver.hpp

@@ -21,13 +21,11 @@
 
 #include "common.hpp"
 #include "queue.hpp"
+#include "socket.hpp"
 #include "tcptransport.hpp"
 
 #if RTC_ENABLE_WEBSOCKET
 
-// Use the socket defines from libjuice
-#include "../deps/libjuice/src/socket.h"
-
 namespace rtc::impl {
 
 class TcpServer {

+ 1 - 1
src/impl/tcptransport.cpp

@@ -83,7 +83,7 @@ bool TcpTransport::stop() {
 
 bool TcpTransport::send(message_ptr message) {
 	std::unique_lock lock(mSockMutex);
-	if(state() == State::Connecting)
+	if (state() == State::Connecting)
 		throw std::runtime_error("Connection is not open");
 
 	if (state() != State::Connected)

+ 2 - 4
src/impl/tcptransport.hpp

@@ -21,17 +21,15 @@
 
 #include "common.hpp"
 #include "queue.hpp"
-#include "transport.hpp"
 #include "selectinterrupter.hpp"
+#include "socket.hpp"
+#include "transport.hpp"
 
 #if RTC_ENABLE_WEBSOCKET
 
 #include <mutex>
 #include <thread>
 
-// Use the socket defines from libjuice
-#include "../deps/libjuice/src/socket.h"
-
 namespace rtc::impl {
 
 class TcpTransport : public Transport {

+ 1 - 1
src/impl/threadpool.cpp

@@ -88,7 +88,7 @@ std::function<void()> ThreadPool::dequeue() {
 		--mBusyWorkers;
 		scope_guard guard([&]() { ++mBusyWorkers; });
 		mWaitingCondition.notify_all();
-		if(time)
+		if (time)
 			mTasksCondition.wait_until(lock, *time);
 		else
 			mTasksCondition.wait(lock);

+ 26 - 0
src/impl/tls.cpp

@@ -20,6 +20,8 @@
 
 #include "internals.hpp"
 
+#include <fstream>
+
 #if USE_GNUTLS
 
 namespace rtc::gnutls {
@@ -126,6 +128,30 @@ bool check(SSL *ssl, int ret, const string &message) {
 	throw std::runtime_error(message + ": " + str);
 }
 
+BIO *BIO_new_from_file(const string &filename) {
+	BIO *bio = nullptr;
+	try {
+		std::ifstream ifs(filename, std::ifstream::in | std::ifstream::binary);
+		if (!ifs.is_open())
+			return nullptr;
+
+		bio = BIO_new(BIO_s_mem());
+
+		const size_t bufferSize = 4096;
+		char buffer[bufferSize];
+		while (ifs.good()) {
+			ifs.read(buffer, bufferSize);
+			BIO_write(bio, buffer, ifs.gcount());
+		}
+		ifs.close();
+		return bio;
+
+	} catch (const std::exception &e) {
+		BIO_free(bio);
+		return nullptr;
+	}
+}
+
 } // namespace rtc::openssl
 
 #endif

+ 2 - 1
src/impl/tls.hpp

@@ -58,7 +58,6 @@ gnutls_datum_t make_datum(char *data, size_t size);
 #include <openssl/bio.h>
 #include <openssl/bn.h>
 #include <openssl/ec.h>
-#include <openssl/ec.h>
 #include <openssl/err.h>
 #include <openssl/pem.h>
 #include <openssl/rsa.h>
@@ -76,6 +75,8 @@ string error_string(unsigned long err);
 bool check(int success, const string &message = "OpenSSL error");
 bool check(SSL *ssl, int ret, const string &message = "OpenSSL error");
 
+BIO *BIO_new_from_file(const string &filename);
+
 } // namespace rtc::openssl
 
 #endif

+ 1 - 1
src/impl/track.cpp

@@ -165,7 +165,7 @@ bool Track::transportSend([[maybe_unused]] message_ptr message) {
 			throw std::runtime_error("Track is closed");
 
 		// Set recommended medium-priority DSCP value
-		// See https://tools.ietf.org/html/draft-ietf-tsvwg-rtcweb-qos-18
+		// See https://datatracker.ietf.org/doc/html/rfc8837#section-5
 		if (mMediaDescription.type() == "audio")
 			message->dscp = 46; // EF: Expedited Forwarding
 		else

+ 2 - 1
src/impl/verifiedtlstransport.hpp

@@ -27,7 +27,8 @@ namespace rtc::impl {
 
 class VerifiedTlsTransport final : public TlsTransport {
 public:
-	VerifiedTlsTransport(shared_ptr<TcpTransport> lower, string host, certificate_ptr certificate, state_callback callback);
+	VerifiedTlsTransport(shared_ptr<TcpTransport> lower, string host, certificate_ptr certificate,
+	                     state_callback callback);
 	~VerifiedTlsTransport();
 };
 

Some files were not shown because too many files changed in this diff