Skip to content

Commit 57df190

Browse files
committed
feat: support setting ICE ufrag and pwd, and missing config values
Updates the implementation to match the latest libdatachannel with features for setting ICE ufrag/pwd, reading the remote cert fingerprint and receiving callbacks for unhandled mux requests. Also adds pass-through for missing config values.
1 parent 5eb8107 commit 57df190

10 files changed

+336
-6
lines changed

API.md

+28
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,19 @@ export interface RtcConfig {
1616
bindAddress?: string;
1717
enableIceTcp?: boolean;
1818
enableIceUdpMux?: boolean;
19+
disableAutoNegotiation?: boolean;
20+
disableFingerprintVerification?: boolean;
21+
disableAutoGathering?: boolean;
22+
forceMediaTransport?: boolean;
1923
portRangeBegin?: number;
2024
portRangeEnd?: number;
2125
maxMessageSize?: number;
2226
mtu?: number;
2327
iceTransportPolicy?: TransportPolicy;
2428
disableFingerprintVerification?: boolean;
29+
certificatePemFile?: string;
30+
keyPemFile?: string;
31+
keyPemPass?: string;
2532
}
2633
2734
export const enum RelayType {
@@ -69,6 +76,27 @@ export const enum DescriptionType {
6976
}
7077
```
7178

79+
**setLocalDescription: (sdp: string, init?: LocalDescriptionInit) => void**
80+
81+
Set Local Description and optionally the ICE ufrag/pwd to use. These should not
82+
be set as they will be generated automatically as per the spec.
83+
```
84+
export interface LocalDescriptionInit {
85+
iceUfrag?: string;
86+
icePwd?: string;
87+
}
88+
```
89+
90+
**remoteFingerprint: () => CertificateFingerprint**
91+
92+
Returns the certificate fingerprint used by the remote peer
93+
```
94+
export interface CertificateFingerprint {
95+
value: string;
96+
algorithm: 'sha-1' | 'sha-224' | 'sha-256' | 'sha-384' | 'sha-512' | 'md5' | 'md2';
97+
}
98+
```
99+
72100
**addRemoteCandidate: (candidate: string, mid: string) => void**
73101

74102
Add remote candidate info

CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ add_library(${PROJECT_NAME} SHARED
5050
src/cpp/media-audio-wrapper.cpp
5151
src/cpp/media-video-wrapper.cpp
5252
src/cpp/data-channel-wrapper.cpp
53+
src/cpp/ice-udp-mux-listener-wrapper.cpp
5354
src/cpp/peer-connection-wrapper.cpp
5455
src/cpp/thread-safe-callback.cpp
5556
src/cpp/web-socket-wrapper.cpp
+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
#include "ice-udp-mux-listener-wrapper.h"
2+
3+
#include "plog/Log.h"
4+
5+
#include <cctype>
6+
#include <sstream>
7+
8+
Napi::FunctionReference IceUdpMuxListenerWrapper::constructor = Napi::FunctionReference();
9+
std::unordered_set<IceUdpMuxListenerWrapper *> IceUdpMuxListenerWrapper::instances;
10+
11+
void IceUdpMuxListenerWrapper::StopAll()
12+
{
13+
PLOG_DEBUG << "IceUdpMuxListenerWrapper StopAll() called";
14+
auto copy(instances);
15+
for (auto inst : copy)
16+
inst->doCleanup();
17+
}
18+
19+
Napi::Object IceUdpMuxListenerWrapper::Init(Napi::Env env, Napi::Object exports)
20+
{
21+
Napi::HandleScope scope(env);
22+
23+
Napi::Function func = DefineClass(
24+
env,
25+
"IceUdpMuxListener",
26+
{
27+
InstanceMethod("stop", &IceUdpMuxListenerWrapper::stop),
28+
InstanceMethod("onUnhandledStunRequest", &IceUdpMuxListenerWrapper::onUnhandledStunRequest),
29+
InstanceMethod("port", &IceUdpMuxListenerWrapper::port),
30+
InstanceMethod("address", &IceUdpMuxListenerWrapper::address)
31+
});
32+
33+
// If this is not the first call, we don't want to reassign the constructor (hot-reload problem)
34+
if(constructor.IsEmpty())
35+
{
36+
constructor = Napi::Persistent(func);
37+
constructor.SuppressDestruct();
38+
}
39+
40+
exports.Set("IceUdpMuxListener", func);
41+
return exports;
42+
}
43+
44+
IceUdpMuxListenerWrapper::IceUdpMuxListenerWrapper(const Napi::CallbackInfo &info) : Napi::ObjectWrap<IceUdpMuxListenerWrapper>(info)
45+
{
46+
PLOG_DEBUG << "IceUdpMuxListenerWrapper Constructor called";
47+
Napi::Env env = info.Env();
48+
int length = info.Length();
49+
50+
// We expect (Number, String?) as param
51+
if (length > 0 && info[0].IsNumber()) {
52+
// Port
53+
mPort = info[0].As<Napi::Number>().ToNumber().Uint32Value();
54+
} else {
55+
Napi::TypeError::New(env, "Port (Number) and optional Address (String) expected").ThrowAsJavaScriptException();
56+
return;
57+
}
58+
59+
if (length > 1 && info[1].IsString()) {
60+
// Address
61+
mAddress = info[1].As<Napi::String>().ToString();
62+
}
63+
64+
iceUdpMuxListenerPtr = std::make_unique<rtc::IceUdpMuxListener>(mPort, mAddress);
65+
instances.insert(this);
66+
}
67+
68+
IceUdpMuxListenerWrapper::~IceUdpMuxListenerWrapper()
69+
{
70+
PLOG_DEBUG << "IceUdpMuxListenerWrapper Destructor called";
71+
doCleanup();
72+
}
73+
74+
void IceUdpMuxListenerWrapper::doCleanup()
75+
{
76+
PLOG_DEBUG << "IceUdpMuxListenerWrapper::doCleanup() called";
77+
78+
if (iceUdpMuxListenerPtr)
79+
{
80+
iceUdpMuxListenerPtr->stop();
81+
iceUdpMuxListenerPtr.reset();
82+
}
83+
84+
mOnUnhandledStunRequestCallback.reset();
85+
instances.erase(this);
86+
}
87+
88+
Napi::Value IceUdpMuxListenerWrapper::port(const Napi::CallbackInfo &info)
89+
{
90+
Napi::Env env = info.Env();
91+
92+
return Napi::Number::New(env, mPort);
93+
}
94+
95+
Napi::Value IceUdpMuxListenerWrapper::address(const Napi::CallbackInfo &info)
96+
{
97+
Napi::Env env = info.Env();
98+
99+
if (!mAddress.has_value()) {
100+
return env.Undefined();
101+
}
102+
103+
return Napi::String::New(env, mAddress.value());
104+
}
105+
106+
void IceUdpMuxListenerWrapper::stop(const Napi::CallbackInfo &info)
107+
{
108+
PLOG_DEBUG << "IceUdpMuxListenerWrapper::stop() called";
109+
doCleanup();
110+
}
111+
112+
void IceUdpMuxListenerWrapper::onUnhandledStunRequest(const Napi::CallbackInfo &info)
113+
{
114+
PLOG_DEBUG << "IceUdpMuxListenerWrapper::onUnhandledStunRequest() called";
115+
Napi::Env env = info.Env();
116+
int length = info.Length();
117+
118+
if (!iceUdpMuxListenerPtr)
119+
{
120+
Napi::Error::New(env, "IceUdpMuxListenerWrapper::onUnhandledStunRequest() called on destroyed IceUdpMuxListener").ThrowAsJavaScriptException();
121+
return;
122+
}
123+
124+
if (length < 1 || !info[0].IsFunction())
125+
{
126+
Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException();
127+
return;
128+
}
129+
130+
// Callback
131+
mOnUnhandledStunRequestCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>());
132+
133+
iceUdpMuxListenerPtr->OnUnhandledStunRequest([&](rtc::IceUdpMuxRequest request)
134+
{
135+
PLOG_DEBUG << "IceUdpMuxListenerWrapper::onUnhandledStunRequest() IceUdpMuxCallback call(1)";
136+
137+
if (mOnUnhandledStunRequestCallback) {
138+
mOnUnhandledStunRequestCallback->call([request = std::move(request)](Napi::Env env, std::vector<napi_value> &args) {
139+
Napi::Object reqObj = Napi::Object::New(env);
140+
reqObj.Set("ufrag", request.remoteUfrag.c_str());
141+
reqObj.Set("host", request.remoteAddress.c_str());
142+
reqObj.Set("port", request.remotePort);
143+
144+
args = {reqObj};
145+
});
146+
}
147+
148+
PLOG_DEBUG << "IceUdpMuxListenerWrapper::onUnhandledStunRequest() IceUdpMuxCallback call(2)";
149+
});
150+
}
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#ifndef ICE_UDP_MUX_LISTENER_WRAPPER_H
2+
#define ICE_UDP_MUX_LISTENER_WRAPPER_H
3+
4+
#include <napi.h>
5+
#include <rtc/rtc.hpp>
6+
#include <unordered_set>
7+
8+
#include "thread-safe-callback.h"
9+
10+
class IceUdpMuxListenerWrapper : public Napi::ObjectWrap<IceUdpMuxListenerWrapper>
11+
{
12+
public:
13+
static Napi::Object Init(Napi::Env env, Napi::Object exports);
14+
IceUdpMuxListenerWrapper(const Napi::CallbackInfo &info);
15+
~IceUdpMuxListenerWrapper();
16+
17+
// Functions
18+
void stop(const Napi::CallbackInfo &info);
19+
void onUnhandledStunRequest(const Napi::CallbackInfo &info);
20+
21+
// Stop listening on all ports
22+
static void StopAll();
23+
24+
// Properties
25+
Napi::Value port(const Napi::CallbackInfo &info);
26+
Napi::Value address(const Napi::CallbackInfo &info);
27+
Napi::Value unhandledStunRequestCallback(const Napi::CallbackInfo &info);
28+
29+
// Callback Ptrs
30+
std::unique_ptr<ThreadSafeCallback> mOnUnhandledStunRequestCallback = nullptr;
31+
32+
private:
33+
static Napi::FunctionReference constructor;
34+
static std::unordered_set<IceUdpMuxListenerWrapper *> instances;
35+
36+
void doCleanup();
37+
38+
std::optional<std::string> mAddress;
39+
uint16_t mPort;
40+
std::unique_ptr<rtc::IceUdpMuxListener> iceUdpMuxListenerPtr = nullptr;
41+
};
42+
43+
#endif // ICE_UDP_MUX_LISTENER_WRAPPER_H

src/cpp/main.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "rtc-wrapper.h"
33
#include "peer-connection-wrapper.h"
44
#include "data-channel-wrapper.h"
5+
#include "ice-udp-mux-listener-wrapper.h"
56
#include "media-rtcpreceivingsession-wrapper.h"
67
#include "media-track-wrapper.h"
78
#include "media-video-wrapper.h"
@@ -17,6 +18,7 @@ Napi::Object InitAll(Napi::Env env, Napi::Object exports)
1718
VideoWrapper::Init(env, exports);
1819
AudioWrapper::Init(env, exports);
1920
DataChannelWrapper::Init(env, exports);
21+
IceUdpMuxListenerWrapper::Init(env, exports);
2022
PeerConnectionWrapper::Init(env, exports);
2123
WebSocketWrapper::Init(env, exports);
2224
WebSocketServerWrapper::Init(env, exports);

src/cpp/peer-connection-wrapper.cpp

+68-2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Napi::Object PeerConnectionWrapper::Init(Napi::Env env, Napi::Object exports)
4141
InstanceMethod("setRemoteDescription", &PeerConnectionWrapper::setRemoteDescription),
4242
InstanceMethod("localDescription", &PeerConnectionWrapper::localDescription),
4343
InstanceMethod("remoteDescription", &PeerConnectionWrapper::remoteDescription),
44+
InstanceMethod("remoteFingerprint", &PeerConnectionWrapper::remoteFingerprint),
4445
InstanceMethod("addRemoteCandidate", &PeerConnectionWrapper::addRemoteCandidate),
4546
InstanceMethod("createDataChannel", &PeerConnectionWrapper::createDataChannel),
4647
InstanceMethod("addTrack", &PeerConnectionWrapper::addTrack),
@@ -214,6 +215,10 @@ PeerConnectionWrapper::PeerConnectionWrapper(const Napi::CallbackInfo &info) : N
214215
if (config.Get("disableAutoNegotiation").IsBoolean())
215216
rtcConfig.disableAutoNegotiation = config.Get("disableAutoNegotiation").As<Napi::Boolean>();
216217

218+
// disableAutoGathering option
219+
if (config.Get("disableAutoGathering").IsBoolean())
220+
rtcConfig.disableAutoGathering = config.Get("disableAutoGathering").As<Napi::Boolean>();
221+
217222
// forceMediaTransport option
218223
if (config.Get("forceMediaTransport").IsBoolean())
219224
rtcConfig.forceMediaTransport = config.Get("forceMediaTransport").As<Napi::Boolean>();
@@ -251,6 +256,17 @@ PeerConnectionWrapper::PeerConnectionWrapper(const Napi::CallbackInfo &info) : N
251256
rtcConfig.disableFingerprintVerification = config.Get("disableFingerprintVerification").As<Napi::Boolean>();
252257
}
253258

259+
// Specify certificate to use if set
260+
if (config.Get("certificatePemFile").IsString()) {
261+
rtcConfig.certificatePemFile = config.Get("certificatePemFile").As<Napi::String>().ToString();
262+
}
263+
if (config.Get("keyPemFile").IsString()) {
264+
rtcConfig.keyPemFile = config.Get("keyPemFile").As<Napi::String>().ToString();
265+
}
266+
if (config.Get("keyPemPass").IsString()) {
267+
rtcConfig.keyPemPass = config.Get("keyPemPass").As<Napi::String>().ToString();
268+
}
269+
254270
// Create peer-connection
255271
try
256272
{
@@ -331,6 +347,7 @@ void PeerConnectionWrapper::setLocalDescription(const Napi::CallbackInfo &info)
331347
}
332348

333349
rtc::Description::Type type = rtc::Description::Type::Unspec;
350+
rtc::LocalDescriptionInit init;
334351

335352
// optional
336353
if (length > 0)
@@ -356,7 +373,29 @@ void PeerConnectionWrapper::setLocalDescription(const Napi::CallbackInfo &info)
356373
type = rtc::Description::Type::Rollback;
357374
}
358375

359-
mRtcPeerConnPtr->setLocalDescription(type);
376+
// optional
377+
if (length > 1)
378+
{
379+
PLOG_DEBUG << "setLocalDescription() called with LocalDescriptionInit";
380+
381+
if (info[1].IsObject())
382+
{
383+
PLOG_DEBUG << "setLocalDescription() called with LocalDescriptionInit as object";
384+
Napi::Object obj = info[1].As<Napi::Object>();
385+
386+
if (obj.Get("iceUfrag").IsString()) {
387+
PLOG_DEBUG << "setLocalDescription() has ufrag";
388+
init.iceUfrag = obj.Get("iceUfrag").As<Napi::String>();
389+
}
390+
391+
if (obj.Get("icePwd").IsString()) {
392+
PLOG_DEBUG << "setLocalDescription() has password";
393+
init.icePwd = obj.Get("icePwd").As<Napi::String>();
394+
}
395+
}
396+
}
397+
398+
mRtcPeerConnPtr->setLocalDescription(type, init);
360399
}
361400

362401
void PeerConnectionWrapper::setRemoteDescription(const Napi::CallbackInfo &info)
@@ -1002,7 +1041,34 @@ Napi::Value PeerConnectionWrapper::maxMessageSize(const Napi::CallbackInfo &info
10021041

10031042
try
10041043
{
1005-
return Napi::Number::New(env, mRtcPeerConnPtr->remoteMaxMessageSize());
1044+
return Napi::Array::New(env, mRtcPeerConnPtr->remoteMaxMessageSize());
1045+
}
1046+
catch (std::exception &ex)
1047+
{
1048+
Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException();
1049+
return Napi::Number::New(info.Env(), 0);
1050+
}
1051+
}
1052+
1053+
Napi::Value PeerConnectionWrapper::remoteFingerprint(const Napi::CallbackInfo &info)
1054+
{
1055+
PLOG_DEBUG << "remoteFingerprints() called";
1056+
Napi::Env env = info.Env();
1057+
1058+
if (!mRtcPeerConnPtr)
1059+
{
1060+
return Napi::Number::New(info.Env(), 0);
1061+
}
1062+
1063+
try
1064+
{
1065+
auto fingerprint = mRtcPeerConnPtr->remoteFingerprint();
1066+
1067+
Napi::Object fingerprintObject = Napi::Object::New(env);
1068+
fingerprintObject.Set("value", fingerprint.value);
1069+
fingerprintObject.Set("algorithm", rtc::CertificateFingerprint::AlgorithmIdentifier(fingerprint.algorithm));
1070+
1071+
return fingerprintObject;
10061072
}
10071073
catch (std::exception &ex)
10081074
{

src/cpp/peer-connection-wrapper.h

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class PeerConnectionWrapper : public Napi::ObjectWrap<PeerConnectionWrapper>
3333
Napi::Value iceState(const Napi::CallbackInfo &info);
3434
Napi::Value signalingState(const Napi::CallbackInfo &info);
3535
Napi::Value gatheringState(const Napi::CallbackInfo &info);
36+
Napi::Value remoteFingerprint(const Napi::CallbackInfo &info);
3637

3738
// Callbacks
3839
void onLocalDescription(const Napi::CallbackInfo &info);

0 commit comments

Comments
 (0)