Skip to content
Merged
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
2 changes: 1 addition & 1 deletion docs/guide/networking/peer-connection-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ config.cryptoOptions = crypto;

`cryptexPolicy` encrypts the RTP header extensions and CSRCs as a whole (RFC 9335) where the peer supports it (`NEGOTIATE`) or always (`REQUIRE`).

`certificates` takes the DTLS certificate a peer connection authenticates with, as an `RTCCertificatePEM`; without one, WebRTC generates a certificate per connection. A certificate that does not parse makes `createPeerConnection()` and `setConfiguration()` throw.
`certificates` takes the DTLS certificate a peer connection authenticates with, as an `RTCCertificatePEM`; without one, WebRTC generates a certificate per connection. A certificate that does not parse makes `createPeerConnection()` and `setConfiguration()` throw. The expiration time passed to its constructor is informational: WebRTC takes it from the certificate. The certificates that `getConfiguration()` and `RTCDtlsTransport.getRemoteCertificates()` return carry that time in `getExpires()`, in milliseconds since the Unix epoch, or `RTCCertificatePEM.EXPIRES_UNKNOWN` (-1) if it cannot be read.

## Signaling

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@
// Uses JNI directly, so reporting an allocation failure needs no C++ allocation.
void ThrowJavaException(JNIEnv * env, const char * className, const char * message) noexcept;
void ThrowCxxJavaException(JNIEnv * env);
// The VM the library is loaded into. JNI_OnLoad sets it before anything else,
// independently of the library's JavaContext, which is published only once it
// is fully initialized: its initialization already needs the VM, through the
// JNI references and AttachCurrentThread.
void SetJavaVM(JavaVM * vm);
JavaVM * GetJavaVM();
JNIEnv * AttachCurrentThread();
bool ExceptionCheck(JNIEnv * env);
jclass FindClass(JNIEnv * env, const char * name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,25 @@
#include "JavaThreadEnv.h"
#include "JavaWrappedException.h"

#include <atomic>
#include <exception>
#include <ios>

namespace
{
std::atomic<JavaVM *> javaVM { nullptr };
}

void SetJavaVM(JavaVM * vm)
{
javaVM.store(vm);
}

JavaVM * GetJavaVM()
{
return javaVM.load();
}

bool ExceptionCheck(JNIEnv * env)
{
if (env->ExceptionCheck()) {
Expand Down Expand Up @@ -162,7 +178,7 @@ jfieldID GetFieldID(JNIEnv * env, jclass cls, const std::string & fieldName, con

JNIEnv * AttachCurrentThread()
{
thread_local std::unique_ptr<jni::JavaThreadEnv> threadEnv(new jni::JavaThreadEnv(javaContext->getVM()));
thread_local std::unique_ptr<jni::JavaThreadEnv> threadEnv(new jni::JavaThreadEnv(GetJavaVM()));

return threadEnv->getEnv();
}
5 changes: 4 additions & 1 deletion webrtc-jni/src/main/cpp/include/WebRTCContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ namespace jni
{
public:
WebRTCContext(JavaVM * vm);
~WebRTCContext() = default;
// Releases what initialize() set up and destroy() has not, also
// after an initialization that failed part way.
~WebRTCContext();

void initialize(JNIEnv * env) override;
void initializeClassLoader(JNIEnv* env, const char * loaderName) override;
Expand All @@ -51,6 +53,7 @@ namespace jni
void initializePowerManagement();

private:
bool sslInitialized;
std::mutex aMutex;
std::mutex vMutex;
std::unique_ptr<avdev::AudioDeviceManager> audioDevManager;
Expand Down
16 changes: 15 additions & 1 deletion webrtc-jni/src/main/cpp/include/rtc/RTCCertificatePEM.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "JavaRef.h"

#include "rtc_base/rtc_certificate.h"
#include "rtc_base/ssl_certificate.h"

#include <jni.h>

Expand All @@ -40,7 +41,20 @@ namespace jni
jfieldID certificate;
};

JavaLocalRef<jobject> toJava(JNIEnv * env, const webrtc::RTCCertificatePEM & certificate);
// The value of RTCCertificatePEM.EXPIRES_UNKNOWN, for a certificate whose
// expiration time cannot be read.
constexpr jlong kExpiresUnknown = -1;

// The expiration time of the certificate in milliseconds relative to the
// Unix epoch, or kExpiresUnknown.
jlong ExpiresMillis(const webrtc::SSLCertificate & certificate);

// A local certificate, with its private key.
JavaLocalRef<jobject> toJava(JNIEnv * env, const webrtc::RTCCertificate & certificate);
// A remote certificate, whose private key is not known.
JavaLocalRef<jobject> toJava(JNIEnv * env, const webrtc::SSLCertificate & certificate);
// `expires` in milliseconds relative to the Unix epoch, or kExpiresUnknown.
JavaLocalRef<jobject> toJava(JNIEnv * env, const webrtc::RTCCertificatePEM & certificate, jlong expires);
webrtc::RTCCertificatePEM toNative(JNIEnv * env, const JavaRef<jobject> & certificate);
}
}
Expand Down
5 changes: 1 addition & 4 deletions webrtc-jni/src/main/cpp/src/JNI_RTCDtlsTransport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,7 @@ JNIEXPORT jobject JNICALL Java_dev_onvoid_webrtc_RTCDtlsTransport_getRemoteCerti
jni::JavaArrayList certificates(env, count);

for (size_t i = 0; i < count; i++) {
const webrtc::SSLCertificate & certificate = certChain->Get(i);
const webrtc::RTCCertificatePEM pem("", certificate.ToPEMString());

certificates.add(jni::RTCCertificatePEM::toJava(env, pem));
certificates.add(jni::RTCCertificatePEM::toJava(env, certChain->Get(i)));
}

return certificates.listObject().release();
Expand Down
62 changes: 46 additions & 16 deletions webrtc-jni/src/main/cpp/src/JNI_WebRTC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,37 +23,66 @@

#include <jni.h>

#include <memory>
#include <utility>

jni::JavaContext * javaContext = nullptr;

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM * vm, void * reserved)
{
JNIEnv * env = nullptr;

if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
return -1;
return JNI_ERR;
}

// libwebrtc initializes its logging configuration on the first log line,
// with everything from LS_INFO upwards written to stderr, and the
// configuration can be set only once. Initialize it here, before anything
// can log, so nothing is written unless the application asks for it through
// Logging.logToDebug or a log sink.
webrtc::LoggingConfig loggingConfig;
loggingConfig.set_log_thread(true);
loggingConfig.set_min_severity(webrtc::LS_NONE);
loggingConfig.set_debug_severity(webrtc::LS_NONE);
webrtc::InitializeLogging(std::move(loggingConfig));

javaContext = new jni::WebRTCContext(vm);
// Before anything that creates a JNI reference: those find the env of their
// thread through the VM.
SetJavaVM(vm);

// No C++ exception may leave JNI_OnLoad. On failure the context is
// destroyed here, which releases what its initialization had set up, the
// translated exception stays pending and JNI_ERR makes the library load
// fail: the JVM throws the pending exception from System.loadLibrary and
// unloads the library. The context is published only once it is complete,
// so no native call can see a partially initialized one.
try {
javaContext->initialize(env);
// libwebrtc initializes its logging configuration on the first log
// line, with everything from LS_INFO upwards written to stderr, and the
// configuration can be set only once. Initialize it here, before
// anything can log, so nothing is written unless the application asks
// for it through Logging.logToDebug or a log sink.
webrtc::LoggingConfig loggingConfig;
loggingConfig.set_log_thread(true);
loggingConfig.set_min_severity(webrtc::LS_NONE);
loggingConfig.set_debug_severity(webrtc::LS_NONE);
webrtc::InitializeLogging(std::move(loggingConfig));

auto context = std::make_unique<jni::WebRTCContext>(vm);
context->initialize(env);

// Not every JNI call of the initialization is followed by a check, a
// Java exception still pending means it failed as well.
if (env->ExceptionCheck()) {
return JNI_ERR;
}

javaContext = context.release();

return JNI_VERSION_1_6;
}
catch (...) {
ThrowCxxJavaException(env);
// Translating can fail as well (creating the Java error needs its
// class): the load fails then without a pending exception, the JVM
// reports the unsupported JNI version.
try {
ThrowCxxJavaException(env);
}
catch (...) {
}
}

return JNI_VERSION_1_6;
return JNI_ERR;
}

JNIEXPORT void JNICALL JNI_OnUnload(JavaVM * vm, void * reserved)
Expand All @@ -73,5 +102,6 @@ JNIEXPORT void JNICALL JNI_OnUnload(JavaVM * vm, void * reserved)
}

delete javaContext;
javaContext = nullptr;
}
}
20 changes: 17 additions & 3 deletions webrtc-jni/src/main/cpp/src/WebRTCContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,28 @@ namespace jni
WebRTCContext::WebRTCContext(JavaVM * vm) :
JavaContext(vm),
webrtcEnv(webrtc::CreateEnvironment()),
sslInitialized(false),
audioDevManager(nullptr),
videoDevManager(nullptr),
powerManagement(nullptr)
{
}

WebRTCContext::~WebRTCContext()
{
if (sslInitialized) {
webrtc::CleanupSSL();
}
}

void WebRTCContext::initialize(JNIEnv * env)
{
if (!webrtc::InitializeSSL()) {
throw Exception("Initialize SSL failed");
}


sslInitialized = true;

JavaEnums::add<webrtc::LoggingSeverity>(env, PKG_LOG"Logging$Severity");
JavaEnums::add<webrtc::MediaType>(env, PKG_MEDIA"MediaType");
JavaEnums::add<webrtc::DataChannelInterface::DataState>(env, PKG"RTCDataChannelState");
Expand Down Expand Up @@ -142,8 +152,12 @@ namespace jni

void WebRTCContext::destroy(JNIEnv * env)
{
if (!webrtc::CleanupSSL()) {
env->Throw(jni::JavaError(env, "Cleanup SSL failed"));
if (sslInitialized) {
sslInitialized = false;

if (!webrtc::CleanupSSL()) {
env->Throw(jni::JavaError(env, "Cleanup SSL failed"));
}
}

audioDevManager = nullptr;
Expand Down
4 changes: 2 additions & 2 deletions webrtc-jni/src/main/cpp/src/api/JavaCallbackThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

#include "api/JavaCallbackThread.h"
#include "JavaContext.h"
#include "JavaUtils.h"

#include "rtc_base/logging.h"

Expand All @@ -38,7 +38,7 @@ namespace jni

void Run(Queue * queue)
{
JavaVM * vm = javaContext->getVM();
JavaVM * vm = GetJavaVM();
JNIEnv * env = nullptr;

JavaVMAttachArgs args;
Expand Down
2 changes: 1 addition & 1 deletion webrtc-jni/src/main/cpp/src/api/RTCConfiguration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ namespace jni
JavaArrayList certificateList(env, certificates.size());

for (auto & certificate : certificates) {
certificateList.add(jni::RTCCertificatePEM::toJava(env, certificate->ToPEM()));
certificateList.add(jni::RTCCertificatePEM::toJava(env, *certificate));
}

auto type = JavaEnums::toJava(env, nativeType.type);
Expand Down
49 changes: 47 additions & 2 deletions webrtc-jni/src/main/cpp/src/rtc/RTCCertificatePEM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,63 @@
#include "rtc_base/ref_count.h"
#include "rtc_base/rtc_certificate_generator.h"

#include <cstdint>
#include <limits>

namespace jni
{
namespace RTCCertificatePEM
{
JavaLocalRef<jobject> toJava(JNIEnv * env, const webrtc::RTCCertificatePEM & certificate)
jlong ExpiresMillis(const webrtc::SSLCertificate & certificate)
{
constexpr int64_t kMillisPerSecond = 1000;
constexpr int64_t kMax = std::numeric_limits<int64_t>::max();
constexpr int64_t kMin = std::numeric_limits<int64_t>::min();

// Seconds relative to the Unix epoch, or -1 when the certificate
// cannot be parsed. RTCCertificate::Expires() would report that as
// 0, which is indistinguishable from an actual time.
const int64_t seconds = certificate.CertificateExpirationTime();

if (seconds == -1) {
return kExpiresUnknown;
}

// No X.509 time comes near the limits, saturate rather than overflow.
if (seconds > kMax / kMillisPerSecond) {
return static_cast<jlong>(kMax);
}
if (seconds < kMin / kMillisPerSecond) {
return static_cast<jlong>(kMin);
}

return static_cast<jlong>(seconds * kMillisPerSecond);
}

JavaLocalRef<jobject> toJava(JNIEnv * env, const webrtc::RTCCertificate & certificate)
{
return toJava(env, certificate.ToPEM(), ExpiresMillis(certificate.GetSSLCertificate()));
}

JavaLocalRef<jobject> toJava(JNIEnv * env, const webrtc::SSLCertificate & certificate)
{
const webrtc::RTCCertificatePEM pem("", certificate.ToPEMString());

return toJava(env, pem, ExpiresMillis(certificate));
}

JavaLocalRef<jobject> toJava(JNIEnv * env, const webrtc::RTCCertificatePEM & certificate, jlong expires)
{
const auto javaClass = JavaClasses::get<JavaRTCCertificatePEMClass>(env);

// The arguments of the variadic NewObject are not converted to the
// types of the constructor's signature, the JVM reads the long
// parameter with va_arg(jlong): it must be passed as a jlong, an int
// literal there is undefined behavior.
jobject obj = env->NewObject(javaClass->cls, javaClass->ctor,
JavaString::toJava(env, certificate.private_key()).get(),
JavaString::toJava(env, certificate.certificate()).get(),
0
expires
);

return JavaLocalRef<jobject>(env, obj);
Expand Down
21 changes: 18 additions & 3 deletions webrtc/src/main/java/dev/onvoid/webrtc/RTCCertificatePEM.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,16 @@
*/
public class RTCCertificatePEM {

/**
* The value of {@link #getExpires()} for a certificate whose expiration
* time cannot be read. No actual expiration time has this value: those
* are whole seconds, multiples of 1000 in milliseconds.
*/
public static final long EXPIRES_UNKNOWN = -1;

/**
* Indicates the time in milliseconds relative to the Unix epoch after which
* the certificate will be considered invalid.
* the certificate will be considered invalid, or {@link #EXPIRES_UNKNOWN}.
*/
private final long expires;

Expand All @@ -48,7 +55,11 @@ public class RTCCertificatePEM {
*
* @param privateKey Private key as PEM string.
* @param certificate Certificate as PEM string.
* @param expires The expiration date of this certificate.
* @param expires The expiration time of this certificate in
* milliseconds relative to the Unix epoch, or
* {@link #EXPIRES_UNKNOWN}. It is informational only:
* WebRTC reads the expiration time from the PEM
* certificate itself.
*/
public RTCCertificatePEM(String privateKey, String certificate, long expires) {
this.privateKey = privateKey;
Expand All @@ -72,7 +83,11 @@ public String getCertificate() {

/**
* @return The expiration time in milliseconds of this certificate relative
* to the Unix epoch.
* to the Unix epoch, or {@link #EXPIRES_UNKNOWN} if it cannot be read from
* the certificate. Certificates returned by the native API, such as those of
* {@link RTCDtlsTransport#getRemoteCertificates()} and
* {@link RTCPeerConnection#getConfiguration()}, carry the time read from
* the certificate.
*/
public long getExpires() {
return expires;
Expand Down
Loading
Loading