فهرست منبع

Merge branch 'master' into fix-uwp

Paul-Louis Ageneau 4 سال پیش
والد
کامیت
4a526f66b6
62فایلهای تغییر یافته به همراه1835 افزوده شده و 946 حذف شده
  1. 2 2
      .github/workflows/build-gnutls.yml
  2. 15 1
      .github/workflows/build-nice.yml
  3. 3 2
      .github/workflows/build-openssl.yml
  4. 3 0
      .gitmodules
  5. 28 27
      CMakeLists.txt
  6. 27 3
      README.md
  7. 1 1
      deps/libjuice
  8. 1 0
      deps/libsrtp
  9. 4 0
      examples/client/getopt.cpp
  10. 2 2
      examples/media/main.cpp
  11. 15 0
      examples/sfu-media/CMakeLists.txt
  12. 134 0
      examples/sfu-media/main.cpp
  13. 87 0
      examples/sfu-media/main.html
  14. 1 1
      examples/web/script.js
  15. 6 3
      include/rtc/candidate.hpp
  16. 2 2
      include/rtc/channel.hpp
  17. 27 11
      include/rtc/datachannel.hpp
  18. 41 16
      include/rtc/description.hpp
  19. 1 1
      include/rtc/log.hpp
  20. 17 9
      include/rtc/peerconnection.hpp
  21. 0 1
      include/rtc/queue.hpp
  22. 0 1
      include/rtc/reliability.hpp
  23. 35 21
      include/rtc/rtc.h
  24. 0 1
      include/rtc/rtc.hpp
  25. 43 11
      include/rtc/rtcp.hpp
  26. 493 0
      include/rtc/rtp.hpp
  27. 4 1
      include/rtc/track.hpp
  28. 1 0
      include/rtc/websocket.hpp
  29. 1 2
      src/base64.cpp
  30. 29 24
      src/candidate.cpp
  31. 127 178
      src/capi.cpp
  32. 11 10
      src/certificate.cpp
  33. 3 10
      src/channel.cpp
  34. 89 56
      src/datachannel.cpp
  35. 160 48
      src/description.cpp
  36. 80 26
      src/dtlssrtptransport.cpp
  37. 11 1
      src/dtlssrtptransport.hpp
  38. 2 3
      src/dtlstransport.cpp
  39. 0 1
      src/dtlstransport.hpp
  40. 10 8
      src/icetransport.cpp
  41. 1 1
      src/icetransport.hpp
  42. 0 1
      src/init.cpp
  43. 1 2
      src/log.cpp
  44. 157 68
      src/peerconnection.cpp
  45. 0 1
      src/processor.cpp
  46. 1 2
      src/processor.hpp
  47. 35 324
      src/rtcp.cpp
  48. 3 3
      src/sctptransport.cpp
  49. 1 1
      src/sctptransport.hpp
  50. 0 1
      src/threadpool.cpp
  51. 7 7
      src/threadpool.hpp
  52. 0 1
      src/tls.cpp
  53. 2 2
      src/tls.hpp
  54. 8 9
      src/tlstransport.cpp
  55. 41 19
      src/track.cpp
  56. 7 5
      src/transport.hpp
  57. 0 2
      src/verifiedtlstransport.cpp
  58. 16 4
      src/websocket.cpp
  59. 1 1
      src/wstransport.cpp
  60. 33 2
      test/connectivity.cpp
  61. 1 1
      test/track.cpp
  62. 4 5
      test/websocket.cpp

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

@@ -12,11 +12,11 @@ jobs:
     steps:
     - uses: actions/checkout@v2
     - name: install packages
-      run: sudo apt update && sudo apt install libgnutls28-dev nettle-dev
+      run: sudo apt update && sudo apt install libgnutls28-dev nettle-dev libsrtp2-dev
     - name: submodules
       run: git submodule update --init --recursive
     - name: cmake
-      run: cmake -B build -DUSE_GNUTLS=1 -DWARNINGS_AS_ERRORS=1
+      run: cmake -B build -DUSE_GNUTLS=1 -DUSE_SYSTEM_SRTP=1 -DWARNINGS_AS_ERRORS=1
     - name: make
       run: (cd build; make -j2)
     - name: test

+ 15 - 1
.github/workflows/build-nice.yml

@@ -7,7 +7,7 @@ on:
     branches:
     - master
 jobs:
-  build-linux:
+  build-media:
     runs-on: ubuntu-latest
     steps:
     - uses: actions/checkout@v2
@@ -21,4 +21,18 @@ jobs:
       run: (cd build; make -j2)
     - name: test
       run: ./build/tests
+  build-no-media:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+    - name: install packages
+      run: sudo apt update && sudo apt install libgnutls28-dev libnice-dev
+    - name: submodules
+      run: git submodule update --init --recursive
+    - name: cmake
+      run: cmake -B build -DUSE_GNUTLS=1 -DUSE_NICE=1 -DNO_MEDIA=1 -DWARNINGS_AS_ERRORS=1
+    - name: make
+      run: (cd build; make -j2)
+    - name: test
+      run: ./build/tests
 

+ 3 - 2
.github/workflows/build-openssl.yml

@@ -12,11 +12,11 @@ jobs:
     steps:
     - uses: actions/checkout@v2
     - name: install packages
-      run: sudo apt update && sudo apt install libssl-dev
+      run: sudo apt update && sudo apt install libssl-dev libsrtp2-dev
     - name: submodules
       run: git submodule update --init --recursive
     - name: cmake
-      run: cmake -B build -DUSE_GNUTLS=0 -DWARNINGS_AS_ERRORS=1
+      run: cmake -B build -DUSE_GNUTLS=0 -DUSE_SYSTEM_SRTP=1 -DWARNINGS_AS_ERRORS=1
     - name: make
       run: (cd build; make -j2)
     - name: test
@@ -52,6 +52,7 @@ jobs:
     - name: nmake
       run: |
         cd build
+        set CL=/MP
         nmake
     - name: test
       run: build/tests.exe

+ 3 - 0
.gitmodules

@@ -10,3 +10,6 @@
 [submodule "deps/json"]
 	path = deps/json
 	url = https://github.com/nlohmann/json.git
+[submodule "deps/libsrtp"]
+	path = deps/libsrtp
+	url = https://github.com/cisco/libsrtp.git

+ 28 - 27
CMakeLists.txt

@@ -7,15 +7,14 @@ set(PROJECT_DESCRIPTION "WebRTC Data Channels Library")
 # 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(NO_WEBSOCKET "Disable WebSocket support" OFF)
+option(NO_MEDIA "Disable media transport support" OFF)
 option(NO_EXAMPLES "Disable examples" OFF)
 option(NO_TESTS "Disable tests build" OFF)
 option(WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
 option(RSA_KEY_BITS_2048 "Use 2048-bit RSA key instead of 3072-bit" OFF)
 option(CAPI_STDCALL "Set calling convention of C API callbacks stdcall" OFF)
-# Option USE_SRTP defaults to AUTO (enabled if libSRTP is found, else disabled)
-set(USE_SRTP AUTO CACHE STRING "Use libSRTP and enable media support")
-set_property(CACHE USE_SRTP PROPERTY STRINGS AUTO ON OFF)
 
 if(USE_NICE)
 	option(USE_JUICE "Use libjuice" OFF)
@@ -90,6 +89,7 @@ set(LIBDATACHANNEL_HEADERS
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/reliability.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtc.h
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtc.hpp
+	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtp.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/track.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/websocket.hpp
 )
@@ -166,32 +166,32 @@ if(WIN32)
 	target_link_libraries(datachannel-static PRIVATE ws2_32) # winsock2
 endif()
 
-if(USE_SRTP STREQUAL "AUTO")
-	find_package(SRTP)
-	if(SRTP_FOUND)
-		message(STATUS "LibSRTP found, compiling with media transport")
-	else()
-		message(STATUS "LibSRTP NOT found, compiling WITHOUT media transport")
-	endif()
-elseif(USE_SRTP)
-	find_package(SRTP REQUIRED)
-endif()
-
-if(USE_SRTP AND SRTP_FOUND)
-	if(NOT TARGET SRTP::SRTP)
-		add_library(SRTP::SRTP UNKNOWN IMPORTED)
-		set_target_properties(SRTP::SRTP PROPERTIES
-			INTERFACE_INCLUDE_DIRECTORIES ${SRTP_INCLUDE_DIRS}
-			IMPORTED_LINK_INTERFACE_LANGUAGES C
-			IMPORTED_LOCATION ${SRTP_LIBRARIES})
-	endif()
-	target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=1)
-	target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=1)
-	target_link_libraries(datachannel PRIVATE SRTP::SRTP)
-	target_link_libraries(datachannel-static PRIVATE SRTP::SRTP)
-else()
+if(NO_MEDIA)
 	target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=0)
 	target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=0)
+else()
+	target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=1)
+	target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=1)
+	if(USE_SYSTEM_SRTP)
+		find_package(SRTP REQUIRED)
+		if(NOT TARGET SRTP::SRTP)
+			add_library(SRTP::SRTP UNKNOWN IMPORTED)
+			set_target_properties(SRTP::SRTP PROPERTIES
+				INTERFACE_INCLUDE_DIRECTORIES ${SRTP_INCLUDE_DIRS}
+				IMPORTED_LINK_INTERFACE_LANGUAGES C
+				IMPORTED_LOCATION ${SRTP_LIBRARIES})
+		endif()
+		target_compile_definitions(datachannel PRIVATE RTC_SYSTEM_SRTP=1)
+		target_compile_definitions(datachannel-static PRIVATE RTC_SYSTEM_SRTP=1)
+		target_link_libraries(datachannel PRIVATE SRTP::SRTP)
+		target_link_libraries(datachannel-static PRIVATE SRTP::SRTP)
+	else()
+		add_subdirectory(deps/libsrtp EXCLUDE_FROM_ALL)
+		target_compile_definitions(datachannel PRIVATE RTC_SYSTEM_SRTP=0)
+		target_compile_definitions(datachannel-static PRIVATE RTC_SYSTEM_SRTP=0)
+		target_link_libraries(datachannel PRIVATE srtp2)
+		target_link_libraries(datachannel-static PRIVATE srtp2)
+	endif()
 endif()
 
 if (USE_GNUTLS)
@@ -300,6 +300,7 @@ if(NOT NO_EXAMPLES AND NOT CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
 	add_subdirectory(deps/json)
 	add_subdirectory(examples/client)
 	add_subdirectory(examples/media)
+	add_subdirectory(examples/sfu-media)
 	add_subdirectory(examples/copy-paste)
 	add_subdirectory(examples/copy-paste-capi)
 endif()

+ 27 - 3
README.md

@@ -19,7 +19,7 @@ The WebRTC stack has been tested to be compatible with Firefox and Chromium.
 
 Protocol stack:
 - SCTP-based Data Channels ([draft-ietf-rtcweb-data-channel-13](https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13))
-- SRTP-based Media Transport ([draft-ietf-rtcweb-rtp-usage-26](https://tools.ietf.org/html/draft-ietf-rtcweb-rtp-usage-26)) with [libSRTP](https://github.com/cisco/libsrtp)
+- SRTP-based Media Transport ([draft-ietf-rtcweb-rtp-usage-26](https://tools.ietf.org/html/draft-ietf-rtcweb-rtp-usage-26))
 - DTLS/UDP ([RFC7350](https://tools.ietf.org/html/rfc7350) and [RFC8261](https://tools.ietf.org/html/rfc8261))
 - ICE ([RFC8445](https://tools.ietf.org/html/rfc8445)) with STUN ([RFC5389](https://tools.ietf.org/html/rfc5389))
 
@@ -53,10 +53,11 @@ Dependencies:
 Submodules:
 - libjuice: https://github.com/paullouisageneau/libjuice
 - usrsctp: https://github.com/sctplab/usrsctp
+- libsrtp: https://github.com/cisco/libsrtp
 
 Optional dependencies:
-- libnice: https://nice.freedesktop.org/ (only if selected as ICE backend instead of libjuice)
-- libSRTP: https://github.com/cisco/libsrtp (only necessary for supporting media transport)
+- libnice: https://nice.freedesktop.org/ (if selected as ICE backend instead of libjuice)
+- libsrtp: https://github.com/cisco/libsrtp (if selected instead of the submodule)
 
 ## Building
 
@@ -81,6 +82,29 @@ $ cd build
 $ make -j2
 ```
 
+#### Apple macOS with XCode project
+
+```bash
+$ cmake -B "$BUILD_DIR" -DUSE_GNUTLS=0 -DUSE_NICE=0 -G Xcode
+```
+
+Xcode project is generated in *build/* directory.
+
+##### Solving **Could NOT find OpenSSL** error
+
+You need to add OpenSSL root directory if your 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)
+```
+
+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

+ 1 - 1
deps/libjuice

@@ -1 +1 @@
-Subproject commit 0a44ac2d26959fcdda204fa6814b39624ebf84d1
+Subproject commit 7afe3940dd73fde781333af556310f8a499cff40

+ 1 - 0
deps/libsrtp

@@ -0,0 +1 @@
+Subproject commit d02d21111e379c297e93a9033d7b653135f732ee

+ 4 - 0
examples/client/getopt.cpp

@@ -36,7 +36,11 @@ PROFITS, BUSINESS INTERRUPTION, LOSS OF PROGRAMS OR OTHER DATA ON
 YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE
 EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 */
+
+#ifndef _CRT_SECURE_NO_WARNINGS
 #define _CRT_SECURE_NO_WARNINGS
+#endif
+
 #include <stdlib.h>
 #include <stdio.h>
 #include <malloc.h>

+ 2 - 2
examples/media/main.cpp

@@ -1,6 +1,6 @@
 /*
  * libdatachannel client example
- * Copyright (c) 2020 Staz M
+ * Copyright (c) 2020 Staz Modrzynski
  * Copyright (c) 2020 Paul-Louis Ageneau
  *
  * This program is free software; you can redistribute it and/or
@@ -67,7 +67,7 @@ int main() {
 
 		auto track = pc->addTrack(media);
 
-		auto session = std::make_shared<rtc::RtcpSession>();
+		auto session = std::make_shared<rtc::RtcpReceivingSession>();
 		track->setRtcpHandler(session);
 
 		track->onMessage(

+ 15 - 0
examples/sfu-media/CMakeLists.txt

@@ -0,0 +1,15 @@
+cmake_minimum_required(VERSION 3.7)
+
+add_executable(datachannel-sfu-media main.cpp)
+set_target_properties(datachannel-sfu-media PROPERTIES
+		CXX_STANDARD 17
+		OUTPUT_NAME sfu-media)
+
+if(WIN32)
+	target_link_libraries(datachannel-sfu-media datachannel-static) # DLL exports only the C API
+else()
+	target_link_libraries(datachannel-sfu-media datachannel)
+endif()
+
+target_link_libraries(datachannel-sfu-media datachannel nlohmann_json)
+

+ 134 - 0
examples/sfu-media/main.cpp

@@ -0,0 +1,134 @@
+/*
+ * libdatachannel client example
+ * Copyright (c) 2020 Staz Modrzynski
+ * Copyright (c) 2020 Paul-Louis Ageneau
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _WINSOCK_DEPRECATED_NO_WARNINGS
+
+#include "rtc/rtc.hpp"
+
+#include <iostream>
+#include <memory>
+
+#include <nlohmann/json.hpp>
+
+using nlohmann::json;
+
+struct Receiver {
+	std::shared_ptr<rtc::PeerConnection> conn;
+	std::shared_ptr<rtc::Track> track;
+};
+int main() {
+	std::vector<std::shared_ptr<Receiver>> receivers;
+
+	try {
+		rtc::InitLogger(rtc::LogLevel::Info);
+
+		auto pc = std::make_shared<rtc::PeerConnection>();
+		pc->onStateChange(
+		    [](rtc::PeerConnection::State state) { std::cout << "State: " << state << std::endl; });
+		pc->onGatheringStateChange([pc](rtc::PeerConnection::GatheringState state) {
+			std::cout << "Gathering State: " << state << std::endl;
+			if (state == rtc::PeerConnection::GatheringState::Complete) {
+				auto description = pc->localDescription();
+				json message = {{"type", description->typeString()},
+				                {"sdp", std::string(description.value())}};
+				std::cout << "Please copy/paste this offer to the SENDER: " << message << std::endl;
+			}
+		});
+
+		rtc::Description::Video media("video", rtc::Description::Direction::RecvOnly);
+		media.addH264Codec(96);
+		media.setBitrate(
+		    3000); // Request 3Mbps (Browsers do not encode more than 2.5MBps from a webcam)
+
+		auto track = pc->addTrack(media);
+		pc->setLocalDescription();
+
+		auto session = std::make_shared<rtc::RtcpReceivingSession>();
+		track->setRtcpHandler(session);
+
+		const rtc::SSRC targetSSRC = 4;
+
+		track->onMessage(
+		    [&receivers, targetSSRC](rtc::binary message) {
+			    // This is an RTP packet
+			    auto rtp = (rtc::RTP *)message.data();
+			    rtp->setSsrc(targetSSRC);
+			    for (auto pc : receivers) {
+				    if (pc->track != nullptr && pc->track->isOpen()) {
+					    pc->track->send(message);
+				    }
+			    }
+		    },
+		    nullptr);
+
+		// Set the SENDERS Answer
+		{
+			std::cout << "Please copy/paste the answer provided by the SENDER: " << std::endl;
+			std::string sdp;
+			std::getline(std::cin, sdp);
+			std::cout << "Got answer" << sdp << std::endl;
+			json j = json::parse(sdp);
+			rtc::Description answer(j["sdp"].get<std::string>(), j["type"].get<std::string>());
+			pc->setRemoteDescription(answer);
+		}
+
+		// For each receiver
+		while (true) {
+			auto pc = std::make_shared<Receiver>();
+			pc->conn = std::make_shared<rtc::PeerConnection>();
+			pc->conn->onStateChange([](rtc::PeerConnection::State state) {
+				std::cout << "State: " << state << std::endl;
+			});
+			pc->conn->onGatheringStateChange([pc](rtc::PeerConnection::GatheringState state) {
+				std::cout << "Gathering State: " << state << std::endl;
+				if (state == rtc::PeerConnection::GatheringState::Complete) {
+					auto description = pc->conn->localDescription();
+					json message = {{"type", description->typeString()},
+					                {"sdp", std::string(description.value())}};
+					std::cout << "Please copy/paste this offer to the RECEIVER: " << message
+					          << std::endl;
+				}
+			});
+			rtc::Description::Video media("video", rtc::Description::Direction::SendOnly);
+			media.addH264Codec(96);
+			media.setBitrate(
+			    3000); // Request 3Mbps (Browsers do not encode more than 2.5MBps from a webcam)
+
+			media.addSSRC(targetSSRC, "video-send");
+
+			pc->track = pc->conn->addTrack(media);
+			pc->conn->setLocalDescription();
+
+			pc->track->onMessage([](rtc::binary var) {}, nullptr);
+
+			std::cout << "Please copy/paste the answer provided by the RECEIVER: " << std::endl;
+			std::string sdp;
+			std::getline(std::cin, sdp);
+			std::cout << "Got answer" << sdp << std::endl;
+			json j = json::parse(sdp);
+			rtc::Description answer(j["sdp"].get<std::string>(), j["type"].get<std::string>());
+			pc->conn->setRemoteDescription(answer);
+
+			receivers.push_back(pc);
+		}
+
+	} catch (const std::exception &e) {
+		std::cerr << "Error: " << e.what() << std::endl;
+	}
+}

+ 87 - 0
examples/sfu-media/main.html

@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>libdatachannel media example</title>
+</head>
+<body>
+
+<div style="display:inline-block; width:40%;">
+    <h1>SENDER</h1>
+    <p id="send-help">Please enter the offer provided to you by the application: </p>
+    <textarea style="width:100%;" id=send-text rows="50"></textarea>
+    <button id=send-btn>Submit</button>
+</div>
+<div style="display:inline-block; width:40%;">
+    <h1>RECEIVER</h1>
+    <p id="recv-help">Please enter the offer provided to you by the application: </p>
+    <textarea id=recv-text style="width:100%;" rows="50"></textarea>
+    <button id=recv-btn>Submit</button>
+</div>
+<div id="videos">
+
+</div>
+<script>
+    document.querySelector('#send-btn').addEventListener('click',  async () => {
+        let offer = JSON.parse(document.querySelector('#send-text').value);
+        rtc = new RTCPeerConnection({
+            // Recommended for libdatachannel
+            bundlePolicy: "max-bundle",
+        });
+
+        rtc.onicegatheringstatechange = (state) => {
+            if (rtc.iceGatheringState === 'complete') {
+                // We only want to provide an answer once all of our candidates have been added to the SDP.
+                let answer = rtc.localDescription;
+                document.querySelector('#send-text').value = JSON.stringify({"type": answer.type, sdp: answer.sdp});
+                document.querySelector('#send-help').value = 'Please paste the answer in the application.';
+                alert('Please paste the answer in the application.');
+            }
+        }
+        await rtc.setRemoteDescription(offer);
+
+        let media = await navigator.mediaDevices.getUserMedia({
+            video: {
+                width: 1280,
+                height: 720
+            }
+        });
+        media.getTracks().forEach(track => rtc.addTrack(track, media));
+        let answer = await rtc.createAnswer();
+        await rtc.setLocalDescription(answer);
+    });
+
+    document.querySelector('#recv-btn').addEventListener('click',  async () => {
+        let offer = JSON.parse(document.querySelector('#recv-text').value);
+        rtc = new RTCPeerConnection({
+            // Recommended for libdatachannel
+            bundlePolicy: "max-bundle",
+        });
+
+        rtc.onicegatheringstatechange = (state) => {
+            if (rtc.iceGatheringState === 'complete') {
+                // We only want to provide an answer once all of our candidates have been added to the SDP.
+                let answer = rtc.localDescription;
+                document.querySelector('#recv-text').value = JSON.stringify({"type": answer.type, sdp: answer.sdp});
+                document.querySelector('#recv-help').value = 'Please paste the answer in the application.';
+                alert('Please paste the answer in the application.');
+            }
+        }
+        let trackCount = 0;
+        rtc.ontrack = (ev) => {
+            let thisID = trackCount++;
+
+            document.querySelector("#videos").innerHTML += "<video width=100% height=100% id='video-" + thisID + "'></video>";
+            let tracks = [];
+            rtc.getReceivers().forEach(recv => tracks.push(recv.track));
+            document.querySelector("#video-" + thisID).srcObject = new MediaStream(tracks);
+            document.querySelector("#video-" + thisID).play();
+        };
+        await rtc.setRemoteDescription(offer);
+        let answer = await rtc.createAnswer();
+        await rtc.setLocalDescription(answer);
+    });
+</script>
+
+</body>
+</html>

+ 1 - 1
examples/web/script.js

@@ -55,7 +55,7 @@ function openSignaling(url) {
     const ws = new WebSocket(url);
     ws.onopen = () => resolve(ws);
     ws.onerror = () => reject(new Error('WebSocket error'));
-    ws.onclosed = () => console.error('WebSocket disconnected');
+    ws.onclose = () => console.error('WebSocket disconnected');
     ws.onmessage = (e) => {
       if(typeof(e.data) != 'string') return;
       const message = JSON.parse(e.data);

+ 6 - 3
include/rtc/candidate.hpp

@@ -31,7 +31,11 @@ public:
 	enum class Type { Unknown, Host, ServerReflexive, PeerReflexive, Relayed };
 	enum class TransportType { Unknown, Udp, TcpActive, TcpPassive, TcpSo, TcpUnknown };
 
-	Candidate(string candidate = "", string mid = "");
+	Candidate();
+	Candidate(string candidate);
+	Candidate(string candidate, string mid);
+
+	void hintMid(string mid);
 
 	enum class ResolveMode { Simple, Lookup };
 	bool resolve(ResolveMode mode = ResolveMode::Simple);
@@ -50,7 +54,7 @@ public:
 
 private:
 	string mCandidate;
-	string mMid;
+	std::optional<string> mMid;
 
 	// Extracted on resolution
 	Family mFamily;
@@ -68,4 +72,3 @@ std::ostream &operator<<(std::ostream &out, const rtc::Candidate::Type &type);
 std::ostream &operator<<(std::ostream &out, const rtc::Candidate::TransportType &transportType);
 
 #endif
-

+ 2 - 2
include/rtc/channel.hpp

@@ -54,7 +54,8 @@ public:
 
 	// Extended API
 	virtual std::optional<message_variant> receive() = 0; // only if onMessage unset
-	virtual size_t availableAmount() const; // total size available to receive
+	virtual std::optional<message_variant> peek() = 0;    // only if onMessage unset
+	virtual size_t availableAmount() const;               // total size available to receive
 	void onAvailable(std::function<void()> callback);
 
 protected:
@@ -81,4 +82,3 @@ private:
 } // namespace rtc
 
 #endif // RTC_CHANNEL_H
-

+ 27 - 11
include/rtc/datachannel.hpp

@@ -36,15 +36,14 @@ namespace rtc {
 class SctpTransport;
 class PeerConnection;
 
-class DataChannel final : public std::enable_shared_from_this<DataChannel>, public Channel {
+class DataChannel : public std::enable_shared_from_this<DataChannel>, public Channel {
 public:
-	DataChannel(std::weak_ptr<PeerConnection> pc, unsigned int stream, string label,
-	            string protocol, Reliability reliability);
-	DataChannel(std::weak_ptr<PeerConnection> pc, std::weak_ptr<SctpTransport> transport,
-	            unsigned int stream);
-	~DataChannel();
+	DataChannel(std::weak_ptr<PeerConnection> pc, uint16_t stream, string label, string protocol,
+	            Reliability reliability);
+	virtual ~DataChannel();
 
-	unsigned int stream() const;
+	uint16_t stream() const;
+	uint16_t id() const;
 	string label() const;
 	string protocol() const;
 	Reliability reliability() const;
@@ -62,18 +61,19 @@ public:
 	// Extended API
 	size_t availableAmount() const override;
 	std::optional<message_variant> receive() override;
+	std::optional<message_variant> peek() override;
 
-private:
+protected:
+	virtual void open(std::shared_ptr<SctpTransport> transport);
+	virtual void processOpenMessage(message_ptr message);
 	void remoteClose();
-	void open(std::shared_ptr<SctpTransport> transport);
 	bool outgoing(message_ptr message);
 	void incoming(message_ptr message);
-	void processOpenMessage(message_ptr message);
 
 	const std::weak_ptr<PeerConnection> mPeerConnection;
 	std::weak_ptr<SctpTransport> mSctpTransport;
 
-	unsigned int mStream;
+	uint16_t mStream;
 	string mLabel;
 	string mProtocol;
 	std::shared_ptr<Reliability> mReliability;
@@ -81,11 +81,27 @@ private:
 	std::atomic<bool> mIsOpen = false;
 	std::atomic<bool> mIsClosed = false;
 
+private:
 	Queue<message_ptr> mRecvQueue;
 
 	friend class PeerConnection;
 };
 
+class NegociatedDataChannel final : public DataChannel {
+public:
+	NegociatedDataChannel(std::weak_ptr<PeerConnection> pc, uint16_t stream, string label,
+	                      string protocol, Reliability reliability);
+	NegociatedDataChannel(std::weak_ptr<PeerConnection> pc, std::weak_ptr<SctpTransport> transport,
+	                      uint16_t stream);
+	~NegociatedDataChannel();
+
+private:
+	void open(std::shared_ptr<SctpTransport> transport) override;
+	void processOpenMessage(message_ptr message) override;
+
+	friend class PeerConnection;
+};
+
 template <typename Buffer> std::pair<const byte *, size_t> to_bytes(const Buffer &buf) {
 	using T = typename std::remove_pointer<decltype(buf.data())>::type;
 	using E = typename std::conditional<std::is_void<T>::value, byte, T>::type;

+ 41 - 16
include/rtc/description.hpp

@@ -1,6 +1,6 @@
 /**
  * Copyright (c) 2019-2020 Paul-Louis Ageneau
- * Copyright (c) 2020 Staz M
+ * Copyright (c) 2020 Staz Modrzynski
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -38,9 +38,8 @@ public:
 	enum class Role { ActPass, Passive, Active };
 	enum class Direction { SendOnly, RecvOnly, SendRecv, Inactive, Unknown };
 
-	Description(const string &sdp, const string &typeString = "");
-	Description(const string &sdp, Type type);
-	Description(const string &sdp, Type type, Role role);
+	Description(const string &sdp, Type type = Type::Unspec, Role role = Role::ActPass);
+	Description(const string &sdp, string typeString);
 
 	Type type() const;
 	string typeString() const;
@@ -78,6 +77,10 @@ public:
 
 		virtual void parseSdpLine(string_view line);
 
+		std::vector<string>::iterator beginAttributes();
+		std::vector<string>::iterator endAttributes();
+		std::vector<string>::iterator removeAttribute(std::vector<string>::iterator iterator);
+
 	protected:
 		Entry(const string &mline, string mid, Direction dir = Direction::Unknown);
 		virtual string generateSdpLines(string_view eol) const;
@@ -127,10 +130,11 @@ public:
 
 		void removeFormat(const string &fmt);
 
-		void addVideoCodec(int payloadType, const string &codec);
-		void addH264Codec(int payloadType);
-		void addVP8Codec(int payloadType);
-		void addVP9Codec(int payloadType);
+		void addSSRC(uint32_t ssrc, std::string name);
+		void addSSRC(uint32_t ssrc);
+		void replaceSSRC(uint32_t oldSSRC, uint32_t ssrc, string name);
+		bool hasSSRC(uint32_t ssrc);
+		std::vector<uint32_t> getSSRCs();
 
 		void setBitrate(int bitrate);
 		int getBitrate() const;
@@ -139,16 +143,13 @@ public:
 
 		virtual void parseSdpLine(string_view line) override;
 
-	private:
-		virtual string generateSdpLines(string_view eol) const override;
-
-		int mBas = -1;
-
 		struct RTPMap {
 			RTPMap(string_view mline);
+			RTPMap() {}
 
 			void removeFB(const string &string);
 			void addFB(const string &string);
+			void addAttribute(std::string attr) { fmtps.emplace_back(attr); }
 
 			int pt;
 			string format;
@@ -157,22 +158,46 @@ public:
 
 			std::vector<string> rtcpFbs;
 			std::vector<string> fmtps;
+
+			static int parsePT(string_view view);
+			void setMLine(string_view view);
 		};
 
+		std::map<int, RTPMap>::iterator beginMaps();
+		std::map<int, RTPMap>::iterator endMaps();
+		std::map<int, RTPMap>::iterator removeMap(std::map<int, RTPMap>::iterator iterator);
+
+	private:
+		virtual string generateSdpLines(string_view eol) const override;
+
+		int mBas = -1;
+
 		Media::RTPMap &getFormat(int fmt);
 		Media::RTPMap &getFormat(const string &fmt);
 
 		std::map<int, RTPMap> mRtpMap;
+		std::vector<uint32_t> mSsrcs;
+
+	public:
+		void addRTPMap(const RTPMap &map);
 	};
 
 	class Audio : public Media {
 	public:
 		Audio(string mid = "audio", Direction dir = Direction::SendOnly);
+
+		void addAudioCodec(int payloadType, const string &codec);
+		void addOpusCodec(int payloadType);
 	};
 
 	class Video : public Media {
 	public:
 		Video(string mid = "video", Direction dir = Direction::SendOnly);
+
+		void addVideoCodec(int payloadType, const string &codec);
+		void addH264Codec(int payloadType);
+		void addVP8Codec(int payloadType);
+		void addVP9Codec(int payloadType);
 	};
 
 	bool hasApplication() const;
@@ -185,9 +210,9 @@ public:
 	int addVideo(string mid = "video", Direction dir = Direction::SendOnly);
 	int addAudio(string mid = "audio", Direction dir = Direction::SendOnly);
 
-	std::variant<Media *, Application *> media(int index);
-	std::variant<const Media *, const Application *> media(int index) const;
-	int mediaCount() const;
+	std::variant<Media *, Application *> media(unsigned int index);
+	std::variant<const Media *, const Application *> media(unsigned int index) const;
+	unsigned int mediaCount() const;
 
 	Application *application();
 

+ 1 - 1
include/rtc/log.hpp

@@ -49,6 +49,6 @@ enum class LogLevel { // Don't change, it must match plog severity
 
 void InitLogger(LogLevel level);
 void InitLogger(plog::Severity severity, plog::IAppender *appender = nullptr);
-}
+} // namespace rtc
 
 #endif

+ 17 - 9
include/rtc/peerconnection.hpp

@@ -50,6 +50,13 @@ class SctpTransport;
 using certificate_ptr = std::shared_ptr<Certificate>;
 using future_certificate_ptr = std::shared_future<certificate_ptr>;
 
+struct DataChannelInit {
+	Reliability reliability = {};
+	bool negotiated = false;
+	std::optional<uint16_t> id = nullopt;
+	string protocol = "";
+};
+
 class PeerConnection final : public std::enable_shared_from_this<PeerConnection> {
 public:
 	enum class State : int {
@@ -75,7 +82,7 @@ public:
 		HaveRemotePranswer = RTC_SIGNALING_HAVE_REMOTE_PRANSWER,
 	} rtcSignalingState;
 
-	PeerConnection(void);
+	PeerConnection();
 	PeerConnection(const Configuration &config);
 	~PeerConnection();
 
@@ -95,15 +102,14 @@ public:
 	bool getSelectedCandidatePair(Candidate *local, Candidate *remote);
 
 	void setLocalDescription(Description::Type type = Description::Type::Unspec);
+
 	void setRemoteDescription(Description description);
 	void addRemoteCandidate(Candidate candidate);
 
-	std::shared_ptr<DataChannel> addDataChannel(string label, string protocol = "",
-	                                            Reliability reliability = {});
+	std::shared_ptr<DataChannel> addDataChannel(string label, DataChannelInit init = {});
 
 	// Equivalent to calling addDataChannel() and setLocalDescription()
-	std::shared_ptr<DataChannel> createDataChannel(string label, string protocol = "",
-	                                               Reliability reliability = {});
+	std::shared_ptr<DataChannel> createDataChannel(string label, DataChannelInit init = {});
 
 	void onDataChannel(std::function<void(std::shared_ptr<DataChannel> dataChannel)> callback);
 	void onLocalDescription(std::function<void(Description description)> callback);
@@ -133,9 +139,10 @@ private:
 	void forwardMessage(message_ptr message);
 	void forwardMedia(message_ptr message);
 	void forwardBufferedAmount(uint16_t stream, size_t amount);
+	std::optional<std::string> getMidFromSsrc(uint32_t ssrc);
 
 	std::shared_ptr<DataChannel> emplaceDataChannel(Description::Role role, string label,
-	                                                string protocol, Reliability reliability);
+	                                                DataChannelInit init);
 	std::shared_ptr<DataChannel> findDataChannel(uint16_t stream);
 	void iterateDataChannels(std::function<void(std::shared_ptr<DataChannel> channel)> func);
 	void openDataChannels();
@@ -173,11 +180,12 @@ private:
 	std::shared_ptr<DtlsTransport> mDtlsTransport;
 	std::shared_ptr<SctpTransport> mSctpTransport;
 
-	std::unordered_map<unsigned int, std::weak_ptr<DataChannel>> mDataChannels; // by stream ID
-	std::unordered_map<string, std::weak_ptr<Track>> mTracks;                   // by mid
+	std::unordered_map<uint16_t, std::weak_ptr<DataChannel>> mDataChannels; // by stream ID
+	std::unordered_map<string, std::weak_ptr<Track>> mTracks;               // by mid
+	std::vector<std::weak_ptr<Track>> mTrackLines;                          // by SDP order
 	std::shared_mutex mDataChannelsMutex, mTracksMutex;
 
-	std::unordered_map<unsigned int, string> mMidFromPayloadType; // cache
+	std::unordered_map<uint32_t, string> mMidFromSsrc; // cache
 
 	std::atomic<State> mState;
 	std::atomic<GatheringState> mGatheringState;

+ 0 - 1
include/rtc/queue.hpp

@@ -170,4 +170,3 @@ template <typename T> std::optional<T> Queue<T>::popImpl() {
 } // namespace rtc
 
 #endif
-

+ 0 - 1
include/rtc/reliability.hpp

@@ -37,4 +37,3 @@ struct Reliability {
 } // namespace rtc
 
 #endif
-

+ 35 - 21
include/rtc/rtc.h

@@ -78,8 +78,10 @@ typedef enum { // Don't change, it must match plog severity
 } rtcLogLevel;
 
 #define RTC_ERR_SUCCESS 0
-#define RTC_ERR_INVALID -1 // invalid argument
-#define RTC_ERR_FAILURE -2 // runtime error
+#define RTC_ERR_INVALID -1   // invalid argument
+#define RTC_ERR_FAILURE -2   // runtime error
+#define RTC_ERR_NOT_AVAIL -3 // element not available
+#define RTC_ERR_TOO_SMALL -4 // buffer too small
 
 typedef struct {
 	const char **iceServers;
@@ -95,20 +97,30 @@ typedef struct {
 	unsigned int maxRetransmits;    // ignored if reliable
 } rtcReliability;
 
-typedef void (RTC_API *rtcLogCallbackFunc)(rtcLogLevel level, const char *message);
-typedef void (RTC_API *rtcDescriptionCallbackFunc)(int pc, const char *sdp, const char *type, void *ptr);
-typedef void (RTC_API *rtcCandidateCallbackFunc)(int pc, const char *cand, const char *mid, void *ptr);
-typedef void (RTC_API *rtcStateChangeCallbackFunc)(int pc, rtcState state, void *ptr);
-typedef void (RTC_API *rtcGatheringStateCallbackFunc)(int pc, rtcGatheringState state, void *ptr);
-typedef void (RTC_API *rtcSignalingStateCallbackFunc)(int pc, rtcSignalingState state, void *ptr);
-typedef void (RTC_API *rtcDataChannelCallbackFunc)(int pc, int dc, void *ptr);
-typedef void (RTC_API *rtcTrackCallbackFunc)(int pc, int tr, void *ptr);
-typedef void (RTC_API *rtcOpenCallbackFunc)(int id, void *ptr);
-typedef void (RTC_API *rtcClosedCallbackFunc)(int id, void *ptr);
-typedef void (RTC_API *rtcErrorCallbackFunc)(int id, const char *error, void *ptr);
-typedef void (RTC_API *rtcMessageCallbackFunc)(int id, const char *message, int size, void *ptr);
-typedef void (RTC_API *rtcBufferedAmountLowCallbackFunc)(int id, void *ptr);
-typedef void (RTC_API *rtcAvailableCallbackFunc)(int id, void *ptr);
+typedef struct {
+	rtcReliability reliability;
+	const char *protocol; // empty string if NULL
+	bool negotiated;
+	bool manualStream;
+	uint16_t stream; // numeric ID 0-65534, ignored if manualStream is false
+} rtcDataChannelInit;
+
+typedef void(RTC_API *rtcLogCallbackFunc)(rtcLogLevel level, const char *message);
+typedef void(RTC_API *rtcDescriptionCallbackFunc)(int pc, const char *sdp, const char *type,
+                                                  void *ptr);
+typedef void(RTC_API *rtcCandidateCallbackFunc)(int pc, const char *cand, const char *mid,
+                                                void *ptr);
+typedef void(RTC_API *rtcStateChangeCallbackFunc)(int pc, rtcState state, void *ptr);
+typedef void(RTC_API *rtcGatheringStateCallbackFunc)(int pc, rtcGatheringState state, void *ptr);
+typedef void(RTC_API *rtcSignalingStateCallbackFunc)(int pc, rtcSignalingState state, void *ptr);
+typedef void(RTC_API *rtcDataChannelCallbackFunc)(int pc, int dc, void *ptr);
+typedef void(RTC_API *rtcTrackCallbackFunc)(int pc, int tr, void *ptr);
+typedef void(RTC_API *rtcOpenCallbackFunc)(int id, void *ptr);
+typedef void(RTC_API *rtcClosedCallbackFunc)(int id, void *ptr);
+typedef void(RTC_API *rtcErrorCallbackFunc)(int id, const char *error, void *ptr);
+typedef void(RTC_API *rtcMessageCallbackFunc)(int id, const char *message, int size, void *ptr);
+typedef void(RTC_API *rtcBufferedAmountLowCallbackFunc)(int id, void *ptr);
+typedef void(RTC_API *rtcAvailableCallbackFunc)(int id, void *ptr);
 
 // Log
 // NULL cb on the first call will log to stdout
@@ -137,19 +149,21 @@ RTC_EXPORT int rtcGetRemoteDescription(int pc, char *buffer, int size);
 RTC_EXPORT int rtcGetLocalAddress(int pc, char *buffer, int size);
 RTC_EXPORT int rtcGetRemoteAddress(int pc, char *buffer, int size);
 
-RTC_EXPORT int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote, int remoteSize);
+RTC_EXPORT int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote,
+                                           int remoteSize);
 
 // DataChannel
 RTC_EXPORT int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb);
 RTC_EXPORT int rtcAddDataChannel(int pc, const char *label); // returns dc id
-RTC_EXPORT int rtcAddDataChannelExt(int pc, const char *label, const char *protocol,
-                                    const rtcReliability *reliability); // returns dc id
+RTC_EXPORT int rtcAddDataChannelEx(int pc, const char *label,
+                                   const rtcDataChannelInit *init); // returns dc id
 // Equivalent to calling rtcAddDataChannel() and rtcSetLocalDescription()
 RTC_EXPORT int rtcCreateDataChannel(int pc, const char *label); // returns dc id
-RTC_EXPORT int rtcCreateDataChannelExt(int pc, const char *label, const char *protocol,
-                                       const rtcReliability *reliability); // returns dc id
+RTC_EXPORT int rtcCreateDataChannelEx(int pc, const char *label,
+                                      const rtcDataChannelInit *init); // returns dc id
 RTC_EXPORT int rtcDeleteDataChannel(int dc);
 
+RTC_EXPORT int rtcGetDataChannelStream(int dc);
 RTC_EXPORT int rtcGetDataChannelLabel(int dc, char *buffer, int size);
 RTC_EXPORT int rtcGetDataChannelProtocol(int dc, char *buffer, int size);
 RTC_EXPORT int rtcGetDataChannelReliability(int dc, rtcReliability *reliability);

+ 0 - 1
include/rtc/rtc.hpp

@@ -27,4 +27,3 @@
 
 // C API
 #include "rtc.h"
-

+ 43 - 11
include/rtc/rtcp.hpp

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2020 Staz M
+ * Copyright (c) 2020 Staz Modrzynski
  * Copyright (c) 2020 Paul-Louis Ageneau
  *
  * This library is free software; you can redistribute it and/or
@@ -20,35 +20,67 @@
 #ifndef RTC_RTCP_H
 #define RTC_RTCP_H
 
+#include <utility>
+
 #include "include.hpp"
 #include "log.hpp"
 #include "message.hpp"
+#include "rtp.hpp"
 
 namespace rtc {
 
-typedef uint32_t SSRC;
-
 class RtcpHandler {
+protected:
+	/**
+	 * Use this callback when trying to send custom data (such as RTCP) to the client.
+	 */
+	synchronized_callback<rtc::message_ptr> outgoingCallback;
+
 public:
-	virtual void onOutgoing(std::function<void(rtc::message_ptr)> cb) = 0;
-	virtual std::optional<rtc::message_ptr> incoming(rtc::message_ptr ptr) = 0;
+	/**
+	 * Called when there is traffic coming from the peer
+	 * @param ptr
+	 * @return
+	 */
+	virtual rtc::message_ptr incoming(rtc::message_ptr ptr) = 0;
+
+	/**
+	 * Called when there is traffic that needs to be sent to the peer
+	 * @param ptr
+	 * @return
+	 */
+	virtual rtc::message_ptr outgoing(rtc::message_ptr ptr) = 0;
+
+	/**
+	 * This callback is used to send traffic back to the peer.
+	 * This callback skips calling the track's methods.
+	 * @param cb
+	 */
+	void onOutgoing(const std::function<void(rtc::message_ptr)> &cb);
+
+	virtual bool requestKeyframe() { return false; }
 };
 
+class Track;
+
 // An RtcpSession can be plugged into a Track to handle the whole RTCP session
-class RtcpSession : public RtcpHandler {
+class RtcpReceivingSession : public RtcpHandler {
 public:
-	void onOutgoing(std::function<void(rtc::message_ptr)> cb) override;
+	rtc::message_ptr incoming(rtc::message_ptr ptr) override;
+	rtc::message_ptr outgoing(rtc::message_ptr ptr) override;
+	bool send(rtc::message_ptr ptr);
 
-	std::optional<rtc::message_ptr> incoming(rtc::message_ptr ptr) override;
 	void requestBitrate(unsigned int newBitrate);
 
-private:
+	bool requestKeyframe() override;
+
+protected:
 	void pushREMB(unsigned int bitrate);
 	void pushRR(unsigned int lastSR_delay);
-	void tx(message_ptr msg);
+
+	void pushPLI();
 
 	unsigned int mRequestedBitrate = 0;
-	synchronized_callback<rtc::message_ptr> mTxCallback;
 	SSRC mSsrc = 0;
 	uint32_t mGreatestSeqNo = 0;
 	uint64_t mSyncRTPTS, mSyncNTPTS;

+ 493 - 0
include/rtc/rtp.hpp

@@ -0,0 +1,493 @@
+/**
+ * Copyright (c) 2020 Staz Modrzynski
+ * 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
+ */
+
+#ifndef RTC_RTP_HPP
+#define RTC_RTP_HPP
+
+#include <rtc/log.hpp>
+
+#include <cmath>
+
+#ifdef _WIN32
+#include <winsock2.h>
+#else
+#include <arpa/inet.h>
+#endif
+
+#ifndef htonll
+#define htonll(x)                                                                                  \
+	((uint64_t)htonl(((uint64_t)(x)&0xFFFFFFFF) << 32) | (uint64_t)htonl((uint64_t)(x) >> 32))
+#endif
+#ifndef ntohll
+#define ntohll(x) htonll(x)
+#endif
+
+namespace rtc {
+
+typedef uint32_t SSRC;
+
+#pragma pack(push, 1)
+
+struct RTP {
+private:
+	uint8_t _first;
+	uint8_t _payloadType;
+	uint16_t _seqNumber;
+	uint32_t _timestamp;
+	SSRC _ssrc;
+
+public:
+	SSRC csrc[16];
+
+	inline uint8_t version() const { return _first >> 6; }
+	inline bool padding() const { return (_first >> 5) & 0x01; }
+	inline uint8_t csrcCount() const { return _first & 0x0F; }
+	inline uint8_t marker() const { return _payloadType & 0b10000000; }
+	inline uint8_t payloadType() const { return _payloadType & 0b01111111; }
+	inline uint16_t seqNumber() const { return ntohs(_seqNumber); }
+	inline uint32_t timestamp() const { return ntohl(_timestamp); }
+	inline uint32_t ssrc() const { return ntohl(_ssrc); }
+
+	inline size_t getSize() const {
+		return ((char *)&csrc) - ((char *)this) + sizeof(SSRC) * csrcCount();
+	}
+
+	char *getBody() const { return ((char *)&csrc) + sizeof(SSRC) * csrcCount(); }
+
+	inline void setSeqNumber(uint16_t newSeqNo) { _seqNumber = htons(newSeqNo); }
+	inline void setPayloadType(uint8_t newPayloadType) {
+		_payloadType = (_payloadType & 0b10000000u) | (0b01111111u & newPayloadType);
+	}
+	inline void setSsrc(uint32_t ssrc) { _ssrc = htonl(ssrc); }
+
+	void setTimestamp(uint32_t i) { _timestamp = htonl(i); }
+};
+
+struct RTCP_ReportBlock {
+	SSRC ssrc;
+
+private:
+	uint32_t _fractionLostAndPacketsLost; // fraction lost is 8-bit, packets lost is 24-bit
+	uint16_t _seqNoCycles;
+	uint16_t _highestSeqNo;
+	uint32_t _jitter;
+	uint32_t _lastReport;
+	uint32_t _delaySinceLastReport;
+
+public:
+	inline void preparePacket(SSRC ssrc, [[maybe_unused]] unsigned int packetsLost,
+	                          [[maybe_unused]] unsigned int totalPackets, uint16_t highestSeqNo,
+	                          uint16_t seqNoCycles, uint32_t jitter, uint64_t lastSR_NTP,
+	                          uint64_t lastSR_DELAY) {
+		setSeqNo(highestSeqNo, seqNoCycles);
+		setJitter(jitter);
+		setSSRC(ssrc);
+
+		// Middle 32 bits of NTP Timestamp
+		//		  this->lastReport = lastSR_NTP >> 16u;
+		setNTPOfSR(uint64_t(lastSR_NTP));
+		setDelaySinceSR(uint32_t(lastSR_DELAY));
+
+		// The delay, expressed in units of 1/65536 seconds
+		//		  this->delaySinceLastReport = lastSR_DELAY;
+	}
+
+	inline void setSSRC(SSRC ssrc) { this->ssrc = htonl(ssrc); }
+	inline SSRC getSSRC() const { return ntohl(ssrc); }
+
+	inline void setPacketsLost([[maybe_unused]] unsigned int packetsLost,
+	                           [[maybe_unused]] unsigned int totalPackets) {
+		// TODO Implement loss percentages.
+		_fractionLostAndPacketsLost = 0;
+	}
+	inline unsigned int getLossPercentage() const {
+		// TODO Implement loss percentages.
+		return 0;
+	}
+	inline unsigned int getPacketLostCount() const {
+		// TODO Implement total packets lost.
+		return 0;
+	}
+
+	inline uint16_t seqNoCycles() const { return ntohs(_seqNoCycles); }
+	inline uint16_t highestSeqNo() const { return ntohs(_highestSeqNo); }
+	inline uint32_t jitter() const { return ntohl(_jitter); }
+
+	inline void setSeqNo(uint16_t highestSeqNo, uint16_t seqNoCycles) {
+		_highestSeqNo = htons(highestSeqNo);
+		_seqNoCycles = htons(seqNoCycles);
+	}
+
+	inline void setJitter(uint32_t jitter) { _jitter = htonl(jitter); }
+
+	inline void setNTPOfSR(uint64_t ntp) { _lastReport = htonll(ntp >> 16u); }
+	inline uint32_t getNTPOfSR() const { return ntohl(_lastReport) << 16u; }
+
+	inline void setDelaySinceSR(uint32_t sr) {
+		// The delay, expressed in units of 1/65536 seconds
+		_delaySinceLastReport = htonl(sr);
+	}
+	inline uint32_t getDelaySinceSR() const { return ntohl(_delaySinceLastReport); }
+
+	inline void log() const {
+		PLOG_VERBOSE << "RTCP report block: "
+		             << "ssrc="
+		             << ntohl(ssrc)
+		             // TODO: Implement these reports
+		             //	<< ", fractionLost=" << fractionLost
+		             //	<< ", packetsLost=" << packetsLost
+		             << ", highestSeqNo=" << highestSeqNo() << ", seqNoCycles=" << seqNoCycles()
+		             << ", jitter=" << jitter() << ", lastSR=" << getNTPOfSR()
+		             << ", lastSRDelay=" << getDelaySinceSR();
+	}
+};
+
+struct RTCP_HEADER {
+private:
+	uint8_t _first;
+	uint8_t _payloadType;
+	uint16_t _length;
+
+public:
+	inline uint8_t version() const { return _first >> 6; }
+	inline bool padding() const { return (_first >> 5) & 0x01; }
+	inline uint8_t reportCount() const { return _first & 0x0F; }
+	inline uint8_t payloadType() const { return _payloadType; }
+	inline uint16_t length() const { return ntohs(_length); }
+	inline size_t lengthInBytes() const { return (1 + length()) * 4; }
+
+	inline void setPayloadType(uint8_t type) { _payloadType = type; }
+	inline void setReportCount(uint8_t count) {
+		_first = (_first & 0b11100000u) | (count & 0b00011111u);
+	}
+	inline void setLength(uint16_t length) { _length = htons(length); }
+
+	inline void prepareHeader(uint8_t payloadType, uint8_t reportCount, uint16_t length) {
+		_first = 0b10000000; // version 2, no padding
+		setReportCount(reportCount);
+		setPayloadType(payloadType);
+		setLength(length);
+	}
+
+	inline void log() const {
+		PLOG_VERBOSE << "RTCP header: "
+		          << "version=" << unsigned(version()) << ", padding=" << padding()
+		          << ", reportCount=" << unsigned(reportCount())
+		          << ", payloadType=" << unsigned(payloadType()) << ", length=" << length();
+	}
+};
+
+struct RTCP_FB_HEADER {
+	RTCP_HEADER header;
+	SSRC packetSender;
+	SSRC mediaSource;
+
+	[[nodiscard]] SSRC getPacketSenderSSRC() const { return ntohl(packetSender); }
+
+	[[nodiscard]] SSRC getMediaSourceSSRC() const { return ntohl(mediaSource); }
+
+	void setPacketSenderSSRC(SSRC ssrc) { this->packetSender = htonl(ssrc); }
+
+	void setMediaSourceSSRC(SSRC ssrc) { this->mediaSource = htonl(ssrc); }
+
+	void log() {
+		header.log();
+		PLOG_VERBOSE << "FB: "
+		             << " packet sender: " << getPacketSenderSSRC()
+		             << " media source: " << getMediaSourceSSRC();
+	}
+};
+
+struct RTCP_SR {
+	RTCP_HEADER header;
+	SSRC _senderSSRC;
+
+private:
+	uint64_t _ntpTimestamp;
+	uint32_t _rtpTimestamp;
+	uint32_t _packetCount;
+	uint32_t _octetCount;
+
+	RTCP_ReportBlock _reportBlocks;
+
+public:
+	inline void preparePacket(SSRC senderSSRC, uint8_t reportCount) {
+		unsigned int length =
+		    ((sizeof(header) + 24 + reportCount * sizeof(RTCP_ReportBlock)) / 4) - 1;
+		header.prepareHeader(200, reportCount, uint16_t(length));
+		this->_senderSSRC = htonl(senderSSRC);
+	}
+
+	inline RTCP_ReportBlock *getReportBlock(int num) { return &_reportBlocks + num; }
+	inline const RTCP_ReportBlock *getReportBlock(int num) const { return &_reportBlocks + num; }
+
+	[[nodiscard]] inline size_t getSize() const {
+		// "length" in packet is one less than the number of 32 bit words in the packet.
+		return sizeof(uint32_t) * (1 + size_t(header.length()));
+	}
+
+	inline uint64_t ntpTimestamp() const { return ntohll(_ntpTimestamp); }
+	inline uint32_t rtpTimestamp() const { return ntohl(_rtpTimestamp); }
+	inline uint32_t packetCount() const { return ntohl(_packetCount); }
+	inline uint32_t octetCount() const { return ntohl(_octetCount); }
+	inline uint32_t senderSSRC() const { return ntohl(_senderSSRC); }
+
+	inline void setNtpTimestamp(uint32_t ts) { _ntpTimestamp = htonll(ts); }
+	inline void setRtpTimestamp(uint32_t ts) { _rtpTimestamp = htonl(ts); }
+
+	inline void log() const {
+		header.log();
+		PLOG_VERBOSE << "RTCP SR: "
+		             << " SSRC=" << senderSSRC() << ", NTP_TS=" << ntpTimestamp()
+		             << ", RTP_TS=" << rtpTimestamp() << ", packetCount=" << packetCount()
+		             << ", octetCount=" << octetCount();
+
+		for (unsigned i = 0; i < unsigned(header.reportCount()); i++) {
+			getReportBlock(i)->log();
+		}
+	}
+};
+
+struct RTCP_RR {
+	RTCP_HEADER header;
+	SSRC _senderSSRC;
+
+private:
+	RTCP_ReportBlock _reportBlocks;
+
+public:
+	inline RTCP_ReportBlock *getReportBlock(int num) { return &_reportBlocks + num; }
+	inline const RTCP_ReportBlock *getReportBlock(int num) const { return &_reportBlocks + num; }
+
+	inline SSRC senderSSRC() const { return ntohl(_senderSSRC); }
+	inline void setSenderSSRC(SSRC ssrc) { this->_senderSSRC = htonl(ssrc); }
+
+	[[nodiscard]] inline size_t getSize() const {
+		// "length" in packet is one less than the number of 32 bit words in the packet.
+		return sizeof(uint32_t) * (1 + size_t(header.length()));
+	}
+
+	inline void preparePacket(SSRC senderSSRC, uint8_t reportCount) {
+		// "length" in packet is one less than the number of 32 bit words in the packet.
+		size_t length = (sizeWithReportBlocks(reportCount) / 4) - 1;
+		header.prepareHeader(201, reportCount, uint16_t(length));
+		this->_senderSSRC = htonl(senderSSRC);
+	}
+
+	inline static size_t sizeWithReportBlocks(uint8_t reportCount) {
+		return sizeof(header) + 4 + size_t(reportCount) * sizeof(RTCP_ReportBlock);
+	}
+
+	inline bool isSenderReport() { return header.payloadType() == 200; }
+
+	inline bool isReceiverReport() { return header.payloadType() == 201; }
+
+	inline void log() const {
+		header.log();
+		PLOG_VERBOSE << "RTCP RR: "
+		             << " SSRC=" << ntohl(_senderSSRC);
+
+		for (unsigned i = 0; i < unsigned(header.reportCount()); i++) {
+			getReportBlock(i)->log();
+		}
+	}
+};
+
+struct RTCP_REMB {
+	RTCP_FB_HEADER header;
+
+	/*! \brief Unique identifier ('R' 'E' 'M' 'B') */
+	char id[4];
+
+	/*! \brief Num SSRC, Br Exp, Br Mantissa (bit mask) */
+	uint32_t bitrate;
+
+	SSRC ssrc[1];
+
+	[[nodiscard]] unsigned int getSize() const {
+		// "length" in packet is one less than the number of 32 bit words in the packet.
+		return sizeof(uint32_t) * (1 + header.header.length());
+	}
+
+	void preparePacket(SSRC senderSSRC, unsigned int numSSRC, unsigned int bitrate) {
+
+		// Report Count becomes the format here.
+		header.header.prepareHeader(206, 15, 0);
+
+		// Always zero.
+		header.setMediaSourceSSRC(0);
+
+		header.setPacketSenderSSRC(senderSSRC);
+
+		id[0] = 'R';
+		id[1] = 'E';
+		id[2] = 'M';
+		id[3] = 'B';
+
+		setBitrate(numSSRC, bitrate);
+	}
+
+	void setBitrate(unsigned int numSSRC, unsigned int bitrate) {
+		unsigned int exp = 0;
+		while (bitrate > pow(2, 18) - 1) {
+			exp++;
+			bitrate /= 2;
+		}
+
+		// "length" in packet is one less than the number of 32 bit words in the packet.
+		header.header.setLength(
+		    uint16_t((offsetof(RTCP_REMB, ssrc) / sizeof(uint32_t)) - 1 + numSSRC));
+
+		this->bitrate = htonl((numSSRC << (32u - 8u)) | (exp << (32u - 8u - 6u)) | bitrate);
+	}
+
+	void setSsrc(int iterator, SSRC newSssrc) { ssrc[iterator] = htonl(newSssrc); }
+
+	size_t static inline sizeWithSSRCs(int count) {
+		return sizeof(RTCP_REMB) + (count - 1) * sizeof(SSRC);
+	}
+};
+
+struct RTCP_PLI {
+	RTCP_FB_HEADER header;
+
+	void preparePacket(SSRC messageSSRC) {
+		header.header.prepareHeader(206, 1, 2);
+		header.setPacketSenderSSRC(messageSSRC);
+		header.setMediaSourceSSRC(messageSSRC);
+	}
+
+	void print() { header.log(); }
+
+	[[nodiscard]] static unsigned int size() { return sizeof(RTCP_FB_HEADER); }
+};
+
+struct RTCP_FIR_PART {
+	uint32_t ssrc;
+	uint8_t seqNo;
+	uint8_t dummy1;
+	uint16_t dummy2;
+};
+
+struct RTCP_FIR {
+	RTCP_FB_HEADER header;
+	RTCP_FIR_PART parts[1];
+
+	void preparePacket(SSRC messageSSRC, uint8_t seqNo) {
+		header.header.prepareHeader(206, 4, 2 + 2 * 1);
+		header.setPacketSenderSSRC(messageSSRC);
+		header.setMediaSourceSSRC(messageSSRC);
+		parts[0].ssrc = htonl(messageSSRC);
+		parts[0].seqNo = seqNo;
+	}
+
+	void print() { header.log(); }
+
+	[[nodiscard]] static unsigned int size() {
+		return sizeof(RTCP_FB_HEADER) + sizeof(RTCP_FIR_PART);
+	}
+};
+
+struct RTCP_NACK_PART {
+	uint16_t pid;
+	uint16_t blp;
+};
+
+class RTCP_NACK {
+public:
+	RTCP_FB_HEADER header;
+	RTCP_NACK_PART parts[1];
+
+public:
+	void preparePacket(SSRC ssrc, unsigned int discreteSeqNoCount) {
+		header.header.prepareHeader(205, 1, 2 + discreteSeqNoCount);
+		header.setMediaSourceSSRC(ssrc);
+		header.setPacketSenderSSRC(ssrc);
+	}
+
+	/**
+	 * Add a packet to the list of missing packets.
+	 * @param fciCount The number of FCI fields that are present in this packet.
+	 *                  Let the number start at zero and let this function grow the number.
+	 * @param fciPID The seq no of the active FCI. It will be initialized automatically, and will
+	 * change automatically.
+	 * @param missingPacket The seq no of the missing packet. This will be added to the queue.
+	 * @return true if the packet has grown, false otherwise.
+	 */
+	bool addMissingPacket(unsigned int *fciCount, uint16_t *fciPID, const uint16_t &missingPacket) {
+		if (*fciCount == 0 || missingPacket < *fciPID || missingPacket > (*fciPID + 16)) {
+			parts[*fciCount].pid = htons(missingPacket);
+			parts[*fciCount].blp = 0;
+			*fciPID = missingPacket;
+			(*fciCount)++;
+			return true;
+		} else {
+			// TODO SPEEED!
+			parts[(*fciCount) - 1].blp = htons(ntohs(parts[(*fciCount) - 1].blp) |
+			                                   (1u << (unsigned int)(missingPacket - *fciPID)));
+			return false;
+		}
+	}
+
+	[[nodiscard]] static unsigned int getSize(unsigned int discreteSeqNoCount) {
+		return offsetof(RTCP_NACK, parts) + sizeof(RTCP_NACK_PART) * discreteSeqNoCount;
+	}
+
+	[[nodiscard]] unsigned int getSeqNoCount() { return header.header.length() - 2; }
+};
+
+class RTP_RTX {
+private:
+	RTP header;
+
+public:
+	size_t copyTo(RTP *dest, size_t totalSize, uint8_t originalPayloadType) {
+		memmove((char *)dest, (char *)this, header.getSize());
+		dest->setSeqNumber(getOriginalSeqNo());
+		dest->setPayloadType(originalPayloadType);
+		memmove(dest->getBody(), getBody(), getBodySize(totalSize));
+		return totalSize;
+	}
+
+	[[nodiscard]] uint16_t getOriginalSeqNo() const {
+		return ntohs(*(uint16_t *)(header.getBody()));
+	}
+
+	char *getBody() { return header.getBody() + sizeof(uint16_t); }
+
+	size_t getBodySize(size_t totalSize) { return totalSize - ((char *)getBody() - (char *)this); }
+
+	RTP &getHeader() { return header; }
+
+	size_t normalizePacket(size_t totalSize, SSRC originalSSRC, uint8_t originalPayloadType) {
+		header.setSeqNumber(getOriginalSeqNo());
+		header.setSsrc(originalSSRC);
+		header.setPayloadType(originalPayloadType);
+		// TODO, the -12 is the size of the header (which is variable!)
+		memmove(header.getBody(), header.getBody() + sizeof(uint16_t),
+		        totalSize - 12 - sizeof(uint16_t));
+		return totalSize - sizeof(uint16_t);
+	}
+};
+
+#pragma pack(pop)
+
+}; // namespace rtc
+
+#endif

+ 4 - 1
include/rtc/track.hpp

@@ -56,9 +56,13 @@ public:
 	// Extended API
 	size_t availableAmount() const override;
 	std::optional<message_variant> receive() override;
+	std::optional<message_variant> peek() override;
+
+	bool requestKeyframe();
 
 	// RTCP handler
 	void setRtcpHandler(std::shared_ptr<RtcpHandler> handler);
+	std::shared_ptr<RtcpHandler> getRtcpHandler();
 
 private:
 #if RTC_ENABLE_MEDIA
@@ -81,4 +85,3 @@ private:
 } // namespace rtc
 
 #endif
-

+ 1 - 0
include/rtc/websocket.hpp

@@ -66,6 +66,7 @@ public:
 
 	// Extended API
 	std::optional<message_variant> receive() override;
+	std::optional<message_variant> peek() override;
 	size_t availableAmount() const override; // total size available to receive
 
 private:

+ 1 - 2
src/base64.cpp

@@ -25,8 +25,7 @@ namespace rtc {
 using std::to_integer;
 
 string to_base64(const binary &data) {
-	static const char tab[] =
-	    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+	static const char tab[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 
 	string out;
 	out.reserve(3 * ((data.size() + 3) / 4));

+ 29 - 24
src/candidate.cpp

@@ -28,8 +28,8 @@
 #include <ws2tcpip.h>
 #else
 #include <netdb.h>
-#include <sys/socket.h>
 #include <netinet/in.h>
+#include <sys/socket.h>
 #endif
 
 #include <sys/types.h>
@@ -48,23 +48,31 @@ inline bool hasprefix(const string &str, const string &prefix) {
 
 namespace rtc {
 
-Candidate::Candidate(string candidate, string mid)
+Candidate::Candidate()
     : mFamily(Family::Unresolved), mType(Type::Unknown), mTransportType(TransportType::Unknown),
-      mPort(0), mPriority(0) {
+      mPort(0), mPriority(0) {}
 
-	if (!candidate.empty()) {
-		const std::array prefixes{"a=", "candidate:"};
-		for (const string &prefix : prefixes)
-			if (hasprefix(candidate, prefix))
-				candidate.erase(0, prefix.size());
-	}
+Candidate::Candidate(string candidate) : Candidate() {
+	const std::array prefixes{"a=", "candidate:"};
+	for (const string &prefix : prefixes)
+		if (hasprefix(candidate, prefix))
+			candidate.erase(0, prefix.size());
 
 	mCandidate = std::move(candidate);
-	mMid = std::move(mid);
+}
+
+Candidate::Candidate(string candidate, string mid) : Candidate(std::move(candidate)) {
+	if (!mid.empty())
+		mMid.emplace(std::move(mid));
+}
+
+void Candidate::hintMid(string mid) {
+	if (!mMid)
+		mMid.emplace(std::move(mid));
 }
 
 bool Candidate::resolve(ResolveMode mode) {
-	using TypeMap_t = std::unordered_map<string, Type>;	
+	using TypeMap_t = std::unordered_map<string, Type>;
 	using TcpTypeMap_t = std::unordered_map<string, TransportType>;
 
 	static const TypeMap_t TypeMap = {{"host", Type::Host},
@@ -79,12 +87,11 @@ bool Candidate::resolve(ResolveMode mode) {
 	if (mFamily != Family::Unresolved)
 		return true;
 
-	if(mCandidate.empty())
+	if (mCandidate.empty())
 		throw std::logic_error("Candidate is empty");
 
 	PLOG_VERBOSE << "Resolving candidate (mode="
-				 << (mode == ResolveMode::Simple ? "simple" : "lookup")
-				 << "): " << mCandidate;
+	             << (mode == ResolveMode::Simple ? "simple" : "lookup") << "): " << mCandidate;
 
 	// See RFC 8445 for format
 	std::istringstream iss(mCandidate);
@@ -100,17 +107,16 @@ bool Candidate::resolve(ResolveMode mode) {
 			mType = it->second;
 		else
 			mType = Type::Unknown;
-		
+
 		if (transport == "UDP" || transport == "udp") {
 			mTransportType = TransportType::Udp;
-		}
-		else if (transport == "TCP" || transport == "tcp") {
+		} else if (transport == "TCP" || transport == "tcp") {
 			std::istringstream iss(left);
 			string tcptype_, tcptype;
-			if(iss >> tcptype_ >> tcptype && tcptype_ == "tcptype") {
+			if (iss >> tcptype_ >> tcptype && tcptype_ == "tcptype") {
 				if (auto it = TcpTypeMap.find(tcptype); it != TcpTypeMap.end())
 					mTransportType = it->second;
-				else 
+				else
 					mTransportType = TransportType::TcpUnknown;
 
 			} else {
@@ -127,8 +133,7 @@ bool Candidate::resolve(ResolveMode mode) {
 		if (mTransportType == TransportType::Udp) {
 			hints.ai_socktype = SOCK_DGRAM;
 			hints.ai_protocol = IPPROTO_UDP;
-		}
-		else if (mTransportType != TransportType::Unknown) {
+		} else if (mTransportType != TransportType::Unknown) {
 			hints.ai_socktype = SOCK_STREAM;
 			hints.ai_protocol = IPPROTO_TCP;
 		}
@@ -146,11 +151,11 @@ bool Candidate::resolve(ResolveMode mode) {
 					if (getnameinfo(p->ai_addr, socklen_t(p->ai_addrlen), nodebuffer,
 					                MAX_NUMERICNODE_LEN, servbuffer, MAX_NUMERICSERV_LEN,
 					                NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
-						
+
 						mAddress = nodebuffer;
 						mPort = uint16_t(std::stoul(servbuffer));
 						mFamily = p->ai_family == AF_INET6 ? Family::Ipv6 : Family::Ipv4;
-						
+
 						const char sp{' '};
 						std::ostringstream oss;
 						oss << foundation << sp << component << sp << transport << sp << priority;
@@ -173,7 +178,7 @@ bool Candidate::resolve(ResolveMode mode) {
 
 string Candidate::candidate() const { return "candidate:" + mCandidate; }
 
-string Candidate::mid() const { return mMid; }
+string Candidate::mid() const { return mMid.value_or("0"); }
 
 Candidate::operator string() const {
 	std::ostringstream line;

+ 127 - 178
src/capi.cpp

@@ -195,6 +195,31 @@ template <typename F> int wrap(F func) {
 		return RTC_ERR_SUCCESS;                                                                    \
 	})
 
+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());
+}
+
 class plogAppender : public plog::IAppender {
 public:
 	plogAppender(rtcLogCallbackFunc cb = nullptr) { setCallback(cb); }
@@ -279,44 +304,48 @@ int rtcDeletePeerConnection(int pc) {
 	});
 }
 
-int rtcAddDataChannel(int pc, const char *label) {
-	return rtcAddDataChannelExt(pc, label, nullptr, nullptr);
-}
+int rtcAddDataChannel(int pc, const char *label) { return rtcAddDataChannelEx(pc, label, nullptr); }
 
-int rtcAddDataChannelExt(int pc, const char *label, const char *protocol,
-                         const rtcReliability *reliability) {
+int rtcAddDataChannelEx(int pc, const char *label, const rtcDataChannelInit *init) {
 	return WRAP({
-		Reliability r = {};
-		if (reliability) {
-			r.unordered = reliability->unordered;
+		DataChannelInit dci = {};
+		if (init) {
+			auto *reliability = &init->reliability;
+			dci.reliability.unordered = reliability->unordered;
 			if (reliability->unreliable) {
 				if (reliability->maxPacketLifeTime > 0) {
-					r.type = Reliability::Type::Timed;
-					r.rexmit = milliseconds(reliability->maxPacketLifeTime);
+					dci.reliability.type = Reliability::Type::Timed;
+					dci.reliability.rexmit = milliseconds(reliability->maxPacketLifeTime);
 				} else {
-					r.type = Reliability::Type::Rexmit;
-					r.rexmit = int(reliability->maxRetransmits);
+					dci.reliability.type = Reliability::Type::Rexmit;
+					dci.reliability.rexmit = int(reliability->maxRetransmits);
 				}
 			} else {
-				r.type = Reliability::Type::Reliable;
+				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->addDataChannel(
-		    string(label ? label : ""), string(protocol ? protocol : ""), r));
+		int dc = emplaceDataChannel(
+		    peerConnection->addDataChannel(string(label ? label : ""), std::move(dci)));
+
 		if (auto ptr = getUserPointer(pc))
 			rtcSetUserPointer(dc, *ptr);
+
 		return dc;
 	});
 }
 
 int rtcCreateDataChannel(int pc, const char *label) {
-	return rtcCreateDataChannelExt(pc, label, nullptr, nullptr);
+	return rtcCreateDataChannelEx(pc, label, nullptr);
 }
 
-int rtcCreateDataChannelExt(int pc, const char *label, const char *protocol,
-                            const rtcReliability *reliability) {
-	int dc = rtcAddDataChannelExt(pc, label, protocol, reliability);
+int rtcCreateDataChannelEx(int pc, const char *label, const rtcDataChannelInit *init) {
+	int dc = rtcAddDataChannelEx(pc, label, init);
 	rtcSetLocalDescription(pc, NULL);
 	return dc;
 }
@@ -345,6 +374,7 @@ int rtcAddTrack(int pc, const char *mediaDescriptionSdp) {
 		int tr = emplaceTrack(peerConnection->addTrack(std::move(media)));
 		if (auto ptr = getUserPointer(pc))
 			rtcSetUserPointer(tr, *ptr);
+
 		return tr;
 	});
 }
@@ -366,19 +396,7 @@ int rtcDeleteTrack(int tr) {
 int rtcGetTrackDescription(int tr, char *buffer, int size) {
 	return WRAP({
 		auto track = getTrack(tr);
-
-		if (size <= 0)
-			return 0;
-
-		if (!buffer)
-			throw std::invalid_argument("Unexpected null pointer for buffer");
-
-		string description(track->description());
-		const char *data = description.data();
-		size = std::min(size - 1, int(description.size()));
-		std::copy(data, data + size, buffer);
-		buffer[size] = '\0';
-		return int(size + 1);
+		return copyAndReturn(track->description(), buffer, size);
 	});
 }
 
@@ -547,22 +565,10 @@ int rtcGetLocalDescription(int pc, char *buffer, int size) {
 	return WRAP({
 		auto peerConnection = getPeerConnection(pc);
 
-		if (size <= 0)
-			return 0;
-
-		if (!buffer)
-			throw std::invalid_argument("Unexpected null pointer for buffer");
-
-		if (auto desc = peerConnection->localDescription()) {
-			auto sdp = string(*desc);
-			const char *data = sdp.data();
-			size = std::min(size - 1, int(sdp.size()));
-			std::copy(data, data + size, buffer);
-			buffer[size] = '\0';
-			return size + 1;
-		}
-
-		return RTC_ERR_FAILURE;
+		if (auto desc = peerConnection->localDescription())
+			return copyAndReturn(string(*desc), buffer, size);
+		else
+			return RTC_ERR_NOT_AVAIL;
 	});
 }
 
@@ -570,22 +576,10 @@ int rtcGetRemoteDescription(int pc, char *buffer, int size) {
 	return WRAP({
 		auto peerConnection = getPeerConnection(pc);
 
-		if (size <= 0)
-			return 0;
-
-		if (!buffer)
-			throw std::invalid_argument("Unexpected null pointer for buffer");
-
-		if (auto desc = peerConnection->remoteDescription()) {
-			auto sdp = string(*desc);
-			const char *data = sdp.data();
-			size = std::min(size - 1, int(sdp.size()));
-			std::copy(data, data + size, buffer);
-			buffer[size] = '\0';
-			return size + 1;
-		}
-
-		return RTC_ERR_FAILURE;
+		if (auto desc = peerConnection->remoteDescription())
+			return copyAndReturn(string(*desc), buffer, size);
+		else
+			return RTC_ERR_NOT_AVAIL;
 	});
 }
 
@@ -593,21 +587,10 @@ int rtcGetLocalAddress(int pc, char *buffer, int size) {
 	return WRAP({
 		auto peerConnection = getPeerConnection(pc);
 
-		if (size <= 0)
-			return 0;
-
-		if (!buffer)
-			throw std::invalid_argument("Unexpected null pointer for buffer");
-
-		if (auto addr = peerConnection->localAddress()) {
-			const char *data = addr->data();
-			size = std::min(size - 1, int(addr->size()));
-			std::copy(data, data + size, buffer);
-			buffer[size] = '\0';
-			return size + 1;
-		}
-
-		return RTC_ERR_FAILURE;
+		if (auto addr = peerConnection->localAddress())
+			return copyAndReturn(std::move(*addr), buffer, size);
+		else
+			return RTC_ERR_NOT_AVAIL;
 	});
 }
 
@@ -615,21 +598,10 @@ int rtcGetRemoteAddress(int pc, char *buffer, int size) {
 	return WRAP({
 		auto peerConnection = getPeerConnection(pc);
 
-		if (size <= 0)
-			return 0;
-
-		if (!buffer)
-			throw std::invalid_argument("Unexpected null pointer for buffer");
-
-		if (auto addr = peerConnection->remoteAddress()) {
-			const char *data = addr->data();
-			size = std::min(size - 1, int(addr->size()));
-			std::copy(data, data + size, buffer);
-			buffer[size] = '\0';
-			return int(size + 1);
-		}
-
-		return RTC_ERR_FAILURE;
+		if (auto addr = peerConnection->remoteAddress())
+			return copyAndReturn(std::move(*addr), buffer, size);
+		else
+			return RTC_ERR_NOT_AVAIL;
 	});
 }
 
@@ -637,68 +609,41 @@ int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote
 	return WRAP({
 		auto peerConnection = getPeerConnection(pc);
 
-		if (!local)
-			localSize = 0;
-		if (!remote)
-			remoteSize = 0;
-
 		Candidate localCand;
 		Candidate remoteCand;
-		if (peerConnection->getSelectedCandidatePair(&localCand, &remoteCand)) {
-			if (localSize > 0) {
-				string localSdp = string(localCand);
-				localSize = std::min(localSize - 1, int(localSdp.size()));
-				std::copy(localSdp.begin(), localSdp.begin() + localSize, local);
-				local[localSize] = '\0';
-			}
-			if (remoteSize > 0) {
-				string remoteSdp = string(remoteCand);
-				remoteSize = std::min(remoteSize - 1, int(remoteSdp.size()));
-				std::copy(remoteSdp.begin(), remoteSdp.begin() + remoteSize, remote);
-				remote[remoteSize] = '\0';
-			}
-			return localSize + remoteSize;
-		}
+		if (!peerConnection->getSelectedCandidatePair(&localCand, &remoteCand))
+			return RTC_ERR_NOT_AVAIL;
 
-		return RTC_ERR_FAILURE;
+		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 rtcGetDataChannelLabel(int dc, char *buffer, int size) {
+int rtcGetDataChannelStream(int dc) {
 	return WRAP({
 		auto dataChannel = getDataChannel(dc);
+		return int(dataChannel->id());
+	});
+}
 
-		if (size <= 0)
-			return 0;
-
-		if (!buffer)
-			throw std::invalid_argument("Unexpected null pointer for buffer");
-
-		string label = dataChannel->label();
-		const char *data = label.data();
-		size = std::min(size - 1, int(label.size()));
-		std::copy(data, data + size, buffer);
-		buffer[size] = '\0';
-		return int(size + 1);
+int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
+	return WRAP({
+		auto dataChannel = getDataChannel(dc);
+		return copyAndReturn(dataChannel->label(), buffer, size);
 	});
 }
 
 int rtcGetDataChannelProtocol(int dc, char *buffer, int size) {
 	return WRAP({
 		auto dataChannel = getDataChannel(dc);
-
-		if (size <= 0)
-			return 0;
-
-		if (!buffer)
-			throw std::invalid_argument("Unexpected null pointer for buffer");
-
-		string protocol = dataChannel->protocol();
-		const char *data = protocol.data();
-		size = std::min(size - 1, int(protocol.size()));
-		std::copy(data, data + size, buffer);
-		buffer[size] = '\0';
-		return int(size + 1);
+		return copyAndReturn(dataChannel->protocol(), buffer, size);
 	});
 }
 
@@ -709,19 +654,19 @@ int rtcGetDataChannelReliability(int dc, rtcReliability *reliability) {
 		if (!reliability)
 			throw std::invalid_argument("Unexpected null pointer for reliability");
 
-		Reliability r = dataChannel->reliability();
+		Reliability dcr = dataChannel->reliability();
 		std::memset(reliability, 0, sizeof(*reliability));
-		reliability->unordered = r.unordered;
-		if (r.type == Reliability::Type::Timed) {
+		reliability->unordered = dcr.unordered;
+		if (dcr.type == Reliability::Type::Timed) {
 			reliability->unreliable = true;
-			reliability->maxPacketLifeTime = unsigned(std::get<milliseconds>(r.rexmit).count());
-		} else if (r.type == Reliability::Type::Rexmit) {
+			reliability->maxPacketLifeTime = unsigned(std::get<milliseconds>(dcr.rexmit).count());
+		} else if (dcr.type == Reliability::Type::Rexmit) {
 			reliability->unreliable = true;
-			reliability->maxRetransmits = unsigned(std::get<int>(r.rexmit));
+			reliability->maxRetransmits = unsigned(std::get<int>(dcr.rexmit));
 		} else {
 			reliability->unreliable = false;
 		}
-		return 0;
+		return RTC_ERR_SUCCESS;
 	});
 }
 
@@ -837,12 +782,12 @@ int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb) {
 	return WRAP({
 		auto channel = getChannel(id);
 		if (cb)
-			channel->onOpen([id, cb]() {
+			channel->onAvailable([id, cb]() {
 				if (auto ptr = getUserPointer(id))
 					cb(id, *ptr);
 			});
 		else
-			channel->onOpen(nullptr);
+			channel->onAvailable(nullptr);
 	});
 }
 
@@ -853,34 +798,38 @@ int rtcReceiveMessage(int id, char *buffer, int *size) {
 		if (!size)
 			throw std::invalid_argument("Unexpected null pointer for size");
 
-		if (!buffer && *size != 0)
-			throw std::invalid_argument("Unexpected null pointer for buffer");
-
-		if (auto message = channel->receive())
-			return std::visit( //
-			    overloaded{    //
-			               [&](binary b) {
-				               if (*size > 0) {
-					               *size = std::min(*size, int(b.size()));
-					               auto data = reinterpret_cast<const char *>(b.data());
-					               std::copy(data, data + *size, buffer);
-				               }
-				               return 1;
-			               },
-			               [&](string s) {
-				               if (*size > 0) {
-					               int len = std::min(*size - 1, int(s.size()));
-					               if (len >= 0) {
-						               std::copy(s.data(), s.data() + len, buffer);
-						               buffer[len] = '\0';
-					               }
-					               *size = -(len + 1);
-				               }
-				               return 1;
-			               }},
-			    *message);
-		else
-			return 0;
+		*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);
 	});
 }
 

+ 11 - 10
src/certificate.cpp

@@ -153,22 +153,23 @@ Certificate::Certificate(string crt_pem, string key_pem) {
 	mFingerprint = make_fingerprint(mX509.get());
 }
 
-Certificate::Certificate(shared_ptr<X509> x509, shared_ptr<EVP_PKEY> pkey) :
-	mX509(std::move(x509)), mPKey(std::move(pkey))
-{
+Certificate::Certificate(shared_ptr<X509> x509, shared_ptr<EVP_PKEY> pkey)
+    : mX509(std::move(x509)), mPKey(std::move(pkey)) {
 	mFingerprint = make_fingerprint(mX509.get());
 }
 
 string Certificate::fingerprint() const { return mFingerprint; }
 
-std::tuple<X509 *, EVP_PKEY *> Certificate::credentials() const { return {mX509.get(), mPKey.get()}; }
+std::tuple<X509 *, EVP_PKEY *> Certificate::credentials() const {
+	return {mX509.get(), mPKey.get()};
+}
 
 string make_fingerprint(X509 *x509) {
 	const size_t size = 32;
-    unsigned char buffer[size];
-    unsigned int len = size;
-    if (!X509_digest(x509, EVP_sha256(), buffer, &len))
-      throw std::runtime_error("X509 fingerprint error");
+	unsigned char buffer[size];
+	unsigned int len = size;
+	if (!X509_digest(x509, EVP_sha256(), buffer, &len))
+		throw std::runtime_error("X509 fingerprint error");
 
 	std::ostringstream oss;
 	oss << std::hex << std::uppercase << std::setfill('0');
@@ -186,10 +187,10 @@ certificate_ptr make_certificate_impl(string commonName) {
 	shared_ptr<X509> x509(X509_new(), X509_free);
 	shared_ptr<EVP_PKEY> pkey(EVP_PKEY_new(), EVP_PKEY_free);
 
-    unique_ptr<RSA, decltype(&RSA_free)> rsa(RSA_new(), RSA_free);
+	unique_ptr<RSA, decltype(&RSA_free)> rsa(RSA_new(), RSA_free);
 	unique_ptr<BIGNUM, decltype(&BN_free)> exponent(BN_new(), BN_free);
 	unique_ptr<BIGNUM, decltype(&BN_free)> serial_number(BN_new(), BN_free);
-    unique_ptr<X509_NAME, decltype(&X509_NAME_free)> name(X509_NAME_new(), X509_NAME_free);
+	unique_ptr<X509_NAME, decltype(&X509_NAME_free)> name(X509_NAME_new(), X509_NAME_free);
 
 	if (!x509 || !pkey || !rsa || !exponent || !serial_number || !name)
 		throw std::runtime_error("Unable allocate structures for certificate generation");

+ 3 - 10
src/channel.cpp

@@ -26,13 +26,9 @@ size_t Channel::bufferedAmount() const { return mBufferedAmount; }
 
 size_t Channel::availableAmount() const { return 0; }
 
-void Channel::onOpen(std::function<void()> callback) {
-	mOpenCallback = callback;
-}
+void Channel::onOpen(std::function<void()> callback) { mOpenCallback = callback; }
 
-void Channel::onClosed(std::function<void()> callback) {
-	mClosedCallback = callback;
-}
+void Channel::onClosed(std::function<void()> callback) { mClosedCallback = callback; }
 
 void Channel::onError(std::function<void(string error)> callback) { mErrorCallback = callback; }
 
@@ -57,9 +53,7 @@ void Channel::onBufferedAmountLow(std::function<void()> callback) {
 
 void Channel::setBufferedAmountLowThreshold(size_t amount) { mBufferedAmountLowThreshold = amount; }
 
-void Channel::onAvailable(std::function<void()> callback) {
-	mAvailableCallback = callback;
-}
+void Channel::onAvailable(std::function<void()> callback) { mAvailableCallback = callback; }
 
 void Channel::triggerOpen() { mOpenCallback(); }
 
@@ -96,4 +90,3 @@ void Channel::resetCallbacks() {
 }
 
 } // namespace rtc
-

+ 89 - 56
src/datachannel.cpp

@@ -72,24 +72,18 @@ struct CloseMessage {
 };
 #pragma pack(pop)
 
-DataChannel::DataChannel(weak_ptr<PeerConnection> pc, unsigned int stream, string label,
+DataChannel::DataChannel(weak_ptr<PeerConnection> pc, uint16_t stream, string label,
                          string protocol, Reliability reliability)
     : mPeerConnection(pc), mStream(stream), mLabel(std::move(label)),
       mProtocol(std::move(protocol)),
       mReliability(std::make_shared<Reliability>(std::move(reliability))),
       mRecvQueue(RECV_QUEUE_LIMIT, message_size_func) {}
 
-DataChannel::DataChannel(weak_ptr<PeerConnection> pc, weak_ptr<SctpTransport> transport,
-                         unsigned int stream)
-    : mPeerConnection(pc), mSctpTransport(transport), mStream(stream),
-      mReliability(std::make_shared<Reliability>()),
-      mRecvQueue(RECV_QUEUE_LIMIT, message_size_func) {}
+DataChannel::~DataChannel() { close(); }
 
-DataChannel::~DataChannel() {
-	close();
-}
+uint16_t DataChannel::stream() const { return mStream; }
 
-unsigned int DataChannel::stream() const { return mStream; }
+uint16_t DataChannel::id() const { return uint16_t(mStream); }
 
 string DataChannel::label() const { return mLabel; }
 
@@ -123,14 +117,29 @@ bool DataChannel::send(const byte *data, size_t size) {
 
 std::optional<message_variant> DataChannel::receive() {
 	while (auto next = mRecvQueue.tryPop()) {
-		message_ptr message = std::move(*next);
-		if (message->type == Message::Control) {
-			auto raw = reinterpret_cast<const uint8_t *>(message->data());
-			if (!message->empty() && raw[0] == MESSAGE_CLOSE)
-				remoteClose();
-		} else {
+		message_ptr message = *next;
+		if (message->type != Message::Control)
 			return to_variant(std::move(*message));
-		}
+
+		auto raw = reinterpret_cast<const uint8_t *>(message->data());
+		if (!message->empty() && raw[0] == MESSAGE_CLOSE)
+			remoteClose();
+	}
+
+	return nullopt;
+}
+
+std::optional<message_variant> DataChannel::peek() {
+	while (auto next = mRecvQueue.peek()) {
+		message_ptr message = *next;
+		if (message->type != Message::Control)
+			return to_variant(std::move(*message));
+
+		auto raw = reinterpret_cast<const uint8_t *>(message->data());
+		if (!message->empty() && raw[0] == MESSAGE_CLOSE)
+			remoteClose();
+
+		mRecvQueue.tryPop();
 	}
 
 	return nullopt;
@@ -156,43 +165,12 @@ size_t DataChannel::availableAmount() const { return mRecvQueue.amount(); }
 void DataChannel::open(shared_ptr<SctpTransport> transport) {
 	mSctpTransport = transport;
 
-	uint8_t channelType;
-	uint32_t reliabilityParameter;
-	switch (mReliability->type) {
-	case Reliability::Type::Rexmit:
-		channelType = CHANNEL_PARTIAL_RELIABLE_REXMIT;
-		reliabilityParameter = uint32_t(std::get<int>(mReliability->rexmit));
-		break;
-
-	case Reliability::Type::Timed:
-		channelType = CHANNEL_PARTIAL_RELIABLE_TIMED;
-		reliabilityParameter = uint32_t(std::get<milliseconds>(mReliability->rexmit).count());
-		break;
-
-	default:
-		channelType = CHANNEL_RELIABLE;
-		reliabilityParameter = 0;
-		break;
-	}
-
-	if (mReliability->unordered)
-		channelType |= 0x80;
-
-	const size_t len = sizeof(OpenMessage) + mLabel.size() + mProtocol.size();
-	binary buffer(len, byte(0));
-	auto &open = *reinterpret_cast<OpenMessage *>(buffer.data());
-	open.type = MESSAGE_OPEN;
-	open.channelType = channelType;
-	open.priority = htons(0);
-	open.reliabilityParameter = htonl(reliabilityParameter);
-	open.labelLength = htons(uint16_t(mLabel.size()));
-	open.protocolLength = htons(uint16_t(mProtocol.size()));
-
-	auto end = reinterpret_cast<char *>(buffer.data() + sizeof(OpenMessage));
-	std::copy(mLabel.begin(), mLabel.end(), end);
-	std::copy(mProtocol.begin(), mProtocol.end(), end + mLabel.size());
+	if (!mIsOpen.exchange(true))
+		triggerOpen();
+}
 
-	transport->send(make_message(buffer.begin(), buffer.end(), Message::Control, mStream));
+void DataChannel::processOpenMessage(message_ptr) {
+	PLOG_WARNING << "Received an open message for a user-negotiated DataChannel, ignoring";
 }
 
 bool DataChannel::outgoing(message_ptr message) {
@@ -252,7 +230,62 @@ void DataChannel::incoming(message_ptr message) {
 	}
 }
 
-void DataChannel::processOpenMessage(message_ptr message) {
+NegociatedDataChannel::NegociatedDataChannel(std::weak_ptr<PeerConnection> pc, uint16_t stream,
+                                             string label, string protocol, Reliability reliability)
+    : DataChannel(pc, stream, std::move(label), std::move(protocol), std::move(reliability)) {}
+
+NegociatedDataChannel::NegociatedDataChannel(std::weak_ptr<PeerConnection> pc,
+                                             std::weak_ptr<SctpTransport> transport,
+                                             uint16_t stream)
+    : DataChannel(pc, stream, "", "", {}) {
+	mSctpTransport = transport;
+}
+
+NegociatedDataChannel::~NegociatedDataChannel() {}
+
+void NegociatedDataChannel::open(shared_ptr<SctpTransport> transport) {
+	mSctpTransport = transport;
+
+	uint8_t channelType;
+	uint32_t reliabilityParameter;
+	switch (mReliability->type) {
+	case Reliability::Type::Rexmit:
+		channelType = CHANNEL_PARTIAL_RELIABLE_REXMIT;
+		reliabilityParameter = uint32_t(std::get<int>(mReliability->rexmit));
+		break;
+
+	case Reliability::Type::Timed:
+		channelType = CHANNEL_PARTIAL_RELIABLE_TIMED;
+		reliabilityParameter = uint32_t(std::get<milliseconds>(mReliability->rexmit).count());
+		break;
+
+	default:
+		channelType = CHANNEL_RELIABLE;
+		reliabilityParameter = 0;
+		break;
+	}
+
+	if (mReliability->unordered)
+		channelType |= 0x80;
+
+	const size_t len = sizeof(OpenMessage) + mLabel.size() + mProtocol.size();
+	binary buffer(len, byte(0));
+	auto &open = *reinterpret_cast<OpenMessage *>(buffer.data());
+	open.type = MESSAGE_OPEN;
+	open.channelType = channelType;
+	open.priority = htons(0);
+	open.reliabilityParameter = htonl(reliabilityParameter);
+	open.labelLength = htons(uint16_t(mLabel.size()));
+	open.protocolLength = htons(uint16_t(mProtocol.size()));
+
+	auto end = reinterpret_cast<char *>(buffer.data() + sizeof(OpenMessage));
+	std::copy(mLabel.begin(), mLabel.end(), end);
+	std::copy(mProtocol.begin(), mProtocol.end(), end + mLabel.size());
+
+	transport->send(make_message(buffer.begin(), buffer.end(), Message::Control, mStream));
+}
+
+void NegociatedDataChannel::processOpenMessage(message_ptr message) {
 	auto transport = mSctpTransport.lock();
 	if (!transport)
 		throw std::runtime_error("DataChannel has no transport");
@@ -294,8 +327,8 @@ void DataChannel::processOpenMessage(message_ptr message) {
 
 	transport->send(make_message(buffer.begin(), buffer.end(), Message::Control, mStream));
 
-	mIsOpen = true;
-	triggerOpen();
+	if (!mIsOpen.exchange(true))
+		triggerOpen();
 }
 
 } // namespace rtc

+ 160 - 48
src/description.cpp

@@ -1,6 +1,6 @@
 /**
  * Copyright (c) 2019-2020 Paul-Louis Ageneau
- * Copyright (c) 2020 Staz M
+ * Copyright (c) 2020 Staz Modrzynski
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -66,11 +66,6 @@ template <typename T> T to_integer(string_view s) {
 
 namespace rtc {
 
-Description::Description(const string &sdp, const string &typeString)
-    : Description(sdp, stringToType(typeString)) {}
-
-Description::Description(const string &sdp, Type type) : Description(sdp, type, Role::ActPass) {}
-
 Description::Description(const string &sdp, Type type, Role role)
     : mType(Type::Unspec), mRole(role) {
 	hintType(type);
@@ -141,6 +136,10 @@ Description::Description(const string &sdp, Type type, Role role)
 	}
 }
 
+Description::Description(const string &sdp, string typeString)
+    : Description(sdp, !typeString.empty() ? stringToType(typeString) : Type::Unspec,
+                  Role::ActPass) {}
+
 Description::Type Description::type() const { return mType; }
 
 string Description::typeString() const { return typeToString(mType); }
@@ -173,12 +172,15 @@ void Description::setFingerprint(string fingerprint) {
 }
 
 void Description::addCandidate(Candidate candidate) {
+	candidate.hintMid(bundleMid());
 	mCandidates.emplace_back(std::move(candidate));
 }
 
 void Description::addCandidates(std::vector<Candidate> candidates) {
-	for(auto candidate : candidates)
+	for (Candidate candidate : candidates) {
+		candidate.hintMid(bundleMid());
 		mCandidates.emplace_back(std::move(candidate));
+	}
 }
 
 void Description::endCandidates() { mEnded = true; }
@@ -383,8 +385,9 @@ int Description::addAudio(string mid, Direction dir) {
 	return addMedia(Audio(std::move(mid), dir));
 }
 
-std::variant<Description::Media *, Description::Application *> Description::media(int index) {
-	if (index < 0 || index >= int(mEntries.size()))
+std::variant<Description::Media *, Description::Application *>
+Description::media(unsigned int index) {
+	if (index >= mEntries.size())
 		throw std::out_of_range("Media index out of range");
 
 	const auto &entry = mEntries[index];
@@ -402,8 +405,8 @@ std::variant<Description::Media *, Description::Application *> Description::medi
 }
 
 std::variant<const Description::Media *, const Description::Application *>
-Description::media(int index) const {
-	if (index < 0 || index >= int(mEntries.size()))
+Description::media(unsigned int index) const {
+	if (index >= mEntries.size())
 		throw std::out_of_range("Media index out of range");
 
 	const auto &entry = mEntries[index];
@@ -420,7 +423,7 @@ Description::media(int index) const {
 	}
 }
 
-int Description::mediaCount() const { return int(mEntries.size()); }
+unsigned int Description::mediaCount() const { return unsigned(mEntries.size()); }
 
 Description::Entry::Entry(const string &mline, string mid, Direction dir)
     : mMid(std::move(mid)), mDirection(dir) {
@@ -468,8 +471,11 @@ string Description::Entry::generateSdpLines(string_view eol) const {
 		break;
 	}
 
-	for (const auto &attr : mAttributes)
-		sdp << "a=" << attr << eol;
+	for (const auto &attr : mAttributes) {
+		if (attr.find("extmap") == std::string::npos &&
+		    attr.find("rtcp-rsize") == std::string::npos)
+			sdp << "a=" << attr << eol;
+	}
 
 	return sdp.str();
 }
@@ -495,6 +501,36 @@ void Description::Entry::parseSdpLine(string_view line) {
 			mAttributes.emplace_back(line.substr(2));
 	}
 }
+std::vector<string>::iterator Description::Entry::beginAttributes() { return mAttributes.begin(); }
+std::vector<string>::iterator Description::Entry::endAttributes() { return mAttributes.end(); }
+std::vector<string>::iterator
+Description::Entry::removeAttribute(std::vector<string>::iterator it) {
+	return mAttributes.erase(it);
+}
+
+void Description::Media::addSSRC(uint32_t ssrc, std::string name) {
+	mAttributes.emplace_back("ssrc:" + std::to_string(ssrc) + " cname:" + name);
+	mSsrcs.emplace_back(ssrc);
+}
+
+void Description::Media::replaceSSRC(uint32_t oldSSRC, uint32_t ssrc, std::string name) {
+	auto it = mAttributes.begin();
+	while (it != mAttributes.end()) {
+		if (it->find("ssrc:" + std::to_string(oldSSRC)) == 0) {
+			it = mAttributes.erase(it);
+		} else
+			it++;
+	}
+	mAttributes.emplace_back("ssrc:" + std::to_string(ssrc) + " cname:" + name);
+}
+
+void Description::Media::addSSRC(uint32_t ssrc) {
+	mAttributes.emplace_back("ssrc:" + std::to_string(ssrc));
+}
+
+bool Description::Media::hasSSRC(uint32_t ssrc) {
+	return std::find(mSsrcs.begin(), mSsrcs.end(), ssrc) != mSsrcs.end();
+}
 
 Description::Application::Application(string mid)
     : Entry("application 9 UDP/DTLS/SCTP", std::move(mid), Direction::SendRecv) {}
@@ -642,24 +678,57 @@ void Description::Media::removeFormat(const string &fmt) {
 	}
 }
 
-void Description::Media::addVideoCodec(int payloadType, const string &codec) {
+void Description::Video::addVideoCodec(int payloadType, const string &codec) {
 	RTPMap map(std::to_string(payloadType) + ' ' + codec + "/90000");
 	map.addFB("nack");
+	map.addFB("nack pli");
+	//    map.addFB("nack fir");
 	map.addFB("goog-remb");
 	if (codec == "H264") {
 		// Use Constrained Baseline profile Level 4.2 (necessary for Firefox)
 		// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#Supported_video_codecs
 		// TODO: Should be 42E0 but 42C0 appears to be more compatible. Investigate this.
-		map.fmtps.emplace_back("profile-level-id=42E02A;level-asymmetry-allowed=1");
+		map.fmtps.emplace_back(
+		    "profile-level-id=4de01f;packetization-mode=1;level-asymmetry-allowed=1");
+
+		// Because certain Android devices don't like me, let us just negotiate some random
+		{
+			RTPMap map(std::to_string(payloadType + 1) + ' ' + codec + "/90000");
+			map.addFB("nack");
+			map.addFB("nack pli");
+			//            map.addFB("nack fir");
+			map.addFB("goog-remb");
+			addRTPMap(map);
+		}
 	}
-	mRtpMap.emplace(map.pt, map);
+	addRTPMap(map);
+
+	//	// RTX Packets
+	/* TODO
+	 *  TIL that Firefox does not properly support the negotiation of RTX! It works, but doesn't
+	 * negotiate the SSRC so we have no idea what SSRC is RTX going to be. Three solutions: One) we
+	 * don't negotitate it and (maybe) break RTX support with Edge. Two) we do negotiate it and
+	 * rebuild the original packet before we send it distribute it to each track. Three) we complain
+	 * to mozilla. This one probably won't do much.
+	 */
+	//    RTPMap rtx(std::to_string(payloadType+1) + " rtx/90000");
+	//    // TODO rtx-time is how long can a request be stashed for before needing to resend it.
+	//    Needs to be parameterized rtx.addAttribute("apt=" + std::to_string(payloadType) +
+	//    ";rtx-time=3000"); addRTPMap(rtx);
+}
+
+void Description::Audio::addAudioCodec(int payloadType, const string &codec) {
+	// TODO This 48000/2 should be parameterized
+	RTPMap map(std::to_string(payloadType) + ' ' + codec + "/48000/2");
+	map.fmtps.emplace_back("maxaveragebitrate=96000; stereo=1; sprop-stereo=1; useinbandfec=1");
+	addRTPMap(map);
 }
 
-void Description::Media::addH264Codec(int pt) { addVideoCodec(pt, "H264"); }
+void Description::Video::addH264Codec(int pt) { addVideoCodec(pt, "H264"); }
 
-void Description::Media::addVP8Codec(int payloadType) { addVideoCodec(payloadType, "VP8"); }
+void Description::Video::addVP8Codec(int payloadType) { addVideoCodec(payloadType, "VP8"); }
 
-void Description::Media::addVP9Codec(int payloadType) { addVideoCodec(payloadType, "VP9"); }
+void Description::Video::addVP9Codec(int payloadType) { addVideoCodec(payloadType, "VP9"); }
 
 void Description::Media::setBitrate(int bitrate) { mBas = bitrate; }
 
@@ -686,8 +755,10 @@ string Description::Media::generateSdpLines(string_view eol) const {
 			sdp << '/' << map.encParams;
 		sdp << eol;
 
-		for (const auto &val : map.rtcpFbs)
-			sdp << "a=rtcp-fb:" << map.pt << ' ' << val << eol;
+		for (const auto &val : map.rtcpFbs) {
+			if (val != "transport-cc")
+				sdp << "a=rtcp-fb:" << map.pt << ' ' << val << eol;
+		}
 		for (const auto &val : map.fmtps)
 			sdp << "a=fmtp:" << map.pt << ' ' << val << eol;
 	}
@@ -701,29 +772,32 @@ void Description::Media::parseSdpLine(string_view line) {
 		auto [key, value] = parse_pair(attr);
 
 		if (key == "rtpmap") {
-			Description::Media::RTPMap map(value);
-			int pt = map.pt;
-			mRtpMap.emplace(pt, std::move(map));
+			auto pt = Description::Media::RTPMap::parsePT(value);
+			auto it = mRtpMap.find(pt);
+			if (it == mRtpMap.end()) {
+				it = mRtpMap.insert(std::make_pair(pt, Description::Media::RTPMap(value))).first;
+			} else {
+				it->second.setMLine(value);
+			}
 		} else if (key == "rtcp-fb") {
 			size_t p = value.find(' ');
 			int pt = to_integer<int>(value.substr(0, p));
 			auto it = mRtpMap.find(pt);
 			if (it == mRtpMap.end()) {
-				PLOG_WARNING << "rtcp-fb applied before the corresponding rtpmap, ignoring";
-			} else {
-				it->second.rtcpFbs.emplace_back(value.substr(p + 1));
+				it = mRtpMap.insert(std::make_pair(pt, Description::Media::RTPMap())).first;
 			}
+			it->second.rtcpFbs.emplace_back(value.substr(p + 1));
 		} else if (key == "fmtp") {
 			size_t p = value.find(' ');
 			int pt = to_integer<int>(value.substr(0, p));
 			auto it = mRtpMap.find(pt);
-			if (it == mRtpMap.end()) {
-				PLOG_WARNING << "fmtp applied before the corresponding rtpmap, ignoring";
-			} else {
-				it->second.fmtps.emplace_back(value.substr(p + 1));
-			}
+			if (it == mRtpMap.end())
+				it = mRtpMap.insert(std::make_pair(pt, Description::Media::RTPMap())).first;
+			it->second.fmtps.emplace_back(value.substr(p + 1));
 		} else if (key == "rtcp-mux") {
 			// always added
+		} else if (key == "ssrc") {
+			mSsrcs.emplace_back(std::stoul((std::string)value));
 		} else {
 			Entry::parseSdpLine(line);
 		}
@@ -734,7 +808,55 @@ void Description::Media::parseSdpLine(string_view line) {
 	}
 }
 
-Description::Media::RTPMap::RTPMap(string_view mline) {
+void Description::Media::addRTPMap(const Description::Media::RTPMap &map) {
+	mRtpMap.emplace(map.pt, map);
+}
+
+std::vector<uint32_t> Description::Media::getSSRCs() {
+	std::vector<uint32_t> vec;
+	for (auto &val : mAttributes) {
+		PLOG_DEBUG << val;
+		if (val.find("ssrc:") == 0) {
+			vec.emplace_back(std::stoul((std::string)val.substr(5, val.find(" "))));
+		}
+	}
+	return vec;
+}
+
+std::map<int, Description::Media::RTPMap>::iterator Description::Media::beginMaps() {
+	return mRtpMap.begin();
+}
+
+std::map<int, Description::Media::RTPMap>::iterator Description::Media::endMaps() {
+	return mRtpMap.end();
+}
+
+std::map<int, Description::Media::RTPMap>::iterator
+Description::Media::removeMap(std::map<int, Description::Media::RTPMap>::iterator iterator) {
+	return mRtpMap.erase(iterator);
+}
+
+Description::Media::RTPMap::RTPMap(string_view mline) { setMLine(mline); }
+
+void Description::Media::RTPMap::removeFB(const string &str) {
+	auto it = rtcpFbs.begin();
+	while (it != rtcpFbs.end()) {
+		if (it->find(str) != std::string::npos) {
+			it = rtcpFbs.erase(it);
+		} else
+			it++;
+	}
+}
+
+void Description::Media::RTPMap::addFB(const string &str) { rtcpFbs.emplace_back(str); }
+
+int Description::Media::RTPMap::parsePT(string_view view) {
+	size_t p = view.find(' ');
+
+	return to_integer<int>(view.substr(0, p));
+}
+
+void Description::Media::RTPMap::setMLine(string_view mline) {
 	size_t p = mline.find(' ');
 
 	this->pt = to_integer<int>(mline.substr(0, p));
@@ -752,25 +874,15 @@ Description::Media::RTPMap::RTPMap(string_view mline) {
 		this->clockRate = to_integer<int>(line);
 	else {
 		this->clockRate = to_integer<int>(line.substr(0, spl));
-		this->encParams = line.substr(spl);
-	}
-}
-
-void Description::Media::RTPMap::removeFB(const string &str) {
-	auto it = rtcpFbs.begin();
-	while (it != rtcpFbs.end()) {
-		if (it->find(str) != std::string::npos) {
-			it = rtcpFbs.erase(it);
-		} else
-			it++;
+		this->encParams = line.substr(spl + 1);
 	}
 }
 
-void Description::Media::RTPMap::addFB(const string &str) { rtcpFbs.emplace_back(str); }
-
 Description::Audio::Audio(string mid, Direction dir)
     : Media("audio 9 UDP/TLS/RTP/SAVPF", std::move(mid), dir) {}
 
+void Description::Audio::addOpusCodec(int payloadType) { addAudioCodec(payloadType, "OPUS"); }
+
 Description::Video::Video(string mid, Direction dir)
     : Media("video 9 UDP/TLS/RTP/SAVPF", std::move(mid), dir) {}
 
@@ -778,7 +890,7 @@ Description::Type Description::stringToType(const string &typeString) {
 	using TypeMap_t = std::unordered_map<string, Type>;
 	static const TypeMap_t TypeMap = {{"unspec", Type::Unspec},
 	                                  {"offer", Type::Offer},
-	                                  {"answer", Type::Pranswer},
+	                                  {"answer", Type::Answer},
 	                                  {"pranswer", Type::Pranswer},
 	                                  {"rollback", Type::Rollback}};
 	auto it = TypeMap.find(typeString);

+ 80 - 26
src/dtlssrtptransport.cpp

@@ -82,7 +82,7 @@ bool DtlsSrtpTransport::sendMedia(message_ptr message) {
 		return false;
 	}
 
-	int size = message->size();
+	int size = int(message->size());
 	PLOG_VERBOSE << "Send size=" << size;
 
 	// The RTP header has a minimum size of 12 bytes
@@ -109,16 +109,31 @@ bool DtlsSrtpTransport::sendMedia(message_ptr message) {
 		if (srtp_err_status_t err = srtp_protect_rtcp(mSrtpOut, message->data(), &size)) {
 			if (err == srtp_err_status_replay_fail)
 				throw std::runtime_error("SRTCP packet is a replay");
-			else
+			else if (err == srtp_err_status_no_ctx) {
+				auto ssrc = ((RTCP_SR *)message->data())->senderSSRC();
+				PLOG_INFO << "Adding SSRC to SRTCP: " << ssrc;
+				addSSRC(ssrc);
+				if ((err = srtp_protect_rtcp(mSrtpOut, message->data(), &size)))
+					throw std::runtime_error("SRTCP protect error, status=" +
+					                         to_string(static_cast<int>(err)));
+			} else {
 				throw std::runtime_error("SRTCP protect error, status=" +
 				                         to_string(static_cast<int>(err)));
+			}
 		}
 		PLOG_VERBOSE << "Protected SRTCP packet, size=" << size;
 	} else {
 		if (srtp_err_status_t err = srtp_protect(mSrtpOut, message->data(), &size)) {
 			if (err == srtp_err_status_replay_fail)
-				throw std::runtime_error("SRTP packet is a replay");
-			else
+				throw std::runtime_error("Outgoing SRTP packet is a replay");
+			else if (err == srtp_err_status_no_ctx) {
+				auto ssrc = ((RTP *)message->data())->ssrc();
+				PLOG_INFO << "Adding SSRC to RTP: " << ssrc;
+				addSSRC(ssrc);
+				if ((err = srtp_protect_rtcp(mSrtpOut, message->data(), &size)))
+					throw std::runtime_error("SRTCP protect error, status=" +
+					                         to_string(static_cast<int>(err)));
+			} else
 				throw std::runtime_error("SRTP protect error, status=" +
 				                         to_string(static_cast<int>(err)));
 		}
@@ -127,7 +142,6 @@ bool DtlsSrtpTransport::sendMedia(message_ptr message) {
 
 	message->resize(size);
 	return outgoing(message);
-//	return DtlsTransport::send(message);
 }
 
 void DtlsSrtpTransport::incoming(message_ptr message) {
@@ -137,7 +151,7 @@ void DtlsSrtpTransport::incoming(message_ptr message) {
 		return;
 	}
 
-	int size = message->size();
+	int size = int(message->size());
 	if (size == 0)
 		return;
 
@@ -174,13 +188,23 @@ void DtlsSrtpTransport::incoming(message_ptr message) {
 					PLOG_WARNING << "Incoming SRTCP packet is a replay";
 				else if (err == srtp_err_status_auth_fail)
 					PLOG_WARNING << "Incoming SRTCP packet failed authentication check";
-				else
-					PLOG_WARNING << "SRTCP unprotect error, status=" << err;
+				else if (err == srtp_err_status_no_ctx) {
+					auto ssrc = ((RTCP_SR *)message->data())->senderSSRC();
+					PLOG_INFO << "Adding SSRC to RTCP: " << ssrc;
+					addSSRC(ssrc);
+					if ((err = srtp_unprotect_rtcp(mSrtpIn, message->data(), &size)))
+						throw std::runtime_error("SRTCP unprotect error, status=" +
+						                         to_string(static_cast<int>(err)));
+				} else {
+					PLOG_WARNING << "SRTCP unprotect error, status=" << err
+					             << " SSRC=" << ((RTCP_SR *)message->data())->senderSSRC();
+				}
 				return;
 			}
 			PLOG_VERBOSE << "Unprotected SRTCP packet, size=" << size;
 			message->type = Message::Type::Control;
-			message->stream = to_integer<uint8_t>(*(message->begin() + 1)); // Payload Type
+			auto rtp = (RTCP_SR *)message->data();
+			message->stream = rtp->senderSSRC();
 		} else {
 			PLOG_VERBOSE << "Incoming SRTP packet, size=" << size;
 			if (srtp_err_status_t err = srtp_unprotect(mSrtpIn, message->data(), &size)) {
@@ -188,13 +212,22 @@ void DtlsSrtpTransport::incoming(message_ptr message) {
 					PLOG_WARNING << "Incoming SRTP packet is a replay";
 				else if (err == srtp_err_status_auth_fail)
 					PLOG_WARNING << "Incoming SRTP packet failed authentication check";
-				else
-					PLOG_WARNING << "SRTP unprotect error, status=" << err;
+				else if (err == srtp_err_status_no_ctx) {
+					auto ssrc = ((RTP *)message->data())->ssrc();
+					PLOG_INFO << "Adding SSRC to RTP: " << ssrc;
+					addSSRC(ssrc);
+					if ((err = srtp_unprotect(mSrtpIn, message->data(), &size)))
+						throw std::runtime_error("SRTCP unprotect error, status=" +
+						                         to_string(static_cast<int>(err)));
+				} else
+					PLOG_WARNING << "SRTP unprotect error, status=" << err
+					             << " SSRC=" << ((RTP *)message->data())->ssrc();
 				return;
 			}
 			PLOG_VERBOSE << "Unprotected SRTP packet, size=" << size;
 			message->type = Message::Type::Binary;
-			message->stream = value2; // Payload Type
+			auto rtp = (RTP *)message->data();
+			message->stream = rtp->ssrc();
 		}
 
 		message->resize(size);
@@ -209,6 +242,8 @@ void DtlsSrtpTransport::postHandshake() {
 	if (mInitDone)
 		return;
 
+	static_assert(SRTP_AES_ICM_128_KEY_LEN_WSALT == SRTP_AES_128_KEY_LEN + SRTP_SALT_LEN);
+
 	const size_t materialLen = SRTP_AES_ICM_128_KEY_LEN_WSALT * 2;
 	unsigned char material[materialLen];
 	const unsigned char *clientKey, *clientSalt, *serverKey, *serverSalt;
@@ -257,22 +292,42 @@ void DtlsSrtpTransport::postHandshake() {
 	serverSalt = clientSalt + SRTP_SALT_LEN;
 #endif
 
-	unsigned char clientSessionKey[SRTP_AES_ICM_128_KEY_LEN_WSALT];
-	std::memcpy(clientSessionKey, clientKey, SRTP_AES_128_KEY_LEN);
-	std::memcpy(clientSessionKey + SRTP_AES_128_KEY_LEN, clientSalt, SRTP_SALT_LEN);
+	std::memcpy(mClientSessionKey, clientKey, SRTP_AES_128_KEY_LEN);
+	std::memcpy(mClientSessionKey + SRTP_AES_128_KEY_LEN, clientSalt, SRTP_SALT_LEN);
 
-	unsigned char serverSessionKey[SRTP_AES_ICM_128_KEY_LEN_WSALT];
-	std::memcpy(serverSessionKey, serverKey, SRTP_AES_128_KEY_LEN);
-	std::memcpy(serverSessionKey + SRTP_AES_128_KEY_LEN, serverSalt, SRTP_SALT_LEN);
+	std::memcpy(mServerSessionKey, serverKey, SRTP_AES_128_KEY_LEN);
+	std::memcpy(mServerSessionKey + SRTP_AES_128_KEY_LEN, serverSalt, SRTP_SALT_LEN);
 
+	// Add SSRC=1 as an inbound because that is what Chrome does.
 	srtp_policy_t inbound = {};
 	srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtp);
 	srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtcp);
-	inbound.ssrc.type = ssrc_any_inbound;
-	inbound.ssrc.value = 0;
-	inbound.key = mIsClient ? serverSessionKey : clientSessionKey;
+	inbound.ssrc.type = ssrc_specific;
+	inbound.ssrc.value = 1;
+	inbound.key = mIsClient ? mServerSessionKey : mClientSessionKey;
 	inbound.next = nullptr;
 
+	if (srtp_err_status_t err = srtp_add_stream(mSrtpIn, &inbound)) {
+		throw std::runtime_error("SRTP add inbound stream failed, status=" +
+		                         to_string(static_cast<int>(err)));
+	}
+
+	mInitDone = true;
+}
+
+void DtlsSrtpTransport::addSSRC(uint32_t ssrc) {
+	if (!mInitDone)
+		throw std::logic_error("Attempted to add SSRC before SRTP keying material is derived");
+
+	srtp_policy_t inbound = {};
+	srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtp);
+	srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtcp);
+	inbound.ssrc.type = ssrc_specific;
+	inbound.ssrc.value = ssrc;
+	inbound.key = mIsClient ? mServerSessionKey : mClientSessionKey;
+	inbound.next = nullptr;
+	inbound.allow_repeat_tx = true;
+
 	if (srtp_err_status_t err = srtp_add_stream(mSrtpIn, &inbound))
 		throw std::runtime_error("SRTP add inbound stream failed, status=" +
 		                         to_string(static_cast<int>(err)));
@@ -280,16 +335,15 @@ void DtlsSrtpTransport::postHandshake() {
 	srtp_policy_t outbound = {};
 	srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&outbound.rtp);
 	srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&outbound.rtcp);
-	outbound.ssrc.type = ssrc_any_outbound;
-	outbound.ssrc.value = 0;
-	outbound.key = mIsClient ? clientSessionKey : serverSessionKey;
+	outbound.ssrc.type = ssrc_specific;
+	outbound.ssrc.value = ssrc;
+	outbound.key = mIsClient ? mClientSessionKey : mServerSessionKey;
 	outbound.next = nullptr;
+	outbound.allow_repeat_tx = true;
 
 	if (srtp_err_status_t err = srtp_add_stream(mSrtpOut, &outbound))
 		throw std::runtime_error("SRTP add outbound stream failed, status=" +
 		                         to_string(static_cast<int>(err)));
-
-	mInitDone = true;
 }
 
 } // namespace rtc

+ 11 - 1
src/dtlssrtptransport.hpp

@@ -24,7 +24,13 @@
 
 #if RTC_ENABLE_MEDIA
 
+#if RTC_SYSTEM_SRTP
 #include <srtp2/srtp.h>
+#else
+#include "srtp.h"
+#endif
+
+#include <atomic>
 
 namespace rtc {
 
@@ -39,6 +45,7 @@ public:
 	~DtlsSrtpTransport();
 
 	bool sendMedia(message_ptr message);
+	void addSSRC(uint32_t ssrc);
 
 private:
 	void incoming(message_ptr message) override;
@@ -47,7 +54,10 @@ private:
 	message_callback mSrtpRecvCallback;
 
 	srtp_t mSrtpIn, mSrtpOut;
-	bool mInitDone = false;
+
+	std::atomic<bool> mInitDone = false;
+	unsigned char mClientSessionKey[SRTP_AES_ICM_128_KEY_LEN_WSALT];
+	unsigned char mServerSessionKey[SRTP_AES_ICM_128_KEY_LEN_WSALT];
 };
 
 } // namespace rtc

+ 2 - 3
src/dtlstransport.cpp

@@ -177,8 +177,8 @@ void DtlsTransport::runRecvLoop() {
 	// Receive loop
 	try {
 		PLOG_INFO << "DTLS handshake finished";
-		changeState(State::Connected);
 		postHandshake();
+		changeState(State::Connected);
 
 		const size_t bufferSize = maxMtu;
 		char buffer[bufferSize];
@@ -453,8 +453,8 @@ void DtlsTransport::runRecvLoop() {
 						SSL_set_mtu(mSsl, maxMtu + 1);
 
 						PLOG_INFO << "DTLS handshake finished";
-						changeState(State::Connected);
 						postHandshake();
+						changeState(State::Connected);
 					}
 				} else {
 					ret = SSL_read(mSsl, buffer, bufferSize);
@@ -575,4 +575,3 @@ long DtlsTransport::BioMethodCtrl(BIO * /*bio*/, int cmd, long /*num*/, void * /
 #endif
 
 } // namespace rtc
-

+ 0 - 1
src/dtlstransport.hpp

@@ -92,4 +92,3 @@ protected:
 } // namespace rtc
 
 #endif
-

+ 10 - 8
src/icetransport.cpp

@@ -130,9 +130,7 @@ IceTransport::~IceTransport() {
 	mAgent.reset();
 }
 
-bool IceTransport::stop() {
-	return Transport::stop();
-}
+bool IceTransport::stop() { return Transport::stop(); }
 
 Description::Role IceTransport::role() const { return mRole; }
 
@@ -141,12 +139,14 @@ Description IceTransport::getLocalDescription(Description::Type type) const {
 	if (juice_get_local_description(mAgent.get(), sdp, JUICE_MAX_SDP_STRING_LEN) < 0)
 		throw std::runtime_error("Failed to generate local SDP");
 
-	return Description(string(sdp), type, mRole);
+	return Description(string(sdp), type,
+	                   type == Description::Type::Offer ? Description::Role::ActPass : mRole);
 }
 
 void IceTransport::setRemoteDescription(const Description &description) {
 	mRole = description.role() == Description::Role::Active ? Description::Role::Passive
 	                                                        : Description::Role::Active;
+
 	mMid = description.bundleMid();
 	if (juice_set_remote_description(mAgent.get(),
 	                                 description.generateApplicationSdp("\r\n").c_str()) < 0)
@@ -191,7 +191,7 @@ bool IceTransport::getSelectedCandidatePair(Candidate *local, Candidate *remote)
 	char sdpLocal[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
 	char sdpRemote[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
 	if (juice_get_selected_candidates(mAgent.get(), sdpLocal, JUICE_MAX_CANDIDATE_SDP_STRING_LEN,
-	                                 sdpRemote, JUICE_MAX_CANDIDATE_SDP_STRING_LEN) == 0) {
+	                                  sdpRemote, JUICE_MAX_CANDIDATE_SDP_STRING_LEN) == 0) {
 		if (local) {
 			*local = Candidate(sdpLocal, mMid);
 			local->resolve(Candidate::ResolveMode::Simple);
@@ -736,15 +736,17 @@ void IceTransport::LogCallback(const gchar * /*logDomain*/, GLogLevelFlags logLe
 
 bool IceTransport::getSelectedCandidatePair(Candidate *local, Candidate *remote) {
 	NiceCandidate *niceLocal, *niceRemote;
-	if(!nice_agent_get_selected_pair(mNiceAgent.get(), mStreamId, 1, &niceLocal, &niceRemote))
+	if (!nice_agent_get_selected_pair(mNiceAgent.get(), mStreamId, 1, &niceLocal, &niceRemote))
 		return false;
 
 	gchar *sdpLocal = nice_agent_generate_local_candidate_sdp(mNiceAgent.get(), niceLocal);
-	if(local) *local = Candidate(sdpLocal, mMid);
+	if (local)
+		*local = Candidate(sdpLocal, mMid);
 	g_free(sdpLocal);
 
 	gchar *sdpRemote = nice_agent_generate_local_candidate_sdp(mNiceAgent.get(), niceRemote);
-	if(remote) *remote = Candidate(sdpRemote, mMid);
+	if (remote)
+		*remote = Candidate(sdpRemote, mMid);
 	g_free(sdpRemote);
 
 	if (local)

+ 1 - 1
src/icetransport.hpp

@@ -20,8 +20,8 @@
 #define RTC_ICE_TRANSPORT_H
 
 #include "candidate.hpp"
-#include "description.hpp"
 #include "configuration.hpp"
+#include "description.hpp"
 #include "include.hpp"
 #include "peerconnection.hpp"
 #include "transport.hpp"

+ 0 - 1
src/init.cpp

@@ -142,4 +142,3 @@ Init::~Init() {
 }
 
 } // namespace rtc
-

+ 1 - 2
src/log.cpp

@@ -44,5 +44,4 @@ void InitLogger(plog::Severity severity, plog::IAppender *appender) {
 			logger->addAppender(appender);
 	}
 }
-}
-
+} // namespace rtc

+ 157 - 68
src/peerconnection.cpp

@@ -1,5 +1,6 @@
 /**
  * Copyright (c) 2019 Paul-Louis Ageneau
+ * Copyright (c) 2020 Filip Klembara (in2core)
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -31,8 +32,20 @@
 #endif
 
 #include <iomanip>
+#include <set>
 #include <thread>
 
+#if __clang__
+namespace {
+template <typename To, typename From>
+inline std::shared_ptr<To> reinterpret_pointer_cast(std::shared_ptr<From> const &ptr) noexcept {
+	return std::shared_ptr<To>(ptr, reinterpret_cast<To *>(ptr.get()));
+}
+} // namespace
+#else
+using std::reinterpret_pointer_cast;
+#endif
+
 namespace rtc {
 
 using namespace std::placeholders;
@@ -311,8 +324,7 @@ std::optional<string> PeerConnection::remoteAddress() const {
 	return iceTransport ? iceTransport->getRemoteAddress() : nullopt;
 }
 
-shared_ptr<DataChannel> PeerConnection::addDataChannel(string label, string protocol,
-                                                       Reliability reliability) {
+shared_ptr<DataChannel> PeerConnection::addDataChannel(string label, DataChannelInit init) {
 	// RFC 5763: The answerer MUST use either a setup attribute value of setup:active or
 	// setup:passive. [...] Thus, setup:active is RECOMMENDED.
 	// See https://tools.ietf.org/html/rfc5763#section-5
@@ -320,8 +332,7 @@ shared_ptr<DataChannel> PeerConnection::addDataChannel(string label, string prot
 	auto iceTransport = std::atomic_load(&mIceTransport);
 	auto role = iceTransport ? iceTransport->role() : Description::Role::Passive;
 
-	auto channel =
-	    emplaceDataChannel(role, std::move(label), std::move(protocol), std::move(reliability));
+	auto channel = emplaceDataChannel(role, std::move(label), std::move(init));
 
 	if (auto transport = std::atomic_load(&mSctpTransport))
 		if (transport->state() == SctpTransport::State::Connected)
@@ -335,9 +346,8 @@ shared_ptr<DataChannel> PeerConnection::addDataChannel(string label, string prot
 	return channel;
 }
 
-shared_ptr<DataChannel> PeerConnection::createDataChannel(string label, string protocol,
-                                                          Reliability reliability) {
-	auto channel = addDataChannel(label, protocol, reliability);
+shared_ptr<DataChannel> PeerConnection::createDataChannel(string label, DataChannelInit init) {
+	auto channel = addDataChannel(std::move(label), std::move(init));
 	setLocalDescription();
 	return channel;
 }
@@ -382,6 +392,7 @@ std::shared_ptr<Track> PeerConnection::addTrack(Description::Media description)
 	if (!track) {
 		track = std::make_shared<Track>(std::move(description));
 		mTracks.emplace(std::make_pair(track->mid(), track));
+		mTrackLines.emplace_back(track);
 	}
 
 	// Renegotiation is needed for the new or updated track
@@ -634,7 +645,8 @@ void PeerConnection::forwardMessage(message_ptr message) {
 		return;
 	}
 
-	auto channel = findDataChannel(uint16_t(message->stream));
+	uint16_t stream = uint16_t(message->stream);
+	auto channel = findDataChannel(stream);
 	if (!channel) {
 		auto iceTransport = std::atomic_load(&mIceTransport);
 		auto sctpTransport = std::atomic_load(&mSctpTransport);
@@ -642,15 +654,15 @@ void PeerConnection::forwardMessage(message_ptr message) {
 			return;
 
 		const byte dataChannelOpenMessage{0x03};
-		unsigned int remoteParity = (iceTransport->role() == Description::Role::Active) ? 1 : 0;
+		uint16_t remoteParity = (iceTransport->role() == Description::Role::Active) ? 1 : 0;
 		if (message->type == Message::Control && *message->data() == dataChannelOpenMessage &&
-		    message->stream % 2 == remoteParity) {
+		    stream % 2 == remoteParity) {
 
-			channel =
-			    std::make_shared<DataChannel>(shared_from_this(), sctpTransport, message->stream);
+			channel = std::make_shared<NegociatedDataChannel>(shared_from_this(), sctpTransport,
+			                                                  message->stream);
 			channel->onOpen(weak_bind(&PeerConnection::triggerDataChannel, this,
 			                          weak_ptr<DataChannel>{channel}));
-			mDataChannels.insert(std::make_pair(message->stream, channel));
+			mDataChannels.emplace(message->stream, channel);
 		} else {
 			// Invalid, close the DataChannel
 			sctpTransport->closeStream(message->stream);
@@ -665,53 +677,117 @@ void PeerConnection::forwardMedia(message_ptr message) {
 	if (!message)
 		return;
 
-	if (message->type == Message::Type::Control) {
+	// Browsers like to compound their packets with a random SSRC.
+	// we have to do this monstrosity to distribute the report blocks
+	if (message->type == Message::Control) {
+		std::set<uint32_t> ssrcs;
+		size_t offset = 0;
+		while ((sizeof(rtc::RTCP_HEADER) + offset) <= message->size()) {
+			auto header = reinterpret_cast<rtc::RTCP_HEADER *>(message->data() + offset);
+			if (header->lengthInBytes() > message->size() - offset) {
+				PLOG_WARNING << "RTCP packet is truncated";
+				break;
+			}
+			offset += header->lengthInBytes();
+			if (header->payloadType() == 205 || header->payloadType() == 206) {
+				auto rtcpfb = reinterpret_cast<RTCP_FB_HEADER *>(header);
+				ssrcs.insert(rtcpfb->getPacketSenderSSRC());
+				ssrcs.insert(rtcpfb->getMediaSourceSSRC());
+
+			} else if (header->payloadType() == 200 || header->payloadType() == 201) {
+				auto rtcpsr = reinterpret_cast<RTCP_SR *>(header);
+				ssrcs.insert(rtcpsr->senderSSRC());
+				for (int i = 0; i < rtcpsr->header.reportCount(); ++i)
+					ssrcs.insert(rtcpsr->getReportBlock(i)->getSSRC());
+			} else {
+				// PT=202 == SDES
+				// PT=207 == Extended Report
+				if (header->payloadType() != 202 && header->payloadType() != 207) {
+					PLOG_WARNING << "Unknown packet type: " << (int)header->version() << " "
+					             << header->payloadType() << "";
+				}
+			}
+		}
+
+		if (!ssrcs.empty()) {
+			for (uint32_t ssrc : ssrcs) {
+				if (auto mid = getMidFromSsrc(ssrc)) {
+					std::shared_lock lock(mTracksMutex); // read-only
+					if (auto it = mTracks.find(*mid); it != mTracks.end())
+						if (auto track = it->second.lock())
+							track->incoming(message);
+				}
+			}
+			return;
+		}
+	}
+
+	uint32_t ssrc = uint32_t(message->stream);
+	if (auto mid = getMidFromSsrc(ssrc)) {
 		std::shared_lock lock(mTracksMutex); // read-only
-		for (auto it = mTracks.begin(); it != mTracks.end(); ++it)
+		if (auto it = mTracks.find(*mid); it != mTracks.end())
 			if (auto track = it->second.lock())
-				return track->incoming(message);
-
-		PLOG_WARNING << "No track available to receive control, dropping";
+				track->incoming(message);
+	} else {
+		/*
+		 * TODO: So the problem is that when stop sending streams, we stop getting report blocks for
+		 * those streams Therefore when we get compound RTCP packets, they are empty, and we can't
+		 * forward them. Therefore, it is expected that we don't know where to forward packets. Is
+		 * this ideal? No! Do I know how to fix it? No!
+		 */
+		// PLOG_WARNING << "Track not found for SSRC " << ssrc << ", dropping";
 		return;
 	}
+} // namespace rtc
 
-	unsigned int payloadType = message->stream;
-	std::optional<string> mid;
-	if (auto it = mMidFromPayloadType.find(payloadType); it != mMidFromPayloadType.end()) {
-		mid = it->second;
-	} else {
-		std::lock_guard lock(mLocalDescriptionMutex);
-		if (!mLocalDescription)
-			return;
+std::optional<std::string> PeerConnection::getMidFromSsrc(uint32_t ssrc) {
+	if (auto it = mMidFromSsrc.find(ssrc); it != mMidFromSsrc.end())
+		return it->second;
 
-		for (int i = 0; i < mLocalDescription->mediaCount(); ++i) {
+	{
+		std::lock_guard lock(mRemoteDescriptionMutex);
+		if (!mRemoteDescription)
+			return nullopt;
+		for (unsigned int i = 0; i < mRemoteDescription->mediaCount(); ++i) {
 			if (auto found = std::visit(
 			        rtc::overloaded{[&](Description::Application *) -> std::optional<string> {
 				                        return std::nullopt;
 			                        },
 			                        [&](Description::Media *media) -> std::optional<string> {
-				                        return media->hasPayloadType(payloadType)
+				                        return media->hasSSRC(ssrc)
 				                                   ? std::make_optional(media->mid())
 				                                   : nullopt;
 			                        }},
-			        mLocalDescription->media(i))) {
+			        mRemoteDescription->media(i))) {
 
-				mMidFromPayloadType.emplace(payloadType, *found);
-				mid = *found;
-				break;
+				mMidFromSsrc.emplace(ssrc, *found);
+				return *found;
 			}
 		}
 	}
+	{
+		std::lock_guard lock(mLocalDescriptionMutex);
+		if (!mLocalDescription)
+			return nullopt;
+		for (unsigned int i = 0; i < mLocalDescription->mediaCount(); ++i) {
+			if (auto found = std::visit(
+			        rtc::overloaded{[&](Description::Application *) -> std::optional<string> {
+				                        return std::nullopt;
+			                        },
+			                        [&](Description::Media *media) -> std::optional<string> {
+				                        return media->hasSSRC(ssrc)
+				                                   ? std::make_optional(media->mid())
+				                                   : nullopt;
+			                        }},
+			        mLocalDescription->media(i))) {
 
-	if (!mid) {
-		PLOG_WARNING << "Track not found for payload type " << payloadType << ", dropping";
-		return;
+				mMidFromSsrc.emplace(ssrc, *found);
+				return *found;
+			}
+		}
 	}
 
-	std::shared_lock lock(mTracksMutex); // read-only
-	if (auto it = mTracks.find(*mid); it != mTracks.end())
-		if (auto track = it->second.lock())
-			track->incoming(message);
+	return nullopt;
 }
 
 void PeerConnection::forwardBufferedAmount(uint16_t stream, size_t amount) {
@@ -720,20 +796,33 @@ void PeerConnection::forwardBufferedAmount(uint16_t stream, size_t amount) {
 }
 
 shared_ptr<DataChannel> PeerConnection::emplaceDataChannel(Description::Role role, string label,
-                                                           string protocol,
-                                                           Reliability reliability) {
-	// The active side must use streams with even identifiers, whereas the passive side must use
-	// streams with odd identifiers.
-	// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-protocol-09#section-6
+                                                           DataChannelInit init) {
 	std::unique_lock lock(mDataChannelsMutex); // we are going to emplace
-	unsigned int stream = (role == Description::Role::Active) ? 0 : 1;
-	while (mDataChannels.find(stream) != mDataChannels.end()) {
-		stream += 2;
-		if (stream >= 65535)
-			throw std::runtime_error("Too many DataChannels");
+	uint16_t stream;
+	if (init.id) {
+		stream = *init.id;
+		if (stream == 65535)
+			throw std::invalid_argument("Invalid DataChannel id");
+	} else {
+		// The active side must use streams with even identifiers, whereas the passive side must use
+		// streams with odd identifiers.
+		// See https://tools.ietf.org/html/draft-ietf-rtcweb-data-protocol-09#section-6
+		stream = (role == Description::Role::Active) ? 0 : 1;
+		while (mDataChannels.find(stream) != mDataChannels.end()) {
+			if (stream >= 65535 - 2)
+				throw std::runtime_error("Too many DataChannels");
+
+			stream += 2;
+		}
 	}
-	auto channel = std::make_shared<DataChannel>(shared_from_this(), stream, std::move(label),
-	                                             std::move(protocol), std::move(reliability));
+	// If the DataChannel is user-negotiated, do not negociate it here
+	auto channel =
+	    init.negotiated
+	        ? std::make_shared<DataChannel>(shared_from_this(), stream, std::move(label),
+	                                        std::move(init.protocol), std::move(init.reliability))
+	        : std::make_shared<NegociatedDataChannel>(shared_from_this(), stream, std::move(label),
+	                                                  std::move(init.protocol),
+	                                                  std::move(init.reliability));
 	mDataChannels.emplace(std::make_pair(stream, channel));
 	return channel;
 }
@@ -800,14 +889,15 @@ void PeerConnection::incomingTrack(Description::Media description) {
 	if (mTracks.find(description.mid()) == mTracks.end()) {
 		auto track = std::make_shared<Track>(std::move(description));
 		mTracks.emplace(std::make_pair(track->mid(), track));
-		triggerTrack(std::move(track));
+		mTrackLines.emplace_back(track);
+		triggerTrack(track);
 	}
 }
 
 void PeerConnection::openTracks() {
 #if RTC_ENABLE_MEDIA
 	if (auto transport = std::atomic_load(&mDtlsTransport)) {
-		auto srtpTransport = std::reinterpret_pointer_cast<DtlsSrtpTransport>(transport);
+		auto srtpTransport = reinterpret_pointer_cast<DtlsSrtpTransport>(transport);
 		std::shared_lock lock(mTracksMutex); // read-only
 		for (auto it = mTracks.begin(); it != mTracks.end(); ++it)
 			if (auto track = it->second.lock())
@@ -831,7 +921,7 @@ void PeerConnection::validateRemoteDescription(const Description &description) {
 		throw std::invalid_argument("Remote description has no media line");
 
 	int activeMediaCount = 0;
-	for (int i = 0; i < description.mediaCount(); ++i)
+	for (unsigned int i = 0; i < description.mediaCount(); ++i)
 		std::visit(rtc::overloaded{[&](const Description::Application *) { ++activeMediaCount; },
 		                           [&](const Description::Media *media) {
 			                           if (media->direction() != Description::Direction::Inactive)
@@ -851,9 +941,10 @@ void PeerConnection::validateRemoteDescription(const Description &description) {
 }
 
 void PeerConnection::processLocalDescription(Description description) {
+
 	if (auto remote = remoteDescription()) {
 		// Reciprocate remote description
-		for (int i = 0; i < remote->mediaCount(); ++i)
+		for (unsigned int i = 0; i < remote->mediaCount(); ++i)
 			std::visit( // reciprocate each media
 			    rtc::overloaded{
 			        [&](Description::Application *remoteApp) {
@@ -907,6 +998,7 @@ void PeerConnection::processLocalDescription(Description description) {
 					        }
 					        return;
 				        }
+				        lock.unlock(); // we are going to call incomingTrack()
 
 				        auto reciprocated = remoteMedia->reciprocate();
 #if !RTC_ENABLE_MEDIA
@@ -945,11 +1037,11 @@ void PeerConnection::processLocalDescription(Description description) {
 
 		// Add media for local tracks
 		std::shared_lock lock(mTracksMutex);
-		for (auto it = mTracks.begin(); it != mTracks.end(); ++it) {
-			if (description.hasMid(it->first))
-				continue;
+		for (auto it = mTrackLines.begin(); it != mTrackLines.end(); ++it) {
+			if (auto track = it->lock()) {
+				if (description.hasMid(track->mid()))
+					continue;
 
-			if (auto track = it->second.lock()) {
 				auto media = track->description();
 #if !RTC_ENABLE_MEDIA
 				// No media support, mark as inactive
@@ -1029,9 +1121,12 @@ void PeerConnection::processRemoteDescription(Description description) {
 }
 
 void PeerConnection::processRemoteCandidate(Candidate candidate) {
+	std::lock_guard lock(mRemoteDescriptionMutex);
 	auto iceTransport = std::atomic_load(&mIceTransport);
-	if (!iceTransport)
-		throw std::logic_error("Remote candidate set without remote description");
+	if (!mRemoteDescription || !iceTransport)
+		throw std::logic_error("Got a remote candidate without remote description");
+
+	candidate.hintMid(mRemoteDescription->bundleMid());
 
 	if (candidate.resolve(Candidate::ResolveMode::Simple)) {
 		iceTransport->addRemoteCandidate(candidate);
@@ -1047,13 +1142,7 @@ void PeerConnection::processRemoteCandidate(Candidate candidate) {
 		t.detach();
 	}
 
-	{
-		std::lock_guard lock(mRemoteDescriptionMutex);
-		if (!mRemoteDescription)
-			throw std::logic_error("Got a remote candidate without remote description");
-
-		mRemoteDescription->addCandidate(candidate);
-	}
+	mRemoteDescription->addCandidate(std::move(candidate));
 }
 
 void PeerConnection::triggerDataChannel(weak_ptr<DataChannel> weakDataChannel) {

+ 0 - 1
src/processor.cpp

@@ -41,4 +41,3 @@ void Processor::schedule() {
 }
 
 } // namespace rtc
-

+ 1 - 2
src/processor.hpp

@@ -44,8 +44,7 @@ public:
 
 	void join();
 
-	template <class F, class... Args>
-	void enqueue(F &&f, Args &&... args);
+	template <class F, class... Args> void enqueue(F &&f, Args &&... args);
 
 protected:
 	void schedule();

+ 35 - 324
src/rtcp.cpp

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2020 Staz M
+ * Copyright (c) 2020 Staz Modrzynski
  * Copyright (c) 2020 Paul-Louis Ageneau
  *
  * This library is free software; you can redistribute it and/or
@@ -19,6 +19,7 @@
 
 #include "rtcp.hpp"
 
+#include "track.hpp"
 #include <cmath>
 #include <utility>
 
@@ -28,310 +29,11 @@
 #include <arpa/inet.h>
 #endif
 
-#ifndef htonll
-#define htonll(x)                                                                                  \
-	((uint64_t)htonl(((uint64_t)(x)&0xFFFFFFFF) << 32) | (uint64_t)htonl((uint64_t)(x) >> 32))
-#endif
-#ifndef ntohll
-#define ntohll(x) htonll(x)
-#endif
-
 namespace rtc {
 
-#pragma pack(push, 1)
-
-struct RTP {
-private:
-	uint8_t _first;
-	uint8_t _payloadType;
-	uint16_t _seqNumber;
-	uint32_t _timestamp;
-
-public:
-	SSRC ssrc;
-	SSRC csrc[16];
-
-	inline uint8_t version() const { return _first >> 6; }
-	inline bool padding() const { return (_first >> 5) & 0x01; }
-	inline uint8_t csrcCount() const { return _first & 0x0F; }
-	inline uint8_t payloadType() const { return _payloadType; }
-	inline uint16_t seqNumber() const { return ntohs(_seqNumber); }
-	inline uint32_t timestamp() const { return ntohl(_timestamp); }
-};
-
-struct RTCP_ReportBlock {
-	SSRC ssrc;
-
-private:
-	uint32_t _fractionLostAndPacketsLost; // fraction lost is 8-bit, packets lost is 24-bit
-	uint16_t _seqNoCycles;
-	uint16_t _highestSeqNo;
-	uint32_t _jitter;
-	uint32_t _lastReport;
-	uint32_t _delaySinceLastReport;
-
-public:
-	inline void preparePacket(SSRC ssrc_, [[maybe_unused]] unsigned int packetsLost,
-	                          [[maybe_unused]] unsigned int totalPackets, uint16_t highestSeqNo,
-	                          uint16_t seqNoCycles, uint32_t jitter, uint64_t lastSR_NTP,
-	                          uint64_t lastSR_DELAY) {
-		setSeqNo(highestSeqNo, seqNoCycles);
-		setJitter(jitter);
-		setSSRC(ssrc_);
-
-		// Middle 32 bits of NTP Timestamp
-		// _lastReport = lastSR_NTP >> 16u;
-		setNTPOfSR(uint32_t(lastSR_NTP));
-		setDelaySinceSR(uint32_t(lastSR_DELAY));
-
-		// The delay, expressed in units of 1/65536 seconds
-		// _delaySinceLastReport = lastSR_DELAY;
-	}
-
-	inline void setSSRC(SSRC ssrc_) { ssrc = htonl(ssrc_); }
-	inline SSRC getSSRC() const { return ntohl(ssrc); }
-
-	inline void setPacketsLost([[maybe_unused]] unsigned int packetsLost,
-	                           [[maybe_unused]] unsigned int totalPackets) {
-		// TODO Implement loss percentages.
-		_fractionLostAndPacketsLost = 0;
-	}
-	inline unsigned int getLossPercentage() const {
-		// TODO Implement loss percentages.
-		return 0;
-	}
-	inline unsigned int getPacketLostCount() const {
-		// TODO Implement total packets lost.
-		return 0;
-	}
-
-	inline uint16_t seqNoCycles() const { return ntohs(_seqNoCycles); }
-	inline uint16_t highestSeqNo() const { return ntohs(_highestSeqNo); }
-	inline uint32_t jitter() const { return ntohl(_jitter); }
-
-	inline void setSeqNo(uint16_t highestSeqNo, uint16_t seqNoCycles) {
-		_highestSeqNo = htons(highestSeqNo);
-		_seqNoCycles = htons(seqNoCycles);
-	}
-
-	inline void setJitter(uint32_t jitter) { _jitter = htonl(jitter); }
-
-	inline void setNTPOfSR(uint32_t ntp) { _lastReport = htonl(ntp >> 16u); }
-	inline uint32_t getNTPOfSR() const { return ntohl(_lastReport) << 16u; }
-
-	inline void setDelaySinceSR(uint32_t sr) {
-		// The delay, expressed in units of 1/65536 seconds
-		_delaySinceLastReport = htonl(sr);
-	}
-	inline uint32_t getDelaySinceSR() const { return ntohl(_delaySinceLastReport); }
-
-	inline void log() const {
-		PLOG_DEBUG << "RTCP report block: "
-		           << "ssrc="
-		           << ntohl(ssrc)
-		           // TODO: Implement these reports
-		           //	<< ", fractionLost=" << fractionLost
-		           //	<< ", packetsLost=" << packetsLost
-		           << ", highestSeqNo=" << highestSeqNo() << ", seqNoCycles=" << seqNoCycles()
-		           << ", jitter=" << jitter() << ", lastSR=" << getNTPOfSR()
-		           << ", lastSRDelay=" << getDelaySinceSR();
-	}
-};
-
-struct RTCP_HEADER {
-private:
-	uint8_t _first;
-	uint8_t _payloadType;
-	uint16_t _length;
-
-public:
-	inline uint8_t version() const { return _first >> 6; }
-	inline bool padding() const { return (_first >> 5) & 0x01; }
-	inline uint8_t reportCount() const { return _first & 0x0F; }
-	inline uint8_t payloadType() const { return _payloadType; }
-	inline uint16_t length() const { return ntohs(_length); }
-
-	inline void setPayloadType(uint8_t type) { _payloadType = type; }
-	inline void setReportCount(uint8_t count) { _first = (_first & 0xF0) | (count & 0x0F); }
-	inline void setLength(uint16_t length) { _length = htons(length); }
-
-	inline void prepareHeader(uint8_t payloadType, uint8_t reportCount, uint16_t length) {
-		_first = 0x02 << 6; // version 2, no padding
-		setReportCount(reportCount);
-		setPayloadType(payloadType);
-		setLength(length);
-	}
-
-	inline void log() const {
-		PLOG_DEBUG << "RTCP header: "
-		           << "version=" << unsigned(version()) << ", padding=" << padding()
-		           << ", reportCount=" << unsigned(reportCount())
-		           << ", payloadType=" << unsigned(payloadType()) << ", length=" << length();
-	}
-};
-
-struct RTCP_SR {
-	RTCP_HEADER header;
-	SSRC senderSsrc;
-
-private:
-	uint64_t _ntpTimestamp;
-	uint32_t _rtpTimestamp;
-	uint32_t _packetCount;
-	uint32_t _octetCount;
-
-	RTCP_ReportBlock _reportBlocks;
-
-public:
-	inline void preparePacket(SSRC senderSsrc_, uint8_t reportCount) {
-		unsigned int length =
-		    ((sizeof(header) + 24 + reportCount * sizeof(RTCP_ReportBlock)) / 4) - 1;
-		header.prepareHeader(200, reportCount, uint16_t(length));
-		senderSsrc = htonl(senderSsrc_);
-	}
-
-	inline RTCP_ReportBlock *getReportBlock(int num) { return &_reportBlocks + num; }
-	inline const RTCP_ReportBlock *getReportBlock(int num) const { return &_reportBlocks + num; }
-
-	[[nodiscard]] inline size_t getSize() const {
-		// "length" in packet is one less than the number of 32 bit words in the packet.
-		return sizeof(uint32_t) * (1 + size_t(header.length()));
-	}
-
-	inline uint32_t ntpTimestamp() const { return ntohll(_ntpTimestamp); }
-	inline uint32_t rtpTimestamp() const { return ntohl(_rtpTimestamp); }
-	inline uint32_t packetCount() const { return ntohl(_packetCount); }
-	inline uint32_t octetCount() const { return ntohl(_octetCount); }
-
-	inline void setNtpTimestamp(uint32_t ts) { _ntpTimestamp = htonll(ts); }
-	inline void setRtpTimestamp(uint32_t ts) { _rtpTimestamp = htonl(ts); }
-
-	inline void log() const {
-		header.log();
-		PLOG_DEBUG << "RTCP SR: "
-		           << " SSRC=" << ntohl(senderSsrc) << ", NTP_TS=" << ntpTimestamp()
-		           << ", RTP_TS=" << rtpTimestamp() << ", packetCount=" << packetCount()
-		           << ", octetCount=" << octetCount();
-
-		for (unsigned i = 0; i < unsigned(header.reportCount()); i++) {
-			getReportBlock(i)->log();
-		}
-	}
-};
-
-struct RTCP_RR {
-	RTCP_HEADER header;
-	SSRC senderSsrc;
-
-private:
-	RTCP_ReportBlock _reportBlocks;
-
-public:
-	inline RTCP_ReportBlock *getReportBlock(int num) { return &_reportBlocks + num; }
-	inline const RTCP_ReportBlock *getReportBlock(int num) const { return &_reportBlocks + num; }
-
-	inline SSRC getSenderSSRC() const { return ntohl(senderSsrc); }
-	inline void setSenderSSRC(SSRC ssrc) { senderSsrc = htonl(ssrc); }
-
-	[[nodiscard]] inline size_t getSize() const {
-		// "length" in packet is one less than the number of 32 bit words in the packet.
-		return sizeof(uint32_t) * (1 + size_t(header.length()));
-	}
-
-	inline void preparePacket(SSRC ssrc, uint8_t reportCount) {
-		// "length" in packet is one less than the number of 32 bit words in the packet.
-		size_t length = (sizeWithReportBlocks(reportCount) / 4) - 1;
-		header.prepareHeader(201, reportCount, uint16_t(length));
-		senderSsrc = htonl(ssrc);
-	}
-
-	inline static size_t sizeWithReportBlocks(uint8_t reportCount) {
-		return sizeof(header) + 4 + size_t(reportCount) * sizeof(RTCP_ReportBlock);
-	}
-
-	inline void log() const {
-		header.log();
-		PLOG_DEBUG << "RTCP RR: "
-		           << " SSRC=" << ntohl(senderSsrc);
-
-		for (unsigned i = 0; i < unsigned(header.reportCount()); i++) {
-			getReportBlock(i)->log();
-		}
-	}
-};
-
-struct RTCP_REMB {
-	RTCP_HEADER header;
-	SSRC senderSsrc;
-	SSRC mediaSourceSSRC;
-
-	// Unique identifier
-	const char id[4] = {'R', 'E', 'M', 'B'};
-
-	// Num SSRC, Br Exp, Br Mantissa (bit mask)
-	uint32_t bitrate;
-
-	SSRC ssrc[1];
-
-	[[nodiscard]] inline size_t getSize() const {
-		// "length" in packet is one less than the number of 32 bit words in the packet.
-		return sizeof(uint32_t) * (1 + size_t(header.length()));
-	}
-
-	inline void preparePacket(SSRC senderSsrc_, unsigned int numSSRC, unsigned int br) {
-		// Report Count becomes the format here.
-		header.prepareHeader(206, 15, 0);
-
-		// Always zero.
-		mediaSourceSSRC = 0;
+rtc::message_ptr RtcpReceivingSession::outgoing(rtc::message_ptr ptr) { return ptr; }
 
-		senderSsrc = htonl(senderSsrc_);
-		setBitrate(numSSRC, br);
-	}
-
-	inline void setBitrate(unsigned int numSSRC, unsigned int br) {
-		unsigned int exp = 0;
-		while (br > pow(2, 18) - 1) {
-			exp++;
-			br /= 2;
-		}
-
-		// "length" in packet is one less than the number of 32 bit words in the packet.
-		header.setLength(uint16_t(((sizeof(header) + 4 * 2 + 4 + 4) / 4) - 1 + numSSRC));
-
-		bitrate = htonl((numSSRC << (32u - 8u)) | (exp << (32u - 8u - 6u)) | br);
-	}
-
-	// TODO Make this work
-	//	  uint64_t getBitrate() const{
-	//		  uint32_t ntohed = ntohl(bitrate);
-	//		  uint64_t bitrate = ntohed & (unsigned int)(pow(2, 18)-1);
-	//		  unsigned int exp = ntohed & ((unsigned int)( (pow(2, 6)-1)) << (32u-8u-6u));
-	//		  return bitrate * pow(2,exp);
-	//	  }
-	//
-	//	  uint8_t getNumSSRCS() const {
-	//		  return ntohl(bitrate) & (((unsigned int) pow(2,8)-1) << (32u-8u));
-	//	  }
-
-	inline void setSSRC(uint8_t iterator, SSRC ssrc_) { ssrc[iterator] = htonl(ssrc_); }
-
-	inline void log() const {
-		header.log();
-		PLOG_DEBUG << "RTCP REMB: "
-		           << " SSRC=" << ntohl(senderSsrc);
-	}
-
-	static unsigned int sizeWithSSRCs(int numSSRC) {
-		return (sizeof(header) + 4 * 2 + 4 + 4) + sizeof(SSRC) * numSSRC;
-	}
-};
-
-#pragma pack(pop)
-
-void RtcpSession::onOutgoing(std::function<void(rtc::message_ptr)> cb) { mTxCallback = cb; }
-
-std::optional<rtc::message_ptr> RtcpSession::incoming(rtc::message_ptr ptr) {
+rtc::message_ptr RtcpReceivingSession::incoming(rtc::message_ptr ptr) {
 	if (ptr->type == rtc::Message::Type::Binary) {
 		auto rtp = reinterpret_cast<const RTP *>(ptr->data());
 
@@ -339,12 +41,12 @@ std::optional<rtc::message_ptr> RtcpSession::incoming(rtc::message_ptr ptr) {
 		if (rtp->version() != 2) {
 			PLOG_WARNING << "RTP packet is not version 2";
 
-			return std::nullopt;
+			return nullptr;
 		}
 		if (rtp->payloadType() == 201 || rtp->payloadType() == 200) {
 			PLOG_WARNING << "RTP packet has a payload type indicating RR/SR";
 
-			return std::nullopt;
+			return nullptr;
 		}
 
 		// TODO Implement the padding bit
@@ -352,13 +54,7 @@ std::optional<rtc::message_ptr> RtcpSession::incoming(rtc::message_ptr ptr) {
 			PLOG_WARNING << "Padding processing not implemented";
 		}
 
-		mSsrc = ntohl(rtp->ssrc);
-
-		uint32_t seqNo = rtp->seqNumber();
-		// uint32_t rtpTS = rtp->getTS();
-
-		if (mGreatestSeqNo < seqNo)
-			mGreatestSeqNo = seqNo;
+		mSsrc = rtp->ssrc();
 
 		return ptr;
 	}
@@ -367,11 +63,11 @@ std::optional<rtc::message_ptr> RtcpSession::incoming(rtc::message_ptr ptr) {
 	auto rr = reinterpret_cast<const RTCP_RR *>(ptr->data());
 	if (rr->header.payloadType() == 201) {
 		// RR
-		mSsrc = rr->getSenderSSRC();
+		mSsrc = rr->senderSSRC();
 		rr->log();
 	} else if (rr->header.payloadType() == 200) {
 		// SR
-		mSsrc = rr->getSenderSSRC();
+		mSsrc = rr->senderSSRC();
 		auto sr = reinterpret_cast<const RTCP_SR *>(ptr->data());
 		mSyncRTPTS = sr->rtpTimestamp();
 		mSyncNTPTS = sr->ntpTimestamp();
@@ -382,28 +78,27 @@ std::optional<rtc::message_ptr> RtcpSession::incoming(rtc::message_ptr ptr) {
 		if (mRequestedBitrate > 0)
 			pushREMB(mRequestedBitrate);
 	}
-	return std::nullopt;
+	return nullptr;
 }
 
-void RtcpSession::requestBitrate(unsigned int newBitrate) {
+void RtcpReceivingSession::requestBitrate(unsigned int newBitrate) {
 	mRequestedBitrate = newBitrate;
 
 	PLOG_DEBUG << "[GOOG-REMB] Requesting bitrate: " << newBitrate << std::endl;
 	pushREMB(newBitrate);
 }
 
-void RtcpSession::pushREMB(unsigned int bitrate) {
+void RtcpReceivingSession::pushREMB(unsigned int bitrate) {
 	rtc::message_ptr msg =
 	    rtc::make_message(RTCP_REMB::sizeWithSSRCs(1), rtc::Message::Type::Control);
 	auto remb = reinterpret_cast<RTCP_REMB *>(msg->data());
 	remb->preparePacket(mSsrc, 1, bitrate);
-	remb->setSSRC(0, mSsrc);
-	remb->log();
+	remb->setSsrc(0, mSsrc);
 
-	tx(msg);
+	send(msg);
 }
 
-void RtcpSession::pushRR(unsigned int lastSR_delay) {
+void RtcpReceivingSession::pushRR(unsigned int lastSR_delay) {
 	auto msg = rtc::make_message(RTCP_RR::sizeWithReportBlocks(1), rtc::Message::Type::Control);
 	auto rr = reinterpret_cast<RTCP_RR *>(msg->data());
 	rr->preparePacket(mSsrc, 1);
@@ -411,16 +106,32 @@ void RtcpSession::pushRR(unsigned int lastSR_delay) {
 	                                     lastSR_delay);
 	rr->log();
 
-	tx(msg);
+	send(msg);
 }
 
-void RtcpSession::tx(message_ptr msg) {
+bool RtcpReceivingSession::send(message_ptr msg) {
 	try {
-		mTxCallback(msg);
+		outgoingCallback(std::move(msg));
+		return true;
 	} catch (const std::exception &e) {
 		LOG_DEBUG << "RTCP tx failed: " << e.what();
 	}
+	return false;
 }
 
-} // namespace rtc
+bool RtcpReceivingSession::requestKeyframe() {
+	pushPLI();
+	return true; // TODO Make this false when it is impossible (i.e. Opus).
+}
+
+void RtcpReceivingSession::pushPLI() {
+	auto msg = rtc::make_message(rtc::RTCP_PLI::size(), rtc::Message::Type::Control);
+	auto *pli = (rtc::RTCP_PLI *)msg->data();
+	pli->preparePacket(mSsrc);
+	send(msg);
+}
 
+void RtcpHandler::onOutgoing(const std::function<void(rtc::message_ptr)> &cb) {
+	this->outgoingCallback = synchronized_callback<rtc::message_ptr>(cb);
+}
+} // namespace rtc

+ 3 - 3
src/sctptransport.cpp

@@ -148,7 +148,7 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
 
 	struct sctp_paddrparams spp = {};
 #if USE_PMTUD
-	// Enabled SCTP path MTU discovery
+	// Enable SCTP path MTU discovery
 	spp.spp_flags = SPP_PMTUD_ENABLE;
 #else
 	// Fall back to a safe MTU value.
@@ -232,7 +232,7 @@ void SctpTransport::close() {
 
 void SctpTransport::connect() {
 	if (!mSock)
-		return;
+		throw std::logic_error("Attempted SCTP connect with closed socket");
 
 	PLOG_DEBUG << "SCTP connecting";
 	changeState(State::Connecting);
@@ -305,7 +305,7 @@ void SctpTransport::incoming(message_ptr message) {
 	// to be sent on our side (i.e. the local INIT) before proceeding.
 	if (!mWrittenOnce) { // test the atomic boolean is not set first to prevent a lock contention
 		std::unique_lock lock(mWriteMutex);
-		mWrittenCondition.wait(lock, [&]() { return mWrittenOnce || state() != State::Connected; });
+		mWrittenCondition.wait(lock, [&]() { return mWrittenOnce.load(); });
 	}
 
 	if (!message) {

+ 1 - 1
src/sctptransport.hpp

@@ -100,7 +100,7 @@ private:
 
 	std::mutex mWriteMutex;
 	std::condition_variable mWrittenCondition;
-	std::atomic<bool> mWritten = false; // written outside lock
+	std::atomic<bool> mWritten = false;     // written outside lock
 	std::atomic<bool> mWrittenOnce = false; // same
 
 	binary mPartialMessage, mPartialNotification;

+ 0 - 1
src/threadpool.cpp

@@ -75,4 +75,3 @@ std::function<void()> ThreadPool::dequeue() {
 }
 
 } // namespace rtc
-

+ 7 - 7
src/threadpool.hpp

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

+ 0 - 1
src/tls.cpp

@@ -127,4 +127,3 @@ bool check(SSL *ssl, int ret, const string &message) {
 } // namespace rtc::openssl
 
 #endif
-

+ 2 - 2
src/tls.hpp

@@ -56,12 +56,12 @@ gnutls_datum_t make_datum(char *data, size_t size);
 #include <openssl/ssl.h>
 
 #include <openssl/bio.h>
+#include <openssl/bn.h>
 #include <openssl/ec.h>
 #include <openssl/err.h>
 #include <openssl/pem.h>
-#include <openssl/x509.h>
 #include <openssl/rsa.h>
-#include <openssl/bn.h>
+#include <openssl/x509.h>
 
 #ifndef BIO_EOF
 #define BIO_EOF -1

+ 8 - 9
src/tlstransport.cpp

@@ -62,10 +62,10 @@ TlsTransport::TlsTransport(shared_ptr<TcpTransport> lower, string host, state_ca
 		gnutls::check(gnutls_priority_set_direct(mSession, priorities, &err_pos),
 		              "Failed to set TLS priorities");
 
-       	PLOG_VERBOSE << "Server Name Indication: " << mHost;
+		PLOG_VERBOSE << "Server Name Indication: " << mHost;
 		gnutls_server_name_set(mSession, GNUTLS_NAME_DNS, mHost.data(), mHost.size());
 
- 		gnutls_session_set_ptr(mSession, this);
+		gnutls_session_set_ptr(mSession, this);
 		gnutls_transport_set_ptr(mSession, this);
 		gnutls_transport_set_push_function(mSession, WriteCallback);
 		gnutls_transport_set_pull_function(mSession, ReadCallback);
@@ -110,7 +110,7 @@ bool TlsTransport::send(message_ptr message) {
 
 	PLOG_VERBOSE << "Send size=" << message->size();
 
-	if(message->size() == 0)
+	if (message->size() == 0)
 		return true;
 
 	ssize_t ret;
@@ -207,10 +207,10 @@ ssize_t TlsTransport::ReadCallback(gnutls_transport_ptr_t ptr, void *data, size_
 	message_ptr &message = t->mIncomingMessage;
 	size_t &position = t->mIncomingMessagePosition;
 
-	if(message && position >= message->size())
+	if (message && position >= message->size())
 		message.reset();
 
-	if(!message) {
+	if (!message) {
 		position = 0;
 		while (auto next = t->mIncomingQueue.pop()) {
 			message = *next;
@@ -221,15 +221,14 @@ ssize_t TlsTransport::ReadCallback(gnutls_transport_ptr_t ptr, void *data, size_
 		}
 	}
 
-	if(message) {
+	if (message) {
 		size_t available = message->size() - position;
 		ssize_t len = std::min(maxlen, available);
 		std::memcpy(data, message->data() + position, len);
-		position+= len;
+		position += len;
 		gnutls_transport_set_errno(t->mSession, 0);
 		return len;
-	}
-	else {
+	} else {
 		// Closed
 		gnutls_transport_set_errno(t->mSession, 0);
 		return 0;

+ 41 - 19
src/track.cpp

@@ -33,7 +33,7 @@ string Track::mid() const { return mMediaDescription.mid(); }
 Description::Media Track::description() const { return mMediaDescription; }
 
 void Track::setDescription(Description::Media description) {
-	if(description.mid() != mMediaDescription.mid())
+	if (description.mid() != mMediaDescription.mid())
 		throw std::logic_error("Media description mid does not match track mid");
 
 	mMediaDescription = std::move(description);
@@ -58,6 +58,13 @@ std::optional<message_variant> Track::receive() {
 	return nullopt;
 }
 
+std::optional<message_variant> Track::peek() {
+	if (auto next = mRecvQueue.peek())
+		return to_variant(std::move(**next));
+
+	return nullopt;
+}
+
 bool Track::isOpen(void) const {
 #if RTC_ENABLE_MEDIA
 	return !mIsClosed && mDtlsSrtpTransport.lock();
@@ -72,9 +79,7 @@ size_t Track::maxMessageSize() const {
 	return 65535 - 12 - 4; // SRTP/UDP
 }
 
-size_t Track::availableAmount() const {
-	return mRecvQueue.amount();
-}
+size_t Track::availableAmount() const { return mRecvQueue.amount(); }
 
 #if RTC_ENABLE_MEDIA
 void Track::open(shared_ptr<DtlsSrtpTransport> transport) {
@@ -84,10 +89,20 @@ void Track::open(shared_ptr<DtlsSrtpTransport> transport) {
 #endif
 
 bool Track::outgoing(message_ptr message) {
+
+	if (mRtcpHandler) {
+		message = mRtcpHandler->outgoing(message);
+		if (!message)
+			return false;
+	}
+
 	auto direction = mMediaDescription.direction();
-	if (direction == Description::Direction::RecvOnly ||
-	    direction == Description::Direction::Inactive)
-		throw std::runtime_error("Track media direction does not allow sending");
+	if ((direction == Description::Direction::RecvOnly ||
+	     direction == Description::Direction::Inactive) &&
+	    message->type != Message::Control) {
+		PLOG_WARNING << "Track media direction does not allow transmission, dropping";
+		return false;
+	}
 
 	if (mIsClosed)
 		throw std::runtime_error("Track is closed");
@@ -112,11 +127,9 @@ void Track::incoming(message_ptr message) {
 		return;
 
 	if (mRtcpHandler) {
-		auto opt = mRtcpHandler->incoming(message);
-		if (!opt)
+		message = mRtcpHandler->incoming(message);
+		if (!message)
 			return;
-
-		message = *opt;
 	}
 
 	auto direction = mMediaDescription.direction();
@@ -124,6 +137,7 @@ void Track::incoming(message_ptr message) {
 	     direction == Description::Direction::Inactive) &&
 	    message->type != Message::Control) {
 		PLOG_WARNING << "Track media direction does not allow reception, dropping";
+		return;
 	}
 
 	// Tail drop if queue is full
@@ -135,21 +149,29 @@ void Track::incoming(message_ptr message) {
 }
 
 void Track::setRtcpHandler(std::shared_ptr<RtcpHandler> handler) {
-	if (mRtcpHandler)
-		mRtcpHandler->onOutgoing(nullptr);
-
 	mRtcpHandler = std::move(handler);
 	if (mRtcpHandler) {
-		mRtcpHandler->onOutgoing([&]([[maybe_unused]] message_ptr message) {
+		mRtcpHandler->onOutgoing([&]([[maybe_unused]] const rtc::message_ptr &message) {
 #if RTC_ENABLE_MEDIA
-			if (auto transport = mDtlsSrtpTransport.lock())
-				transport->sendMedia(message);
+			auto transport = mDtlsSrtpTransport.lock();
+			if (!transport)
+				throw std::runtime_error("Track transport is not open");
+
+			return transport->sendMedia(message);
 #else
-			PLOG_WARNING << "Ignoring RTCP send (not compiled with SRTP support)";
+			PLOG_WARNING << "Ignoring track send (not compiled with SRTP support)";
+			return false;
 #endif
 		});
 	}
 }
 
-} // namespace rtc
+bool Track::requestKeyframe() {
+	if (mRtcpHandler)
+		return mRtcpHandler->requestKeyframe();
+	return false;
+}
+
+std::shared_ptr<RtcpHandler> Track::getRtcpHandler() { return mRtcpHandler; }
 
+} // namespace rtc

+ 7 - 5
src/transport.hpp

@@ -36,8 +36,7 @@ public:
 	using state_callback = std::function<void(State state)>;
 
 	Transport(std::shared_ptr<Transport> lower = nullptr, state_callback callback = nullptr)
-	    : mLower(std::move(lower)), mStateChangeCallback(std::move(callback)) {
-	}
+	    : mLower(std::move(lower)), mStateChangeCallback(std::move(callback)) {}
 
 	virtual ~Transport() { stop(); }
 
@@ -48,15 +47,18 @@ public:
 			return false;
 
 		// We don't want incoming() to be called by the lower layer anymore
-		if (mLower)
+		if (mLower) {
+			PLOG_VERBOSE << "Unregistering incoming callback";
 			mLower->onRecv(nullptr);
-
+		}
 		return true;
 	}
 
 	void registerIncoming() {
-		if (mLower)
+		if (mLower) {
+			PLOG_VERBOSE << "Registering incoming callback";
 			mLower->onRecv(std::bind(&Transport::incoming, this, _1));
+		}
 	}
 
 	void onRecv(message_callback callback) { mRecvCallback = std::move(callback); }

+ 0 - 2
src/verifiedtlstransport.cpp

@@ -28,7 +28,6 @@ using std::weak_ptr;
 
 namespace rtc {
 
-
 VerifiedTlsTransport::VerifiedTlsTransport(shared_ptr<TcpTransport> lower, string host,
                                            state_callback callback)
     : TlsTransport(std::move(lower), std::move(host), std::move(callback)) {
@@ -48,4 +47,3 @@ VerifiedTlsTransport::~VerifiedTlsTransport() {}
 } // namespace rtc
 
 #endif
-

+ 16 - 4
src/websocket.cpp

@@ -125,13 +125,24 @@ size_t WebSocket::maxMessageSize() const { return DEFAULT_MAX_MESSAGE_SIZE; }
 
 std::optional<message_variant> WebSocket::receive() {
 	while (auto next = mRecvQueue.tryPop()) {
-		message_ptr message = std::move(*next);
+		message_ptr message = *next;
 		if (message->type != Message::Control)
 			return to_variant(std::move(*message));
 	}
 	return nullopt;
 }
 
+std::optional<message_variant> WebSocket::peek() {
+	while (auto next = mRecvQueue.peek()) {
+		message_ptr message = *next;
+		if (message->type != Message::Control)
+			return to_variant(std::move(*message));
+
+		mRecvQueue.tryPop();
+	}
+	return nullopt;
+}
+
 size_t WebSocket::availableAmount() const { return mRecvQueue.amount(); }
 
 bool WebSocket::changeState(State state) { return mState.exchange(state) != state; }
@@ -240,12 +251,13 @@ shared_ptr<TlsTransport> WebSocket::initTlsTransport() {
 		if (!mConfig.disableTlsVerification) {
 			PLOG_WARNING << "TLS certificate verification with root CA is not supported on Windows";
 		}
-		transport = std::make_shared<TlsTransport>(lower, mHost, stateChangeCallback);
+		transport = std::make_shared<TlsTransport>(lower, mHostname, stateChangeCallback);
 #else
 		if (mConfig.disableTlsVerification)
-			transport = std::make_shared<TlsTransport>(lower, mHost, stateChangeCallback);
+			transport = std::make_shared<TlsTransport>(lower, mHostname, stateChangeCallback);
 		else
-			transport = std::make_shared<VerifiedTlsTransport>(lower, mHost, stateChangeCallback);
+			transport =
+			    std::make_shared<VerifiedTlsTransport>(lower, mHostname, stateChangeCallback);
 #endif
 
 		std::atomic_store(&mTlsTransport, transport);

+ 1 - 1
src/wstransport.cpp

@@ -17,9 +17,9 @@
  */
 
 #include "wstransport.hpp"
+#include "base64.hpp"
 #include "tcptransport.hpp"
 #include "tlstransport.hpp"
-#include "base64.hpp"
 
 #if RTC_ENABLE_WEBSOCKET
 

+ 33 - 2
test/connectivity.cpp

@@ -156,11 +156,11 @@ void test_connectivity() {
 		cout << "Remote address 2: " << *addr << endl;
 
 	Candidate local, remote;
-	if(pc1->getSelectedCandidatePair(&local, &remote)) {
+	if (pc1->getSelectedCandidatePair(&local, &remote)) {
 		cout << "Local candidate 1:  " << local << endl;
 		cout << "Remote candidate 1: " << remote << endl;
 	}
-	if(pc2->getSelectedCandidatePair(&local, &remote)) {
+	if (pc2->getSelectedCandidatePair(&local, &remote)) {
 		cout << "Local candidate 2:  " << local << endl;
 		cout << "Remote candidate 2: " << remote << endl;
 	}
@@ -208,6 +208,37 @@ void test_connectivity() {
 	    attempts--)
 		this_thread::sleep_for(1s);
 
+	if (!asecond2 || !asecond2->isOpen() || !second1->isOpen())
+		throw runtime_error("Second DataChannel is not open");
+
+	// Try to open a negotiated channel
+	DataChannelInit init;
+	init.negotiated = true;
+	init.id = 42;
+	auto negotiated1 = pc1->createDataChannel("negotiated", init);
+	auto negotiated2 = pc2->createDataChannel("negoctated", init);
+
+	if (!negotiated1->isOpen() || !negotiated2->isOpen())
+		throw runtime_error("Negociated DataChannel is not open");
+
+	std::atomic<bool> received = false;
+	negotiated2->onMessage([&received](const variant<binary, string> &message) {
+		if (holds_alternative<string>(message)) {
+			cout << "Second Message 2: " << get<string>(message) << endl;
+			received = true;
+		}
+	});
+
+	negotiated1->send("Hello from negotiated channel");
+
+	// Wait a bit
+	attempts = 5;
+	while (!received && attempts--)
+		this_thread::sleep_for(1s);
+
+	if (!received)
+		throw runtime_error("Negociated DataChannel failed");
+
 	// Delay close of peer 2 to check closing works properly
 	pc1->close();
 	this_thread::sleep_for(1s);

+ 1 - 1
test/track.cpp

@@ -133,7 +133,7 @@ void test_track() {
 		this_thread::sleep_for(1s);
 
 	if (!at2 || !at2->isOpen() || !t1->isOpen())
-		throw runtime_error("Renegociated track is not open");
+		throw runtime_error("Renegotiated track is not open");
 
 	// TODO: Test sending RTP packets in track
 

+ 4 - 5
test/websocket.cpp

@@ -20,11 +20,11 @@
 
 #if RTC_ENABLE_WEBSOCKET
 
+#include <atomic>
 #include <chrono>
 #include <iostream>
 #include <memory>
 #include <thread>
-#include <atomic>
 
 using namespace rtc;
 using namespace std;
@@ -56,14 +56,14 @@ void test_websocket() {
 	ws->onMessage([&received, &myMessage](variant<binary, string> message) {
 		if (holds_alternative<string>(message)) {
 			string str = std::move(get<string>(message));
-			if((received = (str == myMessage)))
+			if ((received = (str == myMessage)))
 				cout << "WebSocket: Received expected message" << endl;
 			else
 				cout << "WebSocket: Received UNEXPECTED message" << endl;
 		}
 	});
 
-	ws->open("wss://echo.websocket.org/");
+	ws->open("wss://echo.websocket.org:443/");
 
 	int attempts = 10;
 	while ((!ws->isOpen() || !received) && attempts--)
@@ -72,7 +72,7 @@ void test_websocket() {
 	if (!ws->isOpen())
 		throw runtime_error("WebSocket is not open");
 
-	if(!received)
+	if (!received)
 		throw runtime_error("Expected message not received");
 
 	ws->close();
@@ -86,4 +86,3 @@ void test_websocket() {
 }
 
 #endif
-