Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support setting ICE ufrag and pwd, disabling fingerprint validation and specifying certificates #256

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,19 @@ export interface RtcConfig {
bindAddress?: string;
enableIceTcp?: boolean;
enableIceUdpMux?: boolean;
disableAutoNegotiation?: boolean;
disableFingerprintVerification?: boolean;
disableAutoGathering?: boolean;
forceMediaTransport?: boolean;
portRangeBegin?: number;
portRangeEnd?: number;
maxMessageSize?: number;
mtu?: number;
iceTransportPolicy?: TransportPolicy;
disableFingerprintVerification?: boolean;
certificatePemFile?: string;
keyPemFile?: string;
keyPemPass?: string;
}

export const enum RelayType {
Expand Down Expand Up @@ -69,6 +76,27 @@ export const enum DescriptionType {
}
```

**setLocalDescription: (sdp: string, init?: LocalDescriptionInit) => void**

Set Local Description and optionally the ICE ufrag/pwd to use. These should not
be set as they will be generated automatically as per the spec.
```
export interface LocalDescriptionInit {
iceUfrag?: string;
icePwd?: string;
}
```

**remoteFingerprint: () => CertificateFingerprint**

Returns the certificate fingerprint used by the remote peer
```
export interface CertificateFingerprint {
value: string;
algorithm: 'sha-1' | 'sha-224' | 'sha-256' | 'sha-384' | 'sha-512' | 'md5' | 'md2';
}
```

**addRemoteCandidate: (candidate: string, mid: string) => void**

Add remote candidate info
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ add_library(${PROJECT_NAME} SHARED
src/cpp/media-audio-wrapper.cpp
src/cpp/media-video-wrapper.cpp
src/cpp/data-channel-wrapper.cpp
src/cpp/ice-udp-mux-listener-wrapper.cpp
src/cpp/peer-connection-wrapper.cpp
src/cpp/thread-safe-callback.cpp
src/cpp/web-socket-wrapper.cpp
Expand Down
150 changes: 150 additions & 0 deletions src/cpp/ice-udp-mux-listener-wrapper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#include "ice-udp-mux-listener-wrapper.h"

#include "plog/Log.h"

#include <cctype>
#include <sstream>

Napi::FunctionReference IceUdpMuxListenerWrapper::constructor = Napi::FunctionReference();
std::unordered_set<IceUdpMuxListenerWrapper *> IceUdpMuxListenerWrapper::instances;

void IceUdpMuxListenerWrapper::StopAll()
{
PLOG_DEBUG << "IceUdpMuxListenerWrapper StopAll() called";
auto copy(instances);
for (auto inst : copy)
inst->doCleanup();
}

Napi::Object IceUdpMuxListenerWrapper::Init(Napi::Env env, Napi::Object exports)
{
Napi::HandleScope scope(env);

Napi::Function func = DefineClass(
env,
"IceUdpMuxListener",
{
InstanceMethod("stop", &IceUdpMuxListenerWrapper::stop),
InstanceMethod("onUnhandledStunRequest", &IceUdpMuxListenerWrapper::onUnhandledStunRequest),
InstanceMethod("port", &IceUdpMuxListenerWrapper::port),
InstanceMethod("address", &IceUdpMuxListenerWrapper::address)
});

// If this is not the first call, we don't want to reassign the constructor (hot-reload problem)
if(constructor.IsEmpty())
{
constructor = Napi::Persistent(func);
constructor.SuppressDestruct();
}

exports.Set("IceUdpMuxListener", func);
return exports;
}

IceUdpMuxListenerWrapper::IceUdpMuxListenerWrapper(const Napi::CallbackInfo &info) : Napi::ObjectWrap<IceUdpMuxListenerWrapper>(info)
{
PLOG_DEBUG << "IceUdpMuxListenerWrapper Constructor called";
Napi::Env env = info.Env();
int length = info.Length();

// We expect (Number, String?) as param
if (length > 0 && info[0].IsNumber()) {
// Port
mPort = info[0].As<Napi::Number>().ToNumber().Uint32Value();
} else {
Napi::TypeError::New(env, "Port (Number) and optional Address (String) expected").ThrowAsJavaScriptException();
return;
}

if (length > 1 && info[1].IsString()) {
// Address
mAddress = info[1].As<Napi::String>().ToString();
}

iceUdpMuxListenerPtr = std::make_unique<rtc::IceUdpMuxListener>(mPort, mAddress);
instances.insert(this);
}

IceUdpMuxListenerWrapper::~IceUdpMuxListenerWrapper()
{
PLOG_DEBUG << "IceUdpMuxListenerWrapper Destructor called";
doCleanup();
}

void IceUdpMuxListenerWrapper::doCleanup()
{
PLOG_DEBUG << "IceUdpMuxListenerWrapper::doCleanup() called";

if (iceUdpMuxListenerPtr)
{
iceUdpMuxListenerPtr->stop();
iceUdpMuxListenerPtr.reset();
}

mOnUnhandledStunRequestCallback.reset();
instances.erase(this);
}

Napi::Value IceUdpMuxListenerWrapper::port(const Napi::CallbackInfo &info)
{
Napi::Env env = info.Env();

return Napi::Number::New(env, mPort);
}

Napi::Value IceUdpMuxListenerWrapper::address(const Napi::CallbackInfo &info)
{
Napi::Env env = info.Env();

if (!mAddress.has_value()) {
return env.Undefined();
}

return Napi::String::New(env, mAddress.value());
}

void IceUdpMuxListenerWrapper::stop(const Napi::CallbackInfo &info)
{
PLOG_DEBUG << "IceUdpMuxListenerWrapper::stop() called";
doCleanup();
}

void IceUdpMuxListenerWrapper::onUnhandledStunRequest(const Napi::CallbackInfo &info)
{
PLOG_DEBUG << "IceUdpMuxListenerWrapper::onUnhandledStunRequest() called";
Napi::Env env = info.Env();
int length = info.Length();

if (!iceUdpMuxListenerPtr)
{
Napi::Error::New(env, "IceUdpMuxListenerWrapper::onUnhandledStunRequest() called on destroyed IceUdpMuxListener").ThrowAsJavaScriptException();
return;
}

if (length < 1 || !info[0].IsFunction())
{
Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException();
return;
}

// Callback
mOnUnhandledStunRequestCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>());

iceUdpMuxListenerPtr->OnUnhandledStunRequest([&](rtc::IceUdpMuxRequest request)
{
PLOG_DEBUG << "IceUdpMuxListenerWrapper::onUnhandledStunRequest() IceUdpMuxCallback call(1)";

if (mOnUnhandledStunRequestCallback) {
mOnUnhandledStunRequestCallback->call([request = std::move(request)](Napi::Env env, std::vector<napi_value> &args) {
Napi::Object reqObj = Napi::Object::New(env);
reqObj.Set("ufrag", request.remoteUfrag.c_str());
reqObj.Set("host", request.remoteAddress.c_str());
reqObj.Set("port", request.remotePort);

args = {reqObj};
});
}

PLOG_DEBUG << "IceUdpMuxListenerWrapper::onUnhandledStunRequest() IceUdpMuxCallback call(2)";
});
}
43 changes: 43 additions & 0 deletions src/cpp/ice-udp-mux-listener-wrapper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#ifndef ICE_UDP_MUX_LISTENER_WRAPPER_H
#define ICE_UDP_MUX_LISTENER_WRAPPER_H

#include <napi.h>
#include <rtc/rtc.hpp>
#include <unordered_set>

#include "thread-safe-callback.h"

class IceUdpMuxListenerWrapper : public Napi::ObjectWrap<IceUdpMuxListenerWrapper>
{
public:
static Napi::Object Init(Napi::Env env, Napi::Object exports);
IceUdpMuxListenerWrapper(const Napi::CallbackInfo &info);
~IceUdpMuxListenerWrapper();

// Functions
void stop(const Napi::CallbackInfo &info);
void onUnhandledStunRequest(const Napi::CallbackInfo &info);

// Stop listening on all ports
static void StopAll();

// Properties
Napi::Value port(const Napi::CallbackInfo &info);
Napi::Value address(const Napi::CallbackInfo &info);
Napi::Value unhandledStunRequestCallback(const Napi::CallbackInfo &info);

// Callback Ptrs
std::unique_ptr<ThreadSafeCallback> mOnUnhandledStunRequestCallback = nullptr;

private:
static Napi::FunctionReference constructor;
static std::unordered_set<IceUdpMuxListenerWrapper *> instances;

void doCleanup();

std::optional<std::string> mAddress;
uint16_t mPort;
std::unique_ptr<rtc::IceUdpMuxListener> iceUdpMuxListenerPtr = nullptr;
};

#endif // ICE_UDP_MUX_LISTENER_WRAPPER_H
2 changes: 2 additions & 0 deletions src/cpp/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "rtc-wrapper.h"
#include "peer-connection-wrapper.h"
#include "data-channel-wrapper.h"
#include "ice-udp-mux-listener-wrapper.h"
#include "media-rtcpreceivingsession-wrapper.h"
#include "media-track-wrapper.h"
#include "media-video-wrapper.h"
Expand All @@ -17,6 +18,7 @@ Napi::Object InitAll(Napi::Env env, Napi::Object exports)
VideoWrapper::Init(env, exports);
AudioWrapper::Init(env, exports);
DataChannelWrapper::Init(env, exports);
IceUdpMuxListenerWrapper::Init(env, exports);
PeerConnectionWrapper::Init(env, exports);
WebSocketWrapper::Init(env, exports);
WebSocketServerWrapper::Init(env, exports);
Expand Down
70 changes: 68 additions & 2 deletions src/cpp/peer-connection-wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Napi::Object PeerConnectionWrapper::Init(Napi::Env env, Napi::Object exports)
InstanceMethod("setRemoteDescription", &PeerConnectionWrapper::setRemoteDescription),
InstanceMethod("localDescription", &PeerConnectionWrapper::localDescription),
InstanceMethod("remoteDescription", &PeerConnectionWrapper::remoteDescription),
InstanceMethod("remoteFingerprint", &PeerConnectionWrapper::remoteFingerprint),
InstanceMethod("addRemoteCandidate", &PeerConnectionWrapper::addRemoteCandidate),
InstanceMethod("createDataChannel", &PeerConnectionWrapper::createDataChannel),
InstanceMethod("addTrack", &PeerConnectionWrapper::addTrack),
Expand Down Expand Up @@ -214,6 +215,10 @@ PeerConnectionWrapper::PeerConnectionWrapper(const Napi::CallbackInfo &info) : N
if (config.Get("disableAutoNegotiation").IsBoolean())
rtcConfig.disableAutoNegotiation = config.Get("disableAutoNegotiation").As<Napi::Boolean>();

// disableAutoGathering option
if (config.Get("disableAutoGathering").IsBoolean())
rtcConfig.disableAutoGathering = config.Get("disableAutoGathering").As<Napi::Boolean>();

// forceMediaTransport option
if (config.Get("forceMediaTransport").IsBoolean())
rtcConfig.forceMediaTransport = config.Get("forceMediaTransport").As<Napi::Boolean>();
Expand Down Expand Up @@ -251,6 +256,17 @@ PeerConnectionWrapper::PeerConnectionWrapper(const Napi::CallbackInfo &info) : N
rtcConfig.disableFingerprintVerification = config.Get("disableFingerprintVerification").As<Napi::Boolean>();
}

// Specify certificate to use if set
if (config.Get("certificatePemFile").IsString()) {
rtcConfig.certificatePemFile = config.Get("certificatePemFile").As<Napi::String>().ToString();
}
if (config.Get("keyPemFile").IsString()) {
rtcConfig.keyPemFile = config.Get("keyPemFile").As<Napi::String>().ToString();
}
if (config.Get("keyPemPass").IsString()) {
rtcConfig.keyPemPass = config.Get("keyPemPass").As<Napi::String>().ToString();
}

// Create peer-connection
try
{
Expand Down Expand Up @@ -331,6 +347,7 @@ void PeerConnectionWrapper::setLocalDescription(const Napi::CallbackInfo &info)
}

rtc::Description::Type type = rtc::Description::Type::Unspec;
rtc::LocalDescriptionInit init;

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

mRtcPeerConnPtr->setLocalDescription(type);
// optional
if (length > 1)
{
PLOG_DEBUG << "setLocalDescription() called with LocalDescriptionInit";

if (info[1].IsObject())
{
PLOG_DEBUG << "setLocalDescription() called with LocalDescriptionInit as object";
Napi::Object obj = info[1].As<Napi::Object>();

if (obj.Get("iceUfrag").IsString()) {
PLOG_DEBUG << "setLocalDescription() has ufrag";
init.iceUfrag = obj.Get("iceUfrag").As<Napi::String>();
}

if (obj.Get("icePwd").IsString()) {
PLOG_DEBUG << "setLocalDescription() has password";
init.icePwd = obj.Get("icePwd").As<Napi::String>();
}
}
}

mRtcPeerConnPtr->setLocalDescription(type, init);
}

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

try
{
return Napi::Number::New(env, mRtcPeerConnPtr->remoteMaxMessageSize());
return Napi::Array::New(env, mRtcPeerConnPtr->remoteMaxMessageSize());
}
catch (std::exception &ex)
{
Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException();
return Napi::Number::New(info.Env(), 0);
}
}

Napi::Value PeerConnectionWrapper::remoteFingerprint(const Napi::CallbackInfo &info)
{
PLOG_DEBUG << "remoteFingerprints() called";
Napi::Env env = info.Env();

if (!mRtcPeerConnPtr)
{
return Napi::Number::New(info.Env(), 0);
}

try
{
auto fingerprint = mRtcPeerConnPtr->remoteFingerprint();

Napi::Object fingerprintObject = Napi::Object::New(env);
fingerprintObject.Set("value", fingerprint.value);
fingerprintObject.Set("algorithm", rtc::CertificateFingerprint::AlgorithmIdentifier(fingerprint.algorithm));

return fingerprintObject;
}
catch (std::exception &ex)
{
Expand Down
1 change: 1 addition & 0 deletions src/cpp/peer-connection-wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class PeerConnectionWrapper : public Napi::ObjectWrap<PeerConnectionWrapper>
Napi::Value iceState(const Napi::CallbackInfo &info);
Napi::Value signalingState(const Napi::CallbackInfo &info);
Napi::Value gatheringState(const Napi::CallbackInfo &info);
Napi::Value remoteFingerprint(const Napi::CallbackInfo &info);

// Callbacks
void onLocalDescription(const Napi::CallbackInfo &info);
Expand Down
Loading