mirror of
https://github.com/Ylianst/MeshAgent
synced 2025-12-06 00:13:33 +00:00
745 lines
32 KiB
C
745 lines
32 KiB
C
/*
|
|
Copyright 2006 - 2022 Intel Corporation
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
|
|
|
|
#ifdef WIN32
|
|
#include <WinSock2.h>
|
|
#include <WS2tcpip.h>
|
|
#endif
|
|
|
|
#include "ILibDuktape_WebRTC.h"
|
|
#include "microstack/ILibParsers.h"
|
|
#include "ILibDuktape_Helpers.h"
|
|
#include "ILibDuktapeModSearch.h"
|
|
#include "ILibDuktape_EventEmitter.h"
|
|
#include "ILibDuktape_DuplexStream.h"
|
|
|
|
#ifndef MICROSTACK_NOTLS
|
|
#include "microstack/ILibWebRTC.h"
|
|
#include "microstack/ILibWrapperWebRTC.h"
|
|
|
|
|
|
#define ILibDuktape_WebRTC_ConnectionFactoryPtr "\xFF_WebRTC_ConnectionFactoryPtr"
|
|
#define ILibDuktape_WebRTC_ConnectionPtr "\xFF_WebRTC_ConnectionPtr"
|
|
#define ILibDuktape_WebRTC_DataChannelPtr "\xFF_WebRTC_DataChannelPtr"
|
|
extern void ILibWrapper_WebRTC_ConnectionFactory_RemoveFromChain(ILibWrapper_WebRTC_ConnectionFactory factory);
|
|
int ILibDuktape_WebRTC_SSL2Duktape = -1;
|
|
|
|
typedef struct ILibWebRTC_Duktape_Handlers
|
|
{
|
|
duk_context *ctx;
|
|
void *ConnectionObject;
|
|
ILibDuktape_EventEmitter *emitter;
|
|
#ifdef _WEBRTCDEBUG
|
|
int debugRegistered;
|
|
#endif
|
|
|
|
void *OnConnectionSendOK;
|
|
}ILibWebRTC_Duktape_Handlers;
|
|
typedef struct ILibDuktape_WebRTC_DataChannel
|
|
{
|
|
ILibWrapper_WebRTC_DataChannel *dataChannel;
|
|
duk_context *ctx;
|
|
ILibDuktape_EventEmitter *emitter;
|
|
ILibDuktape_DuplexStream *stream;
|
|
int maxFragmentSize;
|
|
}ILibDuktape_WebRTC_DataChannel;
|
|
|
|
extern void* ILibWrapper_WebRTC_Connection_GetStunModule(ILibWrapper_WebRTC_Connection connection);
|
|
|
|
|
|
duk_ret_t ILibWebRTC_Duktape_ConnectionFactory_SetTurn(duk_context *ctx)
|
|
{
|
|
char *host = Duktape_GetStringPropertyValue(ctx, 0, "Host", NULL);
|
|
int port = Duktape_GetIntPropertyValue(ctx, 0, "Port", 3478);
|
|
duk_size_t usernameLen;
|
|
char *username = Duktape_GetStringPropertyValueEx(ctx, 0, "Username", NULL, &usernameLen);
|
|
duk_size_t passwordLen;
|
|
char *password = Duktape_GetStringPropertyValueEx(ctx, 0, "Password", "", &passwordLen);
|
|
ILibWebRTC_TURN_ConnectFlags flags = (ILibWebRTC_TURN_ConnectFlags)Duktape_GetIntPropertyValue(ctx, 0, "Mode", (int)ILibWebRTC_TURN_ENABLED);
|
|
struct sockaddr_in6* server;
|
|
ILibWrapper_WebRTC_ConnectionFactory factory;
|
|
|
|
if (host == NULL || username == NULL) { password = NULL; return(ILibDuktape_Error(ctx, "Invalid TURN parameters")); }
|
|
server = Duktape_IPAddress4_FromString(host, (unsigned short)port);
|
|
|
|
duk_push_this(ctx);
|
|
factory = Duktape_GetPointerProperty(ctx, -1, "FactoryPtr");
|
|
|
|
ILibWrapper_WebRTC_ConnectionFactory_SetTurnServer(factory, server, username, (int)usernameLen, password, (int)passwordLen, flags);
|
|
password = NULL;
|
|
return 0;
|
|
}
|
|
|
|
duk_idx_t ILibWebRTC_Duktape_Connection_AddRemoteCandidate(duk_context *ctx)
|
|
{
|
|
char *username;
|
|
struct sockaddr_in6 *candidate = NULL;
|
|
ILibWrapper_WebRTC_Connection connection;
|
|
|
|
duk_push_this(ctx); // [connection]
|
|
duk_get_prop_string(ctx, -1, "ConnectionPtr"); // [connection][ptr]
|
|
connection = (ILibWrapper_WebRTC_Connection*)duk_to_pointer(ctx, -1);
|
|
if (strcmp(Duktape_GetStringPropertyValue(ctx, 0, "Family", "IPv4"), "IPv4") == 0)
|
|
{
|
|
candidate = Duktape_IPAddress4_FromString(Duktape_GetStringPropertyValue(ctx, 0, "Address", "127.0.0.1"), (unsigned short)Duktape_GetIntPropertyValue(ctx, 0, "Port", 65535));
|
|
username = ILibWrapper_WebRTC_Connection_GetLocalUsername(connection);
|
|
ILibORTC_AddRemoteCandidate(ILibWrapper_WebRTC_Connection_GetStunModule(connection), username, candidate);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------------------------------------*/
|
|
|
|
|
|
ILibWrapper_WebRTC_Connection ILibDuktape_WebRTC_Native_GetConnection(duk_context *ctx)
|
|
{
|
|
ILibWrapper_WebRTC_Connection retVal = NULL;
|
|
duk_push_this(ctx); // [this]
|
|
duk_get_prop_string(ctx, -1, ILibDuktape_WebRTC_ConnectionPtr); // [this][connection]
|
|
retVal = (ILibWrapper_WebRTC_Connection)duk_get_pointer(ctx, -1);
|
|
duk_pop_2(ctx); // ...
|
|
return retVal;
|
|
}
|
|
ILibTransport_DoneState ILibDuktape_WebRTC_DataChannel_Stream_WriteSink(ILibDuktape_DuplexStream *stream, char *buffer, int bufferLen, void *user)
|
|
{
|
|
ILibDuktape_WebRTC_DataChannel *ptrs = (ILibDuktape_WebRTC_DataChannel*)user;
|
|
if (ptrs->dataChannel != NULL)
|
|
{
|
|
if (duk_stream_flags_isBuffer(stream->writableStream->Reserved) && ptrs->maxFragmentSize > 0 && bufferLen > ptrs->maxFragmentSize)
|
|
{
|
|
// We need to fragment the send
|
|
ILibTransport_DoneState ret = ILibTransport_DoneState_ERROR;
|
|
while (bufferLen > 0)
|
|
{
|
|
ret = ILibWrapper_WebRTC_DataChannel_SendEx(ptrs->dataChannel, buffer, bufferLen > ptrs->maxFragmentSize ? ptrs->maxFragmentSize : bufferLen, 53);
|
|
buffer = buffer + (bufferLen > ptrs->maxFragmentSize ? ptrs->maxFragmentSize : bufferLen);
|
|
bufferLen -= (bufferLen > ptrs->maxFragmentSize ? ptrs->maxFragmentSize : bufferLen);
|
|
}
|
|
return(ret);
|
|
}
|
|
else
|
|
{
|
|
// Normal Send
|
|
return(ILibWrapper_WebRTC_DataChannel_SendEx(ptrs->dataChannel, buffer, bufferLen, duk_stream_flags_isString(stream->writableStream->Reserved) ? 51 : 53));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return(ILibTransport_DoneState_ERROR);
|
|
}
|
|
}
|
|
void ILibDuktape_WebRTC_DataChannel_Stream_EndSink(ILibDuktape_DuplexStream *stream, void *user)
|
|
{
|
|
ILibDuktape_WebRTC_DataChannel *ptrs = (ILibDuktape_WebRTC_DataChannel*)user;
|
|
if (ptrs->dataChannel != NULL)
|
|
{
|
|
ILibWrapper_WebRTC_DataChannel_Close(ptrs->dataChannel);
|
|
}
|
|
}
|
|
void ILibDuktape_WebRTC_DataChannel_Stream_PauseSink(ILibDuktape_DuplexStream *sender, void *user)
|
|
{
|
|
ILibDuktape_WebRTC_DataChannel *ptrs = (ILibDuktape_WebRTC_DataChannel*)user;
|
|
if (ptrs->dataChannel != NULL)
|
|
{
|
|
void *sctpSession = ((void**)ptrs->dataChannel->parent)[0];
|
|
ILibSCTP_Pause(sctpSession);
|
|
}
|
|
}
|
|
void ILibDuktape_WebRTC_DataChannel_Stream_ResumeSink(ILibDuktape_DuplexStream *sender, void *user)
|
|
{
|
|
ILibDuktape_WebRTC_DataChannel *ptrs = (ILibDuktape_WebRTC_DataChannel*)user;
|
|
if (ptrs->dataChannel != NULL)
|
|
{
|
|
void *sctpSession = ((void**)ptrs->dataChannel->parent)[0];
|
|
ILibSCTP_Resume(sctpSession);
|
|
}
|
|
}
|
|
void ILibDuktape_WebRTC_OnDataChannelSendOK(void *dataChannel)
|
|
{
|
|
ILibDuktape_WebRTC_DataChannel *ptrs = (ILibDuktape_WebRTC_DataChannel*)((ILibWrapper_WebRTC_DataChannel*)dataChannel)->userData;
|
|
if (ptrs != NULL) { ILibDuktape_DuplexStream_Ready(ptrs->stream); }
|
|
}
|
|
void ILibDuktape_WebRTC_DataChannel_OnClose(struct ILibWrapper_WebRTC_DataChannel* dataChannel)
|
|
{
|
|
ILibDuktape_WebRTC_DataChannel *ptrs = (ILibDuktape_WebRTC_DataChannel*)dataChannel->userData;
|
|
if (ptrs != NULL && ILibMemory_CanaryOK(ptrs))
|
|
{
|
|
ILibDuktape_DuplexStream_WriteEnd(ptrs->stream);
|
|
ptrs->dataChannel = NULL;
|
|
}
|
|
}
|
|
void ILibDuktape_WebRTC_DataChannel_OnData(struct ILibWrapper_WebRTC_DataChannel* dataChannel, char* data, int dataLen, int dataType)
|
|
{
|
|
ILibDuktape_WebRTC_DataChannel *ptrs = (ILibDuktape_WebRTC_DataChannel*)dataChannel->userData;
|
|
|
|
if (ptrs != NULL && ILibMemory_CanaryOK(ptrs)) { ILibDuktape_DuplexStream_WriteDataEx(ptrs->stream, dataType == 51 ? 1 : 0, data, dataLen); }
|
|
}
|
|
duk_ret_t ILibDuktape_WebRTC_DataChannel_Finalizer(duk_context *ctx)
|
|
{
|
|
duk_get_prop_string(ctx, 0, ILibDuktape_WebRTC_DataChannelPtr);
|
|
ILibDuktape_WebRTC_DataChannel *ptrs = (ILibDuktape_WebRTC_DataChannel*)Duktape_GetBuffer(ctx, -1, NULL);
|
|
if (ptrs->dataChannel != NULL)
|
|
{
|
|
//printf("WebRTC Data Channel Finalizer on Connection: %p\n", ptrs->dataChannel->parent);
|
|
ptrs->dataChannel->userData = NULL;
|
|
ILibWrapper_WebRTC_DataChannel_Close(ptrs->dataChannel);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
duk_ret_t ILibDuktape_WebRTC_DataChannel_MaxFragmentSize_SETTER(duk_context *ctx)
|
|
{
|
|
duk_push_this(ctx);
|
|
ILibDuktape_WebRTC_DataChannel *ptrs = (ILibDuktape_WebRTC_DataChannel*)Duktape_GetBufferProperty(ctx, -1, ILibDuktape_WebRTC_DataChannelPtr);
|
|
ptrs->maxFragmentSize = (int)duk_require_int(ctx, 0);
|
|
return(0);
|
|
}
|
|
duk_ret_t ILibDuktape_WebRTC_DataChannel_MaxFragmentSize_GETTER(duk_context *ctx)
|
|
{
|
|
duk_push_this(ctx);
|
|
ILibDuktape_WebRTC_DataChannel *ptrs = (ILibDuktape_WebRTC_DataChannel*)Duktape_GetBufferProperty(ctx, -1, ILibDuktape_WebRTC_DataChannelPtr);
|
|
duk_push_int(ctx, ptrs->maxFragmentSize);
|
|
return(1);
|
|
}
|
|
void ILibDuktape_WebRTC_DataChannel_PUSH(duk_context *ctx, ILibWrapper_WebRTC_DataChannel *dataChannel)
|
|
{
|
|
if (dataChannel == NULL) { duk_push_null(ctx); return; }
|
|
if (dataChannel->userData != NULL) { duk_push_heapptr(((ILibDuktape_WebRTC_DataChannel*)dataChannel->userData)->ctx, ((ILibDuktape_WebRTC_DataChannel*)dataChannel->userData)->emitter->object); return; }
|
|
ILibDuktape_WebRTC_DataChannel *ptrs;
|
|
|
|
dataChannel->Header.transport.SendOkPtr = ILibDuktape_WebRTC_OnDataChannelSendOK;
|
|
dataChannel->OnClosed = ILibDuktape_WebRTC_DataChannel_OnClose;
|
|
dataChannel->Header.DataChannelCallbacks.OnRawData = ILibDuktape_WebRTC_DataChannel_OnData;
|
|
|
|
duk_push_object(ctx); // [dataChannel]
|
|
ILibDuktape_WriteID(ctx, "webRTC.dataChannel");
|
|
ptrs = (ILibDuktape_WebRTC_DataChannel*)Duktape_PushBuffer(ctx, sizeof(ILibDuktape_WebRTC_DataChannel)); // [dataChannel][buffers]
|
|
dataChannel->userData = ptrs;
|
|
duk_put_prop_string(ctx, -2, ILibDuktape_WebRTC_DataChannelPtr); // [dataChannel]
|
|
ptrs->dataChannel = dataChannel;
|
|
ptrs->ctx = ctx;
|
|
ptrs->emitter = ILibDuktape_EventEmitter_Create(ctx);
|
|
ILibDuktape_CreateFinalizer(ctx, ILibDuktape_WebRTC_DataChannel_Finalizer);
|
|
|
|
duk_push_string(ctx, dataChannel->channelName);
|
|
duk_put_prop_string(ctx, -2, "name");
|
|
duk_push_int(ctx, dataChannel->streamId);
|
|
duk_put_prop_string(ctx, -2, "id");
|
|
|
|
ILibDuktape_EventEmitter_CreateEventEx(ptrs->emitter, "ack");
|
|
|
|
ptrs->stream = ILibDuktape_DuplexStream_Init(ctx, ILibDuktape_WebRTC_DataChannel_Stream_WriteSink, ILibDuktape_WebRTC_DataChannel_Stream_EndSink,
|
|
ILibDuktape_WebRTC_DataChannel_Stream_PauseSink, ILibDuktape_WebRTC_DataChannel_Stream_ResumeSink, ptrs);
|
|
ILibDuktape_CreateEventWithGetterAndSetterEx(ctx, "maxFragmentSize", ILibDuktape_WebRTC_DataChannel_MaxFragmentSize_GETTER, ILibDuktape_WebRTC_DataChannel_MaxFragmentSize_SETTER);
|
|
}
|
|
|
|
duk_ret_t ILibDuktape_WebRTC_ConnectionFactory_Finalizer(duk_context *ctx)
|
|
{
|
|
void *chain = Duktape_GetChain(ctx);
|
|
ILibWrapper_WebRTC_ConnectionFactory factory;
|
|
duk_get_prop_string(ctx, 0, ILibDuktape_WebRTC_ConnectionFactoryPtr);
|
|
factory = (ILibWrapper_WebRTC_ConnectionFactory)duk_get_pointer(ctx, -1);
|
|
|
|
//printf("WebRTC Factory Finalizer: %p\n", factory);
|
|
|
|
if (factory != NULL && chain != NULL && ILibIsChainBeingDestroyed(chain) == 0)
|
|
{
|
|
ILibWrapper_WebRTC_ConnectionFactory_RemoveFromChain(factory);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#ifdef _WEBRTCDEBUG
|
|
void ILibDuktape_WebRTC_Connection_Debug(void* dtlsSession, char* debugField, int data)
|
|
{
|
|
SSL *ssl = (SSL*)ILibWrapper_WebRTC_DtlsSessionToSSL(dtlsSession);
|
|
ILibWebRTC_Duktape_Handlers *ptrs = (ILibWebRTC_Duktape_Handlers*)SSL_get_ex_data(ssl, ILibDuktape_WebRTC_SSL2Duktape);
|
|
|
|
if (ptrs != NULL && ptrs->ConnectionObject != NULL)
|
|
{
|
|
if (strcmp(debugField, "OnHold") == 0)
|
|
{
|
|
ILibDuktape_EventEmitter_SetupEmit(ptrs->ctx, ptrs->ConnectionObject, "_hold"); // [emit][this][name]
|
|
}
|
|
else if (strcmp(debugField, "OnCongestionWindowSizeChanged") == 0)
|
|
{
|
|
ILibDuktape_EventEmitter_SetupEmit(ptrs->ctx, ptrs->ConnectionObject, "_congestionWindowSizeChange"); // [emit][this][name]
|
|
}
|
|
else if (strcmp(debugField, "OnRTTCalculated") == 0)
|
|
{
|
|
ILibDuktape_EventEmitter_SetupEmit(ptrs->ctx, ptrs->ConnectionObject, "_rttCalculated"); // [emit][this][name]
|
|
}
|
|
else if (strcmp(debugField, "OnFastRecovery") == 0)
|
|
{
|
|
ILibDuktape_EventEmitter_SetupEmit(ptrs->ctx, ptrs->ConnectionObject, "_fastRecovery"); // [emit][this][name]
|
|
}
|
|
else if (strcmp(debugField, "OnLastSackTime") == 0)
|
|
{
|
|
ILibDuktape_EventEmitter_SetupEmit(ptrs->ctx, ptrs->ConnectionObject, "_lastSackTime"); // [emit][this][name]
|
|
}
|
|
else if (strcmp(debugField, "OnLastSentTime") == 0)
|
|
{
|
|
ILibDuktape_EventEmitter_SetupEmit(ptrs->ctx, ptrs->ConnectionObject, "_lastSentTime"); // [emit][this][name]
|
|
}
|
|
else if (strcmp(debugField, "OnReceiverCredits") == 0)
|
|
{
|
|
ILibDuktape_EventEmitter_SetupEmit(ptrs->ctx, ptrs->ConnectionObject, "_receiverCredits"); // [emit][this][name]
|
|
}
|
|
else if (strcmp(debugField, "OnT3RTX") == 0)
|
|
{
|
|
ILibDuktape_EventEmitter_SetupEmit(ptrs->ctx, ptrs->ConnectionObject, "_t3tx"); // [emit][this][name]
|
|
}
|
|
else if (strcmp(debugField, "OnSendRetry") == 0)
|
|
{
|
|
ILibDuktape_EventEmitter_SetupEmit(ptrs->ctx, ptrs->ConnectionObject, "_retransmit"); // [emit][this][name]
|
|
}
|
|
else if (strcmp(debugField, "OnSACKReceived") == 0)
|
|
{
|
|
ILibDuktape_EventEmitter_SetupEmit(ptrs->ctx, ptrs->ConnectionObject, "_sackReceived"); // [emit][this][name]
|
|
}
|
|
else if (strcmp(debugField, "OnRetryPacket") == 0)
|
|
{
|
|
duk_push_external_buffer(ptrs->ctx); // [extBuffer]
|
|
ILibDuktape_EventEmitter_SetupEmit(ptrs->ctx, ptrs->ConnectionObject, "_retransmitPacket"); // [extBuffer][emit][this][name]
|
|
duk_config_buffer(ptrs->ctx, -4, debugField + 14, data);
|
|
duk_push_buffer_object(ptrs->ctx, -4, 0, data, DUK_BUFOBJ_NODEJS_BUFFER);
|
|
if (duk_pcall_method(ptrs->ctx, 2) != 0) { ILibDuktape_Process_UncaughtException(ptrs->ctx); }
|
|
duk_pop_2(ptrs->ctx); // ...
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
duk_push_int(ptrs->ctx, data); // [emit][this][name][val]
|
|
if (duk_pcall_method(ptrs->ctx, 2) != 0) { ILibDuktape_Process_UncaughtException(ptrs->ctx); }
|
|
duk_pop(ptrs->ctx); // ...
|
|
}
|
|
}
|
|
#endif
|
|
void ILibDuktape_WebRTC_OnConnection(ILibWrapper_WebRTC_Connection connection, int connected)
|
|
{
|
|
ILibWebRTC_Duktape_Handlers *ptrs = (ILibWebRTC_Duktape_Handlers*)ILibMemory_Extra(connection);
|
|
if (!ILibMemory_CanaryOK(ptrs->emitter)) { return; }
|
|
|
|
if (connected == 0)
|
|
{
|
|
ILibDuktape_EventEmitter_SetupEmit(ptrs->ctx, ptrs->ConnectionObject, "disconnected"); // [emit][this][disconnected]
|
|
duk_del_prop_string(ptrs->ctx, -2, ILibDuktape_WebRTC_ConnectionPtr);
|
|
}
|
|
else
|
|
{
|
|
ILibDuktape_EventEmitter_SetupEmit(ptrs->ctx, ptrs->ConnectionObject, "connected"); // [emit][this][connected]
|
|
}
|
|
if (duk_pcall_method(ptrs->ctx, 1) != 0) { ILibDuktape_Process_UncaughtException(ptrs->ctx); }
|
|
duk_pop(ptrs->ctx); // ...
|
|
#ifdef _WEBRTCDEBUG
|
|
if (connected == 0 && ptrs->debugRegistered != 0)
|
|
{
|
|
ILibSCTP_Debug_SetDebugCallback(ILibWrapper_WebRTC_Connection2DtlsSession(connection), "OnHold", NULL);
|
|
ILibSCTP_Debug_SetDebugCallback(ILibWrapper_WebRTC_Connection2DtlsSession(connection), "OnCongestionWindowSizeChanged", NULL);
|
|
ILibSCTP_Debug_SetDebugCallback(ILibWrapper_WebRTC_Connection2DtlsSession(connection), "OnRTTCalculated", NULL);
|
|
ILibSCTP_Debug_SetDebugCallback(ILibWrapper_WebRTC_Connection2DtlsSession(connection), "OnFastRecovery", NULL);
|
|
ILibSCTP_Debug_SetDebugCallback(ILibWrapper_WebRTC_Connection2DtlsSession(connection), "OnReceiverCredits", NULL);
|
|
ILibSCTP_Debug_SetDebugCallback(ILibWrapper_WebRTC_Connection2DtlsSession(connection), "OnT3RTX", NULL);
|
|
ILibSCTP_Debug_SetDebugCallback(ILibWrapper_WebRTC_Connection2DtlsSession(connection), "OnSendRetry", NULL);
|
|
ILibSCTP_Debug_SetDebugCallback(ILibWrapper_WebRTC_Connection2DtlsSession(connection), "OnSendFastRetry", NULL);
|
|
ILibSCTP_Debug_SetDebugCallback(ILibWrapper_WebRTC_Connection2DtlsSession(connection), "OnRetryPacket", NULL);
|
|
ILibSCTP_Debug_SetDebugCallback(ILibWrapper_WebRTC_Connection2DtlsSession(connection), "OnSACKReceived", NULL);
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
void ILibDuktape_WebRTC_OnDataChannel(ILibWrapper_WebRTC_Connection connection, ILibWrapper_WebRTC_DataChannel *dataChannel)
|
|
{
|
|
ILibWebRTC_Duktape_Handlers *ptrs = (ILibWebRTC_Duktape_Handlers*)ILibMemory_Extra(connection);
|
|
if (ILibMemory_CanaryOK(ptrs->emitter))
|
|
{
|
|
ILibDuktape_EventEmitter_SetupEmit(ptrs->ctx, ptrs->ConnectionObject, "dataChannel"); // [emit][this][dataChannel]
|
|
ILibDuktape_WebRTC_DataChannel_PUSH(ptrs->ctx, dataChannel); // [emit][this][dataChannel][dc]
|
|
if (duk_pcall_method(ptrs->ctx, 2) != 0) { ILibDuktape_Process_UncaughtExceptionEx(ptrs->ctx, "webrtc.connection.onDataChannel(): "); }
|
|
duk_pop(ptrs->ctx); // ...
|
|
}
|
|
}
|
|
|
|
void ILibDuktape_WebRTC_offer_onCandidate(ILibWrapper_WebRTC_Connection connection, struct sockaddr_in6* candidate)
|
|
{
|
|
if (candidate != NULL)
|
|
{
|
|
ILibWebRTC_Duktape_Handlers *ptrs = (ILibWebRTC_Duktape_Handlers*)ILibMemory_Extra(connection);
|
|
if (ILibMemory_CanaryOK(ptrs->emitter))
|
|
{
|
|
ILibDuktape_EventEmitter_SetupEmit(ptrs->ctx, ptrs->ConnectionObject, "candidate"); // [emit][this][candidate]
|
|
ILibDuktape_SockAddrToOptions(ptrs->ctx, candidate); // [emit][this][candidate][options]
|
|
if (duk_pcall_method(ptrs->ctx, 2) != 0) { ILibDuktape_Process_UncaughtExceptionEx(ptrs->ctx, "webrtc.connection.onCandidate(): "); }
|
|
duk_pop(ptrs->ctx); // ...
|
|
}
|
|
}
|
|
}
|
|
duk_ret_t ILibDuktape_WebRTC_generateOffer(duk_context *ctx)
|
|
{
|
|
ILibWrapper_WebRTC_Connection connection = ILibDuktape_WebRTC_Native_GetConnection(ctx);
|
|
|
|
char *offer = ILibWrapper_WebRTC_Connection_GenerateOffer(connection, ILibDuktape_WebRTC_offer_onCandidate);
|
|
if (offer != NULL)
|
|
{
|
|
duk_push_string(ctx, offer);
|
|
free(offer);
|
|
}
|
|
else
|
|
{
|
|
duk_push_null(ctx);
|
|
}
|
|
return 1;
|
|
}
|
|
duk_ret_t ILibDuktape_WebRTC_setOffer(duk_context *ctx)
|
|
{
|
|
ILibWrapper_WebRTC_Connection connection = ILibDuktape_WebRTC_Native_GetConnection(ctx);
|
|
|
|
if (!duk_is_string(ctx, 0)) { return(ILibDuktape_Error(ctx, "webrtc.connection.setOffer(): Invalid Parameter")); }
|
|
duk_size_t offerLen;
|
|
char *offer;
|
|
char *counterOffer;
|
|
|
|
offer = (char*)duk_get_lstring(ctx, 0, &offerLen);
|
|
counterOffer = ILibWrapper_WebRTC_Connection_SetOffer(connection, offer, (int)offerLen, ILibDuktape_WebRTC_offer_onCandidate);
|
|
if (counterOffer != NULL)
|
|
{
|
|
duk_push_string(ctx, counterOffer);
|
|
free(counterOffer);
|
|
}
|
|
else
|
|
{
|
|
return(ILibDuktape_Error(ctx, "WebRTC: Error setting offer. Most likely too many outstanding offers"));
|
|
}
|
|
return 1;
|
|
}
|
|
void ILibDuktape_WebRTC_DataChannel_OnAck(struct ILibWrapper_WebRTC_DataChannel* dataChannel)
|
|
{
|
|
ILibDuktape_WebRTC_DataChannel *ptrs = (ILibDuktape_WebRTC_DataChannel*)dataChannel->userData;
|
|
ILibDuktape_EventEmitter_SetupEmit(ptrs->ctx, ptrs->emitter->object, "ack"); // [emit][this][ack]
|
|
if (duk_pcall_method(ptrs->ctx, 1) != 0) { ILibDuktape_Process_UncaughtExceptionEx(ptrs->ctx, "webrtc.dataChannel.onAck(): "); };
|
|
duk_pop(ptrs->ctx); // ...
|
|
}
|
|
duk_ret_t ILibDuktape_WebRTC_createDataChannel(duk_context *ctx)
|
|
{
|
|
int nargs = duk_get_top(ctx);
|
|
ILibWrapper_WebRTC_DataChannel *retVal;
|
|
ILibWrapper_WebRTC_Connection connection = ILibDuktape_WebRTC_Native_GetConnection(ctx);
|
|
|
|
duk_size_t nameLen;
|
|
char *name;
|
|
int stream = -1;
|
|
int i;
|
|
void *OnAck = NULL;
|
|
|
|
name = (char*)duk_get_lstring(ctx, 0, &nameLen);
|
|
|
|
for (i = 1; i < nargs; ++i)
|
|
{
|
|
if (duk_is_number(ctx, i)) { stream = duk_require_int(ctx, i); }
|
|
if (duk_is_function(ctx, i)) { OnAck = duk_require_heapptr(ctx, i); }
|
|
}
|
|
|
|
if (stream < 0)
|
|
{
|
|
retVal = ILibWrapper_WebRTC_DataChannel_Create(connection, name, (int)nameLen, ILibDuktape_WebRTC_DataChannel_OnAck);
|
|
}
|
|
else
|
|
{
|
|
retVal = ILibWrapper_WebRTC_DataChannel_CreateEx(connection, name, (int)nameLen, (unsigned short)stream, ILibDuktape_WebRTC_DataChannel_OnAck);
|
|
}
|
|
|
|
ILibDuktape_WebRTC_DataChannel_PUSH(ctx, retVal);
|
|
if (OnAck != NULL)
|
|
{
|
|
ILibDuktape_EventEmitter_AddOnce(((ILibDuktape_WebRTC_DataChannel*)retVal->userData)->emitter, "ack", OnAck);
|
|
}
|
|
return 1;
|
|
}
|
|
duk_ret_t ILibDuktape_WebRTC_addRemoteCandidate(duk_context *ctx)
|
|
{
|
|
//ILibWrapper_WebRTC_Connection connection = ILibDuktape_WebRTC_Native_GetConnection(ctx);
|
|
//ILibWebRTC_Duktape_Handlers *ptrs = (ILibWebRTC_Duktape_Handlers*)ILibMemory_GetExtraMemory(connection, ILibMemory_WebRTC_Connection_CONTAINERSIZE);
|
|
|
|
|
|
return(ILibDuktape_Error(ctx, "webrtc.connection.addRemoteCandidate(): Not Supported Yet"));
|
|
}
|
|
duk_ret_t ILibDuktape_WebRTC_closeDataChannels(duk_context *ctx)
|
|
{
|
|
ILibWrapper_WebRTC_Connection connection = ILibDuktape_WebRTC_Native_GetConnection(ctx);
|
|
|
|
ILibWrapper_WebRTC_Connection_CloseAllDataChannels(connection);
|
|
return 0;
|
|
}
|
|
duk_ret_t ILibDuktape_WebRTC_Connection_Finalizer(duk_context *ctx)
|
|
{
|
|
ILibWrapper_WebRTC_Connection connection;
|
|
duk_get_prop_string(ctx, 0, ILibDuktape_WebRTC_ConnectionPtr);
|
|
connection = (ILibWrapper_WebRTC_Connection)duk_get_pointer(ctx, -1);
|
|
//printf("WebRTCConnection Finalizer on %p\n", (void*)connection);
|
|
if (connection == NULL) { return 0; }
|
|
|
|
if (ILibWrapper_WebRTC_Connection_IsConnected(connection) != 0)
|
|
{
|
|
ILibWrapper_WebRTC_Connection_CloseAllDataChannels(connection);
|
|
ILibWrapper_WebRTC_Connection_Disconnect(connection);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#ifdef _WEBRTCDEBUG
|
|
void ILibDuktape_WebRTC_Connection_DebugHook(ILibDuktape_EventEmitter *sender, char *eventName, void *hookedCallback)
|
|
{
|
|
// Only register the debug handlers if someone actually subscribed to the event
|
|
duk_push_heapptr(sender->ctx, sender->object); // [connection]
|
|
ILibWrapper_WebRTC_Connection connection = (ILibWrapper_WebRTC_Connection)Duktape_GetPointerProperty(sender->ctx, -1, ILibDuktape_WebRTC_ConnectionPtr);
|
|
ILibWebRTC_Duktape_Handlers *ptrs = (ILibWebRTC_Duktape_Handlers*)ILibMemory_Extra(connection);
|
|
duk_pop(sender->ctx); // ...
|
|
|
|
if (ptrs->debugRegistered == 0)
|
|
{
|
|
SSL* ssl = ILibWrapper_WebRTC_DtlsSessionToSSL(ILibWrapper_WebRTC_Connection2DtlsSession(connection));
|
|
if (ILibDuktape_WebRTC_SSL2Duktape < 0) { ILibDuktape_WebRTC_SSL2Duktape = SSL_get_ex_new_index(0, "ILibDuktape_WebRTC_SSL2Connection index", NULL, NULL, NULL); }
|
|
SSL_set_ex_data(ssl, ILibDuktape_WebRTC_SSL2Duktape, ptrs);
|
|
|
|
ILibSCTP_Debug_SetDebugCallback(ILibWrapper_WebRTC_Connection2DtlsSession(connection), "OnHold", ILibDuktape_WebRTC_Connection_Debug);
|
|
ILibSCTP_Debug_SetDebugCallback(ILibWrapper_WebRTC_Connection2DtlsSession(connection), "OnCongestionWindowSizeChanged", ILibDuktape_WebRTC_Connection_Debug);
|
|
ILibSCTP_Debug_SetDebugCallback(ILibWrapper_WebRTC_Connection2DtlsSession(connection), "OnRTTCalculated", ILibDuktape_WebRTC_Connection_Debug);
|
|
ILibSCTP_Debug_SetDebugCallback(ILibWrapper_WebRTC_Connection2DtlsSession(connection), "OnFastRecovery", ILibDuktape_WebRTC_Connection_Debug);
|
|
ILibSCTP_Debug_SetDebugCallback(ILibWrapper_WebRTC_Connection2DtlsSession(connection), "OnReceiverCredits", ILibDuktape_WebRTC_Connection_Debug);
|
|
ILibSCTP_Debug_SetDebugCallback(ILibWrapper_WebRTC_Connection2DtlsSession(connection), "OnT3RTX", ILibDuktape_WebRTC_Connection_Debug);
|
|
ILibSCTP_Debug_SetDebugCallback(ILibWrapper_WebRTC_Connection2DtlsSession(connection), "OnSendRetry", ILibDuktape_WebRTC_Connection_Debug);
|
|
ILibSCTP_Debug_SetDebugCallback(ILibWrapper_WebRTC_Connection2DtlsSession(connection), "OnSendFastRetry", ILibDuktape_WebRTC_Connection_Debug);
|
|
ILibSCTP_Debug_SetDebugCallback(ILibWrapper_WebRTC_Connection2DtlsSession(connection), "OnRetryPacket", ILibDuktape_WebRTC_Connection_Debug);
|
|
ILibSCTP_Debug_SetDebugCallback(ILibWrapper_WebRTC_Connection2DtlsSession(connection), "OnSACKReceived", ILibDuktape_WebRTC_Connection_Debug);
|
|
}
|
|
}
|
|
#endif
|
|
duk_ret_t ILibDuktape_WebRTC_CreateConnection(duk_context *ctx)
|
|
{
|
|
ILibWebRTC_Duktape_Handlers *ptrs;
|
|
ILibWrapper_WebRTC_Connection connection;
|
|
ILibWrapper_WebRTC_ConnectionFactory factory;
|
|
duk_push_this(ctx); // [factory]
|
|
duk_get_prop_string(ctx, -1, ILibDuktape_WebRTC_ConnectionFactoryPtr);
|
|
factory = (ILibWrapper_WebRTC_ConnectionFactory)duk_get_pointer(ctx, -1);
|
|
|
|
duk_push_object(ctx); // [factory][connection]
|
|
ILibDuktape_WriteID(ctx, "webRTC.peerConnection");
|
|
connection = ILibWrapper_WebRTC_ConnectionFactory_CreateConnection2(factory, ILibDuktape_WebRTC_OnConnection, ILibDuktape_WebRTC_OnDataChannel, NULL, sizeof(ILibWebRTC_Duktape_Handlers));
|
|
ptrs = (ILibWebRTC_Duktape_Handlers*)ILibMemory_Extra(connection);
|
|
ptrs->ctx = ctx;
|
|
ptrs->ConnectionObject = duk_get_heapptr(ctx, -1);
|
|
ptrs->emitter = ILibDuktape_EventEmitter_Create(ctx);
|
|
|
|
ILibDuktape_EventEmitter_CreateEventEx(ptrs->emitter, "candidate");
|
|
ILibDuktape_EventEmitter_CreateEventEx(ptrs->emitter, "dataChannel");
|
|
ILibDuktape_EventEmitter_CreateEventEx(ptrs->emitter, "connected");
|
|
ILibDuktape_EventEmitter_CreateEventEx(ptrs->emitter, "disconnected");
|
|
|
|
#ifdef _WEBRTCDEBUG
|
|
ptrs->debugRegistered = 0;
|
|
ILibDuktape_EventEmitter_CreateEventEx(ptrs->emitter, "_hold");
|
|
ILibDuktape_EventEmitter_CreateEventEx(ptrs->emitter, "_lastSackTime");
|
|
ILibDuktape_EventEmitter_CreateEventEx(ptrs->emitter, "_lastSentTime");
|
|
ILibDuktape_EventEmitter_CreateEventEx(ptrs->emitter, "_congestionWindowSizeChange");
|
|
ILibDuktape_EventEmitter_CreateEventEx(ptrs->emitter, "_fastRecovery");
|
|
ILibDuktape_EventEmitter_CreateEventEx(ptrs->emitter, "_rttCalculated");
|
|
ILibDuktape_EventEmitter_CreateEventEx(ptrs->emitter, "_receiverCredits");
|
|
ILibDuktape_EventEmitter_CreateEventEx(ptrs->emitter, "_t3tx");
|
|
ILibDuktape_EventEmitter_CreateEventEx(ptrs->emitter, "_retransmit");
|
|
ILibDuktape_EventEmitter_CreateEventEx(ptrs->emitter, "_retransmitPacket");
|
|
ILibDuktape_EventEmitter_CreateEventEx(ptrs->emitter, "_sackReceived");
|
|
|
|
ILibDuktape_EventEmitter_AddHook(ptrs->emitter, "_hold", ILibDuktape_WebRTC_Connection_DebugHook);
|
|
ILibDuktape_EventEmitter_AddHook(ptrs->emitter, "_lastSackTime", ILibDuktape_WebRTC_Connection_DebugHook);
|
|
ILibDuktape_EventEmitter_AddHook(ptrs->emitter, "_lastSentTime", ILibDuktape_WebRTC_Connection_DebugHook);
|
|
ILibDuktape_EventEmitter_AddHook(ptrs->emitter, "_congestionWindowSizeChange", ILibDuktape_WebRTC_Connection_DebugHook);
|
|
ILibDuktape_EventEmitter_AddHook(ptrs->emitter, "_fastRecovery", ILibDuktape_WebRTC_Connection_DebugHook);
|
|
ILibDuktape_EventEmitter_AddHook(ptrs->emitter, "_rttCalculated", ILibDuktape_WebRTC_Connection_DebugHook);
|
|
ILibDuktape_EventEmitter_AddHook(ptrs->emitter, "_receiverCredits", ILibDuktape_WebRTC_Connection_DebugHook);
|
|
ILibDuktape_EventEmitter_AddHook(ptrs->emitter, "_t3tx", ILibDuktape_WebRTC_Connection_DebugHook);
|
|
ILibDuktape_EventEmitter_AddHook(ptrs->emitter, "_retransmit", ILibDuktape_WebRTC_Connection_DebugHook);
|
|
ILibDuktape_EventEmitter_AddHook(ptrs->emitter, "_retransmitPacket", ILibDuktape_WebRTC_Connection_DebugHook);
|
|
ILibDuktape_EventEmitter_AddHook(ptrs->emitter, "_sackReceived", ILibDuktape_WebRTC_Connection_DebugHook);
|
|
#endif
|
|
duk_push_pointer(ctx, connection); // [factory][connection][ptr]
|
|
duk_put_prop_string(ctx, -2, ILibDuktape_WebRTC_ConnectionPtr); // [factory][connection]
|
|
|
|
duk_push_int(ctx, ILibWrapper_WebRTC_Connection_GetID(connection)); // [factory][connection][id]
|
|
duk_put_prop_string(ctx, -2, "ID"); // [factory][connection]
|
|
|
|
ILibDuktape_CreateInstanceMethod(ctx, "generateOffer", ILibDuktape_WebRTC_generateOffer, DUK_VARARGS);
|
|
ILibDuktape_CreateInstanceMethod(ctx, "setOffer", ILibDuktape_WebRTC_setOffer, DUK_VARARGS);
|
|
ILibDuktape_CreateInstanceMethod(ctx, "createDataChannel", ILibDuktape_WebRTC_createDataChannel, DUK_VARARGS);
|
|
ILibDuktape_CreateInstanceMethod(ctx, "closeDataChannels", ILibDuktape_WebRTC_closeDataChannels, 0);
|
|
ILibDuktape_CreateInstanceMethod(ctx, "addRemoteCandidate", ILibDuktape_WebRTC_addRemoteCandidate, 1);
|
|
ILibDuktape_CreateFinalizer(ctx, ILibDuktape_WebRTC_Connection_Finalizer);
|
|
|
|
return 1;
|
|
}
|
|
duk_ret_t ILibDuktape_WebRTC_newFactory(duk_context *ctx)
|
|
{
|
|
ILibWrapper_WebRTC_ConnectionFactory factory;
|
|
struct util_cert *rtcert = NULL;
|
|
|
|
if (duk_peval_string(ctx, "require('MeshAgent');") == 0) // [MeshAgent]
|
|
{
|
|
// We can use the Agent Cert
|
|
rtcert = (struct util_cert*)Duktape_GetPointerProperty(ctx, -1, ILibDuktape_MeshAgent_Cert_Server);
|
|
}
|
|
duk_pop(ctx); // ...
|
|
|
|
duk_push_object(ctx); // [factory]
|
|
ILibDuktape_WriteID(ctx, "webRTC");
|
|
factory = ILibWrapper_WebRTC_ConnectionFactory_CreateConnectionFactory2(duk_ctx_chain(ctx), 0, rtcert);
|
|
duk_push_pointer(ctx, factory); // [factory][ptr]
|
|
duk_put_prop_string(ctx, -2, ILibDuktape_WebRTC_ConnectionFactoryPtr); // [factory]
|
|
ILibDuktape_CreateFinalizer(ctx, ILibDuktape_WebRTC_ConnectionFactory_Finalizer);
|
|
|
|
ILibDuktape_CreateInstanceMethod(ctx, "createConnection", ILibDuktape_WebRTC_CreateConnection, 0);
|
|
return(1);
|
|
}
|
|
void ILibDuktape_WebRTC_Push(duk_context *ctx, void *chain)
|
|
{
|
|
ILibWrapper_WebRTC_ConnectionFactory factory;
|
|
struct util_cert *rtcert = NULL;
|
|
|
|
if (duk_peval_string(ctx, "require('MeshAgent');") == 0) // [MeshAgent]
|
|
{
|
|
// We can use the Agent Cert
|
|
rtcert = (struct util_cert*)Duktape_GetPointerProperty(ctx, -1, ILibDuktape_MeshAgent_Cert_Server);
|
|
}
|
|
duk_pop(ctx); // ...
|
|
|
|
duk_push_object(ctx); // [factory]
|
|
ILibDuktape_WriteID(ctx, "webRTC");
|
|
factory = ILibWrapper_WebRTC_ConnectionFactory_CreateConnectionFactory2(chain, 0, rtcert);
|
|
duk_push_pointer(ctx, factory); // [factory][ptr]
|
|
duk_put_prop_string(ctx, -2, ILibDuktape_WebRTC_ConnectionFactoryPtr); // [factory]
|
|
ILibDuktape_CreateFinalizer(ctx, ILibDuktape_WebRTC_ConnectionFactory_Finalizer);
|
|
|
|
ILibDuktape_CreateInstanceMethod(ctx, "createConnection", ILibDuktape_WebRTC_CreateConnection, 0);
|
|
ILibDuktape_CreateInstanceMethod(ctx, "createNewFactory", ILibDuktape_WebRTC_newFactory, 0);
|
|
|
|
}
|
|
void ILibDuktape_WebRTC_Init(duk_context * ctx)
|
|
{
|
|
ILibDuktape_ModSearch_AddHandler(ctx, "ILibWebRTC", ILibDuktape_WebRTC_Push);
|
|
Duktape_CreateEnum(ctx, "ILibWebRTC_TURN_ConnectModes", (char*[]) { "DISABLED", "ENABLED", "ALWAYS_RELAY" }, (int[]) { 0, 1, 2 }, 3);
|
|
}
|
|
#else
|
|
void ILibDuktape_WebRTC_Init(duk_context * ctx)
|
|
{
|
|
Duktape_CreateEnum(ctx, "ILibWebRTC_TURN_ConnectModes", (char*[]) { "DISABLED", "ENABLED", "ALWAYS_RELAY" }, (int[]) { 0, 1, 2 }, 3);
|
|
}
|
|
#endif
|
|
|
|
#ifdef __DOXY__
|
|
/*!
|
|
\brief WebRTC Connection Factory. <b>Note:</b> To use, must <b>require('ILibWebRTC')</b>
|
|
*/
|
|
class ILibWebRTC
|
|
{
|
|
public:
|
|
/*!
|
|
\brief Creates a new unconnected peer Connection object.
|
|
\return <Connection> Unconnected Connection
|
|
*/
|
|
static Connection createConnection();
|
|
|
|
/*!
|
|
\implements EventEmitter
|
|
\brief WebRTC Connection
|
|
*/
|
|
class Connection
|
|
{
|
|
public:
|
|
/*!
|
|
\brief Event emitted when a connection candidate is found
|
|
\param options <Object>\n
|
|
<b>host</b> Host Name or IP Address of the candidate\n
|
|
<b>port</b> Port Number of the candidate\n
|
|
*/
|
|
void candidate;
|
|
/*!
|
|
\brief Event emitted when a DataChannel is created
|
|
\param channel DataChannel
|
|
*/
|
|
void dataChannel;
|
|
/*!
|
|
\brief Event emitted when a peer connection is established
|
|
*/
|
|
void connected;
|
|
/*!
|
|
\brief Event emitted when a peer connection is severed
|
|
*/
|
|
void disconnected;
|
|
|
|
/*!
|
|
\brief Generates a WebRTC SDP Offer.
|
|
\return \<String\|NULL\> On succes, the SDP offer is returned, otherwise NULL.
|
|
*/
|
|
String generateOffer();
|
|
/*!
|
|
\brief Sets the remote WebRTC SDP Offer
|
|
\return \<String\|NULL\> On success, returns the counter WebRTC SDP offer, otherwise NULL.
|
|
*/
|
|
String setOffer(offer);
|
|
/*!
|
|
\brief Creates a WebRTC DataChannel instance
|
|
\param friendlyName \<String\> Friendly name to associate with the new DataChannel
|
|
\param streamNumber <integer> Optional. If specified, uses the desired stream number. Otherwise, one will be auto-generated.
|
|
\param callback <func> Optional. If specified, will be added as one time listener to DataChannel.ack event.
|
|
\return DataChannel instance
|
|
*/
|
|
DataChannel createDataChannel(friendlyName[, streamNumber][, callback]);
|
|
/*!
|
|
\brief Closes all DataChannel instances associated with this Connection.
|
|
*/
|
|
void closeDataChannels();
|
|
ILibDuktape_CreateInstanceMethod(ctx, "addRemoteCandidate", ILibDuktape_WebRTC_addRemoteCandidate, 1);
|
|
};
|
|
/*!
|
|
\implements EventEmitter
|
|
\implements DuplexStream
|
|
\brief WebRTC Data Channel
|
|
*/
|
|
class DataChannel
|
|
{
|
|
public:
|
|
/*!
|
|
\brief WebRTC Stream ID
|
|
*/
|
|
integer id;
|
|
/*!
|
|
\brief WebRTC DataChannel Friendly Name
|
|
*/
|
|
String name;
|
|
/*!
|
|
\brief Event emitted when the connected peer ACK's this DataChannel creation
|
|
*/
|
|
void ack;
|
|
};
|
|
};
|
|
#endif
|