Skip to content

Commit e0ddbd4

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 4dfb169 commit e0ddbd4

File tree

9 files changed

+334
-5
lines changed

9 files changed

+334
-5
lines changed

API.md

Lines changed: 28 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ endif()
5252
set(SRC_FILES
5353
src/cpp/rtc-wrapper.cpp
5454
src/cpp/data-channel-wrapper.cpp
55+
src/cpp/ice-udp-mux-listener-wrapper.cpp
5556
src/cpp/peer-connection-wrapper.cpp
5657
src/cpp/thread-safe-callback.cpp
5758
src/cpp/main.cpp
Lines changed: 150 additions & 0 deletions
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+
}
Lines changed: 43 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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

67
#if RTC_ENABLE_MEDIA == 1
78
#include "media-rtcpreceivingsession-wrapper.h"
@@ -27,6 +28,7 @@ Napi::Object InitAll(Napi::Env env, Napi::Object exports)
2728
#endif
2829

2930
DataChannelWrapper::Init(env, exports);
31+
IceUdpMuxListenerWrapper::Init(env, exports);
3032
PeerConnectionWrapper::Init(env, exports);
3133

3234
#if RTC_ENABLE_WEBSOCKET == 1

src/cpp/peer-connection-wrapper.cpp

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Napi::Object PeerConnectionWrapper::Init(Napi::Env env, Napi::Object exports)
4444
InstanceMethod("setRemoteDescription", &PeerConnectionWrapper::setRemoteDescription),
4545
InstanceMethod("localDescription", &PeerConnectionWrapper::localDescription),
4646
InstanceMethod("remoteDescription", &PeerConnectionWrapper::remoteDescription),
47+
InstanceMethod("remoteFingerprint", &PeerConnectionWrapper::remoteFingerprint),
4748
InstanceMethod("addRemoteCandidate", &PeerConnectionWrapper::addRemoteCandidate),
4849
InstanceMethod("createDataChannel", &PeerConnectionWrapper::createDataChannel),
4950

@@ -220,6 +221,10 @@ PeerConnectionWrapper::PeerConnectionWrapper(const Napi::CallbackInfo &info) : N
220221
if (config.Get("disableAutoNegotiation").IsBoolean())
221222
rtcConfig.disableAutoNegotiation = config.Get("disableAutoNegotiation").As<Napi::Boolean>();
222223

224+
// disableAutoGathering option
225+
if (config.Get("disableAutoGathering").IsBoolean())
226+
rtcConfig.disableAutoGathering = config.Get("disableAutoGathering").As<Napi::Boolean>();
227+
223228
// forceMediaTransport option
224229
if (config.Get("forceMediaTransport").IsBoolean())
225230
rtcConfig.forceMediaTransport = config.Get("forceMediaTransport").As<Napi::Boolean>();
@@ -257,6 +262,17 @@ PeerConnectionWrapper::PeerConnectionWrapper(const Napi::CallbackInfo &info) : N
257262
rtcConfig.disableFingerprintVerification = config.Get("disableFingerprintVerification").As<Napi::Boolean>();
258263
}
259264

265+
// Specify certificate to use if set
266+
if (config.Get("certificatePemFile").IsString()) {
267+
rtcConfig.certificatePemFile = config.Get("certificatePemFile").As<Napi::String>().ToString();
268+
}
269+
if (config.Get("keyPemFile").IsString()) {
270+
rtcConfig.keyPemFile = config.Get("keyPemFile").As<Napi::String>().ToString();
271+
}
272+
if (config.Get("keyPemPass").IsString()) {
273+
rtcConfig.keyPemPass = config.Get("keyPemPass").As<Napi::String>().ToString();
274+
}
275+
260276
// Create peer-connection
261277
try
262278
{
@@ -337,6 +353,7 @@ void PeerConnectionWrapper::setLocalDescription(const Napi::CallbackInfo &info)
337353
}
338354

339355
rtc::Description::Type type = rtc::Description::Type::Unspec;
356+
rtc::LocalDescriptionInit init;
340357

341358
// optional
342359
if (length > 0)
@@ -362,7 +379,29 @@ void PeerConnectionWrapper::setLocalDescription(const Napi::CallbackInfo &info)
362379
type = rtc::Description::Type::Rollback;
363380
}
364381

365-
mRtcPeerConnPtr->setLocalDescription(type);
382+
// optional
383+
if (length > 1)
384+
{
385+
PLOG_DEBUG << "setLocalDescription() called with LocalDescriptionInit";
386+
387+
if (info[1].IsObject())
388+
{
389+
PLOG_DEBUG << "setLocalDescription() called with LocalDescriptionInit as object";
390+
Napi::Object obj = info[1].As<Napi::Object>();
391+
392+
if (obj.Get("iceUfrag").IsString()) {
393+
PLOG_DEBUG << "setLocalDescription() has ufrag";
394+
init.iceUfrag = obj.Get("iceUfrag").As<Napi::String>();
395+
}
396+
397+
if (obj.Get("icePwd").IsString()) {
398+
PLOG_DEBUG << "setLocalDescription() has password";
399+
init.icePwd = obj.Get("icePwd").As<Napi::String>();
400+
}
401+
}
402+
}
403+
404+
mRtcPeerConnPtr->setLocalDescription(type, init);
366405
}
367406

368407
void PeerConnectionWrapper::setRemoteDescription(const Napi::CallbackInfo &info)
@@ -1008,7 +1047,34 @@ Napi::Value PeerConnectionWrapper::maxMessageSize(const Napi::CallbackInfo &info
10081047

10091048
try
10101049
{
1011-
return Napi::Number::New(env, mRtcPeerConnPtr->remoteMaxMessageSize());
1050+
return Napi::Array::New(env, mRtcPeerConnPtr->remoteMaxMessageSize());
1051+
}
1052+
catch (std::exception &ex)
1053+
{
1054+
Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException();
1055+
return Napi::Number::New(info.Env(), 0);
1056+
}
1057+
}
1058+
1059+
Napi::Value PeerConnectionWrapper::remoteFingerprint(const Napi::CallbackInfo &info)
1060+
{
1061+
PLOG_DEBUG << "remoteFingerprints() called";
1062+
Napi::Env env = info.Env();
1063+
1064+
if (!mRtcPeerConnPtr)
1065+
{
1066+
return Napi::Number::New(info.Env(), 0);
1067+
}
1068+
1069+
try
1070+
{
1071+
auto fingerprint = mRtcPeerConnPtr->remoteFingerprint();
1072+
1073+
Napi::Object fingerprintObject = Napi::Object::New(env);
1074+
fingerprintObject.Set("value", fingerprint.value);
1075+
fingerprintObject.Set("algorithm", rtc::CertificateFingerprint::AlgorithmIdentifier(fingerprint.algorithm));
1076+
1077+
return fingerprintObject;
10121078
}
10131079
catch (std::exception &ex)
10141080
{

src/cpp/peer-connection-wrapper.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class PeerConnectionWrapper : public Napi::ObjectWrap<PeerConnectionWrapper>
3838
Napi::Value iceState(const Napi::CallbackInfo &info);
3939
Napi::Value signalingState(const Napi::CallbackInfo &info);
4040
Napi::Value gatheringState(const Napi::CallbackInfo &info);
41+
Napi::Value remoteFingerprint(const Napi::CallbackInfo &info);
4142

4243
// Callbacks
4344
void onLocalDescription(const Napi::CallbackInfo &info);

0 commit comments

Comments
 (0)