浏览代码

Merge branch 'master' into dependency-descriptor

melpon 1 年之前
父节点
当前提交
4a5d732f2b

+ 16 - 14
CMakeLists.txt

@@ -1,6 +1,6 @@
 cmake_minimum_required(VERSION 3.7)
 project(libdatachannel
-	VERSION 0.21.0
+	VERSION 0.21.1
 	LANGUAGES CXX)
 set(PROJECT_DESCRIPTION "C/C++ WebRTC network library featuring Data Channels, Media Transport, and WebSockets")
 
@@ -83,7 +83,6 @@ set(LIBDATACHANNEL_SOURCES
 	${CMAKE_CURRENT_SOURCE_DIR}/src/h265rtppacketizer.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/h265nalunit.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/av1rtppacketizer.cpp
-	${CMAKE_CURRENT_SOURCE_DIR}/src/nalunit.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/rtcpnackresponder.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/rtp.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/capi.cpp
@@ -122,7 +121,6 @@ set(LIBDATACHANNEL_HEADERS
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h265rtppacketizer.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h265nalunit.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/av1rtppacketizer.hpp
-	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/nalunit.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcpnackresponder.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/utils.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/plihandler.hpp
@@ -476,20 +474,24 @@ install(
 	DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/LibDataChannel
 )
 
-# Export config
-install(
-	FILES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/LibDataChannelConfig.cmake
-	DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/LibDataChannel
-)
-
-# Export config version
 include(CMakePackageConfigHelpers)
+configure_package_config_file(
+    ${CMAKE_CURRENT_SOURCE_DIR}/cmake/LibDataChannelConfig.cmake.in
+    ${CMAKE_BINARY_DIR}/LibDataChannelConfig.cmake
+    INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/LibDataChannel
+    NO_SET_AND_CHECK_MACRO
+    NO_CHECK_REQUIRED_COMPONENTS_MACRO
+)
 write_basic_package_version_file(
+    ${CMAKE_BINARY_DIR}/LibDataChannelConfigVersion.cmake
+    VERSION ${PROJECT_VERSION}
+    COMPATIBILITY SameMajorVersion
+)
+# Export config and version files
+install(FILES
+	${CMAKE_BINARY_DIR}/LibDataChannelConfig.cmake
 	${CMAKE_BINARY_DIR}/LibDataChannelConfigVersion.cmake
-	VERSION ${PROJECT_VERSION}
-	COMPATIBILITY SameMajorVersion)
-install(FILES ${CMAKE_BINARY_DIR}/LibDataChannelConfigVersion.cmake
-	DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/LibDataChannel)
+    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/LibDataChannel)
 
 # Tests
 if(NOT NO_TESTS)

+ 2 - 0
cmake/LibDataChannelConfig.cmake → cmake/LibDataChannelConfig.cmake.in

@@ -1,2 +1,4 @@
+@PACKAGE_INIT@
+
 include("${CMAKE_CURRENT_LIST_DIR}/LibDataChannelTargets.cmake")
 

+ 1 - 1
deps/libjuice

@@ -1 +1 @@
-Subproject commit 502da067489073ecaef892e6639d0adf63ba1ac6
+Subproject commit 2de35247f0b15fa385406f3e2020d0e3d4d5cfcc

+ 2 - 2
examples/copy-paste-capi/CMakeLists.txt

@@ -30,7 +30,7 @@ else()
 endif()
 
 set_target_properties(datachannel-copy-paste-capi-offerer PROPERTIES
-	OUTPUT_NAME offerer)
+	OUTPUT_NAME offerer-capi)
 
 set_target_properties(datachannel-copy-paste-capi-offerer PROPERTIES
 	XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.github.paullouisageneau.libdatachannel.examples.copypaste.capi.offerer)
@@ -44,7 +44,7 @@ else()
 endif()
 
 set_target_properties(datachannel-copy-paste-capi-answerer PROPERTIES
-	OUTPUT_NAME answerer)
+	OUTPUT_NAME answerer-capi)
 
 set_target_properties(datachannel-copy-paste-capi-answerer PROPERTIES
 	XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.github.paullouisageneau.libdatachannel.examples.copypaste.capi.answerer)

+ 9 - 9
examples/signaling-server-nodejs/package-lock.json

@@ -9,7 +9,7 @@
       "version": "0.2.0",
       "license": "MPL-2.0",
       "dependencies": {
-        "websocket": "^1.0.34"
+        "websocket": "^1.0.35"
       }
     },
     "node_modules/bufferutil": {
@@ -161,13 +161,13 @@
       }
     },
     "node_modules/websocket": {
-      "version": "1.0.34",
-      "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz",
-      "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==",
+      "version": "1.0.35",
+      "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.35.tgz",
+      "integrity": "sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==",
       "dependencies": {
         "bufferutil": "^4.0.1",
         "debug": "^2.2.0",
-        "es5-ext": "^0.10.50",
+        "es5-ext": "^0.10.63",
         "typedarray-to-buffer": "^3.1.5",
         "utf-8-validate": "^5.0.2",
         "yaeti": "^0.0.6"
@@ -325,13 +325,13 @@
       }
     },
     "websocket": {
-      "version": "1.0.34",
-      "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz",
-      "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==",
+      "version": "1.0.35",
+      "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.35.tgz",
+      "integrity": "sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==",
       "requires": {
         "bufferutil": "^4.0.1",
         "debug": "^2.2.0",
-        "es5-ext": "^0.10.50",
+        "es5-ext": "^0.10.63",
         "typedarray-to-buffer": "^3.1.5",
         "utf-8-validate": "^5.0.2",
         "yaeti": "^0.0.6"

+ 1 - 1
examples/signaling-server-nodejs/package.json

@@ -18,6 +18,6 @@
   },
   "homepage": "https://github.com/paullouisageneau/libdatachannel#readme",
   "dependencies": {
-    "websocket": "^1.0.34"
+    "websocket": "^1.0.35"
   }
 }

+ 6 - 0
include/rtc/configuration.hpp

@@ -77,6 +77,7 @@ struct RTC_CPP_EXPORT Configuration {
 	bool disableAutoNegotiation = false;
 	bool disableAutoGathering = false;
 	bool forceMediaTransport = false;
+	bool disableFingerprintVerification = false;
 
 	// Port range
 	uint16_t portRangeBegin = 1024;
@@ -87,6 +88,11 @@ struct RTC_CPP_EXPORT Configuration {
 
 	// Local maximum message size for Data Channels
 	optional<size_t> maxMessageSize;
+
+	// Certificates and private keys
+	optional<string> certificatePemFile;
+	optional<string> keyPemFile;
+	optional<string> keyPemPass;
 };
 
 #ifdef RTC_ENABLE_WEBSOCKET

+ 4 - 2
include/rtc/description.hpp

@@ -90,6 +90,7 @@ public:
 		virtual ~Entry() = default;
 
 		virtual string type() const;
+		virtual string protocol() const;
 		virtual string description() const;
 		virtual string mid() const;
 
@@ -140,6 +141,7 @@ public:
 
 	private:
 		string mType;
+		string mProtocol;
 		string mDescription;
 		string mMid;
 		std::vector<string> mRids;
@@ -153,7 +155,6 @@ public:
 		Application(const string &mline, string mid);
 		virtual ~Application() = default;
 
-		string description() const override;
 		Application reciprocate() const;
 
 		void setSctpPort(uint16_t port);
@@ -175,8 +176,8 @@ public:
 	// Media (non-data)
 	class RTC_CPP_EXPORT Media : public Entry {
 	public:
-		Media(const string &sdp);
 		Media(const string &mline, string mid, Direction dir = Direction::SendOnly);
+		Media(const string &sdp);
 		virtual ~Media() = default;
 
 		string description() const override;
@@ -234,6 +235,7 @@ public:
 
 		int mBas = -1;
 
+		std::vector<int> mOrderedPayloadTypes;
 		std::map<int, RtpMap> mRtpMaps;
 		std::vector<uint32_t> mSsrcs;
 		std::map<uint32_t, string> mCNameMap;

+ 2 - 2
include/rtc/version.h

@@ -3,7 +3,7 @@
 
 #define RTC_VERSION_MAJOR 0
 #define RTC_VERSION_MINOR 21
-#define RTC_VERSION_PATCH 0
-#define RTC_VERSION "0.21.0"
+#define RTC_VERSION_PATCH 1
+#define RTC_VERSION "0.21.1"
 
 #endif

+ 56 - 37
src/description.cpp

@@ -218,7 +218,9 @@ void Description::hintType(Type type) {
 
 void Description::setFingerprint(CertificateFingerprint f) {
 	if (!f.isValid())
-		throw std::invalid_argument("Invalid " + CertificateFingerprint::AlgorithmIdentifier(f.algorithm) + " fingerprint \"" + f.value + "\"");
+		throw std::invalid_argument("Invalid " +
+		                            CertificateFingerprint::AlgorithmIdentifier(f.algorithm) +
+		                            " fingerprint \"" + f.value + "\"");
 
 	std::transform(f.value.begin(), f.value.end(), f.value.begin(),
 	               [](char c) { return char(std::toupper(c)); });
@@ -308,12 +310,7 @@ string Description::generateSdp(string_view eol) const {
 
 	// Session-level attributes
 	sdp << "a=msid-semantic:WMS *" << eol;
-	sdp << "a=setup:" << mRole << eol;
 
-	if (mIceUfrag)
-		sdp << "a=ice-ufrag:" << *mIceUfrag << eol;
-	if (mIcePwd)
-		sdp << "a=ice-pwd:" << *mIcePwd << eol;
 	if (!mIceOptions.empty())
 		sdp << "a=ice-options:" << utils::implode(mIceOptions, ',') << eol;
 	if (mFingerprint)
@@ -337,6 +334,14 @@ string Description::generateSdp(string_view eol) const {
 	for (const auto &entry : mEntries) {
 		sdp << entry->generateSdp(eol, addr, port);
 
+		// RFC 8829: Attributes that SDP permits to be at either the session level or the media level
+		// SHOULD generally be at the media level even if they are identical.
+		sdp << "a=setup:" << mRole << eol;
+		if (mIceUfrag)
+			sdp << "a=ice-ufrag:" << *mIceUfrag << eol;
+		if (mIcePwd)
+			sdp << "a=ice-pwd:" << *mIcePwd << eol;
+
 		if (!entry->isRemoved() && std::exchange(first, false)) {
 			// Candidates
 			for (const auto &candidate : mCandidates)
@@ -540,9 +545,11 @@ Description::Entry::Entry(const string &mline, string mid, Direction dir)
 	std::istringstream ss(match_prefix(mline, "m=") ? mline.substr(2) : mline);
 	ss >> mType;
 	ss >> port;
-	ss >> mDescription;
+	ss >> mProtocol;
+	ss >> std::ws;
+	std::getline(ss, mDescription);
 
-	if (mType.empty() || mDescription.empty())
+	if (mType.empty() || mProtocol.empty())
 		throw std::invalid_argument("Invalid media description line");
 
 	// RFC 3264: Existing media streams are removed by creating a new SDP with the port number for
@@ -555,6 +562,8 @@ Description::Entry::Entry(const string &mline, string mid, Direction dir)
 
 string Description::Entry::type() const { return mType; }
 
+string Description::Entry::protocol() const { return mProtocol; }
+
 string Description::Entry::description() const { return mDescription; }
 
 string Description::Entry::mid() const { return mMid; }
@@ -621,7 +630,8 @@ string Description::Entry::generateSdp(string_view eol, string_view addr, uint16
 	// RFC 3264: Existing media streams are removed by creating a new SDP with the port number for
 	// that stream set to zero. [...] A stream that is offered with a port of zero MUST be marked
 	// with port zero in the answer.
-	sdp << "m=" << type() << ' ' << (mIsRemoved ? 0 : port) << ' ' << description() << eol;
+	sdp << "m=" << type() << ' ' << (mIsRemoved ? 0 : port) << ' ' << protocol() << ' '
+	    << description() << eol;
 	sdp << "c=IN " << addr << eol;
 	sdp << generateSdpLines(eol);
 
@@ -825,15 +835,12 @@ optional<string> Description::Media::getCNameForSsrc(uint32_t ssrc) const {
 }
 
 Description::Application::Application(string mid)
-    : Entry("application 9 UDP/DTLS/SCTP", std::move(mid), Direction::SendRecv) {}
+    : Entry("application 9 UDP/DTLS/SCTP webrtc-datachannel", std::move(mid), Direction::SendRecv) {
+}
 
 Description::Application::Application(const string &mline, string mid)
     : Entry(mline, std::move(mid), Direction::SendRecv) {}
 
-string Description::Application::description() const {
-	return Entry::description() + " webrtc-datachannel";
-}
-
 Description::Application Description::Application::reciprocate() const {
 	Application reciprocated(*this);
 
@@ -882,7 +889,15 @@ void Description::Application::parseSdpLine(string_view line) {
 	}
 }
 
-Description::Media::Media(const string &sdp) : Entry(get_first_line(sdp), "", Direction::Unknown) {
+Description::Media::Media(const string &mline, string mid, Direction dir)
+    : Entry(mline, std::move(mid), dir) {
+	std::istringstream ss(Entry::description());
+	int payloadType;
+	while (ss >> payloadType)
+		mOrderedPayloadTypes.push_back(payloadType);
+}
+
+Description::Media::Media(const string &sdp) : Media(get_first_line(sdp), "", Direction::Unknown) {
 	string line;
 	std::istringstream ss(sdp);
 	std::getline(ss, line); // discard first line
@@ -899,16 +914,16 @@ Description::Media::Media(const string &sdp) : Entry(get_first_line(sdp), "", Di
 		throw std::invalid_argument("Missing mid in media description");
 }
 
-Description::Media::Media(const string &mline, string mid, Direction dir)
-    : Entry(mline, std::move(mid), dir) {}
-
 string Description::Media::description() const {
-	std::ostringstream desc;
-	desc << Entry::description();
-	for (auto it = mRtpMaps.begin(); it != mRtpMaps.end(); ++it)
-		desc << ' ' << it->first;
+	std::ostringstream ss;
+	for (auto it = mOrderedPayloadTypes.begin(); it != mOrderedPayloadTypes.end(); ++it) {
+		if (it != mOrderedPayloadTypes.begin())
+			ss << ' ';
+
+		ss << *it;
+	}
 
-	return desc.str();
+	return ss.str();
 }
 
 Description::Media Description::Media::reciprocate() const {
@@ -961,14 +976,7 @@ bool Description::Media::hasPayloadType(int payloadType) const {
 	return mRtpMaps.find(payloadType) != mRtpMaps.end();
 }
 
-std::vector<int> Description::Media::payloadTypes() const {
-	std::vector<int> result;
-	result.reserve(mRtpMaps.size());
-	for (auto it = mRtpMaps.begin(); it != mRtpMaps.end(); ++it)
-		result.push_back(it->first);
-
-	return result;
-}
+std::vector<int> Description::Media::payloadTypes() const { return mOrderedPayloadTypes; }
 
 Description::Media::RtpMap *Description::Media::rtpMap(int payloadType) {
 	auto it = mRtpMaps.find(payloadType);
@@ -987,12 +995,19 @@ const Description::Media::RtpMap *Description::Media::rtpMap(int payloadType) co
 }
 
 void Description::Media::addRtpMap(RtpMap map) {
-	auto payloadType = map.payloadType;
+	int payloadType = map.payloadType;
+	if (std::find(mOrderedPayloadTypes.begin(), mOrderedPayloadTypes.end(), payloadType) ==
+	    mOrderedPayloadTypes.end())
+		mOrderedPayloadTypes.push_back(payloadType);
+
 	mRtpMaps.emplace(payloadType, std::move(map));
 }
 
 void Description::Media::removeRtpMap(int payloadType) {
 	// Remove the actual format
+	mOrderedPayloadTypes.erase(
+	    std::remove(mOrderedPayloadTypes.begin(), mOrderedPayloadTypes.end(), payloadType),
+	    mOrderedPayloadTypes.end());
 	mRtpMaps.erase(payloadType);
 
 	// Remove any other rtpmaps that depend on the format we just removed
@@ -1000,10 +1015,14 @@ void Description::Media::removeRtpMap(int payloadType) {
 	while (it != mRtpMaps.end()) {
 		const auto &fmtps = it->second.fmtps;
 		if (std::find(fmtps.begin(), fmtps.end(), "apt=" + std::to_string(payloadType)) !=
-		    fmtps.end())
+		    fmtps.end()) {
+			mOrderedPayloadTypes.erase(
+			    std::remove(mOrderedPayloadTypes.begin(), mOrderedPayloadTypes.end(), it->first),
+			    mOrderedPayloadTypes.end());
 			it = mRtpMaps.erase(it);
-		else
+		} else {
 			++it;
+		}
 	}
 }
 
@@ -1306,8 +1325,8 @@ CertificateFingerprint::AlgorithmSize(CertificateFingerprint::Algorithm fingerpr
 		return 48;
 	case CertificateFingerprint::Algorithm::Sha512:
 		return 64;
-    default:
-        return 0;
+	default:
+		return 0;
 	}
 }
 
@@ -1325,7 +1344,7 @@ std::string CertificateFingerprint::AlgorithmIdentifier(
 	case CertificateFingerprint::Algorithm::Sha512:
 		return "sha-512";
 	default:
-	    return "unknown";
+		return "unknown";
 	}
 }
 

+ 6 - 2
src/h264rtpdepacketizer.cpp

@@ -13,6 +13,8 @@
 
 #include "impl/internals.hpp"
 
+#include <algorithm>
+
 namespace rtc {
 
 const binary naluLongStartCode = {byte{0}, byte{0}, byte{0}, byte{1}};
@@ -44,7 +46,6 @@ message_vector H264RtpDepacketizer::buildFrames(message_vector::iterator begin,
 	message_vector out = {};
 	auto accessUnit = binary{};
 	auto frameInfo = std::make_shared<FrameInfo>(payloadType, timestamp);
-	auto nFrags = 0;
 
 	for (auto it = begin; it != end; ++it) {
 		auto pkt = it->get();
@@ -67,7 +68,10 @@ message_vector H264RtpDepacketizer::buildFrames(message_vector::iterator begin,
 			auto nalUnitFragmentHeader = NalUnitFragmentHeader{
 			    std::to_integer<uint8_t>(pkt->at(rtpHeaderSize + sizeof(NalUnitHeader)))};
 
-			if (nFrags++ == 0) {
+			// RFC 6184: When set to one, the Start bit indicates the start of a fragmented NAL
+			// unit. When the following FU payload is not the start of a fragmented NAL unit
+			// payload, the Start bit is set to zero.
+			if (nalUnitFragmentHeader.isStart() || accessUnit.empty()) {
 				addSeparator(accessUnit);
 				accessUnit.emplace_back(
 				    byte(nalUnitHeader.idc() | nalUnitFragmentHeader.unitType()));

+ 1 - 0
src/h264rtppacketizer.cpp

@@ -12,6 +12,7 @@
 
 #include "impl/internals.hpp"
 
+#include <algorithm>
 #include <cassert>
 
 #ifdef _WIN32

+ 2 - 2
src/impl/certificate.cpp

@@ -228,11 +228,11 @@ Certificate Certificate::FromString(string crt_pem, string key_pem) {
 
 	mbedtls::check(mbedtls_x509_crt_parse(crt.get(),
 	                                      reinterpret_cast<const unsigned char *>(crt_pem.c_str()),
-	                                      crt_pem.length()),
+	                                      crt_pem.size() + 1),
 	               "Failed to parse certificate");
 	mbedtls::check(mbedtls_pk_parse_key(pk.get(),
 	                                    reinterpret_cast<const unsigned char *>(key_pem.c_str()),
-	                                    key_pem.size(), NULL, 0, NULL, 0),
+	                                    key_pem.size() + 1, NULL, 0, NULL, 0),
 	               "Failed to parse key");
 
 	return Certificate(std::move(crt), std::move(pk));

+ 1 - 1
src/impl/internals.hpp

@@ -43,7 +43,7 @@ const size_t DEFAULT_REMOTE_MAX_MESSAGE_SIZE = 65536;     // Remote max message
 
 const size_t DEFAULT_WS_MAX_MESSAGE_SIZE = 256 * 1024;   // Default max message size for WebSockets
 
-const size_t RECV_QUEUE_LIMIT = 1024 * 1024; // Max per-channel queue size
+const size_t RECV_QUEUE_LIMIT = 1024; // Max per-channel queue size (messages)
 
 const int MIN_THREADPOOL_SIZE = 4; // Minimum number of threads in the global thread pool (>= 2)
 

+ 22 - 6
src/impl/peerconnection.cpp

@@ -44,10 +44,28 @@ static LogCounter
     COUNTER_UNKNOWN_PACKET_TYPE(plog::warning,
                                 "Number of unknown RTCP packet types over past second");
 
+const string PemBeginCertificateTag = "-----BEGIN CERTIFICATE-----";
+
 PeerConnection::PeerConnection(Configuration config_)
-    : config(std::move(config_)), mCertificate(make_certificate(config.certificateType)) {
+    : config(std::move(config_)) {
 	PLOG_VERBOSE << "Creating PeerConnection";
 
+
+	if (config.certificatePemFile && config.keyPemFile) {
+		std::promise<certificate_ptr> cert;
+		cert.set_value(std::make_shared<Certificate>(
+			config.certificatePemFile->find(PemBeginCertificateTag) != string::npos
+				? Certificate::FromString(*config.certificatePemFile, *config.keyPemFile)
+				: Certificate::FromFile(*config.certificatePemFile, *config.keyPemFile,
+										config.keyPemPass.value_or(""))));
+		mCertificate = cert.get_future();
+	} else if (!config.certificatePemFile && !config.keyPemFile) {
+		mCertificate = make_certificate(config.certificateType);
+	} else {
+		throw std::invalid_argument(
+			"Either none or both certificate and key PEM files must be specified");
+	}
+
 	if (config.portRangeEnd && config.portRangeBegin > config.portRangeEnd)
 		throw std::invalid_argument("Invalid port range");
 
@@ -426,6 +444,9 @@ bool PeerConnection::checkFingerprint(const std::string &fingerprint) const {
 	if (!mRemoteDescription || !mRemoteDescription->fingerprint())
 		return false;
 
+	if (config.disableFingerprintVerification)
+		return true;
+
 	auto expectedFingerprint = mRemoteDescription->fingerprint()->value;
 	if (expectedFingerprint  == fingerprint) {
 		PLOG_VERBOSE << "Valid fingerprint \"" << fingerprint << "\"";
@@ -858,11 +879,6 @@ void PeerConnection::validateRemoteDescription(const Description &description) {
 	if (activeMediaCount == 0)
 		throw std::invalid_argument("Remote description has no active media");
 
-	if (auto local = localDescription(); local && local->iceUfrag() && local->icePwd())
-		if (*description.iceUfrag() == *local->iceUfrag() &&
-		    *description.icePwd() == *local->icePwd())
-			throw std::logic_error("Got the local description as remote description");
-
 	PLOG_VERBOSE << "Remote description looks valid";
 }
 

+ 1 - 1
src/impl/peerconnection.hpp

@@ -131,7 +131,7 @@ private:
 	void updateTrackSsrcCache(const Description &description);
 
 	const init_token mInitToken = Init::Instance().token();
-	const future_certificate_ptr mCertificate;
+	future_certificate_ptr mCertificate;
 
 	Processor mProcessor;
 	optional<Description> mLocalDescription, mRemoteDescription;

+ 5 - 7
src/impl/queue.hpp

@@ -23,7 +23,8 @@ template <typename T> class Queue {
 public:
 	using amount_function = std::function<size_t(const T &element)>;
 
-	Queue(size_t limit = 0, amount_function func = nullptr);
+	Queue(size_t limit = 0, // elements (0 means no limit)
+	      amount_function func = nullptr);
 	~Queue();
 
 	void stop();
@@ -50,10 +51,7 @@ private:
 
 template <typename T>
 Queue<T>::Queue(size_t limit, amount_function func) : mLimit(limit), mAmount(0) {
-	mAmountFunction = func ? func : [](const T &element) -> size_t {
-		static_cast<void>(element);
-		return 1;
-	};
+	mAmountFunction = func ? func : []([[maybe_unused]] const T &element) -> size_t { return 1; };
 }
 
 template <typename T> Queue<T>::~Queue() { stop(); }
@@ -76,7 +74,7 @@ template <typename T> bool Queue<T>::empty() const {
 
 template <typename T> bool Queue<T>::full() const {
 	std::lock_guard lock(mMutex);
-	return mQueue.size() >= mLimit;
+	return mLimit > 0 && mQueue.size() >= mLimit;
 }
 
 template <typename T> size_t Queue<T>::size() const {
@@ -91,7 +89,7 @@ template <typename T> size_t Queue<T>::amount() const {
 
 template <typename T> void Queue<T>::push(T element) {
 	std::unique_lock lock(mMutex);
-	mPushCondition.wait(lock, [this]() { return !mLimit || mQueue.size() < mLimit || mStopping; });
+	mPushCondition.wait(lock, [this]() { return mLimit == 0 || mQueue.size() < mLimit || mStopping; });
 	if (mStopping)
 		return;
 

+ 6 - 2
src/impl/tcpserver.cpp

@@ -66,8 +66,12 @@ shared_ptr<TcpTransport> TcpServer::accept() {
 			socket_t incomingSock = ::accept(mSock, (struct sockaddr *)&addr, &addrlen);
 
 			if (incomingSock != INVALID_SOCKET) {
-				return std::make_shared<TcpTransport>(incomingSock, nullptr); // no state callback
-
+				try {
+					return std::make_shared<TcpTransport>(incomingSock, nullptr); // no state callback
+				} catch(const std::exception &e) {
+					PLOG_WARNING << e.what();
+					::closesocket(incomingSock);
+				}
 			} else if (sockerrno != SEAGAIN && sockerrno != SEWOULDBLOCK) {
 				PLOG_ERROR << "TCP server failed, errno=" << sockerrno;
 				throw std::runtime_error("TCP server failed");

+ 1 - 1
src/impl/tcptransport.cpp

@@ -69,7 +69,7 @@ TcpTransport::TcpTransport(socket_t sock, state_callback callback)
 	struct sockaddr_storage addr;
 	socklen_t addrlen = sizeof(addr);
 	if (::getpeername(mSock, reinterpret_cast<struct sockaddr *>(&addr), &addrlen) < 0)
-		throw std::runtime_error("getsockname failed");
+		throw std::runtime_error("getpeername failed");
 
 	unmap_inet6_v4mapped(reinterpret_cast<struct sockaddr *>(&addr), &addrlen);
 

+ 22 - 16
src/impl/websocket.cpp

@@ -34,11 +34,28 @@ using namespace std::placeholders;
 using namespace std::chrono_literals;
 using std::chrono::milliseconds;
 
+const string PemBeginCertificateTag = "-----BEGIN CERTIFICATE-----";
+
 WebSocket::WebSocket(optional<Configuration> optConfig, certificate_ptr certificate)
     : config(optConfig ? std::move(*optConfig) : Configuration()),
-      mCertificate(certificate ? std::move(certificate) : std::move(loadCertificate(config))),
-      mIsSecure(mCertificate != nullptr), mRecvQueue(RECV_QUEUE_LIMIT, message_size_func) {
+      mRecvQueue(RECV_QUEUE_LIMIT, message_size_func) {
 	PLOG_VERBOSE << "Creating WebSocket";
+
+	if (certificate) {
+		mCertificate = std::move(certificate);
+	} else if (config.certificatePemFile && config.keyPemFile) {
+		mCertificate = std::make_shared<Certificate>(
+		    config.certificatePemFile->find(PemBeginCertificateTag) != string::npos
+		        ? Certificate::FromString(*config.certificatePemFile, *config.keyPemFile)
+		        : Certificate::FromFile(*config.certificatePemFile, *config.keyPemFile,
+		                                config.keyPemPass.value_or("")));
+	} else if (config.certificatePemFile || config.keyPemFile) {
+		throw std::invalid_argument(
+		    "Either none or both certificate and key PEM files must be specified");
+	}
+
+	mIsSecure = mCertificate != nullptr;
+
 	if (config.proxyServer) {
 		if (config.proxyServer->type == ProxyServer::Type::Socks5)
 			throw std::invalid_argument(
@@ -49,19 +66,6 @@ WebSocket::WebSocket(optional<Configuration> optConfig, certificate_ptr certific
 	}
 }
 
-certificate_ptr WebSocket::loadCertificate(const Configuration& config) {
-	if (!config.certificatePemFile)
-		return nullptr;
-
-	if (config.keyPemFile)
-		return std::make_shared<Certificate>(
-			Certificate::FromFile(*config.certificatePemFile, *config.keyPemFile,
-										config.keyPemPass.value_or("")));
-
-	throw std::invalid_argument(
-		"Either none or both certificate and key PEM files must be specified");
-}
-
 WebSocket::~WebSocket() { PLOG_VERBOSE << "Destroying WebSocket"; }
 
 void WebSocket::open(const string &url) {
@@ -156,7 +160,9 @@ bool WebSocket::isOpen() const { return state == State::Open; }
 
 bool WebSocket::isClosed() const { return state == State::Closed; }
 
-size_t WebSocket::maxMessageSize() const { return config.maxMessageSize.value_or(DEFAULT_WS_MAX_MESSAGE_SIZE); }
+size_t WebSocket::maxMessageSize() const {
+	return config.maxMessageSize.value_or(DEFAULT_WS_MAX_MESSAGE_SIZE);
+}
 
 optional<message_variant> WebSocket::receive() {
 	auto next = mRecvQueue.pop();

+ 1 - 1
src/impl/websocket.hpp

@@ -73,7 +73,7 @@ private:
 
 	const init_token mInitToken = Init::Instance().token();
 
-	const certificate_ptr mCertificate;
+	certificate_ptr mCertificate;
 	bool mIsSecure;
 
 	optional<string> mHostname; // for TLS SNI and Proxy

+ 52 - 0
test/connectivity.cpp

@@ -263,3 +263,55 @@ void test_connectivity(bool signal_wrong_fingerprint) {
 
 	cout << "Success" << endl;
 }
+
+const char* key_pem =
+"-----BEGIN PRIVATE KEY-----\n"
+"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg3bbuT2SjSlMZH/J1\n"
+"vHwmF0Blb/DBc/v7f1Za9GPUXHmhRANCAATDpmYxZozjVw6xlERNjJJGgfY3bEmj\n"
+"xAKFRq3nbxbDHvMEs34u9HntMZWJ0hp3GUC+Ax7JHTv3cYqSaAg2SpR4\n"
+"-----END PRIVATE KEY-----\n";
+
+const char* cert_pem =
+"-----BEGIN CERTIFICATE-----\n"
+"MIIBgjCCASigAwIBAgIJAPMXEoZXOaDEMAoGCCqGSM49BAMCMEoxDzANBgNVBAMM\n"
+"BmNhLmNvbTELMAkGA1UEBhMCVVMxCzAJBgNVBAcMAkNBMRAwDgYDVQQKDAdleGFt\n"
+"cGxlMQswCQYDVQQIDAJDQTAeFw0yNDA1MDUxNjAzMjFaFw0yNDA4MTMxNjAzMjFa\n"
+"MDExCzAJBgNVBAYTAkNOMRAwDgYDVQQKDAdiYW96LmNuMRAwDgYDVQQDDAdiYW96\n"
+"Lm1lMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEw6ZmMWaM41cOsZRETYySRoH2\n"
+"N2xJo8QChUat528Wwx7zBLN+LvR57TGVidIadxlAvgMeyR0793GKkmgINkqUeKMQ\n"
+"MA4wDAYDVR0TAQH/BAIwADAKBggqhkjOPQQDAgNIADBFAiAPNldqGJHryfjPFyX3\n"
+"zfHHWlO7xSDTzdyoxzroFdwy+gIhAKmZizEVvDlBiIe+3ptCArU3dbp+bzLynTcr\n"
+"Ma9ayzQy\n"
+"-----END CERTIFICATE-----\n";
+
+void test_pem() {
+	InitLogger(LogLevel::Debug);
+
+	Configuration config1;
+
+	config1.certificatePemFile = cert_pem;
+	config1.keyPemFile = key_pem;
+
+	PeerConnection pc1(config1);
+	atomic_bool done;
+	string f;
+
+	pc1.onLocalDescription([&done, &f](Description sdp) {
+		f = sdp.fingerprint().value().value;
+		done = true;
+	});
+
+	auto dc1 = pc1.createDataChannel("test");
+
+	// Wait a bit
+	int attempts = 10;
+	while (!done && attempts--)
+		this_thread::sleep_for(1s);
+
+	cout << "Fingerprint: " << f << endl;
+
+	if (f != "07:E5:6F:2A:1A:0C:2C:32:0E:C1:C3:9C:34:5A:78:4E:A5:8B:32:05:D1:57:D6:F4:E7:02:41:12:E6:01:C6:8F")
+		throw runtime_error("The fingerprint of the specified certificate do not match");
+
+	cout << "Success" << endl;
+}

+ 9 - 0
test/main.cpp

@@ -16,6 +16,7 @@ using namespace std;
 using namespace chrono_literals;
 
 void test_connectivity(bool signal_wrong_fingerprint);
+void test_pem();
 void test_negotiated();
 void test_reliability();
 void test_turn_connectivity();
@@ -56,6 +57,14 @@ int main(int argc, char **argv) {
 	} catch (const exception &) {
 	}
 
+	try {
+		cout << endl << "*** Running pem test..." << endl;
+		test_pem();
+	} catch (const exception &e) {
+		cerr << "pem test failed: " << e.what() << endl;
+		return -1;
+	}
+
 // TODO: Temporarily disabled as the Open Relay TURN server is unreliable
 /*
 	try {

+ 3 - 1
test/track.cpp

@@ -97,8 +97,10 @@ void test_track() {
 
 	const auto mediaSdp1 = string(media);
 	const auto mediaSdp2 = string(Description::Media(mediaSdp1));
-	if (mediaSdp2 != mediaSdp1)
+	if (mediaSdp2 != mediaSdp1) {
+		cout << mediaSdp2 << endl;
 		throw runtime_error("Media description parsing test failed");
+	}
 
 	auto t1 = pc1.addTrack(media);