mirror of
https://github.com/mii443/libdatachannel.git
synced 2025-08-22 15:15:28 +00:00
Merged paullouisageneau/libdatachannel with stazio/libdatachannel
This commit is contained in:
2
.github/workflows/build-gnutls.yml
vendored
2
.github/workflows/build-gnutls.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
run: cmake -B build -DUSE_GNUTLS=1 -DWARNINGS_AS_ERRORS=1
|
||||
run: cmake -B build -DUSE_GNUTLS=1 -DWARNINGS_AS_ERRORS=1 -DENABLE_LOCAL_ADDRESS_TRANSLATION=1
|
||||
- name: make
|
||||
run: (cd build; make -j2)
|
||||
- name: test
|
||||
|
2
.github/workflows/build-openssl.yml
vendored
2
.github/workflows/build-openssl.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: cmake
|
||||
run: cmake -B build -DUSE_GNUTLS=0 -WARNINGS_AS_ERRORS=1
|
||||
run: cmake -B build -DUSE_GNUTLS=0 -WARNINGS_AS_ERRORS=1 -DENABLE_LOCAL_ADDRESS_TRANSLATION=1
|
||||
env:
|
||||
OPENSSL_ROOT_DIR: /usr/local/opt/openssl
|
||||
OPENSSL_LIBRARIES: /usr/local/opt/openssl/lib
|
||||
|
@ -1,8 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
project(libdatachannel
|
||||
DESCRIPTION "WebRTC Data Channels Library"
|
||||
VERSION 0.9.0
|
||||
VERSION 0.9.4
|
||||
LANGUAGES CXX)
|
||||
set(PROJECT_DESCRIPTION "WebRTC Data Channels Library")
|
||||
|
||||
# Options
|
||||
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
|
||||
@ -11,6 +11,11 @@ option(NO_WEBSOCKET "Disable WebSocket support" OFF)
|
||||
option(NO_EXAMPLES "Disable examples" OFF)
|
||||
option(NO_TESTS "Disable tests build" OFF)
|
||||
option(WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
|
||||
option(RSA_KEY_BITS_2048 "Use 2048-bit RSA key instead of 3072-bit" OFF)
|
||||
option(CAPI_STDCALL "Set calling convention of C API callbacks stdcall" OFF)
|
||||
# Option USE_SRTP defaults to AUTO (enabled if libSRTP is found, else disabled)
|
||||
set(USE_SRTP AUTO CACHE STRING "Use libSRTP and enable media support")
|
||||
set_property(CACHE USE_SRTP PROPERTY STRINGS AUTO ON OFF)
|
||||
|
||||
if(USE_NICE)
|
||||
option(USE_JUICE "Use libjuice" OFF)
|
||||
@ -19,9 +24,9 @@ else()
|
||||
endif()
|
||||
|
||||
if(USE_GNUTLS)
|
||||
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" ON)
|
||||
option(USE_NETTLE "Use Nettle in libjuice" ON)
|
||||
else()
|
||||
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" OFF)
|
||||
option(USE_NETTLE "Use Nettle in libjuice" OFF)
|
||||
endif()
|
||||
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
@ -157,16 +162,46 @@ target_link_libraries(datachannel-static PUBLIC Threads::Threads plog::plog)
|
||||
target_link_libraries(datachannel-static PRIVATE Usrsctp::UsrsctpStatic)
|
||||
|
||||
if(WIN32)
|
||||
target_link_libraries(datachannel PRIVATE wsock32 ws2_32) # winsock2
|
||||
target_link_libraries(datachannel-static PRIVATE wsock32 ws2_32) # winsock2
|
||||
target_link_libraries(datachannel PRIVATE ws2_32) # winsock2
|
||||
target_link_libraries(datachannel-static PRIVATE ws2_32) # winsock2
|
||||
endif()
|
||||
|
||||
add_subdirectory(deps/libsrtp EXCLUDE_FROM_ALL)
|
||||
message(STATUS "LibSRTP found, compiling with media transport")
|
||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=1)
|
||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=1)
|
||||
target_link_libraries(datachannel PRIVATE srtp2)
|
||||
target_link_libraries(datachannel-static PRIVATE srtp2)
|
||||
if(USE_SRTP STREQUAL "AUTO")
|
||||
find_package(SRTP)
|
||||
if(SRTP_FOUND)
|
||||
message(STATUS "LibSRTP found, compiling with media transport")
|
||||
else()
|
||||
message(STATUS "LibSRTP NOT found, compiling WITHOUT media transport")
|
||||
endif()
|
||||
elseif (USE_SRTP STREQUAL "COMPILE")
|
||||
message(STATUS "Compiling LibSRTP from source; compiling with media transport")
|
||||
add_subdirectory(deps/libsrtp EXCLUDE_FROM_ALL)
|
||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=1)
|
||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=1)
|
||||
target_compile_definitions(datachannel PUBLIC RTC_SRTP_FROM_SOURCE=1)
|
||||
target_compile_definitions(datachannel-static PUBLIC RTC_SRTP_FROM_SOURCE=1)
|
||||
target_link_libraries(datachannel PRIVATE srtp2)
|
||||
target_link_libraries(datachannel-static PRIVATE srtp2)
|
||||
elseif(USE_SRTP)
|
||||
find_package(SRTP REQUIRED)
|
||||
endif()
|
||||
|
||||
if(USE_SRTP AND SRTP_FOUND)
|
||||
if(NOT TARGET SRTP::SRTP)
|
||||
add_library(SRTP::SRTP UNKNOWN IMPORTED)
|
||||
set_target_properties(SRTP::SRTP PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${SRTP_INCLUDE_DIRS}
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES C
|
||||
IMPORTED_LOCATION ${SRTP_LIBRARIES})
|
||||
endif()
|
||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=1)
|
||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=1)
|
||||
target_link_libraries(datachannel PRIVATE SRTP::SRTP)
|
||||
target_link_libraries(datachannel-static PRIVATE SRTP::SRTP)
|
||||
elseif (NOT USE_SRTP STREQUAL "COMPILE")
|
||||
target_compile_definitions(datachannel PUBLIC RTC_ENABLE_MEDIA=0)
|
||||
target_compile_definitions(datachannel-static PUBLIC RTC_ENABLE_MEDIA=0)
|
||||
endif()
|
||||
|
||||
if (USE_GNUTLS)
|
||||
find_package(GnuTLS REQUIRED)
|
||||
@ -204,6 +239,16 @@ else()
|
||||
target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuiceStatic)
|
||||
endif()
|
||||
|
||||
if(RSA_KEY_BITS_2048)
|
||||
target_compile_definitions(datachannel PUBLIC RSA_KEY_BITS_2048)
|
||||
target_compile_definitions(datachannel-static PUBLIC RSA_KEY_BITS_2048)
|
||||
endif()
|
||||
|
||||
if(CAPI_STDCALL)
|
||||
target_compile_definitions(datachannel PUBLIC CAPI_STDCALL)
|
||||
target_compile_definitions(datachannel-static PUBLIC CAPI_STDCALL)
|
||||
endif()
|
||||
|
||||
add_library(LibDataChannel::LibDataChannel ALIAS datachannel)
|
||||
add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static)
|
||||
|
||||
|
@ -202,5 +202,6 @@ ws->open("wss://my.websocket/service");
|
||||
## External resources
|
||||
- Rust wrapper for libdatachannel: [datachannel-rs](https://github.com/lerouxrgd/datachannel-rs)
|
||||
- Node.js wrapper for libdatachannel: [node-datachannel](https://github.com/murat-dogan/node-datachannel)
|
||||
- Unity wrapper for Windows 10 and Hololens: [datachannel-unity](https://github.com/hanseuljun/datachannel-unity)
|
||||
- WebAssembly wrapper compatible with libdatachannel: [datachannel-wasm](https://github.com/paullouisageneau/datachannel-wasm)
|
||||
|
||||
|
2
deps/libjuice
vendored
2
deps/libjuice
vendored
Submodule deps/libjuice updated: ddc3064890...0a44ac2d26
2
deps/usrsctp
vendored
2
deps/usrsctp
vendored
Submodule deps/usrsctp updated: ffed0925f2...0db9691000
@ -3,7 +3,12 @@ if(POLICY CMP0079)
|
||||
cmake_policy(SET CMP0079 NEW)
|
||||
endif()
|
||||
|
||||
add_executable(datachannel-client main.cpp)
|
||||
if(WIN32)
|
||||
add_executable(datachannel-client main.cpp parse_cl.cpp parse_cl.h getopt.cpp getopt.h)
|
||||
target_compile_definitions(datachannel-client PUBLIC STATIC_GETOPT)
|
||||
else()
|
||||
add_executable(datachannel-client main.cpp parse_cl.cpp parse_cl.h)
|
||||
endif()
|
||||
set_target_properties(datachannel-client PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
OUTPUT_NAME client)
|
||||
|
973
examples/client/getopt.cpp
Normal file
973
examples/client/getopt.cpp
Normal file
@ -0,0 +1,973 @@
|
||||
/* Getopt for Microsoft C
|
||||
This code is a modification of the Free Software Foundation, Inc.
|
||||
Getopt library for parsing command line argument the purpose was
|
||||
to provide a Microsoft Visual C friendly derivative. This code
|
||||
provides functionality for both Unicode and Multibyte builds.
|
||||
|
||||
Date: 02/03/2011 - Ludvik Jerabek - Initial Release
|
||||
Version: 1.0
|
||||
Comment: Supports getopt, getopt_long, and getopt_long_only
|
||||
and POSIXLY_CORRECT environment flag
|
||||
License: LGPL
|
||||
|
||||
Revisions:
|
||||
|
||||
02/03/2011 - Ludvik Jerabek - Initial Release
|
||||
02/20/2011 - Ludvik Jerabek - Fixed compiler warnings at Level 4
|
||||
07/05/2011 - Ludvik Jerabek - Added no_argument, required_argument, optional_argument defs
|
||||
08/03/2011 - Ludvik Jerabek - Fixed non-argument runtime bug which caused runtime exception
|
||||
08/09/2011 - Ludvik Jerabek - Added code to export functions for DLL and LIB
|
||||
02/15/2012 - Ludvik Jerabek - Fixed _GETOPT_THROW definition missing in implementation file
|
||||
08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi
|
||||
10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features
|
||||
06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable
|
||||
|
||||
**DISCLAIMER**
|
||||
THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
||||
EITHER EXPRESS OR IMPLIED, INCLUDING, BUT Not LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, OR NON-INFRINGEMENT. SOME JURISDICTIONS DO NOT ALLOW THE
|
||||
EXCLUSION OF IMPLIED WARRANTIES, SO THE ABOVE EXCLUSION MAY NOT
|
||||
APPLY TO YOU. IN NO EVENT WILL I BE LIABLE TO ANY PARTY FOR ANY
|
||||
DIRECT, INDIRECT, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES FOR ANY
|
||||
USE OF THIS MATERIAL INCLUDING, WITHOUT LIMITATION, ANY LOST
|
||||
PROFITS, BUSINESS INTERRUPTION, LOSS OF PROGRAMS OR OTHER DATA ON
|
||||
YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE
|
||||
EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
*/
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <malloc.h>
|
||||
#include "getopt.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define _GETOPT_THROW throw()
|
||||
#else
|
||||
#define _GETOPT_THROW
|
||||
#endif
|
||||
|
||||
int optind = 1;
|
||||
int opterr = 1;
|
||||
int optopt = '?';
|
||||
enum ENUM_ORDERING { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER };
|
||||
|
||||
//
|
||||
//
|
||||
// Ansi structures and functions follow
|
||||
//
|
||||
//
|
||||
|
||||
static struct _getopt_data_a
|
||||
{
|
||||
int optind;
|
||||
int opterr;
|
||||
int optopt;
|
||||
char *optarg;
|
||||
int __initialized;
|
||||
char *__nextchar;
|
||||
enum ENUM_ORDERING __ordering;
|
||||
int __posixly_correct;
|
||||
int __first_nonopt;
|
||||
int __last_nonopt;
|
||||
} getopt_data_a;
|
||||
char *optarg_a;
|
||||
|
||||
static void exchange_a(char **argv, struct _getopt_data_a *d)
|
||||
{
|
||||
int bottom = d->__first_nonopt;
|
||||
int middle = d->__last_nonopt;
|
||||
int top = d->optind;
|
||||
char *tem;
|
||||
while (top > middle && middle > bottom)
|
||||
{
|
||||
if (top - middle > middle - bottom)
|
||||
{
|
||||
int len = middle - bottom;
|
||||
int i;
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
tem = argv[bottom + i];
|
||||
argv[bottom + i] = argv[top - (middle - bottom) + i];
|
||||
argv[top - (middle - bottom) + i] = tem;
|
||||
}
|
||||
top -= len;
|
||||
}
|
||||
else
|
||||
{
|
||||
int len = top - middle;
|
||||
int i;
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
tem = argv[bottom + i];
|
||||
argv[bottom + i] = argv[middle + i];
|
||||
argv[middle + i] = tem;
|
||||
}
|
||||
bottom += len;
|
||||
}
|
||||
}
|
||||
d->__first_nonopt += (d->optind - d->__last_nonopt);
|
||||
d->__last_nonopt = d->optind;
|
||||
}
|
||||
static const char *_getopt_initialize_a (const char *optstring, struct _getopt_data_a *d, int posixly_correct)
|
||||
{
|
||||
d->__first_nonopt = d->__last_nonopt = d->optind;
|
||||
d->__nextchar = NULL;
|
||||
d->__posixly_correct = posixly_correct | !!getenv("POSIXLY_CORRECT");
|
||||
if (optstring[0] == '-')
|
||||
{
|
||||
d->__ordering = RETURN_IN_ORDER;
|
||||
++optstring;
|
||||
}
|
||||
else if (optstring[0] == '+')
|
||||
{
|
||||
d->__ordering = REQUIRE_ORDER;
|
||||
++optstring;
|
||||
}
|
||||
else if (d->__posixly_correct)
|
||||
d->__ordering = REQUIRE_ORDER;
|
||||
else
|
||||
d->__ordering = PERMUTE;
|
||||
return optstring;
|
||||
}
|
||||
int _getopt_internal_r_a (int argc, char *const *argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, struct _getopt_data_a *d, int posixly_correct)
|
||||
{
|
||||
int print_errors = d->opterr;
|
||||
if (argc < 1)
|
||||
return -1;
|
||||
d->optarg = NULL;
|
||||
if (d->optind == 0 || !d->__initialized)
|
||||
{
|
||||
if (d->optind == 0)
|
||||
d->optind = 1;
|
||||
optstring = _getopt_initialize_a (optstring, d, posixly_correct);
|
||||
d->__initialized = 1;
|
||||
}
|
||||
else if (optstring[0] == '-' || optstring[0] == '+')
|
||||
optstring++;
|
||||
if (optstring[0] == ':')
|
||||
print_errors = 0;
|
||||
if (d->__nextchar == NULL || *d->__nextchar == '\0')
|
||||
{
|
||||
if (d->__last_nonopt > d->optind)
|
||||
d->__last_nonopt = d->optind;
|
||||
if (d->__first_nonopt > d->optind)
|
||||
d->__first_nonopt = d->optind;
|
||||
if (d->__ordering == PERMUTE)
|
||||
{
|
||||
if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
|
||||
exchange_a ((char **) argv, d);
|
||||
else if (d->__last_nonopt != d->optind)
|
||||
d->__first_nonopt = d->optind;
|
||||
while (d->optind < argc && (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0'))
|
||||
d->optind++;
|
||||
d->__last_nonopt = d->optind;
|
||||
}
|
||||
if (d->optind != argc && !strcmp(argv[d->optind], "--"))
|
||||
{
|
||||
d->optind++;
|
||||
if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
|
||||
exchange_a((char **) argv, d);
|
||||
else if (d->__first_nonopt == d->__last_nonopt)
|
||||
d->__first_nonopt = d->optind;
|
||||
d->__last_nonopt = argc;
|
||||
d->optind = argc;
|
||||
}
|
||||
if (d->optind == argc)
|
||||
{
|
||||
if (d->__first_nonopt != d->__last_nonopt)
|
||||
d->optind = d->__first_nonopt;
|
||||
return -1;
|
||||
}
|
||||
if ((argv[d->optind][0] != '-' || argv[d->optind][1] == '\0'))
|
||||
{
|
||||
if (d->__ordering == REQUIRE_ORDER)
|
||||
return -1;
|
||||
d->optarg = argv[d->optind++];
|
||||
return 1;
|
||||
}
|
||||
d->__nextchar = (argv[d->optind] + 1 + (longopts != NULL && argv[d->optind][1] == '-'));
|
||||
}
|
||||
if (longopts != NULL && (argv[d->optind][1] == '-' || (long_only && (argv[d->optind][2] || !strchr(optstring, argv[d->optind][1])))))
|
||||
{
|
||||
char *nameend;
|
||||
unsigned int namelen;
|
||||
const struct option_a *p;
|
||||
const struct option_a *pfound = NULL;
|
||||
struct option_list
|
||||
{
|
||||
const struct option_a *p;
|
||||
struct option_list *next;
|
||||
} *ambig_list = NULL;
|
||||
int exact = 0;
|
||||
int indfound = -1;
|
||||
int option_index;
|
||||
for (nameend = d->__nextchar; *nameend && *nameend != '='; nameend++);
|
||||
namelen = (unsigned int)(nameend - d->__nextchar);
|
||||
for (p = longopts, option_index = 0; p->name; p++, option_index++)
|
||||
if (!strncmp(p->name, d->__nextchar, namelen))
|
||||
{
|
||||
if (namelen == (unsigned int)strlen(p->name))
|
||||
{
|
||||
pfound = p;
|
||||
indfound = option_index;
|
||||
exact = 1;
|
||||
break;
|
||||
}
|
||||
else if (pfound == NULL)
|
||||
{
|
||||
pfound = p;
|
||||
indfound = option_index;
|
||||
}
|
||||
else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val)
|
||||
{
|
||||
struct option_list *newp = (struct option_list*)alloca(sizeof(*newp));
|
||||
newp->p = p;
|
||||
newp->next = ambig_list;
|
||||
ambig_list = newp;
|
||||
}
|
||||
}
|
||||
if (ambig_list != NULL && !exact)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
struct option_list first;
|
||||
first.p = pfound;
|
||||
first.next = ambig_list;
|
||||
ambig_list = &first;
|
||||
fprintf (stderr, "%s: option '%s' is ambiguous; possibilities:", argv[0], argv[d->optind]);
|
||||
do
|
||||
{
|
||||
fprintf (stderr, " '--%s'", ambig_list->p->name);
|
||||
ambig_list = ambig_list->next;
|
||||
}
|
||||
while (ambig_list != NULL);
|
||||
fputc ('\n', stderr);
|
||||
}
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
d->optind++;
|
||||
d->optopt = 0;
|
||||
return '?';
|
||||
}
|
||||
if (pfound != NULL)
|
||||
{
|
||||
option_index = indfound;
|
||||
d->optind++;
|
||||
if (*nameend)
|
||||
{
|
||||
if (pfound->has_arg)
|
||||
d->optarg = nameend + 1;
|
||||
else
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
if (argv[d->optind - 1][1] == '-')
|
||||
{
|
||||
fprintf(stderr, "%s: option '--%s' doesn't allow an argument\n",argv[0], pfound->name);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "%s: option '%c%s' doesn't allow an argument\n",argv[0], argv[d->optind - 1][0],pfound->name);
|
||||
}
|
||||
}
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
d->optopt = pfound->val;
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
else if (pfound->has_arg == 1)
|
||||
{
|
||||
if (d->optind < argc)
|
||||
d->optarg = argv[d->optind++];
|
||||
else
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fprintf(stderr,"%s: option '--%s' requires an argument\n",argv[0], pfound->name);
|
||||
}
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
d->optopt = pfound->val;
|
||||
return optstring[0] == ':' ? ':' : '?';
|
||||
}
|
||||
}
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
if (longind != NULL)
|
||||
*longind = option_index;
|
||||
if (pfound->flag)
|
||||
{
|
||||
*(pfound->flag) = pfound->val;
|
||||
return 0;
|
||||
}
|
||||
return pfound->val;
|
||||
}
|
||||
if (!long_only || argv[d->optind][1] == '-' || strchr(optstring, *d->__nextchar) == NULL)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
if (argv[d->optind][1] == '-')
|
||||
{
|
||||
fprintf(stderr, "%s: unrecognized option '--%s'\n",argv[0], d->__nextchar);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "%s: unrecognized option '%c%s'\n",argv[0], argv[d->optind][0], d->__nextchar);
|
||||
}
|
||||
}
|
||||
d->__nextchar = (char *)"";
|
||||
d->optind++;
|
||||
d->optopt = 0;
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
{
|
||||
char c = *d->__nextchar++;
|
||||
char *temp = (char*)strchr(optstring, c);
|
||||
if (*d->__nextchar == '\0')
|
||||
++d->optind;
|
||||
if (temp == NULL || c == ':' || c == ';')
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fprintf(stderr, "%s: invalid option -- '%c'\n", argv[0], c);
|
||||
}
|
||||
d->optopt = c;
|
||||
return '?';
|
||||
}
|
||||
if (temp[0] == 'W' && temp[1] == ';')
|
||||
{
|
||||
char *nameend;
|
||||
const struct option_a *p;
|
||||
const struct option_a *pfound = NULL;
|
||||
int exact = 0;
|
||||
int ambig = 0;
|
||||
int indfound = 0;
|
||||
int option_index;
|
||||
if (longopts == NULL)
|
||||
goto no_longs;
|
||||
if (*d->__nextchar != '\0')
|
||||
{
|
||||
d->optarg = d->__nextchar;
|
||||
d->optind++;
|
||||
}
|
||||
else if (d->optind == argc)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fprintf(stderr,"%s: option requires an argument -- '%c'\n",argv[0], c);
|
||||
}
|
||||
d->optopt = c;
|
||||
if (optstring[0] == ':')
|
||||
c = ':';
|
||||
else
|
||||
c = '?';
|
||||
return c;
|
||||
}
|
||||
else
|
||||
d->optarg = argv[d->optind++];
|
||||
for (d->__nextchar = nameend = d->optarg; *nameend && *nameend != '='; nameend++);
|
||||
for (p = longopts, option_index = 0; p->name; p++, option_index++)
|
||||
if (!strncmp(p->name, d->__nextchar, nameend - d->__nextchar))
|
||||
{
|
||||
if ((unsigned int) (nameend - d->__nextchar) == strlen(p->name))
|
||||
{
|
||||
pfound = p;
|
||||
indfound = option_index;
|
||||
exact = 1;
|
||||
break;
|
||||
}
|
||||
else if (pfound == NULL)
|
||||
{
|
||||
pfound = p;
|
||||
indfound = option_index;
|
||||
}
|
||||
else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val)
|
||||
ambig = 1;
|
||||
}
|
||||
if (ambig && !exact)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fprintf(stderr, "%s: option '-W %s' is ambiguous\n",argv[0], d->optarg);
|
||||
}
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
d->optind++;
|
||||
return '?';
|
||||
}
|
||||
if (pfound != NULL)
|
||||
{
|
||||
option_index = indfound;
|
||||
if (*nameend)
|
||||
{
|
||||
if (pfound->has_arg)
|
||||
d->optarg = nameend + 1;
|
||||
else
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fprintf(stderr, "%s: option '-W %s' doesn't allow an argument\n",argv[0], pfound->name);
|
||||
}
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
else if (pfound->has_arg == 1)
|
||||
{
|
||||
if (d->optind < argc)
|
||||
d->optarg = argv[d->optind++];
|
||||
else
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fprintf(stderr, "%s: option '-W %s' requires an argument\n",argv[0], pfound->name);
|
||||
}
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
return optstring[0] == ':' ? ':' : '?';
|
||||
}
|
||||
}
|
||||
else
|
||||
d->optarg = NULL;
|
||||
d->__nextchar += strlen(d->__nextchar);
|
||||
if (longind != NULL)
|
||||
*longind = option_index;
|
||||
if (pfound->flag)
|
||||
{
|
||||
*(pfound->flag) = pfound->val;
|
||||
return 0;
|
||||
}
|
||||
return pfound->val;
|
||||
}
|
||||
no_longs:
|
||||
d->__nextchar = NULL;
|
||||
return 'W';
|
||||
}
|
||||
if (temp[1] == ':')
|
||||
{
|
||||
if (temp[2] == ':')
|
||||
{
|
||||
if (*d->__nextchar != '\0')
|
||||
{
|
||||
d->optarg = d->__nextchar;
|
||||
d->optind++;
|
||||
}
|
||||
else
|
||||
d->optarg = NULL;
|
||||
d->__nextchar = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (*d->__nextchar != '\0')
|
||||
{
|
||||
d->optarg = d->__nextchar;
|
||||
d->optind++;
|
||||
}
|
||||
else if (d->optind == argc)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fprintf(stderr,"%s: option requires an argument -- '%c'\n",argv[0], c);
|
||||
}
|
||||
d->optopt = c;
|
||||
if (optstring[0] == ':')
|
||||
c = ':';
|
||||
else
|
||||
c = '?';
|
||||
}
|
||||
else
|
||||
d->optarg = argv[d->optind++];
|
||||
d->__nextchar = NULL;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
int _getopt_internal_a (int argc, char *const *argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, int posixly_correct)
|
||||
{
|
||||
int result;
|
||||
getopt_data_a.optind = optind;
|
||||
getopt_data_a.opterr = opterr;
|
||||
result = _getopt_internal_r_a (argc, argv, optstring, longopts,longind, long_only, &getopt_data_a,posixly_correct);
|
||||
optind = getopt_data_a.optind;
|
||||
optarg_a = getopt_data_a.optarg;
|
||||
optopt = getopt_data_a.optopt;
|
||||
return result;
|
||||
}
|
||||
int getopt_a (int argc, char *const *argv, const char *optstring) _GETOPT_THROW
|
||||
{
|
||||
return _getopt_internal_a (argc, argv, optstring, (const struct option_a *) 0, (int *) 0, 0, 0);
|
||||
}
|
||||
int getopt_long_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW
|
||||
{
|
||||
return _getopt_internal_a (argc, argv, options, long_options, opt_index, 0, 0);
|
||||
}
|
||||
int getopt_long_only_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW
|
||||
{
|
||||
return _getopt_internal_a (argc, argv, options, long_options, opt_index, 1, 0);
|
||||
}
|
||||
int _getopt_long_r_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index, struct _getopt_data_a *d)
|
||||
{
|
||||
return _getopt_internal_r_a (argc, argv, options, long_options, opt_index,0, d, 0);
|
||||
}
|
||||
int _getopt_long_only_r_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index, struct _getopt_data_a *d)
|
||||
{
|
||||
return _getopt_internal_r_a (argc, argv, options, long_options, opt_index, 1, d, 0);
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// Unicode Structures and Functions
|
||||
//
|
||||
//
|
||||
|
||||
static struct _getopt_data_w
|
||||
{
|
||||
int optind;
|
||||
int opterr;
|
||||
int optopt;
|
||||
wchar_t *optarg;
|
||||
int __initialized;
|
||||
wchar_t *__nextchar;
|
||||
enum ENUM_ORDERING __ordering;
|
||||
int __posixly_correct;
|
||||
int __first_nonopt;
|
||||
int __last_nonopt;
|
||||
} getopt_data_w;
|
||||
wchar_t *optarg_w;
|
||||
|
||||
static void exchange_w(wchar_t **argv, struct _getopt_data_w *d)
|
||||
{
|
||||
int bottom = d->__first_nonopt;
|
||||
int middle = d->__last_nonopt;
|
||||
int top = d->optind;
|
||||
wchar_t *tem;
|
||||
while (top > middle && middle > bottom)
|
||||
{
|
||||
if (top - middle > middle - bottom)
|
||||
{
|
||||
int len = middle - bottom;
|
||||
int i;
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
tem = argv[bottom + i];
|
||||
argv[bottom + i] = argv[top - (middle - bottom) + i];
|
||||
argv[top - (middle - bottom) + i] = tem;
|
||||
}
|
||||
top -= len;
|
||||
}
|
||||
else
|
||||
{
|
||||
int len = top - middle;
|
||||
int i;
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
tem = argv[bottom + i];
|
||||
argv[bottom + i] = argv[middle + i];
|
||||
argv[middle + i] = tem;
|
||||
}
|
||||
bottom += len;
|
||||
}
|
||||
}
|
||||
d->__first_nonopt += (d->optind - d->__last_nonopt);
|
||||
d->__last_nonopt = d->optind;
|
||||
}
|
||||
static const wchar_t *_getopt_initialize_w (const wchar_t *optstring, struct _getopt_data_w *d, int posixly_correct)
|
||||
{
|
||||
d->__first_nonopt = d->__last_nonopt = d->optind;
|
||||
d->__nextchar = NULL;
|
||||
d->__posixly_correct = posixly_correct | !!_wgetenv(L"POSIXLY_CORRECT");
|
||||
if (optstring[0] == L'-')
|
||||
{
|
||||
d->__ordering = RETURN_IN_ORDER;
|
||||
++optstring;
|
||||
}
|
||||
else if (optstring[0] == L'+')
|
||||
{
|
||||
d->__ordering = REQUIRE_ORDER;
|
||||
++optstring;
|
||||
}
|
||||
else if (d->__posixly_correct)
|
||||
d->__ordering = REQUIRE_ORDER;
|
||||
else
|
||||
d->__ordering = PERMUTE;
|
||||
return optstring;
|
||||
}
|
||||
int _getopt_internal_r_w (int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, struct _getopt_data_w *d, int posixly_correct)
|
||||
{
|
||||
int print_errors = d->opterr;
|
||||
if (argc < 1)
|
||||
return -1;
|
||||
d->optarg = NULL;
|
||||
if (d->optind == 0 || !d->__initialized)
|
||||
{
|
||||
if (d->optind == 0)
|
||||
d->optind = 1;
|
||||
optstring = _getopt_initialize_w (optstring, d, posixly_correct);
|
||||
d->__initialized = 1;
|
||||
}
|
||||
else if (optstring[0] == L'-' || optstring[0] == L'+')
|
||||
optstring++;
|
||||
if (optstring[0] == L':')
|
||||
print_errors = 0;
|
||||
if (d->__nextchar == NULL || *d->__nextchar == L'\0')
|
||||
{
|
||||
if (d->__last_nonopt > d->optind)
|
||||
d->__last_nonopt = d->optind;
|
||||
if (d->__first_nonopt > d->optind)
|
||||
d->__first_nonopt = d->optind;
|
||||
if (d->__ordering == PERMUTE)
|
||||
{
|
||||
if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
|
||||
exchange_w((wchar_t **) argv, d);
|
||||
else if (d->__last_nonopt != d->optind)
|
||||
d->__first_nonopt = d->optind;
|
||||
while (d->optind < argc && (argv[d->optind][0] != L'-' || argv[d->optind][1] == L'\0'))
|
||||
d->optind++;
|
||||
d->__last_nonopt = d->optind;
|
||||
}
|
||||
if (d->optind != argc && !wcscmp(argv[d->optind], L"--"))
|
||||
{
|
||||
d->optind++;
|
||||
if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
|
||||
exchange_w((wchar_t **) argv, d);
|
||||
else if (d->__first_nonopt == d->__last_nonopt)
|
||||
d->__first_nonopt = d->optind;
|
||||
d->__last_nonopt = argc;
|
||||
d->optind = argc;
|
||||
}
|
||||
if (d->optind == argc)
|
||||
{
|
||||
if (d->__first_nonopt != d->__last_nonopt)
|
||||
d->optind = d->__first_nonopt;
|
||||
return -1;
|
||||
}
|
||||
if ((argv[d->optind][0] != L'-' || argv[d->optind][1] == L'\0'))
|
||||
{
|
||||
if (d->__ordering == REQUIRE_ORDER)
|
||||
return -1;
|
||||
d->optarg = argv[d->optind++];
|
||||
return 1;
|
||||
}
|
||||
d->__nextchar = (argv[d->optind] + 1 + (longopts != NULL && argv[d->optind][1] == L'-'));
|
||||
}
|
||||
if (longopts != NULL && (argv[d->optind][1] == L'-' || (long_only && (argv[d->optind][2] || !wcschr(optstring, argv[d->optind][1])))))
|
||||
{
|
||||
wchar_t *nameend;
|
||||
unsigned int namelen;
|
||||
const struct option_w *p;
|
||||
const struct option_w *pfound = NULL;
|
||||
struct option_list
|
||||
{
|
||||
const struct option_w *p;
|
||||
struct option_list *next;
|
||||
} *ambig_list = NULL;
|
||||
int exact = 0;
|
||||
int indfound = -1;
|
||||
int option_index;
|
||||
for (nameend = d->__nextchar; *nameend && *nameend != L'='; nameend++);
|
||||
namelen = (unsigned int)(nameend - d->__nextchar);
|
||||
for (p = longopts, option_index = 0; p->name; p++, option_index++)
|
||||
if (!wcsncmp(p->name, d->__nextchar, namelen))
|
||||
{
|
||||
if (namelen == (unsigned int)wcslen(p->name))
|
||||
{
|
||||
pfound = p;
|
||||
indfound = option_index;
|
||||
exact = 1;
|
||||
break;
|
||||
}
|
||||
else if (pfound == NULL)
|
||||
{
|
||||
pfound = p;
|
||||
indfound = option_index;
|
||||
}
|
||||
else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val)
|
||||
{
|
||||
struct option_list *newp = (struct option_list*)alloca(sizeof(*newp));
|
||||
newp->p = p;
|
||||
newp->next = ambig_list;
|
||||
ambig_list = newp;
|
||||
}
|
||||
}
|
||||
if (ambig_list != NULL && !exact)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
struct option_list first;
|
||||
first.p = pfound;
|
||||
first.next = ambig_list;
|
||||
ambig_list = &first;
|
||||
fwprintf(stderr, L"%s: option '%s' is ambiguous; possibilities:", argv[0], argv[d->optind]);
|
||||
do
|
||||
{
|
||||
fwprintf (stderr, L" '--%s'", ambig_list->p->name);
|
||||
ambig_list = ambig_list->next;
|
||||
}
|
||||
while (ambig_list != NULL);
|
||||
fputwc (L'\n', stderr);
|
||||
}
|
||||
d->__nextchar += wcslen(d->__nextchar);
|
||||
d->optind++;
|
||||
d->optopt = 0;
|
||||
return L'?';
|
||||
}
|
||||
if (pfound != NULL)
|
||||
{
|
||||
option_index = indfound;
|
||||
d->optind++;
|
||||
if (*nameend)
|
||||
{
|
||||
if (pfound->has_arg)
|
||||
d->optarg = nameend + 1;
|
||||
else
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
if (argv[d->optind - 1][1] == L'-')
|
||||
{
|
||||
fwprintf(stderr, L"%s: option '--%s' doesn't allow an argument\n",argv[0], pfound->name);
|
||||
}
|
||||
else
|
||||
{
|
||||
fwprintf(stderr, L"%s: option '%c%s' doesn't allow an argument\n",argv[0], argv[d->optind - 1][0],pfound->name);
|
||||
}
|
||||
}
|
||||
d->__nextchar += wcslen(d->__nextchar);
|
||||
d->optopt = pfound->val;
|
||||
return L'?';
|
||||
}
|
||||
}
|
||||
else if (pfound->has_arg == 1)
|
||||
{
|
||||
if (d->optind < argc)
|
||||
d->optarg = argv[d->optind++];
|
||||
else
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fwprintf(stderr,L"%s: option '--%s' requires an argument\n",argv[0], pfound->name);
|
||||
}
|
||||
d->__nextchar += wcslen(d->__nextchar);
|
||||
d->optopt = pfound->val;
|
||||
return optstring[0] == L':' ? L':' : L'?';
|
||||
}
|
||||
}
|
||||
d->__nextchar += wcslen(d->__nextchar);
|
||||
if (longind != NULL)
|
||||
*longind = option_index;
|
||||
if (pfound->flag)
|
||||
{
|
||||
*(pfound->flag) = pfound->val;
|
||||
return 0;
|
||||
}
|
||||
return pfound->val;
|
||||
}
|
||||
if (!long_only || argv[d->optind][1] == L'-' || wcschr(optstring, *d->__nextchar) == NULL)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
if (argv[d->optind][1] == L'-')
|
||||
{
|
||||
fwprintf(stderr, L"%s: unrecognized option '--%s'\n",argv[0], d->__nextchar);
|
||||
}
|
||||
else
|
||||
{
|
||||
fwprintf(stderr, L"%s: unrecognized option '%c%s'\n",argv[0], argv[d->optind][0], d->__nextchar);
|
||||
}
|
||||
}
|
||||
d->__nextchar = (wchar_t *)L"";
|
||||
d->optind++;
|
||||
d->optopt = 0;
|
||||
return L'?';
|
||||
}
|
||||
}
|
||||
{
|
||||
wchar_t c = *d->__nextchar++;
|
||||
wchar_t *temp = (wchar_t*)wcschr(optstring, c);
|
||||
if (*d->__nextchar == L'\0')
|
||||
++d->optind;
|
||||
if (temp == NULL || c == L':' || c == L';')
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fwprintf(stderr, L"%s: invalid option -- '%c'\n", argv[0], c);
|
||||
}
|
||||
d->optopt = c;
|
||||
return L'?';
|
||||
}
|
||||
if (temp[0] == L'W' && temp[1] == L';')
|
||||
{
|
||||
wchar_t *nameend;
|
||||
const struct option_w *p;
|
||||
const struct option_w *pfound = NULL;
|
||||
int exact = 0;
|
||||
int ambig = 0;
|
||||
int indfound = 0;
|
||||
int option_index;
|
||||
if (longopts == NULL)
|
||||
goto no_longs;
|
||||
if (*d->__nextchar != L'\0')
|
||||
{
|
||||
d->optarg = d->__nextchar;
|
||||
d->optind++;
|
||||
}
|
||||
else if (d->optind == argc)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fwprintf(stderr,L"%s: option requires an argument -- '%c'\n",argv[0], c);
|
||||
}
|
||||
d->optopt = c;
|
||||
if (optstring[0] == L':')
|
||||
c = L':';
|
||||
else
|
||||
c = L'?';
|
||||
return c;
|
||||
}
|
||||
else
|
||||
d->optarg = argv[d->optind++];
|
||||
for (d->__nextchar = nameend = d->optarg; *nameend && *nameend != L'='; nameend++);
|
||||
for (p = longopts, option_index = 0; p->name; p++, option_index++)
|
||||
if (!wcsncmp(p->name, d->__nextchar, nameend - d->__nextchar))
|
||||
{
|
||||
if ((unsigned int) (nameend - d->__nextchar) == wcslen(p->name))
|
||||
{
|
||||
pfound = p;
|
||||
indfound = option_index;
|
||||
exact = 1;
|
||||
break;
|
||||
}
|
||||
else if (pfound == NULL)
|
||||
{
|
||||
pfound = p;
|
||||
indfound = option_index;
|
||||
}
|
||||
else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val)
|
||||
ambig = 1;
|
||||
}
|
||||
if (ambig && !exact)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fwprintf(stderr, L"%s: option '-W %s' is ambiguous\n",argv[0], d->optarg);
|
||||
}
|
||||
d->__nextchar += wcslen(d->__nextchar);
|
||||
d->optind++;
|
||||
return L'?';
|
||||
}
|
||||
if (pfound != NULL)
|
||||
{
|
||||
option_index = indfound;
|
||||
if (*nameend)
|
||||
{
|
||||
if (pfound->has_arg)
|
||||
d->optarg = nameend + 1;
|
||||
else
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fwprintf(stderr, L"%s: option '-W %s' doesn't allow an argument\n",argv[0], pfound->name);
|
||||
}
|
||||
d->__nextchar += wcslen(d->__nextchar);
|
||||
return L'?';
|
||||
}
|
||||
}
|
||||
else if (pfound->has_arg == 1)
|
||||
{
|
||||
if (d->optind < argc)
|
||||
d->optarg = argv[d->optind++];
|
||||
else
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fwprintf(stderr, L"%s: option '-W %s' requires an argument\n",argv[0], pfound->name);
|
||||
}
|
||||
d->__nextchar += wcslen(d->__nextchar);
|
||||
return optstring[0] == L':' ? L':' : L'?';
|
||||
}
|
||||
}
|
||||
else
|
||||
d->optarg = NULL;
|
||||
d->__nextchar += wcslen(d->__nextchar);
|
||||
if (longind != NULL)
|
||||
*longind = option_index;
|
||||
if (pfound->flag)
|
||||
{
|
||||
*(pfound->flag) = pfound->val;
|
||||
return 0;
|
||||
}
|
||||
return pfound->val;
|
||||
}
|
||||
no_longs:
|
||||
d->__nextchar = NULL;
|
||||
return L'W';
|
||||
}
|
||||
if (temp[1] == L':')
|
||||
{
|
||||
if (temp[2] == L':')
|
||||
{
|
||||
if (*d->__nextchar != L'\0')
|
||||
{
|
||||
d->optarg = d->__nextchar;
|
||||
d->optind++;
|
||||
}
|
||||
else
|
||||
d->optarg = NULL;
|
||||
d->__nextchar = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (*d->__nextchar != L'\0')
|
||||
{
|
||||
d->optarg = d->__nextchar;
|
||||
d->optind++;
|
||||
}
|
||||
else if (d->optind == argc)
|
||||
{
|
||||
if (print_errors)
|
||||
{
|
||||
fwprintf(stderr,L"%s: option requires an argument -- '%c'\n",argv[0], c);
|
||||
}
|
||||
d->optopt = c;
|
||||
if (optstring[0] == L':')
|
||||
c = L':';
|
||||
else
|
||||
c = L'?';
|
||||
}
|
||||
else
|
||||
d->optarg = argv[d->optind++];
|
||||
d->__nextchar = NULL;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
int _getopt_internal_w (int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, int posixly_correct)
|
||||
{
|
||||
int result;
|
||||
getopt_data_w.optind = optind;
|
||||
getopt_data_w.opterr = opterr;
|
||||
result = _getopt_internal_r_w (argc, argv, optstring, longopts,longind, long_only, &getopt_data_w,posixly_correct);
|
||||
optind = getopt_data_w.optind;
|
||||
optarg_w = getopt_data_w.optarg;
|
||||
optopt = getopt_data_w.optopt;
|
||||
return result;
|
||||
}
|
||||
int getopt_w (int argc, wchar_t *const *argv, const wchar_t *optstring) _GETOPT_THROW
|
||||
{
|
||||
return _getopt_internal_w (argc, argv, optstring, (const struct option_w *) 0, (int *) 0, 0, 0);
|
||||
}
|
||||
int getopt_long_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW
|
||||
{
|
||||
return _getopt_internal_w (argc, argv, options, long_options, opt_index, 0, 0);
|
||||
}
|
||||
int getopt_long_only_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW
|
||||
{
|
||||
return _getopt_internal_w (argc, argv, options, long_options, opt_index, 1, 0);
|
||||
}
|
||||
int _getopt_long_r_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d)
|
||||
{
|
||||
return _getopt_internal_r_w (argc, argv, options, long_options, opt_index,0, d, 0);
|
||||
}
|
||||
int _getopt_long_only_r_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d)
|
||||
{
|
||||
return _getopt_internal_r_w (argc, argv, options, long_options, opt_index, 1, d, 0);
|
||||
}
|
136
examples/client/getopt.h
Normal file
136
examples/client/getopt.h
Normal file
@ -0,0 +1,136 @@
|
||||
/* Getopt for Microsoft C
|
||||
This code is a modification of the Free Software Foundation, Inc.
|
||||
Getopt library for parsing command line argument the purpose was
|
||||
to provide a Microsoft Visual C friendly derivative. This code
|
||||
provides functionality for both Unicode and Multibyte builds.
|
||||
|
||||
Date: 02/03/2011 - Ludvik Jerabek - Initial Release
|
||||
Version: 1.0
|
||||
Comment: Supports getopt, getopt_long, and getopt_long_only
|
||||
and POSIXLY_CORRECT environment flag
|
||||
License: LGPL
|
||||
|
||||
Revisions:
|
||||
|
||||
02/03/2011 - Ludvik Jerabek - Initial Release
|
||||
02/20/2011 - Ludvik Jerabek - Fixed compiler warnings at Level 4
|
||||
07/05/2011 - Ludvik Jerabek - Added no_argument, required_argument, optional_argument defs
|
||||
08/03/2011 - Ludvik Jerabek - Fixed non-argument runtime bug which caused runtime exception
|
||||
08/09/2011 - Ludvik Jerabek - Added code to export functions for DLL and LIB
|
||||
02/15/2012 - Ludvik Jerabek - Fixed _GETOPT_THROW definition missing in implementation file
|
||||
08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi
|
||||
10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features
|
||||
06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable
|
||||
|
||||
**DISCLAIMER**
|
||||
THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
||||
EITHER EXPRESS OR IMPLIED, INCLUDING, BUT Not LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, OR NON-INFRINGEMENT. SOME JURISDICTIONS DO NOT ALLOW THE
|
||||
EXCLUSION OF IMPLIED WARRANTIES, SO THE ABOVE EXCLUSION MAY NOT
|
||||
APPLY TO YOU. IN NO EVENT WILL I BE LIABLE TO ANY PARTY FOR ANY
|
||||
DIRECT, INDIRECT, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES FOR ANY
|
||||
USE OF THIS MATERIAL INCLUDING, WITHOUT LIMITATION, ANY LOST
|
||||
PROFITS, BUSINESS INTERRUPTION, LOSS OF PROGRAMS OR OTHER DATA ON
|
||||
YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE
|
||||
EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
*/
|
||||
#ifndef __GETOPT_H_
|
||||
#define __GETOPT_H_
|
||||
|
||||
#ifdef _GETOPT_API
|
||||
#undef _GETOPT_API
|
||||
#endif
|
||||
|
||||
#if defined(EXPORTS_GETOPT) && defined(STATIC_GETOPT)
|
||||
#error "The preprocessor definitions of EXPORTS_GETOPT and STATIC_GETOPT can only be used individually"
|
||||
#elif defined(STATIC_GETOPT)
|
||||
#pragma message("Warning static builds of getopt violate the Lesser GNU Public License")
|
||||
#define _GETOPT_API
|
||||
#elif defined(EXPORTS_GETOPT)
|
||||
#pragma message("Exporting getopt library")
|
||||
#define _GETOPT_API __declspec(dllexport)
|
||||
#else
|
||||
#pragma message("Importing getopt library")
|
||||
#define _GETOPT_API __declspec(dllimport)
|
||||
#endif
|
||||
|
||||
// Change behavior for C\C++
|
||||
#ifdef __cplusplus
|
||||
#define _BEGIN_EXTERN_C extern "C" {
|
||||
#define _END_EXTERN_C }
|
||||
#define _GETOPT_THROW throw()
|
||||
#else
|
||||
#define _BEGIN_EXTERN_C
|
||||
#define _END_EXTERN_C
|
||||
#define _GETOPT_THROW
|
||||
#endif
|
||||
|
||||
// Standard GNU options
|
||||
#define null_argument 0 /*Argument Null*/
|
||||
#define no_argument 0 /*Argument Switch Only*/
|
||||
#define required_argument 1 /*Argument Required*/
|
||||
#define optional_argument 2 /*Argument Optional*/
|
||||
|
||||
// Shorter Options
|
||||
#define ARG_NULL 0 /*Argument Null*/
|
||||
#define ARG_NONE 0 /*Argument Switch Only*/
|
||||
#define ARG_REQ 1 /*Argument Required*/
|
||||
#define ARG_OPT 2 /*Argument Optional*/
|
||||
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
|
||||
_BEGIN_EXTERN_C
|
||||
|
||||
extern _GETOPT_API int optind;
|
||||
extern _GETOPT_API int opterr;
|
||||
extern _GETOPT_API int optopt;
|
||||
|
||||
// Ansi
|
||||
struct option_a
|
||||
{
|
||||
const char* name;
|
||||
int has_arg;
|
||||
int *flag;
|
||||
int val;
|
||||
};
|
||||
extern _GETOPT_API char *optarg_a;
|
||||
extern _GETOPT_API int getopt_a(int argc, char *const *argv, const char *optstring) _GETOPT_THROW;
|
||||
extern _GETOPT_API int getopt_long_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW;
|
||||
extern _GETOPT_API int getopt_long_only_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW;
|
||||
|
||||
// Unicode
|
||||
struct option_w
|
||||
{
|
||||
const wchar_t* name;
|
||||
int has_arg;
|
||||
int *flag;
|
||||
int val;
|
||||
};
|
||||
extern _GETOPT_API wchar_t *optarg_w;
|
||||
extern _GETOPT_API int getopt_w(int argc, wchar_t *const *argv, const wchar_t *optstring) _GETOPT_THROW;
|
||||
extern _GETOPT_API int getopt_long_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW;
|
||||
extern _GETOPT_API int getopt_long_only_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW;
|
||||
|
||||
_END_EXTERN_C
|
||||
|
||||
#undef _BEGIN_EXTERN_C
|
||||
#undef _END_EXTERN_C
|
||||
#undef _GETOPT_THROW
|
||||
#undef _GETOPT_API
|
||||
|
||||
#ifdef _UNICODE
|
||||
#define getopt getopt_w
|
||||
#define getopt_long getopt_long_w
|
||||
#define getopt_long_only getopt_long_only_w
|
||||
#define option option_w
|
||||
#define optarg optarg_w
|
||||
#else
|
||||
#define getopt getopt_a
|
||||
#define getopt_long getopt_long_a
|
||||
#define getopt_long_only getopt_long_only_a
|
||||
#define option option_a
|
||||
#define optarg optarg_a
|
||||
#endif
|
||||
#endif // __GETOPT_H_
|
@ -5,6 +5,7 @@
|
||||
* Copyright (c) 2020 Will Munn
|
||||
* Copyright (c) 2020 Nico Chatzi
|
||||
* Copyright (c) 2020 Lara Mackey
|
||||
* Copyright (c) 2020 Erik Cota-Robles
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -29,7 +30,10 @@
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <thread>
|
||||
#include <future>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
#include "parse_cl.h"
|
||||
|
||||
using namespace rtc;
|
||||
using namespace std;
|
||||
@ -43,28 +47,56 @@ unordered_map<string, shared_ptr<PeerConnection>> peerConnectionMap;
|
||||
unordered_map<string, shared_ptr<DataChannel>> dataChannelMap;
|
||||
|
||||
string localId;
|
||||
bool echoDataChannelMessages = false;
|
||||
|
||||
shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
||||
weak_ptr<WebSocket> wws, string id);
|
||||
void printReceived(bool echoed, string id, string type, size_t length);
|
||||
string randomId(size_t length);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
int main(int argc, char **argv) try {
|
||||
auto params = std::make_unique<Cmdline>(argc, argv);
|
||||
|
||||
rtc::InitLogger(LogLevel::Debug);
|
||||
|
||||
Configuration config;
|
||||
config.iceServers.emplace_back("stun:stun.l.google.com:19302"); // change to your STUN server
|
||||
string stunServer = "";
|
||||
if (params->noStun()) {
|
||||
cout << "No STUN server is configured. Only local hosts and public IP addresses supported." << endl;
|
||||
} else {
|
||||
if (params->stunServer().substr(0,5).compare("stun:") != 0) {
|
||||
stunServer = "stun:";
|
||||
}
|
||||
stunServer += params->stunServer() + ":" + to_string(params->stunPort());
|
||||
cout << "Stun server is " << stunServer << endl;
|
||||
config.iceServers.emplace_back(stunServer);
|
||||
}
|
||||
|
||||
localId = randomId(4);
|
||||
cout << "The local ID is: " << localId << endl;
|
||||
|
||||
echoDataChannelMessages = params->echoDataChannelMessages();
|
||||
cout << "Received data channel messages will be "
|
||||
<< (echoDataChannelMessages ? "echoed back to sender" : "printed to stdout") << endl;
|
||||
|
||||
auto ws = make_shared<WebSocket>();
|
||||
|
||||
ws->onOpen([]() { cout << "WebSocket connected, signaling ready" << endl; });
|
||||
std::promise<void> wsPromise;
|
||||
auto wsFuture = wsPromise.get_future();
|
||||
|
||||
ws->onOpen([&wsPromise]() {
|
||||
cout << "WebSocket connected, signaling ready" << endl;
|
||||
wsPromise.set_value();
|
||||
});
|
||||
|
||||
ws->onError([&wsPromise](string s) {
|
||||
cout << "WebSocket error" << endl;
|
||||
wsPromise.set_exception(std::make_exception_ptr(std::runtime_error(s)));
|
||||
});
|
||||
|
||||
ws->onClosed([]() { cout << "WebSocket closed" << endl; });
|
||||
|
||||
ws->onError([](const string &error) { cout << "WebSocket failed: " << error << endl; });
|
||||
|
||||
|
||||
ws->onMessage([&](variant<binary, string> data) {
|
||||
if (!holds_alternative<string>(data))
|
||||
return;
|
||||
@ -101,15 +133,17 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
});
|
||||
|
||||
const string url = "ws://localhost:8000/" + localId;
|
||||
ws->open(url);
|
||||
|
||||
cout << "Waiting for signaling to be connected..." << endl;
|
||||
while (!ws->isOpen()) {
|
||||
if (ws->isClosed())
|
||||
return 1;
|
||||
this_thread::sleep_for(100ms);
|
||||
string wsPrefix = "";
|
||||
if (params->webSocketServer().substr(0,5).compare("ws://") != 0) {
|
||||
wsPrefix = "ws://";
|
||||
}
|
||||
const string url = wsPrefix + params->webSocketServer() + ":" +
|
||||
to_string(params->webSocketPort()) + "/" + localId;
|
||||
cout << "Url is " << url << endl;
|
||||
ws->open(url);
|
||||
|
||||
cout << "Waiting for signaling to be connected..." << endl;
|
||||
wsFuture.get();
|
||||
|
||||
while (true) {
|
||||
string id;
|
||||
@ -137,16 +171,23 @@ int main(int argc, char **argv) {
|
||||
|
||||
dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
|
||||
|
||||
dc->onMessage([id](const variant<binary, string> &message) {
|
||||
if (!holds_alternative<string>(message))
|
||||
return;
|
||||
|
||||
cout << "Message from " << id << " received: " << get<string>(message) << endl;
|
||||
dc->onMessage([id, wdc = make_weak_ptr(dc)](const variant<binary, string> &message) {
|
||||
static bool firstMessage = true;
|
||||
if (holds_alternative<string>(message) && (!echoDataChannelMessages || firstMessage)) {
|
||||
cout << "Message from " << id << " received: " << get<string>(message) << endl;
|
||||
firstMessage = false;
|
||||
} else if (echoDataChannelMessages) {
|
||||
bool echoed = false;
|
||||
if (auto dc = wdc.lock()) {
|
||||
dc->send(message);
|
||||
echoed = true;
|
||||
}
|
||||
printReceived(echoed, id, (holds_alternative<string>(message) ? "text" : "binary"),
|
||||
get<string>(message).length());
|
||||
}
|
||||
});
|
||||
|
||||
dataChannelMap.emplace(id, dc);
|
||||
|
||||
this_thread::sleep_for(1s);
|
||||
}
|
||||
|
||||
cout << "Cleaning up..." << endl;
|
||||
@ -154,6 +195,12 @@ int main(int argc, char **argv) {
|
||||
dataChannelMap.clear();
|
||||
peerConnectionMap.clear();
|
||||
return 0;
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
std::cout << "Error: " << e.what() << std::endl;
|
||||
dataChannelMap.clear();
|
||||
peerConnectionMap.clear();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create and setup a PeerConnection
|
||||
@ -190,11 +237,20 @@ shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
||||
|
||||
dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
|
||||
|
||||
dc->onMessage([id](const variant<binary, string> &message) {
|
||||
if (!holds_alternative<string>(message))
|
||||
return;
|
||||
|
||||
cout << "Message from " << id << " received: " << get<string>(message) << endl;
|
||||
dc->onMessage([id, wdc = make_weak_ptr(dc)](const variant<binary, string> &message) {
|
||||
static bool firstMessage = true;
|
||||
if (holds_alternative<string>(message) && (!echoDataChannelMessages || firstMessage)) {
|
||||
cout << "Message from " << id << " received: " << get<string>(message) << endl;
|
||||
firstMessage = false;
|
||||
} else if (echoDataChannelMessages) {
|
||||
bool echoed = false;
|
||||
if (auto dc = wdc.lock()) {
|
||||
dc->send(message);
|
||||
echoed = true;
|
||||
}
|
||||
printReceived(echoed, id, (holds_alternative<string>(message) ? "text" : "binary"),
|
||||
get<string>(message).length());
|
||||
}
|
||||
});
|
||||
|
||||
dc->send("Hello from " + localId);
|
||||
@ -206,6 +262,20 @@ shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
||||
return pc;
|
||||
};
|
||||
|
||||
// Helper function to print received pings
|
||||
void printReceived(bool echoed, string id, string type, size_t length) {
|
||||
static long count = 0;
|
||||
static long freq = 100;
|
||||
if (!(++count%freq)) {
|
||||
cout << "Received " << count << " pings in total from " << id << ", most recent of type "
|
||||
<< type << " and " << (echoed ? "" : "un") << "successfully echoed most recent ping of size "
|
||||
<< length << " back to " << id << endl;
|
||||
if (count >= (freq * 10) && freq < 1000000) {
|
||||
freq *= 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to generate a random ID
|
||||
string randomId(size_t length) {
|
||||
static const string characters(
|
||||
|
182
examples/client/parse_cl.cpp
Normal file
182
examples/client/parse_cl.cpp
Normal file
@ -0,0 +1,182 @@
|
||||
/******************************************************************************
|
||||
**
|
||||
** parse_cl.cpp
|
||||
**
|
||||
** Thu Aug 6 19:42:25 2020
|
||||
** Linux 5.4.0-42-generic (#46-Ubuntu SMP Fri Jul 10 00:24:02 UTC 2020) x86_64
|
||||
** cerik@Erik-VBox-Ubuntu (Erik Cota-Robles)
|
||||
**
|
||||
** Copyright (c) 2020 Erik Cota-Robles
|
||||
**
|
||||
** Definition of command line parser class
|
||||
**
|
||||
** Automatically created by genparse v0.9.3
|
||||
**
|
||||
** See http://genparse.sourceforge.net for details and updates
|
||||
**
|
||||
**
|
||||
******************************************************************************/
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#if defined(_WIN32) || defined(WIN32)
|
||||
#include "getopt.h"
|
||||
#else
|
||||
#include <getopt.h>
|
||||
#endif
|
||||
|
||||
#include "parse_cl.h"
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
**
|
||||
** Cmdline::Cmdline ()
|
||||
**
|
||||
** Constructor method.
|
||||
**
|
||||
**--------------------------------------------------------------------------*/
|
||||
|
||||
Cmdline::Cmdline (int argc, char *argv[]) // ISO C++17 not allowed: throw (std::string )
|
||||
{
|
||||
extern char *optarg;
|
||||
extern int optind;
|
||||
int c;
|
||||
|
||||
static struct option long_options[] =
|
||||
{
|
||||
{"echo", no_argument, NULL, 'e'},
|
||||
{"noStun", no_argument, NULL, 'n'},
|
||||
{"stunServer", required_argument, NULL, 's'},
|
||||
{"stunPort", required_argument, NULL, 't'},
|
||||
{"webSocketServer", required_argument, NULL, 'w'},
|
||||
{"webSocketPort", required_argument, NULL, 'x'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"version", no_argument, NULL, 'v'},
|
||||
{NULL, 0, NULL, 0}
|
||||
};
|
||||
|
||||
_program_name += argv[0];
|
||||
|
||||
/* default values */
|
||||
_e = false;
|
||||
_n = false;
|
||||
_s = "stun.l.google.com";
|
||||
_t = 19302;
|
||||
_w = "localhost";
|
||||
_x = 8000;
|
||||
_h = false;
|
||||
_v = false;
|
||||
|
||||
optind = 0;
|
||||
while ((c = getopt_long (argc, argv, "s:t:w:x:enhv", long_options, &optind)) != - 1)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case 'e':
|
||||
_e = true;
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
_n = true;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
_s = optarg;
|
||||
break;
|
||||
|
||||
case 't':
|
||||
_t = atoi (optarg);
|
||||
if (_t < 0)
|
||||
{
|
||||
std::string err;
|
||||
err += "parameter range error: t must be >= 0";
|
||||
throw (std::range_error(err));
|
||||
}
|
||||
if (_t > 65535)
|
||||
{
|
||||
std::string err;
|
||||
err += "parameter range error: t must be <= 65535";
|
||||
throw (std::range_error(err));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
_w = optarg;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
_x = atoi (optarg);
|
||||
if (_x < 0)
|
||||
{
|
||||
std::string err;
|
||||
err += "parameter range error: x must be >= 0";
|
||||
throw (std::range_error(err));
|
||||
}
|
||||
if (_x > 65535)
|
||||
{
|
||||
std::string err;
|
||||
err += "parameter range error: x must be <= 65535";
|
||||
throw (std::range_error(err));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
_h = true;
|
||||
this->usage (EXIT_SUCCESS);
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
_v = true;
|
||||
this->version (EXIT_SUCCESS);
|
||||
break;
|
||||
|
||||
default:
|
||||
this->usage (EXIT_FAILURE);
|
||||
|
||||
}
|
||||
} /* while */
|
||||
|
||||
_optind = optind;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
**
|
||||
** Cmdline::usage () and version()
|
||||
**
|
||||
** Print out usage (or version) information, then exit.
|
||||
**
|
||||
**--------------------------------------------------------------------------*/
|
||||
|
||||
void Cmdline::usage (int status)
|
||||
{
|
||||
if (status != EXIT_SUCCESS)
|
||||
std::cerr << "Try `" << _program_name << " --help' for more information.\n";
|
||||
else
|
||||
{
|
||||
std::cout << "\
|
||||
usage: " << _program_name << " [ -enstwxhv ] \n\
|
||||
libdatachannel client implementing WebRTC Data Channels with WebSocket signaling\n\
|
||||
[ -e ] [ --echo ] (type=FLAG)\n\
|
||||
Echo data channel messages back to sender rather than putting to stdout.\n\
|
||||
[ -n ] [ --noStun ] (type=FLAG)\n\
|
||||
Do NOT use a stun server (overrides -s and -t).\n\
|
||||
[ -s ] [ --stunServer ] (type=STRING, default=stun.l.google.com)\n\
|
||||
Stun server URL or IP address.\n\
|
||||
[ -t ] [ --stunPort ] (type=INTEGER, range=0...65535, default=19302)\n\
|
||||
Stun server port.\n\
|
||||
[ -w ] [ --webSocketServer ] (type=STRING, default=localhost)\n\
|
||||
Web socket server URL or IP address.\n\
|
||||
[ -x ] [ --webSocketPort ] (type=INTEGER, range=0...65535, default=8000)\n\
|
||||
Web socket server port.\n\
|
||||
[ -h ] [ --help ] (type=FLAG)\n\
|
||||
Display this help and exit.\n\
|
||||
[ -v ] [ --version ] (type=FLAG)\n\
|
||||
Output version information and exit.\n";
|
||||
}
|
||||
exit (status);
|
||||
}
|
||||
|
||||
void Cmdline::version (int status)
|
||||
{
|
||||
std::cout << _program_name << " v0.5\n";
|
||||
exit (status);
|
||||
}
|
74
examples/client/parse_cl.h
Normal file
74
examples/client/parse_cl.h
Normal file
@ -0,0 +1,74 @@
|
||||
/******************************************************************************
|
||||
**
|
||||
** parse_cl.h
|
||||
**
|
||||
** Thu Aug 6 19:42:25 2020
|
||||
** Linux 5.4.0-42-generic (#46-Ubuntu SMP Fri Jul 10 00:24:02 UTC 2020) x86_64
|
||||
** cerik@Erik-VBox-Ubuntu (Erik Cota-Robles)
|
||||
**
|
||||
** Copyright (c) 2020 Erik Cota-Robles
|
||||
**
|
||||
** Header file for command line parser class
|
||||
**
|
||||
** Automatically created by genparse v0.9.3
|
||||
**
|
||||
** See http://genparse.sourceforge.net for details and updates
|
||||
**
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef CMDLINE_H
|
||||
#define CMDLINE_H
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
**
|
||||
** class Cmdline
|
||||
**
|
||||
** command line parser class
|
||||
**
|
||||
**--------------------------------------------------------------------------*/
|
||||
|
||||
class Cmdline
|
||||
{
|
||||
private:
|
||||
/* parameters */
|
||||
bool _e;
|
||||
bool _n;
|
||||
std::string _s;
|
||||
int _t;
|
||||
std::string _w;
|
||||
int _x;
|
||||
bool _h;
|
||||
bool _v;
|
||||
|
||||
/* other stuff to keep track of */
|
||||
std::string _program_name;
|
||||
int _optind;
|
||||
|
||||
public:
|
||||
/* constructor and destructor */
|
||||
Cmdline (int, char **); // ISO C++17 not allowed: throw (std::string);
|
||||
~Cmdline (){}
|
||||
|
||||
/* usage function */
|
||||
void usage (int status);
|
||||
|
||||
/* version function */
|
||||
void version (int status);
|
||||
|
||||
/* return next (non-option) parameter */
|
||||
int next_param () { return _optind; }
|
||||
|
||||
bool echoDataChannelMessages () const { return _e; }
|
||||
bool noStun () const { return _n; }
|
||||
std::string stunServer () const { return _s; }
|
||||
int stunPort () const { return _t; }
|
||||
std::string webSocketServer () const { return _w; }
|
||||
int webSocketPort () const { return _x; }
|
||||
bool h () const { return _h; }
|
||||
bool v () const { return _v; }
|
||||
};
|
||||
|
||||
#endif
|
@ -41,13 +41,13 @@ typedef struct {
|
||||
bool connected;
|
||||
} Peer;
|
||||
|
||||
static void dataChannelCallback(int dc, void *ptr);
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr);
|
||||
static void stateChangeCallback(rtcState state, void *ptr);
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
|
||||
static void closedCallback(void *ptr);
|
||||
static void messageCallback(const char *message, int size, void *ptr);
|
||||
static void dataChannelCallback(int pc, int dc, void *ptr);
|
||||
static void descriptionCallback(int pc, const char *sdp, const char *type, void *ptr);
|
||||
static void candidateCallback(int pc, const char *cand, const char *mid, void *ptr);
|
||||
static void stateChangeCallback(int pc, rtcState state, void *ptr);
|
||||
static void gatheringStateCallback(int pc, rtcGatheringState state, void *ptr);
|
||||
static void closedCallback(int id, void *ptr);
|
||||
static void messageCallback(int id, const char *message, int size, void *ptr);
|
||||
static void deletePeer(Peer *peer);
|
||||
|
||||
char* state_print(rtcState state);
|
||||
@ -194,35 +194,35 @@ int main(int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
||||
static void descriptionCallback(int pc, const char *sdp, const char *type, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
printf("Description %s:\n%s\n", "answerer", sdp);
|
||||
}
|
||||
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr) {
|
||||
static void candidateCallback(int pc, const char *cand, const char *mid, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
printf("Candidate %s: %s\n", "answerer", cand);
|
||||
|
||||
}
|
||||
|
||||
static void stateChangeCallback(rtcState state, void *ptr) {
|
||||
static void stateChangeCallback(int pc, rtcState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->state = state;
|
||||
printf("State %s: %s\n", "answerer", state_print(state));
|
||||
}
|
||||
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
|
||||
static void gatheringStateCallback(int pc, rtcGatheringState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->gatheringState = state;
|
||||
printf("Gathering state %s: %s\n", "answerer", rtcGatheringState_print(state));
|
||||
}
|
||||
|
||||
static void closedCallback(void *ptr) {
|
||||
static void closedCallback(int id, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
}
|
||||
|
||||
static void messageCallback(const char *message, int size, void *ptr) {
|
||||
static void messageCallback(int id, const char *message, int size, void *ptr) {
|
||||
if (size < 0) { // negative size indicates a null-terminated string
|
||||
printf("Message %s: %s\n", "answerer", message);
|
||||
} else {
|
||||
@ -240,7 +240,7 @@ static void deletePeer(Peer *peer) {
|
||||
}
|
||||
}
|
||||
|
||||
static void dataChannelCallback(int dc, void *ptr) {
|
||||
static void dataChannelCallback(int pc, int dc, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->dc = dc;
|
||||
peer->connected = true;
|
||||
|
@ -41,13 +41,13 @@ typedef struct {
|
||||
bool connected;
|
||||
} Peer;
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr);
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr);
|
||||
static void stateChangeCallback(rtcState state, void *ptr);
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr);
|
||||
static void openCallback(void *ptr);
|
||||
static void closedCallback(void *ptr);
|
||||
static void messageCallback(const char *message, int size, void *ptr);
|
||||
static void descriptionCallback(int pc, const char *sdp, const char *type, void *ptr);
|
||||
static void candidateCallback(int pc, const char *cand, const char *mid, void *ptr);
|
||||
static void stateChangeCallback(int pc, rtcState state, void *ptr);
|
||||
static void gatheringStateCallback(int pc, rtcGatheringState state, void *ptr);
|
||||
static void openCallback(int id, void *ptr);
|
||||
static void closedCallback(int id, void *ptr);
|
||||
static void messageCallback(int id, const char *message, int size, void *ptr);
|
||||
static void deletePeer(Peer *peer);
|
||||
|
||||
char *state_print(rtcState state);
|
||||
@ -199,30 +199,30 @@ int main(int argc, char **argv){
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
||||
static void descriptionCallback(int pc, const char *sdp, const char *type, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
printf("Description %s:\n%s\n", "offerer", sdp);
|
||||
}
|
||||
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr) {
|
||||
static void candidateCallback(int pc, const char *cand, const char *mid, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
printf("Candidate %s: %s\n", "offerer", cand);
|
||||
|
||||
}
|
||||
|
||||
static void stateChangeCallback(rtcState state, void *ptr) {
|
||||
static void stateChangeCallback(int pc, rtcState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->state = state;
|
||||
printf("State %s: %s\n", "offerer", state_print(state));
|
||||
}
|
||||
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
|
||||
static void gatheringStateCallback(int pc, rtcGatheringState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->gatheringState = state;
|
||||
printf("Gathering state %s: %s\n", "offerer", rtcGatheringState_print(state));
|
||||
}
|
||||
|
||||
static void openCallback(void *ptr) {
|
||||
static void openCallback(int id, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = true;
|
||||
char buffer[256];
|
||||
@ -230,12 +230,12 @@ static void openCallback(void *ptr) {
|
||||
printf("DataChannel %s: Received with label \"%s\"\n","offerer", buffer);
|
||||
}
|
||||
|
||||
static void closedCallback(void *ptr) {
|
||||
static void closedCallback(int id, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
}
|
||||
|
||||
static void messageCallback(const char *message, int size, void *ptr) {
|
||||
static void messageCallback(int id, const char *message, int size, void *ptr) {
|
||||
// Peer *peer = (Peer *)ptr;
|
||||
if (size < 0) { // negative size indicates a null-terminated string
|
||||
printf("Message %s: %s\n", "offerer", message);
|
||||
|
@ -127,13 +127,11 @@ int main(int argc, char **argv) {
|
||||
cout << "** Channel is not Open ** ";
|
||||
break;
|
||||
}
|
||||
CandidateInfo local, remote;
|
||||
Candidate local, remote;
|
||||
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
|
||||
if (pc->getSelectedCandidatePair(&local, &remote)) {
|
||||
cout << "Local: " << local.address << ":" << local.port << " " << local.type << " "
|
||||
<< local.transportType << endl;
|
||||
cout << "Remote: " << remote.address << ":" << remote.port << " " << remote.type
|
||||
<< " " << remote.transportType << endl;
|
||||
cout << "Local: " << local << endl;
|
||||
cout << "Remote: " << remote << endl;
|
||||
cout << "Bytes Sent:" << pc->bytesSent()
|
||||
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
|
||||
if (rtt.has_value())
|
||||
|
@ -127,13 +127,11 @@ int main(int argc, char **argv) {
|
||||
cout << "** Channel is not Open ** ";
|
||||
break;
|
||||
}
|
||||
CandidateInfo local, remote;
|
||||
Candidate local, remote;
|
||||
std::optional<std::chrono::milliseconds> rtt = pc->rtt();
|
||||
if (pc->getSelectedCandidatePair(&local, &remote)) {
|
||||
cout << "Local: " << local.address << ":" << local.port << " " << local.type << " "
|
||||
<< local.transportType << endl;
|
||||
cout << "Remote: " << remote.address << ":" << remote.port << " " << remote.type
|
||||
<< " " << remote.transportType << endl;
|
||||
cout << "Local: " << local << endl;
|
||||
cout << "Remote: " << remote << endl;
|
||||
cout << "Bytes Sent:" << pc->bytesSent()
|
||||
<< " / Bytes Received:" << pc->bytesReceived() << " / Round-Trip Time:";
|
||||
if (rtt.has_value())
|
||||
|
@ -73,7 +73,7 @@ int main() {
|
||||
track->onMessage(
|
||||
[session, sock, addr](rtc::binary message) {
|
||||
// This is an RTP packet
|
||||
sendto(sock, reinterpret_cast<const char *>(message.data()), message.size(), 0,
|
||||
sendto(sock, reinterpret_cast<const char *>(message.data()), int(message.size()), 0,
|
||||
reinterpret_cast<const struct sockaddr *>(&addr), sizeof(addr));
|
||||
},
|
||||
nullptr);
|
||||
|
@ -63,16 +63,20 @@ async def handle_websocket(websocket, path):
|
||||
print('Client {} disconnected'.format(client_id))
|
||||
|
||||
if __name__ == '__main__':
|
||||
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8000
|
||||
# Usage: ./server.py [[host:]port] [SSL certificate file]
|
||||
endpoint_or_port = sys.argv[1] if len(sys.argv) > 1 else "8000"
|
||||
ssl_cert = sys.argv[2] if len(sys.argv) > 2 else None
|
||||
|
||||
endpoint = endpoint_or_port if ':' in endpoint_or_port else "127.0.0.1:" + endpoint_or_port
|
||||
|
||||
if ssl_cert:
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
ssl_context.load_cert_chain(ssl_cert)
|
||||
else:
|
||||
ssl_context = None
|
||||
|
||||
print('Listening on port {}'.format(port))
|
||||
start_server = websockets.serve(handle_websocket, '127.0.0.1', port, ssl=ssl_context)
|
||||
print('Listening on {}'.format(endpoint))
|
||||
host, port = endpoint.rsplit(':', 1)
|
||||
start_server = websockets.serve(handle_websocket, host, int(port), ssl=ssl_context)
|
||||
asyncio.get_event_loop().run_until_complete(start_server)
|
||||
asyncio.get_event_loop().run_forever()
|
||||
|
2
examples/signaling-server-rust/Cargo.lock
generated
2
examples/signaling-server-rust/Cargo.lock
generated
@ -348,7 +348,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701"
|
||||
|
||||
[[package]]
|
||||
name = "libdatachannel_signaling_example"
|
||||
name = "libdatachannel_signaling_server_example"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
|
@ -92,7 +92,9 @@ async fn handle(clients: ClientsMap, stream: TcpStream) {
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), std::io::Error> {
|
||||
let service = env::args().nth(1).unwrap_or("8000".to_string());
|
||||
let endpoint = format!("127.0.0.1:{}", service);
|
||||
let endpoint = if service.contains(':') { service } else { format!("127.0.0.1:{}", service) };
|
||||
|
||||
println!("Listening on {}", endpoint);
|
||||
|
||||
let mut listener = TcpListener::bind(endpoint)
|
||||
.await.expect("Listener binding failed");
|
||||
|
@ -98,8 +98,10 @@ wsServer.on('request', (req) => {
|
||||
clients[id] = conn;
|
||||
});
|
||||
|
||||
const hostname = '127.0.0.1';
|
||||
const port = 8000;
|
||||
const endpoint = process.env.PORT || '8000';
|
||||
const splitted = endpoint.split(':');
|
||||
const port = splitted.pop();
|
||||
const hostname = splitted.join(':') || '127.0.0.1';
|
||||
|
||||
httpServer.listen(port, hostname, () => {
|
||||
console.log(`Server listening on ${hostname}:${port}`);
|
||||
|
@ -25,38 +25,47 @@
|
||||
|
||||
namespace rtc {
|
||||
|
||||
enum class CandidateType { Host = 0, ServerReflexive, PeerReflexive, Relayed };
|
||||
enum class CandidateTransportType { Udp = 0, TcpActive, TcpPassive, TcpSo };
|
||||
struct CandidateInfo {
|
||||
string address;
|
||||
int port;
|
||||
CandidateType type;
|
||||
CandidateTransportType transportType;
|
||||
};
|
||||
|
||||
class Candidate {
|
||||
public:
|
||||
Candidate(string candidate, string mid = "");
|
||||
enum class Family { Unresolved, Ipv4, Ipv6 };
|
||||
enum class Type { Unknown, Host, ServerReflexive, PeerReflexive, Relayed };
|
||||
enum class TransportType { Unknown, Udp, TcpActive, TcpPassive, TcpSo, TcpUnknown };
|
||||
|
||||
Candidate(string candidate = "", string mid = "");
|
||||
|
||||
enum class ResolveMode { Simple, Lookup };
|
||||
bool resolve(ResolveMode mode = ResolveMode::Simple);
|
||||
bool isResolved() const;
|
||||
|
||||
string candidate() const;
|
||||
string mid() const;
|
||||
operator string() const;
|
||||
|
||||
bool isResolved() const;
|
||||
Family family() const;
|
||||
Type type() const;
|
||||
TransportType transportType() const;
|
||||
std::optional<string> address() const;
|
||||
std::optional<uint16_t> port() const;
|
||||
std::optional<uint32_t> priority() const;
|
||||
|
||||
private:
|
||||
string mCandidate;
|
||||
string mMid;
|
||||
bool mIsResolved;
|
||||
|
||||
// Extracted on resolution
|
||||
Family mFamily;
|
||||
Type mType;
|
||||
TransportType mTransportType;
|
||||
string mAddress;
|
||||
uint16_t mPort;
|
||||
uint32_t mPriority;
|
||||
};
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate);
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::CandidateType &type);
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::CandidateTransportType &transportType);
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::Candidate::Type &type);
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::Candidate::TransportType &transportType);
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -34,8 +34,8 @@ namespace rtc {
|
||||
|
||||
class Description {
|
||||
public:
|
||||
enum class Type { Unspec = 0, Offer = 1, Answer = 2 };
|
||||
enum class Role { ActPass = 0, Passive = 1, Active = 2 };
|
||||
enum class Type { Unspec, Offer, Answer, Pranswer, Rollback };
|
||||
enum class Role { ActPass, Passive, Active };
|
||||
enum class Direction { SendOnly, RecvOnly, SendRecv, Inactive, Unknown };
|
||||
|
||||
Description(const string &sdp, const string &typeString = "");
|
||||
@ -45,8 +45,9 @@ public:
|
||||
Type type() const;
|
||||
string typeString() const;
|
||||
Role role() const;
|
||||
string roleString() const;
|
||||
string bundleMid() const;
|
||||
std::optional<string> iceUfrag() const;
|
||||
std::optional<string> icePwd() const;
|
||||
std::optional<string> fingerprint() const;
|
||||
bool ended() const;
|
||||
|
||||
@ -54,6 +55,7 @@ public:
|
||||
void setFingerprint(string fingerprint);
|
||||
|
||||
void addCandidate(Candidate candidate);
|
||||
void addCandidates(std::vector<Candidate> candidates);
|
||||
void endCandidates();
|
||||
std::vector<Candidate> extractCandidates();
|
||||
|
||||
@ -72,7 +74,7 @@ public:
|
||||
void setDirection(Direction dir);
|
||||
|
||||
operator string() const;
|
||||
string generateSdp(string_view eol) const;
|
||||
string generateSdp(string_view eol, string_view addr, string_view port) const;
|
||||
|
||||
virtual void parseSdpLine(string_view line);
|
||||
|
||||
@ -196,6 +198,7 @@ public:
|
||||
|
||||
bool hasApplication() const;
|
||||
bool hasAudioOrVideo() const;
|
||||
bool hasMid(string_view mid) const;
|
||||
|
||||
int addMedia(Media media);
|
||||
int addMedia(Application application);
|
||||
@ -205,11 +208,15 @@ public:
|
||||
|
||||
std::variant<Media *, Application *> media(int index);
|
||||
std::variant<const Media *, const Application *> media(int index) const;
|
||||
unsigned int mediaCount() const;
|
||||
size_t mediaCount() const;
|
||||
|
||||
Application *application();
|
||||
|
||||
static Type stringToType(const string &typeString);
|
||||
static string typeToString(Type type);
|
||||
|
||||
private:
|
||||
std::optional<Candidate> defaultCandidate() const;
|
||||
std::shared_ptr<Entry> createEntry(string mline, string mid, Direction dir);
|
||||
void removeApplication();
|
||||
|
||||
@ -217,8 +224,9 @@ private:
|
||||
|
||||
// Session-level attributes
|
||||
Role mRole;
|
||||
string mUsername;
|
||||
string mSessionId;
|
||||
string mIceUfrag, mIcePwd;
|
||||
std::optional<string> mIceUfrag, mIcePwd;
|
||||
std::optional<string> mFingerprint;
|
||||
|
||||
// Entries
|
||||
@ -228,14 +236,12 @@ private:
|
||||
// Candidates
|
||||
std::vector<Candidate> mCandidates;
|
||||
bool mEnded = false;
|
||||
|
||||
static Type stringToType(const string &typeString);
|
||||
static string typeToString(Type type);
|
||||
static string roleToString(Role role);
|
||||
};
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::Description &description);
|
||||
std::ostream &operator<<(std::ostream &out, rtc::Description::Type type);
|
||||
std::ostream &operator<<(std::ostream &out, rtc::Description::Role role);
|
||||
|
||||
#endif
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef _WIN32_WINNT
|
||||
#define _WIN32_WINNT 0x0602
|
||||
#define _WIN32_WINNT 0x0602 // Windows 8
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@ -62,7 +62,7 @@ using std::uint8_t;
|
||||
const size_t MAX_NUMERICNODE_LEN = 48; // Max IPv6 string representation length
|
||||
const size_t MAX_NUMERICSERV_LEN = 6; // Max port string representation length
|
||||
|
||||
const uint16_t DEFAULT_SCTP_PORT = 5000; // SCTP port to use by default
|
||||
const uint16_t DEFAULT_SCTP_PORT = 5000; // SCTP port to use by default
|
||||
const size_t DEFAULT_MAX_MESSAGE_SIZE = 65536; // Remote max message size if not specified in SDP
|
||||
const size_t LOCAL_MAX_MESSAGE_SIZE = 256 * 1024; // Local max message size
|
||||
|
||||
@ -72,7 +72,7 @@ const int THREADPOOL_SIZE = 4; // Number of threads in the global thread pool
|
||||
|
||||
// overloaded helper
|
||||
template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
|
||||
template <class... Ts> overloaded(Ts...)->overloaded<Ts...>;
|
||||
template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
|
||||
|
||||
// weak_ptr bind helper
|
||||
template <typename F, typename T, typename... Args> auto weak_bind(F &&f, T *t, Args &&... _args) {
|
||||
@ -85,6 +85,23 @@ template <typename F, typename T, typename... Args> auto weak_bind(F &&f, T *t,
|
||||
};
|
||||
}
|
||||
|
||||
// scope_guard helper
|
||||
class scope_guard {
|
||||
public:
|
||||
scope_guard(std::function<void()> func) : function(std::move(func)) {}
|
||||
scope_guard(scope_guard &&other) = delete;
|
||||
scope_guard(const scope_guard &) = delete;
|
||||
void operator=(const scope_guard &) = delete;
|
||||
|
||||
~scope_guard() {
|
||||
if (function)
|
||||
function();
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void()> function;
|
||||
};
|
||||
|
||||
template <typename... P> class synchronized_callback {
|
||||
public:
|
||||
synchronized_callback() = default;
|
||||
@ -127,6 +144,6 @@ private:
|
||||
std::function<void(P...)> callback;
|
||||
mutable std::recursive_mutex mutex;
|
||||
};
|
||||
}
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
@ -67,6 +67,14 @@ public:
|
||||
Complete = RTC_GATHERING_COMPLETE
|
||||
};
|
||||
|
||||
enum class SignalingState : int {
|
||||
Stable = RTC_SIGNALING_STABLE,
|
||||
HaveLocalOffer = RTC_SIGNALING_HAVE_LOCAL_OFFER,
|
||||
HaveRemoteOffer = RTC_SIGNALING_HAVE_REMOTE_OFFER,
|
||||
HaveLocalPranswer = RTC_SIGNALING_HAVE_LOCAL_PRANSWER,
|
||||
HaveRemotePranswer = RTC_SIGNALING_HAVE_REMOTE_PRANSWER,
|
||||
} rtcSignalingState;
|
||||
|
||||
PeerConnection(void);
|
||||
PeerConnection(const Configuration &config);
|
||||
~PeerConnection();
|
||||
@ -76,14 +84,18 @@ public:
|
||||
const Configuration *config() const;
|
||||
State state() const;
|
||||
GatheringState gatheringState() const;
|
||||
SignalingState signalingState() const;
|
||||
bool hasLocalDescription() const;
|
||||
bool hasRemoteDescription() const;
|
||||
bool hasMedia() const;
|
||||
std::optional<Description> localDescription() const;
|
||||
std::optional<Description> remoteDescription() const;
|
||||
std::optional<string> localAddress() const;
|
||||
std::optional<string> remoteAddress() const;
|
||||
bool hasMedia() const;
|
||||
bool getSelectedCandidatePair(Candidate *local, Candidate *remote);
|
||||
|
||||
void setLocalDescription(Description::Type type = Description::Type::Unspec);
|
||||
|
||||
void setLocalDescription();
|
||||
void processLocalDescription(Description description);
|
||||
void setRemoteDescription(Description description);
|
||||
void addRemoteCandidate(Candidate candidate);
|
||||
|
||||
@ -99,6 +111,7 @@ public:
|
||||
void onLocalCandidate(std::function<void(Candidate candidate)> callback);
|
||||
void onStateChange(std::function<void(State state)> callback);
|
||||
void onGatheringStateChange(std::function<void(GatheringState state)> callback);
|
||||
void onSignalingStateChange(std::function<void(SignalingState state)> callback);
|
||||
|
||||
// Stats
|
||||
void clearStats();
|
||||
@ -110,9 +123,6 @@ public:
|
||||
std::shared_ptr<Track> addTrack(Description::Media description);
|
||||
void onTrack(std::function<void(std::shared_ptr<Track> track)> callback);
|
||||
|
||||
// libnice only
|
||||
bool getSelectedCandidatePair(CandidateInfo *local, CandidateInfo *remote);
|
||||
|
||||
private:
|
||||
std::shared_ptr<IceTransport> initIceTransport(Description::Role role);
|
||||
std::shared_ptr<DtlsTransport> initDtlsTransport();
|
||||
@ -137,11 +147,17 @@ private:
|
||||
void incomingTrack(Description::Media description);
|
||||
void openTracks();
|
||||
|
||||
|
||||
void validateRemoteDescription(const Description &description);
|
||||
void processLocalDescription(Description description);
|
||||
void processLocalCandidate(Candidate candidate);
|
||||
void processRemoteDescription(Description description);
|
||||
void processRemoteCandidate(Candidate candidate);
|
||||
void triggerDataChannel(std::weak_ptr<DataChannel> weakDataChannel);
|
||||
void triggerTrack(std::shared_ptr<Track> track);
|
||||
bool changeState(State state);
|
||||
bool changeGatheringState(GatheringState state);
|
||||
bool changeSignalingState(SignalingState state);
|
||||
|
||||
void resetCallbacks();
|
||||
|
||||
@ -153,6 +169,7 @@ private:
|
||||
const std::unique_ptr<Processor> mProcessor;
|
||||
|
||||
std::optional<Description> mLocalDescription, mRemoteDescription;
|
||||
std::optional<Description> mCurrentLocalDescription;
|
||||
mutable std::mutex mLocalDescriptionMutex, mRemoteDescriptionMutex;
|
||||
|
||||
std::shared_ptr<IceTransport> mIceTransport;
|
||||
@ -168,18 +185,22 @@ private:
|
||||
|
||||
std::atomic<State> mState;
|
||||
std::atomic<GatheringState> mGatheringState;
|
||||
std::atomic<SignalingState> mSignalingState;
|
||||
std::atomic<bool> mNegotiationNeeded;
|
||||
|
||||
synchronized_callback<std::shared_ptr<DataChannel>> mDataChannelCallback;
|
||||
synchronized_callback<Description> mLocalDescriptionCallback;
|
||||
synchronized_callback<Candidate> mLocalCandidateCallback;
|
||||
synchronized_callback<State> mStateChangeCallback;
|
||||
synchronized_callback<GatheringState> mGatheringStateChangeCallback;
|
||||
synchronized_callback<SignalingState> mSignalingStateChangeCallback;
|
||||
synchronized_callback<std::shared_ptr<Track>> mTrackCallback;
|
||||
};
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::State &state);
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::GatheringState &state);
|
||||
std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::State state);
|
||||
std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::GatheringState state);
|
||||
std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::SignalingState state);
|
||||
|
||||
#endif
|
||||
|
@ -38,17 +38,22 @@ public:
|
||||
~Queue();
|
||||
|
||||
void stop();
|
||||
bool running() const;
|
||||
bool empty() const;
|
||||
bool full() const;
|
||||
size_t size() const; // elements
|
||||
size_t amount() const; // amount
|
||||
void push(T element);
|
||||
std::optional<T> pop();
|
||||
std::optional<T> tryPop();
|
||||
std::optional<T> peek();
|
||||
std::optional<T> exchange(T element);
|
||||
bool wait(const std::optional<std::chrono::milliseconds> &duration = nullopt);
|
||||
|
||||
private:
|
||||
void pushImpl(T element);
|
||||
std::optional<T> popImpl();
|
||||
|
||||
const size_t mLimit;
|
||||
size_t mAmount;
|
||||
std::queue<T> mQueue;
|
||||
@ -76,6 +81,11 @@ template <typename T> void Queue<T>::stop() {
|
||||
mPushCondition.notify_all();
|
||||
}
|
||||
|
||||
template <typename T> bool Queue<T>::running() const {
|
||||
std::lock_guard lock(mMutex);
|
||||
return !mQueue.empty() || !mStopping;
|
||||
}
|
||||
|
||||
template <typename T> bool Queue<T>::empty() const {
|
||||
std::lock_guard lock(mMutex);
|
||||
return mQueue.empty();
|
||||
@ -99,43 +109,32 @@ template <typename T> size_t Queue<T>::amount() const {
|
||||
template <typename T> void Queue<T>::push(T element) {
|
||||
std::unique_lock lock(mMutex);
|
||||
mPushCondition.wait(lock, [this]() { return !mLimit || mQueue.size() < mLimit || mStopping; });
|
||||
if (!mStopping) {
|
||||
mAmount += mAmountFunction(element);
|
||||
mQueue.emplace(std::move(element));
|
||||
mPopCondition.notify_one();
|
||||
}
|
||||
pushImpl(std::move(element));
|
||||
}
|
||||
|
||||
template <typename T> std::optional<T> Queue<T>::pop() {
|
||||
std::unique_lock lock(mMutex);
|
||||
mPopCondition.wait(lock, [this]() { return !mQueue.empty() || mStopping; });
|
||||
if (!mQueue.empty()) {
|
||||
mAmount -= mAmountFunction(mQueue.front());
|
||||
std::optional<T> element{std::move(mQueue.front())};
|
||||
mQueue.pop();
|
||||
return element;
|
||||
} else {
|
||||
return nullopt;
|
||||
}
|
||||
return popImpl();
|
||||
}
|
||||
|
||||
template <typename T> std::optional<T> Queue<T>::tryPop() {
|
||||
std::unique_lock lock(mMutex);
|
||||
return popImpl();
|
||||
}
|
||||
|
||||
template <typename T> std::optional<T> Queue<T>::peek() {
|
||||
std::unique_lock lock(mMutex);
|
||||
if (!mQueue.empty()) {
|
||||
return std::optional<T>{mQueue.front()};
|
||||
} else {
|
||||
return nullopt;
|
||||
}
|
||||
return !mQueue.empty() ? std::make_optional(mQueue.front()) : nullopt;
|
||||
}
|
||||
|
||||
template <typename T> std::optional<T> Queue<T>::exchange(T element) {
|
||||
std::unique_lock lock(mMutex);
|
||||
if (!mQueue.empty()) {
|
||||
std::swap(mQueue.front(), element);
|
||||
return std::optional<T>{element};
|
||||
} else {
|
||||
if (mQueue.empty())
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
std::swap(mQueue.front(), element);
|
||||
return std::make_optional(std::move(element));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@ -145,7 +144,27 @@ bool Queue<T>::wait(const std::optional<std::chrono::milliseconds> &duration) {
|
||||
mPopCondition.wait_for(lock, *duration, [this]() { return !mQueue.empty() || mStopping; });
|
||||
else
|
||||
mPopCondition.wait(lock, [this]() { return !mQueue.empty() || mStopping; });
|
||||
return !mStopping;
|
||||
|
||||
return !mQueue.empty();
|
||||
}
|
||||
|
||||
template <typename T> void Queue<T>::pushImpl(T element) {
|
||||
if (mStopping)
|
||||
return;
|
||||
|
||||
mAmount += mAmountFunction(element);
|
||||
mQueue.emplace(std::move(element));
|
||||
mPopCondition.notify_one();
|
||||
}
|
||||
|
||||
template <typename T> std::optional<T> Queue<T>::popImpl() {
|
||||
if (mQueue.empty())
|
||||
return nullopt;
|
||||
|
||||
mAmount -= mAmountFunction(mQueue.front());
|
||||
std::optional<T> element{std::move(mQueue.front())};
|
||||
mQueue.pop();
|
||||
return element;
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
@ -29,6 +29,12 @@ extern "C" {
|
||||
#define RTC_EXPORT
|
||||
#endif
|
||||
|
||||
#ifdef CAPI_STDCALL
|
||||
#define RTC_API __stdcall
|
||||
#else
|
||||
#define RTC_API
|
||||
#endif
|
||||
|
||||
#ifndef RTC_ENABLE_WEBSOCKET
|
||||
#define RTC_ENABLE_WEBSOCKET 1
|
||||
#endif
|
||||
@ -53,6 +59,14 @@ typedef enum {
|
||||
RTC_GATHERING_COMPLETE = 2
|
||||
} rtcGatheringState;
|
||||
|
||||
typedef enum {
|
||||
RTC_SIGNALING_STABLE = 0,
|
||||
RTC_SIGNALING_HAVE_LOCAL_OFFER = 1,
|
||||
RTC_SIGNALING_HAVE_REMOTE_OFFER = 2,
|
||||
RTC_SIGNALING_HAVE_LOCAL_PRANSWER = 3,
|
||||
RTC_SIGNALING_HAVE_REMOTE_PRANSWER = 4,
|
||||
} rtcSignalingState;
|
||||
|
||||
typedef enum { // Don't change, it must match plog severity
|
||||
RTC_LOG_NONE = 0,
|
||||
RTC_LOG_FATAL = 1,
|
||||
@ -81,22 +95,24 @@ typedef struct {
|
||||
unsigned int maxRetransmits; // ignored if reliable
|
||||
} rtcReliability;
|
||||
|
||||
typedef void (*rtcLogCallbackFunc)(rtcLogLevel level, const char *message);
|
||||
typedef void (*rtcDescriptionCallbackFunc)(const char *sdp, const char *type, void *ptr);
|
||||
typedef void (*rtcCandidateCallbackFunc)(const char *cand, const char *mid, void *ptr);
|
||||
typedef void (*rtcStateChangeCallbackFunc)(rtcState state, void *ptr);
|
||||
typedef void (*rtcGatheringStateCallbackFunc)(rtcGatheringState state, void *ptr);
|
||||
typedef void (*rtcDataChannelCallbackFunc)(int dc, void *ptr);
|
||||
typedef void (*rtcTrackCallbackFunc)(int tr, void *ptr);
|
||||
typedef void (*rtcOpenCallbackFunc)(void *ptr);
|
||||
typedef void (*rtcClosedCallbackFunc)(void *ptr);
|
||||
typedef void (*rtcErrorCallbackFunc)(const char *error, void *ptr);
|
||||
typedef void (*rtcMessageCallbackFunc)(const char *message, int size, void *ptr);
|
||||
typedef void (*rtcBufferedAmountLowCallbackFunc)(void *ptr);
|
||||
typedef void (*rtcAvailableCallbackFunc)(void *ptr);
|
||||
typedef void (RTC_API *rtcLogCallbackFunc)(rtcLogLevel level, const char *message);
|
||||
typedef void (RTC_API *rtcDescriptionCallbackFunc)(int pc, const char *sdp, const char *type, void *ptr);
|
||||
typedef void (RTC_API *rtcCandidateCallbackFunc)(int pc, const char *cand, const char *mid, void *ptr);
|
||||
typedef void (RTC_API *rtcStateChangeCallbackFunc)(int pc, rtcState state, void *ptr);
|
||||
typedef void (RTC_API *rtcGatheringStateCallbackFunc)(int pc, rtcGatheringState state, void *ptr);
|
||||
typedef void (RTC_API *rtcSignalingStateCallbackFunc)(int pc, rtcSignalingState state, void *ptr);
|
||||
typedef void (RTC_API *rtcDataChannelCallbackFunc)(int pc, int dc, void *ptr);
|
||||
typedef void (RTC_API *rtcTrackCallbackFunc)(int pc, int tr, void *ptr);
|
||||
typedef void (RTC_API *rtcOpenCallbackFunc)(int id, void *ptr);
|
||||
typedef void (RTC_API *rtcClosedCallbackFunc)(int id, void *ptr);
|
||||
typedef void (RTC_API *rtcErrorCallbackFunc)(int id, const char *error, void *ptr);
|
||||
typedef void (RTC_API *rtcMessageCallbackFunc)(int id, const char *message, int size, void *ptr);
|
||||
typedef void (RTC_API *rtcBufferedAmountLowCallbackFunc)(int id, void *ptr);
|
||||
typedef void (RTC_API *rtcAvailableCallbackFunc)(int id, void *ptr);
|
||||
|
||||
// Log
|
||||
RTC_EXPORT void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb); // NULL cb to log to stdout
|
||||
// NULL cb on the first call will log to stdout
|
||||
RTC_EXPORT void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb);
|
||||
|
||||
// User pointer
|
||||
RTC_EXPORT void rtcSetUserPointer(int id, void *ptr);
|
||||
@ -109,14 +125,20 @@ RTC_EXPORT int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc
|
||||
RTC_EXPORT int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb);
|
||||
RTC_EXPORT int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb);
|
||||
RTC_EXPORT int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb);
|
||||
RTC_EXPORT int rtcSetSignalingStateChangeCallback(int pc, rtcSignalingStateCallbackFunc cb);
|
||||
|
||||
RTC_EXPORT int rtcSetLocalDescription(int pc);
|
||||
RTC_EXPORT int rtcSetLocalDescription(int pc, const char *type);
|
||||
RTC_EXPORT int rtcSetRemoteDescription(int pc, const char *sdp, const char *type);
|
||||
RTC_EXPORT int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid);
|
||||
|
||||
RTC_EXPORT int rtcGetLocalDescription(int pc, char *buffer, int size);
|
||||
RTC_EXPORT int rtcGetRemoteDescription(int pc, char *buffer, int size);
|
||||
|
||||
RTC_EXPORT int rtcGetLocalAddress(int pc, char *buffer, int size);
|
||||
RTC_EXPORT int rtcGetRemoteAddress(int pc, char *buffer, int size);
|
||||
|
||||
RTC_EXPORT int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote, int remoteSize);
|
||||
|
||||
// DataChannel
|
||||
RTC_EXPORT int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb);
|
||||
RTC_EXPORT int rtcAddDataChannel(int pc, const char *label); // returns dc id
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
@ -47,20 +48,40 @@ inline bool hasprefix(const string &str, const string &prefix) {
|
||||
|
||||
namespace rtc {
|
||||
|
||||
Candidate::Candidate(string candidate, string mid) : mIsResolved(false) {
|
||||
const std::array prefixes{"a=", "candidate:"};
|
||||
for (string prefix : prefixes)
|
||||
if (hasprefix(candidate, prefix))
|
||||
candidate.erase(0, prefix.size());
|
||||
Candidate::Candidate(string candidate, string mid)
|
||||
: mFamily(Family::Unresolved), mType(Type::Unknown), mTransportType(TransportType::Unknown),
|
||||
mPort(0), mPriority(0) {
|
||||
|
||||
if (!candidate.empty()) {
|
||||
const std::array prefixes{"a=", "candidate:"};
|
||||
for (const string &prefix : prefixes)
|
||||
if (hasprefix(candidate, prefix))
|
||||
candidate.erase(0, prefix.size());
|
||||
}
|
||||
|
||||
mCandidate = std::move(candidate);
|
||||
mMid = std::move(mid);
|
||||
}
|
||||
|
||||
bool Candidate::resolve(ResolveMode mode) {
|
||||
if (mIsResolved)
|
||||
using TypeMap_t = std::unordered_map<string, Type>;
|
||||
using TcpTypeMap_t = std::unordered_map<string, TransportType>;
|
||||
|
||||
static const TypeMap_t TypeMap = {{"host", Type::Host},
|
||||
{"srflx", Type::ServerReflexive},
|
||||
{"prflx", Type::PeerReflexive},
|
||||
{"relay", Type::Relayed}};
|
||||
|
||||
static const TcpTypeMap_t TcpTypeMap = {{"active", TransportType::TcpActive},
|
||||
{"passive", TransportType::TcpPassive},
|
||||
{"so", TransportType::TcpSo}};
|
||||
|
||||
if (mFamily != Family::Unresolved)
|
||||
return true;
|
||||
|
||||
if(mCandidate.empty())
|
||||
throw std::logic_error("Candidate is empty");
|
||||
|
||||
PLOG_VERBOSE << "Resolving candidate (mode="
|
||||
<< (mode == ResolveMode::Simple ? "simple" : "lookup")
|
||||
<< "): " << mCandidate;
|
||||
@ -75,16 +96,39 @@ bool Candidate::resolve(ResolveMode mode) {
|
||||
string left;
|
||||
std::getline(iss, left);
|
||||
|
||||
if (auto it = TypeMap.find(type); it != TypeMap.end())
|
||||
mType = it->second;
|
||||
else
|
||||
mType = Type::Unknown;
|
||||
|
||||
if (transport == "UDP" || transport == "udp") {
|
||||
mTransportType = TransportType::Udp;
|
||||
}
|
||||
else if (transport == "TCP" || transport == "tcp") {
|
||||
std::istringstream iss(left);
|
||||
string tcptype_, tcptype;
|
||||
if(iss >> tcptype_ >> tcptype && tcptype_ == "tcptype") {
|
||||
if (auto it = TcpTypeMap.find(tcptype); it != TcpTypeMap.end())
|
||||
mTransportType = it->second;
|
||||
else
|
||||
mTransportType = TransportType::TcpUnknown;
|
||||
|
||||
} else {
|
||||
mTransportType = TransportType::TcpUnknown;
|
||||
}
|
||||
} else {
|
||||
mTransportType = TransportType::Unknown;
|
||||
}
|
||||
|
||||
// Try to resolve the node
|
||||
struct addrinfo hints = {};
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_flags = AI_ADDRCONFIG;
|
||||
if (transport == "UDP" || transport == "udp") {
|
||||
if (mTransportType == TransportType::Udp) {
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
hints.ai_protocol = IPPROTO_UDP;
|
||||
}
|
||||
|
||||
if (transport == "TCP" || transport == "tcp") {
|
||||
else if (mTransportType != TransportType::Unknown) {
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
}
|
||||
@ -94,7 +138,7 @@ bool Candidate::resolve(ResolveMode mode) {
|
||||
|
||||
struct addrinfo *result = nullptr;
|
||||
if (getaddrinfo(node.c_str(), service.c_str(), &hints, &result) == 0) {
|
||||
for (auto p = result; p; p = p->ai_next)
|
||||
for (auto p = result; p; p = p->ai_next) {
|
||||
if (p->ai_family == AF_INET || p->ai_family == AF_INET6) {
|
||||
// Rewrite the candidate
|
||||
char nodebuffer[MAX_NUMERICNODE_LEN];
|
||||
@ -102,27 +146,31 @@ bool Candidate::resolve(ResolveMode mode) {
|
||||
if (getnameinfo(p->ai_addr, socklen_t(p->ai_addrlen), nodebuffer,
|
||||
MAX_NUMERICNODE_LEN, servbuffer, MAX_NUMERICSERV_LEN,
|
||||
NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
|
||||
|
||||
mAddress = nodebuffer;
|
||||
mPort = uint16_t(std::stoul(servbuffer));
|
||||
mFamily = p->ai_family == AF_INET6 ? Family::Ipv6 : Family::Ipv4;
|
||||
|
||||
const char sp{' '};
|
||||
std::ostringstream oss;
|
||||
oss << foundation << sp << component << sp << transport << sp << priority;
|
||||
oss << sp << nodebuffer << sp << servbuffer << sp << "typ" << sp << type;
|
||||
oss << left;
|
||||
mCandidate = oss.str();
|
||||
mIsResolved = true;
|
||||
|
||||
PLOG_VERBOSE << "Resolved candidate: " << mCandidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeaddrinfo(result);
|
||||
freeaddrinfo(result);
|
||||
}
|
||||
}
|
||||
|
||||
return mIsResolved;
|
||||
return mFamily != Family::Unresolved;
|
||||
}
|
||||
|
||||
bool Candidate::isResolved() const { return mIsResolved; }
|
||||
|
||||
string Candidate::candidate() const { return "candidate:" + mCandidate; }
|
||||
|
||||
string Candidate::mid() const { return mMid; }
|
||||
@ -133,38 +181,60 @@ Candidate::operator string() const {
|
||||
return line.str();
|
||||
}
|
||||
|
||||
bool Candidate::isResolved() const { return mFamily != Family::Unresolved; }
|
||||
|
||||
Candidate::Family Candidate::family() const { return mFamily; }
|
||||
|
||||
Candidate::Type Candidate::type() const { return mType; }
|
||||
|
||||
Candidate::TransportType Candidate::transportType() const { return mTransportType; }
|
||||
|
||||
std::optional<string> Candidate::address() const {
|
||||
return isResolved() ? std::make_optional(mAddress) : nullopt;
|
||||
}
|
||||
|
||||
std::optional<uint16_t> Candidate::port() const {
|
||||
return isResolved() ? std::make_optional(mPort) : nullopt;
|
||||
}
|
||||
|
||||
std::optional<uint32_t> Candidate::priority() const {
|
||||
return isResolved() ? std::make_optional(mPriority) : nullopt;
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::Candidate &candidate) {
|
||||
return out << std::string(candidate);
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::CandidateType &type) {
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::Candidate::Type &type) {
|
||||
switch (type) {
|
||||
case rtc::CandidateType::Host:
|
||||
return out << "Host";
|
||||
case rtc::CandidateType::PeerReflexive:
|
||||
return out << "PeerReflexive";
|
||||
case rtc::CandidateType::Relayed:
|
||||
return out << "Relayed";
|
||||
case rtc::CandidateType::ServerReflexive:
|
||||
return out << "ServerReflexive";
|
||||
case rtc::Candidate::Type::Host:
|
||||
return out << "host";
|
||||
case rtc::Candidate::Type::PeerReflexive:
|
||||
return out << "peer_reflexive";
|
||||
case rtc::Candidate::Type::ServerReflexive:
|
||||
return out << "server_reflexive";
|
||||
case rtc::Candidate::Type::Relayed:
|
||||
return out << "relayed";
|
||||
default:
|
||||
return out << "Unknown";
|
||||
return out << "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::CandidateTransportType &transportType) {
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::Candidate::TransportType &transportType) {
|
||||
switch (transportType) {
|
||||
case rtc::CandidateTransportType::TcpActive:
|
||||
return out << "TcpActive";
|
||||
case rtc::CandidateTransportType::TcpPassive:
|
||||
return out << "TcpPassive";
|
||||
case rtc::CandidateTransportType::TcpSo:
|
||||
return out << "TcpSo";
|
||||
case rtc::CandidateTransportType::Udp:
|
||||
return out << "Udp";
|
||||
case rtc::Candidate::TransportType::Udp:
|
||||
return out << "UDP";
|
||||
case rtc::Candidate::TransportType::TcpActive:
|
||||
return out << "TCP_active";
|
||||
case rtc::Candidate::TransportType::TcpPassive:
|
||||
return out << "TCP_passive";
|
||||
case rtc::Candidate::TransportType::TcpSo:
|
||||
return out << "TCP_so";
|
||||
case rtc::Candidate::TransportType::TcpUnknown:
|
||||
return out << "TCP_unknown";
|
||||
default:
|
||||
return out << "Unknown";
|
||||
return out << "unknown";
|
||||
}
|
||||
}
|
||||
|
162
src/capi.cpp
162
src/capi.cpp
@ -195,12 +195,17 @@ template <typename F> int wrap(F func) {
|
||||
return RTC_ERR_SUCCESS; \
|
||||
})
|
||||
|
||||
class plog_appender : public plog::IAppender {
|
||||
class plogAppender : public plog::IAppender {
|
||||
public:
|
||||
plog_appender(rtcLogCallbackFunc cb = nullptr) { set_callback(cb); }
|
||||
plogAppender(rtcLogCallbackFunc cb = nullptr) { setCallback(cb); }
|
||||
|
||||
void set_callback(rtcLogCallbackFunc cb) {
|
||||
std::lock_guard lock(mutex);
|
||||
plogAppender(plogAppender &&appender) : callback(nullptr) {
|
||||
std::lock_guard lock(appender.callbackMutex);
|
||||
std::swap(appender.callback, callback);
|
||||
}
|
||||
|
||||
void setCallback(rtcLogCallbackFunc cb) {
|
||||
std::lock_guard lock(callbackMutex);
|
||||
callback = cb;
|
||||
}
|
||||
|
||||
@ -215,7 +220,7 @@ public:
|
||||
#else
|
||||
std::string str = formatted;
|
||||
#endif
|
||||
std::lock_guard lock(mutex);
|
||||
std::lock_guard lock(callbackMutex);
|
||||
if (callback)
|
||||
callback(static_cast<rtcLogLevel>(record.getSeverity()), str.c_str());
|
||||
else
|
||||
@ -224,18 +229,24 @@ public:
|
||||
|
||||
private:
|
||||
rtcLogCallbackFunc callback;
|
||||
std::mutex callbackMutex;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb) {
|
||||
static std::optional<plog_appender> appender;
|
||||
if (appender)
|
||||
appender->set_callback(cb);
|
||||
else if (cb)
|
||||
appender.emplace(plog_appender(cb));
|
||||
|
||||
InitLogger(static_cast<plog::Severity>(level), appender ? &appender.value() : nullptr);
|
||||
static std::optional<plogAppender> appender;
|
||||
const auto severity = static_cast<plog::Severity>(level);
|
||||
std::lock_guard lock(mutex);
|
||||
if (appender) {
|
||||
appender->setCallback(cb);
|
||||
InitLogger(severity, nullptr); // change the severity
|
||||
} else if (cb) {
|
||||
appender.emplace(plogAppender(cb));
|
||||
InitLogger(severity, &appender.value());
|
||||
} else {
|
||||
InitLogger(severity, nullptr); // log to stdout
|
||||
}
|
||||
}
|
||||
|
||||
void rtcSetUserPointer(int i, void *ptr) { setUserPointer(i, ptr); }
|
||||
@ -306,7 +317,7 @@ int rtcCreateDataChannel(int pc, const char *label) {
|
||||
int rtcCreateDataChannelExt(int pc, const char *label, const char *protocol,
|
||||
const rtcReliability *reliability) {
|
||||
int dc = rtcAddDataChannelExt(pc, label, protocol, reliability);
|
||||
rtcSetLocalDescription(pc);
|
||||
rtcSetLocalDescription(pc, NULL);
|
||||
return dc;
|
||||
}
|
||||
|
||||
@ -411,7 +422,7 @@ int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc cb) {
|
||||
if (cb)
|
||||
peerConnection->onLocalDescription([pc, cb](Description desc) {
|
||||
if (auto ptr = getUserPointer(pc))
|
||||
cb(string(desc).c_str(), desc.typeString().c_str(), *ptr);
|
||||
cb(pc, string(desc).c_str(), desc.typeString().c_str(), *ptr);
|
||||
});
|
||||
else
|
||||
peerConnection->onLocalDescription(nullptr);
|
||||
@ -424,7 +435,7 @@ int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb) {
|
||||
if (cb)
|
||||
peerConnection->onLocalCandidate([pc, cb](Candidate cand) {
|
||||
if (auto ptr = getUserPointer(pc))
|
||||
cb(cand.candidate().c_str(), cand.mid().c_str(), *ptr);
|
||||
cb(pc, cand.candidate().c_str(), cand.mid().c_str(), *ptr);
|
||||
});
|
||||
else
|
||||
peerConnection->onLocalCandidate(nullptr);
|
||||
@ -437,7 +448,7 @@ int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb) {
|
||||
if (cb)
|
||||
peerConnection->onStateChange([pc, cb](PeerConnection::State state) {
|
||||
if (auto ptr = getUserPointer(pc))
|
||||
cb(static_cast<rtcState>(state), *ptr);
|
||||
cb(pc, static_cast<rtcState>(state), *ptr);
|
||||
});
|
||||
else
|
||||
peerConnection->onStateChange(nullptr);
|
||||
@ -450,7 +461,20 @@ int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb)
|
||||
if (cb)
|
||||
peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) {
|
||||
if (auto ptr = getUserPointer(pc))
|
||||
cb(static_cast<rtcGatheringState>(state), *ptr);
|
||||
cb(pc, static_cast<rtcGatheringState>(state), *ptr);
|
||||
});
|
||||
else
|
||||
peerConnection->onGatheringStateChange(nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
int rtcSetSignalingStateChangeCallback(int pc, rtcSignalingStateCallbackFunc cb) {
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
if (cb)
|
||||
peerConnection->onSignalingStateChange([pc, cb](PeerConnection::SignalingState state) {
|
||||
if (auto ptr = getUserPointer(pc))
|
||||
cb(pc, static_cast<rtcSignalingState>(state), *ptr);
|
||||
});
|
||||
else
|
||||
peerConnection->onGatheringStateChange(nullptr);
|
||||
@ -465,7 +489,7 @@ int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb) {
|
||||
int dc = emplaceDataChannel(dataChannel);
|
||||
if (auto ptr = getUserPointer(pc)) {
|
||||
rtcSetUserPointer(dc, *ptr);
|
||||
cb(dc, *ptr);
|
||||
cb(pc, dc, *ptr);
|
||||
}
|
||||
});
|
||||
else
|
||||
@ -481,7 +505,7 @@ int rtcSetTrackCallback(int pc, rtcTrackCallbackFunc cb) {
|
||||
int tr = emplaceTrack(track);
|
||||
if (auto ptr = getUserPointer(pc)) {
|
||||
rtcSetUserPointer(tr, *ptr);
|
||||
cb(tr, *ptr);
|
||||
cb(pc, tr, *ptr);
|
||||
}
|
||||
});
|
||||
else
|
||||
@ -489,10 +513,11 @@ int rtcSetTrackCallback(int pc, rtcTrackCallbackFunc cb) {
|
||||
});
|
||||
}
|
||||
|
||||
int rtcSetLocalDescription(int pc) {
|
||||
int rtcSetLocalDescription(int pc, const char *type) {
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
peerConnection->setLocalDescription();
|
||||
peerConnection->setLocalDescription(type ? Description::stringToType(type)
|
||||
: Description::Type::Unspec);
|
||||
});
|
||||
}
|
||||
|
||||
@ -518,6 +543,52 @@ int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid) {
|
||||
});
|
||||
}
|
||||
|
||||
int rtcGetLocalDescription(int pc, char *buffer, int size) {
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
|
||||
if (size <= 0)
|
||||
return 0;
|
||||
|
||||
if (!buffer)
|
||||
throw std::invalid_argument("Unexpected null pointer for buffer");
|
||||
|
||||
if (auto desc = peerConnection->localDescription()) {
|
||||
auto sdp = string(*desc);
|
||||
const char *data = sdp.data();
|
||||
size = std::min(size - 1, int(sdp.size()));
|
||||
std::copy(data, data + size, buffer);
|
||||
buffer[size] = '\0';
|
||||
return size + 1;
|
||||
}
|
||||
|
||||
return RTC_ERR_FAILURE;
|
||||
});
|
||||
}
|
||||
|
||||
int rtcGetRemoteDescription(int pc, char *buffer, int size) {
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
|
||||
if (size <= 0)
|
||||
return 0;
|
||||
|
||||
if (!buffer)
|
||||
throw std::invalid_argument("Unexpected null pointer for buffer");
|
||||
|
||||
if (auto desc = peerConnection->remoteDescription()) {
|
||||
auto sdp = string(*desc);
|
||||
const char *data = sdp.data();
|
||||
size = std::min(size - 1, int(sdp.size()));
|
||||
std::copy(data, data + size, buffer);
|
||||
buffer[size] = '\0';
|
||||
return size + 1;
|
||||
}
|
||||
|
||||
return RTC_ERR_FAILURE;
|
||||
});
|
||||
}
|
||||
|
||||
int rtcGetLocalAddress(int pc, char *buffer, int size) {
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
@ -535,6 +606,8 @@ int rtcGetLocalAddress(int pc, char *buffer, int size) {
|
||||
buffer[size] = '\0';
|
||||
return size + 1;
|
||||
}
|
||||
|
||||
return RTC_ERR_FAILURE;
|
||||
});
|
||||
}
|
||||
|
||||
@ -555,6 +628,39 @@ int rtcGetRemoteAddress(int pc, char *buffer, int size) {
|
||||
buffer[size] = '\0';
|
||||
return int(size + 1);
|
||||
}
|
||||
|
||||
return RTC_ERR_FAILURE;
|
||||
});
|
||||
}
|
||||
|
||||
int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote, int remoteSize) {
|
||||
return WRAP({
|
||||
auto peerConnection = getPeerConnection(pc);
|
||||
|
||||
if (!local)
|
||||
localSize = 0;
|
||||
if (!remote)
|
||||
remoteSize = 0;
|
||||
|
||||
Candidate localCand;
|
||||
Candidate remoteCand;
|
||||
if (peerConnection->getSelectedCandidatePair(&localCand, &remoteCand)) {
|
||||
if (localSize > 0) {
|
||||
string localSdp = string(localCand);
|
||||
localSize = std::min(localSize - 1, int(localSdp.size()));
|
||||
std::copy(localSdp.begin(), localSdp.begin() + localSize, local);
|
||||
local[localSize] = '\0';
|
||||
}
|
||||
if (remoteSize > 0) {
|
||||
string remoteSdp = string(remoteCand);
|
||||
remoteSize = std::min(remoteSize - 1, int(remoteSdp.size()));
|
||||
std::copy(remoteSdp.begin(), remoteSdp.begin() + remoteSize, remote);
|
||||
remote[remoteSize] = '\0';
|
||||
}
|
||||
return localSize + remoteSize;
|
||||
}
|
||||
|
||||
return RTC_ERR_FAILURE;
|
||||
});
|
||||
}
|
||||
|
||||
@ -625,7 +731,7 @@ int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb) {
|
||||
if (cb)
|
||||
channel->onOpen([id, cb]() {
|
||||
if (auto ptr = getUserPointer(id))
|
||||
cb(*ptr);
|
||||
cb(id, *ptr);
|
||||
});
|
||||
else
|
||||
channel->onOpen(nullptr);
|
||||
@ -638,7 +744,7 @@ int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb) {
|
||||
if (cb)
|
||||
channel->onClosed([id, cb]() {
|
||||
if (auto ptr = getUserPointer(id))
|
||||
cb(*ptr);
|
||||
cb(id, *ptr);
|
||||
});
|
||||
else
|
||||
channel->onClosed(nullptr);
|
||||
@ -651,7 +757,7 @@ int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb) {
|
||||
if (cb)
|
||||
channel->onError([id, cb](string error) {
|
||||
if (auto ptr = getUserPointer(id))
|
||||
cb(error.c_str(), *ptr);
|
||||
cb(id, error.c_str(), *ptr);
|
||||
});
|
||||
else
|
||||
channel->onError(nullptr);
|
||||
@ -665,11 +771,11 @@ int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb) {
|
||||
channel->onMessage(
|
||||
[id, cb](binary b) {
|
||||
if (auto ptr = getUserPointer(id))
|
||||
cb(reinterpret_cast<const char *>(b.data()), int(b.size()), *ptr);
|
||||
cb(id, reinterpret_cast<const char *>(b.data()), int(b.size()), *ptr);
|
||||
},
|
||||
[id, cb](string s) {
|
||||
if (auto ptr = getUserPointer(id))
|
||||
cb(s.c_str(), -int(s.size() + 1), *ptr);
|
||||
cb(id, s.c_str(), -int(s.size() + 1), *ptr);
|
||||
});
|
||||
else
|
||||
channel->onMessage(nullptr);
|
||||
@ -716,7 +822,7 @@ int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb)
|
||||
if (cb)
|
||||
channel->onBufferedAmountLow([id, cb]() {
|
||||
if (auto ptr = getUserPointer(id))
|
||||
cb(*ptr);
|
||||
cb(id, *ptr);
|
||||
});
|
||||
else
|
||||
channel->onBufferedAmountLow(nullptr);
|
||||
@ -733,7 +839,7 @@ int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb) {
|
||||
if (cb)
|
||||
channel->onOpen([id, cb]() {
|
||||
if (auto ptr = getUserPointer(id))
|
||||
cb(*ptr);
|
||||
cb(id, *ptr);
|
||||
});
|
||||
else
|
||||
channel->onOpen(nullptr);
|
||||
|
@ -99,7 +99,11 @@ certificate_ptr make_certificate_impl(string commonName) {
|
||||
unique_ptr<gnutls_x509_crt_t, decltype(&free_crt)> crt(new_crt(), free_crt);
|
||||
unique_ptr<gnutls_x509_privkey_t, decltype(&free_privkey)> privkey(new_privkey(), free_privkey);
|
||||
|
||||
#ifdef RSA_KEY_BITS_2048
|
||||
const unsigned int bits = 2048;
|
||||
#else
|
||||
const unsigned int bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_HIGH);
|
||||
#endif
|
||||
gnutls::check(gnutls_x509_privkey_generate(*privkey, GNUTLS_PK_RSA, bits, 0),
|
||||
"Unable to generate key pair");
|
||||
|
||||
@ -190,7 +194,11 @@ certificate_ptr make_certificate_impl(string commonName) {
|
||||
if (!x509 || !pkey || !rsa || !exponent || !serial_number || !name)
|
||||
throw std::runtime_error("Unable allocate structures for certificate generation");
|
||||
|
||||
const int bits = 4096;
|
||||
#ifdef RSA_KEY_BITS_2048
|
||||
const int bits = 2048;
|
||||
#else
|
||||
const int bits = 3072;
|
||||
#endif
|
||||
const unsigned int e = 65537; // 2^16 + 1
|
||||
|
||||
if (!pkey || !rsa || !exponent || !BN_set_word(exponent.get(), e) ||
|
||||
|
@ -122,8 +122,8 @@ bool DataChannel::send(const byte *data, size_t size) {
|
||||
}
|
||||
|
||||
std::optional<message_variant> DataChannel::receive() {
|
||||
while (!mRecvQueue.empty()) {
|
||||
auto message = *mRecvQueue.pop();
|
||||
while (auto next = mRecvQueue.tryPop()) {
|
||||
message_ptr message = std::move(*next);
|
||||
if (message->type == Message::Control) {
|
||||
auto raw = reinterpret_cast<const uint8_t *>(message->data());
|
||||
if (!message->empty() && raw[0] == MESSAGE_CLOSE)
|
||||
@ -218,6 +218,8 @@ void DataChannel::incoming(message_ptr message) {
|
||||
|
||||
switch (message->type) {
|
||||
case Message::Control: {
|
||||
if (message->size() == 0)
|
||||
break; // Ignore
|
||||
auto raw = reinterpret_cast<const uint8_t *>(message->data());
|
||||
switch (raw[0]) {
|
||||
case MESSAGE_OPEN:
|
||||
|
@ -25,6 +25,8 @@
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
//#include <sstream>
|
||||
//#include <unordered_map>
|
||||
|
||||
using std::shared_ptr;
|
||||
using std::size_t;
|
||||
@ -73,27 +75,24 @@ Description::Description(const string &sdp, Type type, Role role)
|
||||
: mType(Type::Unspec), mRole(role) {
|
||||
hintType(type);
|
||||
|
||||
auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
|
||||
std::default_random_engine generator(seed);
|
||||
std::uniform_int_distribution<uint32_t> uniform;
|
||||
mSessionId = std::to_string(uniform(generator));
|
||||
|
||||
std::istringstream ss(sdp);
|
||||
std::shared_ptr<Entry> current;
|
||||
|
||||
int index = -1;
|
||||
string line;
|
||||
while (std::getline(ss, line) || !line.empty()) {
|
||||
std::shared_ptr<Entry> current;
|
||||
std::istringstream ss(sdp);
|
||||
while (ss) {
|
||||
string line;
|
||||
std::getline(ss, line);
|
||||
trim_end(line);
|
||||
if (line.empty())
|
||||
continue;
|
||||
|
||||
// Media description line (aka m-line)
|
||||
if (match_prefix(line, "m=")) {
|
||||
++index;
|
||||
string mline = line.substr(2);
|
||||
current = createEntry(std::move(mline), std::to_string(index), Direction::Unknown);
|
||||
if (match_prefix(line, "m=")) { // Media description line (aka m-line)
|
||||
current = createEntry(line.substr(2), std::to_string(++index), Direction::Unknown);
|
||||
|
||||
// Attribute line
|
||||
} else if (match_prefix(line, "a=")) {
|
||||
} else if (match_prefix(line, "o=")) { // Origin line
|
||||
std::istringstream origin(line.substr(2));
|
||||
origin >> mUsername >> mSessionId;
|
||||
|
||||
} else if (match_prefix(line, "a=")) { // Attribute line
|
||||
string attr = line.substr(2);
|
||||
auto [key, value] = parse_pair(attr);
|
||||
|
||||
@ -129,7 +128,17 @@ Description::Description(const string &sdp, Type type, Role role)
|
||||
} else if (current) {
|
||||
current->parseSdpLine(std::move(line));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (mUsername.empty())
|
||||
mUsername = "rtc";
|
||||
|
||||
if (mSessionId.empty()) {
|
||||
auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
|
||||
std::default_random_engine generator(seed);
|
||||
std::uniform_int_distribution<uint32_t> uniform;
|
||||
mSessionId = std::to_string(uniform(generator));
|
||||
}
|
||||
}
|
||||
|
||||
Description::Type Description::type() const { return mType; }
|
||||
@ -138,13 +147,15 @@ string Description::typeString() const { return typeToString(mType); }
|
||||
|
||||
Description::Role Description::role() const { return mRole; }
|
||||
|
||||
string Description::roleString() const { return roleToString(mRole); }
|
||||
|
||||
string Description::bundleMid() const {
|
||||
// Get the mid of the first media
|
||||
return !mEntries.empty() ? mEntries[0]->mid() : "0";
|
||||
}
|
||||
|
||||
std::optional<string> Description::iceUfrag() const { return mIceUfrag; }
|
||||
|
||||
std::optional<string> Description::icePwd() const { return mIcePwd; }
|
||||
|
||||
std::optional<string> Description::fingerprint() const { return mFingerprint; }
|
||||
|
||||
bool Description::ended() const { return mEnded; }
|
||||
@ -165,6 +176,11 @@ void Description::addCandidate(Candidate candidate) {
|
||||
mCandidates.emplace_back(std::move(candidate));
|
||||
}
|
||||
|
||||
void Description::addCandidates(std::vector<Candidate> candidates) {
|
||||
for(auto candidate : candidates)
|
||||
mCandidates.emplace_back(std::move(candidate));
|
||||
}
|
||||
|
||||
void Description::endCandidates() { mEnded = true; }
|
||||
|
||||
std::vector<Candidate> Description::extractCandidates() {
|
||||
@ -181,7 +197,7 @@ string Description::generateSdp(string_view eol) const {
|
||||
|
||||
// Header
|
||||
sdp << "v=0" << eol;
|
||||
sdp << "o=- " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
|
||||
sdp << "o=" << mUsername << " " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
|
||||
sdp << "s=-" << eol;
|
||||
sdp << "t=0 0" << eol;
|
||||
|
||||
@ -204,20 +220,29 @@ string Description::generateSdp(string_view eol) const {
|
||||
|
||||
// Session-level attributes
|
||||
sdp << "a=msid-semantic:WMS *" << eol;
|
||||
sdp << "a=setup:" << roleToString(mRole) << eol;
|
||||
sdp << "a=ice-ufrag:" << mIceUfrag << eol;
|
||||
sdp << "a=ice-pwd:" << mIcePwd << eol;
|
||||
sdp << "a=setup:" << mRole << eol;
|
||||
|
||||
if (mIceUfrag)
|
||||
sdp << "a=ice-ufrag:" << *mIceUfrag << eol;
|
||||
if (mIcePwd)
|
||||
sdp << "a=ice-pwd:" << *mIcePwd << eol;
|
||||
if (!mEnded)
|
||||
sdp << "a=ice-options:trickle" << eol;
|
||||
|
||||
if (mFingerprint)
|
||||
sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
|
||||
|
||||
auto cand = defaultCandidate();
|
||||
const string addr = cand && cand->isResolved()
|
||||
? (string(cand->family() == Candidate::Family::Ipv6 ? "IP6" : "IP4") +
|
||||
" " + *cand->address())
|
||||
: "IP4 0.0.0.0";
|
||||
const string port = std::to_string(
|
||||
cand && cand->isResolved() ? *cand->port() : 9); // Port 9 is the discard protocol
|
||||
|
||||
// Entries
|
||||
bool first = true;
|
||||
for (const auto &entry : mEntries) {
|
||||
sdp << entry->generateSdp(eol);
|
||||
sdp << entry->generateSdp(eol, addr, port);
|
||||
|
||||
if (std::exchange(first, false)) {
|
||||
// Candidates
|
||||
@ -237,23 +262,32 @@ string Description::generateApplicationSdp(string_view eol) const {
|
||||
|
||||
// Header
|
||||
sdp << "v=0" << eol;
|
||||
sdp << "o=- " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
|
||||
sdp << "o=" << mUsername << " " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
|
||||
sdp << "s=-" << eol;
|
||||
sdp << "t=0 0" << eol;
|
||||
|
||||
auto cand = defaultCandidate();
|
||||
const string addr = cand && cand->isResolved()
|
||||
? (string(cand->family() == Candidate::Family::Ipv6 ? "IP6" : "IP4") +
|
||||
" " + *cand->address())
|
||||
: "IP4 0.0.0.0";
|
||||
const string port = std::to_string(
|
||||
cand && cand->isResolved() ? *cand->port() : 9); // Port 9 is the discard protocol
|
||||
|
||||
// Application
|
||||
auto app = mApplication ? mApplication : std::make_shared<Application>();
|
||||
sdp << app->generateSdp(eol);
|
||||
sdp << app->generateSdp(eol, addr, port);
|
||||
|
||||
// Session-level attributes
|
||||
sdp << "a=msid-semantic:WMS *" << eol;
|
||||
sdp << "a=setup:" << roleToString(mRole) << eol;
|
||||
sdp << "a=ice-ufrag:" << mIceUfrag << eol;
|
||||
sdp << "a=ice-pwd:" << mIcePwd << eol;
|
||||
sdp << "a=setup:" << mRole << eol;
|
||||
|
||||
if (mIceUfrag)
|
||||
sdp << "a=ice-ufrag:" << *mIceUfrag << eol;
|
||||
if (mIcePwd)
|
||||
sdp << "a=ice-pwd:" << *mIcePwd << eol;
|
||||
if (!mEnded)
|
||||
sdp << "a=ice-options:trickle" << eol;
|
||||
|
||||
if (mFingerprint)
|
||||
sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
|
||||
|
||||
@ -267,6 +301,21 @@ string Description::generateApplicationSdp(string_view eol) const {
|
||||
return sdp.str();
|
||||
}
|
||||
|
||||
std::optional<Candidate> Description::defaultCandidate() const {
|
||||
// Return the first host candidate with highest priority, favoring IPv4
|
||||
std::optional<Candidate> result;
|
||||
for (const auto &c : mCandidates) {
|
||||
if (c.type() == Candidate::Type::Host) {
|
||||
if (!result ||
|
||||
(result->family() == Candidate::Family::Ipv6 &&
|
||||
c.family() == Candidate::Family::Ipv4) ||
|
||||
(result->family() == c.family() && result->priority() < c.priority()))
|
||||
result.emplace(c);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
shared_ptr<Description::Entry> Description::createEntry(string mline, string mid, Direction dir) {
|
||||
string type = mline.substr(0, mline.find(' '));
|
||||
if (type == "application") {
|
||||
@ -302,6 +351,14 @@ bool Description::hasAudioOrVideo() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Description::hasMid(string_view mid) const {
|
||||
for (const auto &entry : mEntries)
|
||||
if (entry->mid() == mid)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int Description::addMedia(Media media) {
|
||||
mEntries.emplace_back(std::make_shared<Media>(std::move(media)));
|
||||
return int(mEntries.size()) - 1;
|
||||
@ -363,7 +420,7 @@ Description::media(int index) const {
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Description::mediaCount() const { return mEntries.size(); }
|
||||
size_t Description::mediaCount() const { return mEntries.size(); }
|
||||
|
||||
Description::Entry::Entry(const string &mline, string mid, Direction dir)
|
||||
: mMid(std::move(mid)), mDirection(dir) {
|
||||
@ -378,13 +435,12 @@ Description::Entry::Entry(const string &mline, string mid, Direction dir)
|
||||
|
||||
void Description::Entry::setDirection(Direction dir) { mDirection = dir; }
|
||||
|
||||
Description::Entry::operator string() const { return generateSdp("\r\n"); }
|
||||
Description::Entry::operator string() const { return generateSdp("\r\n", "IP4 0.0.0.0", "9"); }
|
||||
|
||||
string Description::Entry::generateSdp(string_view eol) const {
|
||||
string Description::Entry::generateSdp(string_view eol, string_view addr, string_view port) const {
|
||||
std::ostringstream sdp;
|
||||
// Port 9 is the discard protocol
|
||||
sdp << "m=" << type() << ' ' << 9 << ' ' << description() << eol;
|
||||
sdp << "c=IN IP4 0.0.0.0" << eol;
|
||||
sdp << "m=" << type() << ' ' << port << ' ' << description() << eol;
|
||||
sdp << "c=IN " << addr << eol;
|
||||
sdp << generateSdpLines(eol);
|
||||
|
||||
return sdp.str();
|
||||
@ -515,9 +571,13 @@ void Description::Application::parseSdpLine(string_view line) {
|
||||
|
||||
Description::Media::Media(const string &sdp) : Entry(sdp, "", Direction::Unknown) {
|
||||
std::istringstream ss(sdp);
|
||||
string line;
|
||||
while (std::getline(ss, line) || !line.empty()) {
|
||||
while (ss) {
|
||||
string line;
|
||||
std::getline(ss, line);
|
||||
trim_end(line);
|
||||
if (line.empty())
|
||||
continue;
|
||||
|
||||
parseSdpLine(line);
|
||||
}
|
||||
|
||||
@ -526,8 +586,7 @@ Description::Media::Media(const string &sdp) : Entry(sdp, "", Direction::Unknown
|
||||
}
|
||||
|
||||
Description::Media::Media(const string &mline, string mid, Direction dir)
|
||||
: Entry(mline, std::move(mid), dir) {
|
||||
}
|
||||
: Entry(mline, std::move(mid), dir) {}
|
||||
|
||||
string Description::Media::description() const {
|
||||
std::ostringstream desc;
|
||||
@ -796,33 +855,30 @@ Description::Video::Video(string mid, Direction dir)
|
||||
: Media("video 9 UDP/TLS/RTP/SAVPF", std::move(mid), dir) {}
|
||||
|
||||
Description::Type Description::stringToType(const string &typeString) {
|
||||
if (typeString == "offer")
|
||||
return Type::Offer;
|
||||
else if (typeString == "answer")
|
||||
return Type::Answer;
|
||||
else
|
||||
return Type::Unspec;
|
||||
using TypeMap_t = std::unordered_map<string, Type>;
|
||||
static const TypeMap_t TypeMap = {{"unspec", Type::Unspec},
|
||||
{"offer", Type::Offer},
|
||||
{"answer", Type::Pranswer},
|
||||
{"pranswer", Type::Pranswer},
|
||||
{"rollback", Type::Rollback}};
|
||||
auto it = TypeMap.find(typeString);
|
||||
return it != TypeMap.end() ? it->second : Type::Unspec;
|
||||
}
|
||||
|
||||
string Description::typeToString(Type type) {
|
||||
switch (type) {
|
||||
case Type::Unspec:
|
||||
return "unspec";
|
||||
case Type::Offer:
|
||||
return "offer";
|
||||
case Type::Answer:
|
||||
return "answer";
|
||||
case Type::Pranswer:
|
||||
return "pranswer";
|
||||
case Type::Rollback:
|
||||
return "rollback";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
string Description::roleToString(Role role) {
|
||||
switch (role) {
|
||||
case Role::Active:
|
||||
return "active";
|
||||
case Role::Passive:
|
||||
return "passive";
|
||||
default:
|
||||
return "actpass";
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
@ -831,3 +887,25 @@ string Description::roleToString(Role role) {
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::Description &description) {
|
||||
return out << std::string(description);
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, rtc::Description::Type type) {
|
||||
return out << rtc::Description::typeToString(type);
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, rtc::Description::Role role) {
|
||||
using Role = rtc::Description::Role;
|
||||
const char *str;
|
||||
// Used for SDP generation, do not change
|
||||
switch (role) {
|
||||
case Role::Active:
|
||||
str = "active";
|
||||
break;
|
||||
case Role::Passive:
|
||||
str = "passive";
|
||||
break;
|
||||
default:
|
||||
str = "actpass";
|
||||
break;
|
||||
}
|
||||
return out << str;
|
||||
}
|
||||
|
@ -24,8 +24,11 @@
|
||||
|
||||
#if RTC_ENABLE_MEDIA
|
||||
|
||||
#ifdef RTC_SRTP_FROM_SOURCE
|
||||
#include "srtp.h"
|
||||
|
||||
#else
|
||||
#include <srtp2/srtp.h>
|
||||
#endif
|
||||
#include <atomic>
|
||||
|
||||
namespace rtc {
|
||||
|
@ -258,7 +258,7 @@ ssize_t DtlsTransport::WriteCallback(gnutls_transport_ptr_t ptr, const void *dat
|
||||
ssize_t DtlsTransport::ReadCallback(gnutls_transport_ptr_t ptr, void *data, size_t maxlen) {
|
||||
DtlsTransport *t = static_cast<DtlsTransport *>(ptr);
|
||||
if (auto next = t->mIncomingQueue.pop()) {
|
||||
auto message = *next;
|
||||
message_ptr message = std::move(*next);
|
||||
ssize_t len = std::min(maxlen, message->size());
|
||||
std::memcpy(data, message->data(), len);
|
||||
gnutls_transport_set_errno(t->mSession, 0);
|
||||
@ -271,9 +271,9 @@ ssize_t DtlsTransport::ReadCallback(gnutls_transport_ptr_t ptr, void *data, size
|
||||
|
||||
int DtlsTransport::TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms) {
|
||||
DtlsTransport *t = static_cast<DtlsTransport *>(ptr);
|
||||
t->mIncomingQueue.wait(ms != GNUTLS_INDEFINITE_TIMEOUT ? std::make_optional(milliseconds(ms))
|
||||
: nullopt);
|
||||
return !t->mIncomingQueue.empty() ? 1 : 0;
|
||||
bool notEmpty = t->mIncomingQueue.wait(
|
||||
ms != GNUTLS_INDEFINITE_TIMEOUT ? std::make_optional(milliseconds(ms)) : nullopt);
|
||||
return notEmpty ? 1 : 0;
|
||||
}
|
||||
|
||||
#else // USE_GNUTLS==0
|
||||
@ -435,10 +435,10 @@ void DtlsTransport::runRecvLoop() {
|
||||
|
||||
const size_t bufferSize = maxMtu;
|
||||
byte buffer[bufferSize];
|
||||
while (true) {
|
||||
while (mIncomingQueue.running()) {
|
||||
// Process pending messages
|
||||
while (!mIncomingQueue.empty()) {
|
||||
auto message = *mIncomingQueue.pop();
|
||||
while (auto next = mIncomingQueue.tryPop()) {
|
||||
message_ptr message = std::move(*next);
|
||||
BIO_write(mInBio, message->data(), int(message->size()));
|
||||
|
||||
if (state() == State::Connecting) {
|
||||
@ -492,8 +492,7 @@ void DtlsTransport::runRecvLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!mIncomingQueue.wait(duration))
|
||||
break; // queue is stopped
|
||||
mIncomingQueue.wait(duration);
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_ERROR << "DTLS recv: " << e.what();
|
||||
|
@ -58,8 +58,32 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
if (config.enableIceTcp) {
|
||||
PLOG_WARNING << "ICE-TCP is not supported with libjuice";
|
||||
}
|
||||
|
||||
juice_log_level_t level;
|
||||
auto logger = plog::get();
|
||||
switch (logger ? logger->getMaxSeverity() : plog::none) {
|
||||
case plog::none:
|
||||
level = JUICE_LOG_LEVEL_NONE;
|
||||
break;
|
||||
case plog::fatal:
|
||||
level = JUICE_LOG_LEVEL_VERBOSE;
|
||||
break;
|
||||
case plog::error:
|
||||
level = JUICE_LOG_LEVEL_ERROR;
|
||||
break;
|
||||
case plog::warning:
|
||||
level = JUICE_LOG_LEVEL_WARN;
|
||||
break;
|
||||
case plog::info:
|
||||
case plog::debug: // juice debug is output as verbose
|
||||
level = JUICE_LOG_LEVEL_INFO;
|
||||
break;
|
||||
default:
|
||||
level = JUICE_LOG_LEVEL_VERBOSE;
|
||||
break;
|
||||
}
|
||||
juice_set_log_handler(IceTransport::LogCallback);
|
||||
juice_set_log_level(JUICE_LOG_LEVEL_VERBOSE);
|
||||
juice_set_log_level(level);
|
||||
|
||||
juice_config_t jconfig = {};
|
||||
jconfig.cb_state_changed = IceTransport::StateChangeCallback;
|
||||
@ -163,6 +187,24 @@ std::optional<string> IceTransport::getRemoteAddress() const {
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
bool IceTransport::getSelectedCandidatePair(Candidate *local, Candidate *remote) {
|
||||
char sdpLocal[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
|
||||
char sdpRemote[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
|
||||
if (juice_get_selected_candidates(mAgent.get(), sdpLocal, JUICE_MAX_CANDIDATE_SDP_STRING_LEN,
|
||||
sdpRemote, JUICE_MAX_CANDIDATE_SDP_STRING_LEN) == 0) {
|
||||
if (local) {
|
||||
*local = Candidate(sdpLocal, mMid);
|
||||
local->resolve(Candidate::ResolveMode::Simple);
|
||||
}
|
||||
if (remote) {
|
||||
*remote = Candidate(sdpRemote, mMid);
|
||||
remote->resolve(Candidate::ResolveMode::Simple);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IceTransport::send(message_ptr message) {
|
||||
auto s = state();
|
||||
if (!message || (s != State::Connected && s != State::Completed))
|
||||
@ -357,8 +399,11 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
hints.ai_protocol = IPPROTO_UDP;
|
||||
hints.ai_flags = AI_ADDRCONFIG;
|
||||
struct addrinfo *result = nullptr;
|
||||
if (getaddrinfo(server.hostname.c_str(), server.service.c_str(), &hints, &result) != 0)
|
||||
if (getaddrinfo(server.hostname.c_str(), server.service.c_str(), &hints, &result) != 0) {
|
||||
PLOG_WARNING << "Unable to resolve STUN server address: " << server.hostname << ':'
|
||||
<< server.service;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto p = result; p; p = p->ai_next) {
|
||||
if (p->ai_family == AF_INET) {
|
||||
@ -400,8 +445,11 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
||||
server.relayType == IceServer::RelayType::TurnUdp ? IPPROTO_UDP : IPPROTO_TCP;
|
||||
hints.ai_flags = AI_ADDRCONFIG;
|
||||
struct addrinfo *result = nullptr;
|
||||
if (getaddrinfo(server.hostname.c_str(), server.service.c_str(), &hints, &result) != 0)
|
||||
if (getaddrinfo(server.hostname.c_str(), server.service.c_str(), &hints, &result) != 0) {
|
||||
PLOG_WARNING << "Unable to resolve TURN server address: " << server.hostname << ':'
|
||||
<< server.service;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto p = result; p; p = p->ai_next) {
|
||||
if (p->ai_family == AF_INET || p->ai_family == AF_INET6) {
|
||||
@ -686,58 +734,26 @@ void IceTransport::LogCallback(const gchar * /*logDomain*/, GLogLevelFlags logLe
|
||||
PLOG(severity) << "nice: " << message;
|
||||
}
|
||||
|
||||
bool IceTransport::getSelectedCandidatePair(CandidateInfo *localInfo, CandidateInfo *remoteInfo) {
|
||||
NiceCandidate *local, *remote;
|
||||
gboolean result = nice_agent_get_selected_pair(mNiceAgent.get(), mStreamId, 1, &local, &remote);
|
||||
|
||||
if (!result)
|
||||
bool IceTransport::getSelectedCandidatePair(Candidate *local, Candidate *remote) {
|
||||
NiceCandidate *niceLocal, *niceRemote;
|
||||
if(!nice_agent_get_selected_pair(mNiceAgent.get(), mStreamId, 1, &niceLocal, &niceRemote))
|
||||
return false;
|
||||
|
||||
char ipaddr[INET6_ADDRSTRLEN];
|
||||
nice_address_to_string(&local->addr, ipaddr);
|
||||
localInfo->address = std::string(ipaddr);
|
||||
localInfo->port = nice_address_get_port(&local->addr);
|
||||
localInfo->type = IceTransport::NiceTypeToCandidateType(local->type);
|
||||
localInfo->transportType =
|
||||
IceTransport::NiceTransportTypeToCandidateTransportType(local->transport);
|
||||
gchar *sdpLocal = nice_agent_generate_local_candidate_sdp(mNiceAgent.get(), niceLocal);
|
||||
if(local) *local = Candidate(sdpLocal, mMid);
|
||||
g_free(sdpLocal);
|
||||
|
||||
nice_address_to_string(&remote->addr, ipaddr);
|
||||
remoteInfo->address = std::string(ipaddr);
|
||||
remoteInfo->port = nice_address_get_port(&remote->addr);
|
||||
remoteInfo->type = IceTransport::NiceTypeToCandidateType(remote->type);
|
||||
remoteInfo->transportType =
|
||||
IceTransport::NiceTransportTypeToCandidateTransportType(remote->transport);
|
||||
gchar *sdpRemote = nice_agent_generate_local_candidate_sdp(mNiceAgent.get(), niceRemote);
|
||||
if(remote) *remote = Candidate(sdpRemote, mMid);
|
||||
g_free(sdpRemote);
|
||||
|
||||
if (local)
|
||||
local->resolve(Candidate::ResolveMode::Simple);
|
||||
if (remote)
|
||||
remote->resolve(Candidate::ResolveMode::Simple);
|
||||
return true;
|
||||
}
|
||||
|
||||
CandidateType IceTransport::NiceTypeToCandidateType(NiceCandidateType type) {
|
||||
switch (type) {
|
||||
case NiceCandidateType::NICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
|
||||
return CandidateType::PeerReflexive;
|
||||
case NiceCandidateType::NICE_CANDIDATE_TYPE_RELAYED:
|
||||
return CandidateType::Relayed;
|
||||
case NiceCandidateType::NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
|
||||
return CandidateType::ServerReflexive;
|
||||
default:
|
||||
return CandidateType::Host;
|
||||
}
|
||||
}
|
||||
|
||||
CandidateTransportType
|
||||
IceTransport::NiceTransportTypeToCandidateTransportType(NiceCandidateTransport type) {
|
||||
switch (type) {
|
||||
case NiceCandidateTransport::NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE:
|
||||
return CandidateTransportType::TcpActive;
|
||||
case NiceCandidateTransport::NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE:
|
||||
return CandidateTransportType::TcpPassive;
|
||||
case NiceCandidateTransport::NICE_CANDIDATE_TRANSPORT_TCP_SO:
|
||||
return CandidateTransportType::TcpSo;
|
||||
default:
|
||||
return CandidateTransportType::Udp;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
@ -63,9 +63,7 @@ public:
|
||||
bool stop() override;
|
||||
bool send(message_ptr message) override; // false if dropped
|
||||
|
||||
#if USE_NICE
|
||||
bool getSelectedCandidatePair(CandidateInfo *local, CandidateInfo *remote);
|
||||
#endif
|
||||
bool getSelectedCandidatePair(Candidate *local, Candidate *remote);
|
||||
|
||||
private:
|
||||
bool outgoing(message_ptr message) override;
|
||||
@ -113,9 +111,6 @@ private:
|
||||
static gboolean TimeoutCallback(gpointer userData);
|
||||
static void LogCallback(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message,
|
||||
gpointer user_data);
|
||||
static CandidateType NiceTypeToCandidateType(NiceCandidateType type);
|
||||
static CandidateTransportType
|
||||
NiceTransportTypeToCandidateTransportType(NiceCandidateTransport type);
|
||||
#endif
|
||||
};
|
||||
|
||||
|
@ -24,6 +24,8 @@
|
||||
#include "plog/Log.h"
|
||||
#include "plog/Logger.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace rtc {
|
||||
|
||||
void InitLogger(LogLevel level) { InitLogger(static_cast<plog::Severity>(level)); }
|
||||
@ -31,6 +33,8 @@ void InitLogger(LogLevel level) { InitLogger(static_cast<plog::Severity>(level))
|
||||
void InitLogger(plog::Severity severity, plog::IAppender *appender) {
|
||||
static plog::ColorConsoleAppender<plog::TxtFormatter> consoleAppender;
|
||||
static plog::Logger<0> *logger = nullptr;
|
||||
static std::mutex mutex;
|
||||
std::lock_guard lock(mutex);
|
||||
if (!logger) {
|
||||
logger = &plog::init(severity, appender ? appender : &consoleAppender);
|
||||
PLOG_DEBUG << "Logger initialized";
|
||||
|
@ -44,7 +44,8 @@ PeerConnection::PeerConnection() : PeerConnection(Configuration()) {}
|
||||
|
||||
PeerConnection::PeerConnection(const Configuration &config)
|
||||
: mConfig(config), mCertificate(make_certificate()), mProcessor(std::make_unique<Processor>()),
|
||||
mState(State::New), mGatheringState(GatheringState::New) {
|
||||
mState(State::New), mGatheringState(GatheringState::New),
|
||||
mSignalingState(SignalingState::Stable), mNegotiationNeeded(false) {
|
||||
PLOG_VERBOSE << "Creating PeerConnection";
|
||||
|
||||
if (config.portRangeEnd && config.portRangeBegin > config.portRangeEnd)
|
||||
@ -60,6 +61,8 @@ PeerConnection::~PeerConnection() {
|
||||
void PeerConnection::close() {
|
||||
PLOG_VERBOSE << "Closing PeerConnection";
|
||||
|
||||
mNegotiationNeeded = false;
|
||||
|
||||
// Close data channels asynchronously
|
||||
mProcessor->enqueue(std::bind(&PeerConnection::closeDataChannels, this));
|
||||
|
||||
@ -72,6 +75,8 @@ PeerConnection::State PeerConnection::state() const { return mState; }
|
||||
|
||||
PeerConnection::GatheringState PeerConnection::gatheringState() const { return mGatheringState; }
|
||||
|
||||
PeerConnection::SignalingState PeerConnection::signalingState() const { return mSignalingState; }
|
||||
|
||||
std::optional<Description> PeerConnection::localDescription() const {
|
||||
std::lock_guard lock(mLocalDescriptionMutex);
|
||||
return mLocalDescription;
|
||||
@ -82,66 +87,194 @@ std::optional<Description> PeerConnection::remoteDescription() const {
|
||||
return mRemoteDescription;
|
||||
}
|
||||
|
||||
void PeerConnection::setLocalDescription() {
|
||||
PLOG_VERBOSE << "Setting local description";
|
||||
bool PeerConnection::hasLocalDescription() const {
|
||||
std::lock_guard lock(mLocalDescriptionMutex);
|
||||
return bool(mLocalDescription);
|
||||
}
|
||||
|
||||
if (!std::atomic_load(&mIceTransport)) {
|
||||
// RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of
|
||||
// setup:actpass.
|
||||
// See https://tools.ietf.org/html/rfc5763#section-5
|
||||
auto iceTransport = initIceTransport(Description::Role::ActPass);
|
||||
Description localDescription = iceTransport->getLocalDescription(Description::Type::Offer);
|
||||
processLocalDescription(localDescription);
|
||||
iceTransport->gatherLocalCandidates();
|
||||
} else {
|
||||
auto localDescription = std::atomic_load(&mIceTransport)->getLocalDescription(Description::Type::Offer);
|
||||
processLocalDescription(localDescription);
|
||||
bool PeerConnection::hasRemoteDescription() const {
|
||||
std::lock_guard lock(mRemoteDescriptionMutex);
|
||||
return bool(mRemoteDescription);
|
||||
}
|
||||
|
||||
bool PeerConnection::hasMedia() const {
|
||||
auto local = localDescription();
|
||||
return local && local->hasAudioOrVideo();
|
||||
}
|
||||
|
||||
void PeerConnection::setLocalDescription(Description::Type type) {
|
||||
PLOG_VERBOSE << "Setting local description, type=" << Description::typeToString(type);
|
||||
|
||||
|
||||
SignalingState signalingState = mSignalingState.load();
|
||||
if (type == Description::Type::Rollback) {
|
||||
if (signalingState == SignalingState::HaveLocalOffer ||
|
||||
signalingState == SignalingState::HaveLocalPranswer) {
|
||||
PLOG_DEBUG << "Rolling back pending local description";
|
||||
|
||||
std::unique_lock lock(mLocalDescriptionMutex);
|
||||
if (mCurrentLocalDescription) {
|
||||
std::vector<Candidate> existingCandidates;
|
||||
if (mLocalDescription)
|
||||
existingCandidates = mLocalDescription->extractCandidates();
|
||||
|
||||
mLocalDescription.emplace(std::move(*mCurrentLocalDescription));
|
||||
mLocalDescription->addCandidates(std::move(existingCandidates));
|
||||
mCurrentLocalDescription.reset();
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
changeSignalingState(SignalingState::Stable);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Guess the description type if unspecified
|
||||
if (type == Description::Type::Unspec) {
|
||||
if (mSignalingState == SignalingState::HaveRemoteOffer)
|
||||
type = Description::Type::Answer;
|
||||
else
|
||||
type = Description::Type::Offer;
|
||||
}
|
||||
|
||||
// Only a local offer resets the negotiation needed flag
|
||||
if (type == Description::Type::Offer && !mNegotiationNeeded.exchange(false)) {
|
||||
PLOG_DEBUG << "No negotiation needed";
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the new signaling state
|
||||
SignalingState newSignalingState;
|
||||
switch (signalingState) {
|
||||
case SignalingState::Stable:
|
||||
if (type != Description::Type::Offer) {
|
||||
std::ostringstream oss;
|
||||
oss << "Unexpected local desciption type " << type << " in signaling state "
|
||||
<< signalingState;
|
||||
throw std::logic_error(oss.str());
|
||||
}
|
||||
newSignalingState = SignalingState::HaveLocalOffer;
|
||||
break;
|
||||
|
||||
case SignalingState::HaveRemoteOffer:
|
||||
case SignalingState::HaveLocalPranswer:
|
||||
if (type != Description::Type::Answer && type != Description::Type::Pranswer) {
|
||||
std::ostringstream oss;
|
||||
oss << "Unexpected local description type " << type
|
||||
<< " description in signaling state " << signalingState;
|
||||
throw std::logic_error(oss.str());
|
||||
}
|
||||
newSignalingState = SignalingState::Stable;
|
||||
break;
|
||||
|
||||
default: {
|
||||
std::ostringstream oss;
|
||||
oss << "Unexpected local description in signaling state " << signalingState << ", ignoring";
|
||||
LOG_WARNING << oss.str();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||
if (!iceTransport) {
|
||||
// RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of
|
||||
// setup:actpass.
|
||||
// See https://tools.ietf.org/html/rfc5763#section-5
|
||||
iceTransport = initIceTransport(Description::Role::ActPass);
|
||||
}
|
||||
|
||||
Description localDescription = iceTransport->getLocalDescription(type);
|
||||
processLocalDescription(std::move(localDescription));
|
||||
|
||||
changeSignalingState(newSignalingState);
|
||||
|
||||
if (mGatheringState == GatheringState::New)
|
||||
iceTransport->gatherLocalCandidates();
|
||||
}
|
||||
|
||||
void PeerConnection::setRemoteDescription(Description description) {
|
||||
PLOG_VERBOSE << "Setting remote description: " << string(description);
|
||||
|
||||
if (description.mediaCount() == 0)
|
||||
throw std::invalid_argument("Remote description has no media line");
|
||||
if (description.type() == Description::Type::Rollback) {
|
||||
// This is mostly useless because we accept any offer
|
||||
PLOG_VERBOSE << "Rolling back pending remote description";
|
||||
changeSignalingState(SignalingState::Stable);
|
||||
return;
|
||||
}
|
||||
|
||||
int activeMediaCount = 0;
|
||||
for (unsigned int i = 0; i < description.mediaCount(); ++i)
|
||||
std::visit( // reciprocate each media
|
||||
rtc::overloaded{[&](Description::Application *) { ++activeMediaCount; },
|
||||
[&](Description::Media *media) {
|
||||
if (media->direction() != Description::Direction::Inactive)
|
||||
++activeMediaCount;
|
||||
}},
|
||||
description.media(i));
|
||||
validateRemoteDescription(description);
|
||||
|
||||
// if (activeMediaCount == 0)
|
||||
// throw std::invalid_argument("Remote description has no active media");
|
||||
// Get the new signaling state
|
||||
SignalingState signalingState = mSignalingState.load();
|
||||
SignalingState newSignalingState;
|
||||
switch (signalingState) {
|
||||
case SignalingState::Stable:
|
||||
description.hintType(Description::Type::Offer);
|
||||
if (description.type() != Description::Type::Offer) {
|
||||
std::ostringstream oss;
|
||||
oss << "Unexpected remote " << description.type() << " description in signaling state "
|
||||
<< signalingState;
|
||||
throw std::logic_error(oss.str());
|
||||
}
|
||||
newSignalingState = SignalingState::HaveRemoteOffer;
|
||||
break;
|
||||
|
||||
if (!description.fingerprint())
|
||||
throw std::invalid_argument("Remote description has no fingerprint");
|
||||
case SignalingState::HaveLocalOffer:
|
||||
description.hintType(Description::Type::Answer);
|
||||
if (description.type() == Description::Type::Offer) {
|
||||
// The ICE agent will automatically initiate a rollback when a peer that had previously
|
||||
// created an offer receives an offer from the remote peer
|
||||
setLocalDescription(Description::Type::Rollback);
|
||||
newSignalingState = SignalingState::HaveRemoteOffer;
|
||||
break;
|
||||
}
|
||||
if (description.type() != Description::Type::Answer &&
|
||||
description.type() != Description::Type::Pranswer) {
|
||||
std::ostringstream oss;
|
||||
oss << "Unexpected remote " << description.type() << " description in signaling state "
|
||||
<< signalingState;
|
||||
throw std::logic_error(oss.str());
|
||||
}
|
||||
newSignalingState = SignalingState::Stable;
|
||||
break;
|
||||
|
||||
description.hintType(localDescription() ? Description::Type::Answer : Description::Type::Offer);
|
||||
case SignalingState::HaveRemotePranswer:
|
||||
description.hintType(Description::Type::Answer);
|
||||
if (description.type() != Description::Type::Answer &&
|
||||
description.type() != Description::Type::Pranswer) {
|
||||
std::ostringstream oss;
|
||||
oss << "Unexpected remote " << description.type() << " description in signaling state "
|
||||
<< signalingState;
|
||||
throw std::logic_error(oss.str());
|
||||
}
|
||||
newSignalingState = SignalingState::Stable;
|
||||
break;
|
||||
|
||||
default: {
|
||||
std::ostringstream oss;
|
||||
oss << "Unexpected remote description in signaling state " << signalingState;
|
||||
throw std::logic_error(oss.str());
|
||||
}
|
||||
}
|
||||
|
||||
// Candidates will be added at the end, extract them for now
|
||||
auto remoteCandidates = description.extractCandidates();
|
||||
auto type = description.type();
|
||||
auto remoteCandidates = description.extractCandidates(); // Candidates will be added at the end
|
||||
|
||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||
if (!iceTransport)
|
||||
iceTransport = initIceTransport(Description::Role::ActPass);
|
||||
iceTransport->setRemoteDescription(description);
|
||||
|
||||
{
|
||||
std::lock_guard lock(mRemoteDescriptionMutex);
|
||||
mRemoteDescription.emplace(std::move(description));
|
||||
}
|
||||
iceTransport->setRemoteDescription(description);
|
||||
processRemoteDescription(std::move(description));
|
||||
|
||||
changeSignalingState(newSignalingState);
|
||||
|
||||
if (type == Description::Type::Offer) {
|
||||
// This is an offer and we are the answerer.
|
||||
Description localDescription = iceTransport->getLocalDescription(Description::Type::Answer);
|
||||
processLocalDescription(localDescription);
|
||||
iceTransport->gatherLocalCandidates();
|
||||
// This is an offer, we need to answer
|
||||
setLocalDescription(Description::Type::Answer);
|
||||
} else {
|
||||
// This is an answer and we are the offerer.
|
||||
// This is an answer
|
||||
auto sctpTransport = std::atomic_load(&mSctpTransport);
|
||||
if (!sctpTransport && iceTransport->role() == Description::Role::Active) {
|
||||
// Since we assumed passive role during DataChannel creation, we need to shift the
|
||||
@ -162,34 +295,11 @@ void PeerConnection::setRemoteDescription(Description description) {
|
||||
|
||||
for (const auto &candidate : remoteCandidates)
|
||||
addRemoteCandidate(candidate);
|
||||
if (auto transport = std::atomic_load(&mDtlsTransport); transport && transport->state() == rtc::DtlsTransport::State::Connected) {
|
||||
openTracks();
|
||||
}
|
||||
}
|
||||
|
||||
void PeerConnection::addRemoteCandidate(Candidate candidate) {
|
||||
PLOG_VERBOSE << "Adding remote candidate: " << string(candidate);
|
||||
|
||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||
if (!mRemoteDescription || !iceTransport)
|
||||
throw std::logic_error("Remote candidate set without remote description");
|
||||
|
||||
if (candidate.resolve(Candidate::ResolveMode::Simple)) {
|
||||
iceTransport->addRemoteCandidate(candidate);
|
||||
} else {
|
||||
// OK, we might need a lookup, do it asynchronously
|
||||
// We don't use the thread pool because we have no control on the timeout
|
||||
weak_ptr<IceTransport> weakIceTransport{iceTransport};
|
||||
std::thread t([weakIceTransport, candidate]() mutable {
|
||||
if (candidate.resolve(Candidate::ResolveMode::Lookup))
|
||||
if (auto iceTransport = weakIceTransport.lock())
|
||||
iceTransport->addRemoteCandidate(candidate);
|
||||
});
|
||||
t.detach();
|
||||
}
|
||||
|
||||
std::lock_guard lock(mRemoteDescriptionMutex);
|
||||
mRemoteDescription->addCandidate(candidate);
|
||||
processRemoteCandidate(std::move(candidate));
|
||||
}
|
||||
|
||||
std::optional<string> PeerConnection::localAddress() const {
|
||||
@ -204,11 +314,6 @@ std::optional<string> PeerConnection::remoteAddress() const {
|
||||
|
||||
shared_ptr<DataChannel> PeerConnection::addDataChannel(string label, string protocol,
|
||||
Reliability reliability) {
|
||||
if (auto local = localDescription(); local && !local->hasApplication()) {
|
||||
PLOG_ERROR << "The PeerConnection was negociated without DataChannel support.";
|
||||
throw std::runtime_error("No DataChannel support on the PeerConnection");
|
||||
}
|
||||
|
||||
// RFC 5763: The answerer MUST use either a setup attribute value of setup:active or
|
||||
// setup:passive. [...] Thus, setup:active is RECOMMENDED.
|
||||
// See https://tools.ietf.org/html/rfc5763#section-5
|
||||
@ -223,6 +328,11 @@ shared_ptr<DataChannel> PeerConnection::addDataChannel(string label, string prot
|
||||
if (transport->state() == SctpTransport::State::Connected)
|
||||
channel->open(transport);
|
||||
|
||||
// Renegotiation is needed iff the current local description does not have application
|
||||
std::lock_guard lock(mLocalDescriptionMutex);
|
||||
if (!mLocalDescription || !mLocalDescription->hasApplication())
|
||||
mNegotiationNeeded = true;
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
@ -254,14 +364,11 @@ void PeerConnection::onGatheringStateChange(std::function<void(GatheringState st
|
||||
mGatheringStateChangeCallback = callback;
|
||||
}
|
||||
|
||||
bool PeerConnection::hasMedia() const {
|
||||
auto local = localDescription();
|
||||
return local && local->hasAudioOrVideo();
|
||||
void PeerConnection::onSignalingStateChange(std::function<void(SignalingState state)> callback) {
|
||||
mSignalingStateChangeCallback = callback;
|
||||
}
|
||||
|
||||
std::shared_ptr<Track> PeerConnection::addTrack(Description::Media description) {
|
||||
// if (localDescription())
|
||||
// throw std::logic_error("Tracks must be created before local description");
|
||||
|
||||
if (auto it = mTracks.find(description.mid()); it != mTracks.end())
|
||||
if (auto track = it->second.lock())
|
||||
@ -274,7 +381,10 @@ std::shared_ptr<Track> PeerConnection::addTrack(Description::Media description)
|
||||
#endif
|
||||
auto track = std::make_shared<Track>(std::move(description));
|
||||
mTracks.emplace(std::make_pair(track->mid(), track));
|
||||
mTrackLines.emplace_back(track);
|
||||
|
||||
// Renegotiation is needed for the new track
|
||||
mNegotiationNeeded = true;
|
||||
|
||||
return track;
|
||||
}
|
||||
|
||||
@ -283,6 +393,7 @@ void PeerConnection::onTrack(std::function<void(std::shared_ptr<Track>)> callbac
|
||||
}
|
||||
|
||||
shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role) {
|
||||
PLOG_VERBOSE << "Starting ICE transport";
|
||||
try {
|
||||
if (auto transport = std::atomic_load(&mIceTransport))
|
||||
return transport;
|
||||
@ -345,6 +456,7 @@ shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role
|
||||
}
|
||||
|
||||
shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
|
||||
PLOG_VERBOSE << "Starting DTLS transport";
|
||||
try {
|
||||
if (auto transport = std::atomic_load(&mDtlsTransport))
|
||||
return transport;
|
||||
@ -360,12 +472,12 @@ shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
|
||||
|
||||
switch (state) {
|
||||
case DtlsTransport::State::Connected:
|
||||
if (auto local = localDescription(); local && local->hasApplication())
|
||||
if (auto remote = remoteDescription(); remote && remote->hasApplication())
|
||||
initSctpTransport();
|
||||
else
|
||||
changeState(State::Connected);
|
||||
|
||||
openTracks();
|
||||
mProcessor->enqueue(std::bind(&PeerConnection::openTracks, this));
|
||||
break;
|
||||
case DtlsTransport::State::Failed:
|
||||
changeState(State::Failed);
|
||||
@ -415,13 +527,14 @@ shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
|
||||
}
|
||||
|
||||
shared_ptr<SctpTransport> PeerConnection::initSctpTransport() {
|
||||
PLOG_VERBOSE << "Starting SCTP transport";
|
||||
try {
|
||||
if (auto transport = std::atomic_load(&mSctpTransport))
|
||||
return transport;
|
||||
|
||||
auto remote = remoteDescription();
|
||||
if (!remote || !remote->application())
|
||||
throw std::logic_error("Initializing SCTP transport without application description");
|
||||
throw std::logic_error("Starting SCTP transport without application description");
|
||||
|
||||
uint16_t sctpPort = remote->application()->sctpPort().value_or(DEFAULT_SCTP_PORT);
|
||||
auto lower = std::atomic_load(&mDtlsTransport);
|
||||
@ -435,16 +548,16 @@ shared_ptr<SctpTransport> PeerConnection::initSctpTransport() {
|
||||
switch (state) {
|
||||
case SctpTransport::State::Connected:
|
||||
changeState(State::Connected);
|
||||
openDataChannels();
|
||||
mProcessor->enqueue(std::bind(&PeerConnection::openDataChannels, this));
|
||||
break;
|
||||
case SctpTransport::State::Failed:
|
||||
LOG_WARNING << "SCTP transport failed";
|
||||
remoteCloseDataChannels();
|
||||
changeState(State::Failed);
|
||||
mProcessor->enqueue(std::bind(&PeerConnection::remoteCloseDataChannels, this));
|
||||
break;
|
||||
case SctpTransport::State::Disconnected:
|
||||
remoteCloseDataChannels();
|
||||
changeState(State::Disconnected);
|
||||
mProcessor->enqueue(std::bind(&PeerConnection::remoteCloseDataChannels, this));
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
@ -471,7 +584,8 @@ void PeerConnection::closeTransports() {
|
||||
PLOG_VERBOSE << "Closing transports";
|
||||
|
||||
// Change state to sink state Closed
|
||||
changeState(State::Closed);
|
||||
if (!changeState(State::Closed))
|
||||
return; // already closed
|
||||
|
||||
// Reset callbacks now that state is changed
|
||||
resetCallbacks();
|
||||
@ -519,17 +633,17 @@ void PeerConnection::forwardMessage(message_ptr message) {
|
||||
}
|
||||
|
||||
auto channel = findDataChannel(uint16_t(message->stream));
|
||||
|
||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||
auto sctpTransport = std::atomic_load(&mSctpTransport);
|
||||
if (!iceTransport || !sctpTransport)
|
||||
return;
|
||||
|
||||
if (!channel) {
|
||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||
auto sctpTransport = std::atomic_load(&mSctpTransport);
|
||||
if (!iceTransport || !sctpTransport)
|
||||
return;
|
||||
|
||||
const byte dataChannelOpenMessage{0x03};
|
||||
unsigned int remoteParity = (iceTransport->role() == Description::Role::Active) ? 1 : 0;
|
||||
if (message->type == Message::Control && *message->data() == dataChannelOpenMessage &&
|
||||
message->stream % 2 == remoteParity) {
|
||||
|
||||
channel =
|
||||
std::make_shared<DataChannel>(shared_from_this(), sctpTransport, message->stream);
|
||||
channel->onOpen(weak_bind(&PeerConnection::triggerDataChannel, this,
|
||||
@ -792,34 +906,49 @@ void PeerConnection::openTracks() {
|
||||
if (auto transport = std::atomic_load(&mDtlsTransport)) {
|
||||
auto srtpTransport = std::reinterpret_pointer_cast<DtlsSrtpTransport>(transport);
|
||||
std::shared_lock lock(mTracksMutex); // read-only
|
||||
for (unsigned int i = 0; i < mTrackLines.size(); i++) {
|
||||
if (auto track = mTrackLines[i].lock()) {
|
||||
// srtpTransport->addSSRC(0);
|
||||
|
||||
// for (auto ssrc : track->description().getSSRCs()) {
|
||||
// PLOG_DEBUG << "Adding " << ssrc << " to list";
|
||||
// srtpTransport->addSSRC(ssrc);
|
||||
// }
|
||||
// for (auto ssrc : std::get<rtc::Description::Media *>(remoteDescription()->media(i))->getSSRCs()) {
|
||||
// PLOG_DEBUG << "Adding " << ssrc << " to list";
|
||||
// srtpTransport->addSSRC(ssrc);
|
||||
// }
|
||||
|
||||
if (!track->isOpen()) {
|
||||
track->open(srtpTransport);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto it = mTracks.begin(); it != mTracks.end(); ++it)
|
||||
if (auto track = it->second.lock())
|
||||
if (!track->isOpen())
|
||||
track->open(srtpTransport);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void PeerConnection::validateRemoteDescription(const Description &description) {
|
||||
if (!description.iceUfrag())
|
||||
throw std::invalid_argument("Remote description has no ICE user fragment");
|
||||
|
||||
if (!description.icePwd())
|
||||
throw std::invalid_argument("Remote description has no ICE password");
|
||||
|
||||
if (!description.fingerprint())
|
||||
throw std::invalid_argument("Remote description has no fingerprint");
|
||||
|
||||
if (description.mediaCount() == 0)
|
||||
throw std::invalid_argument("Remote description has no media line");
|
||||
|
||||
int activeMediaCount = 0;
|
||||
for (size_t i = 0; i < description.mediaCount(); ++i)
|
||||
std::visit(rtc::overloaded{[&](const Description::Application *) { ++activeMediaCount; },
|
||||
[&](const Description::Media *media) {
|
||||
if (media->direction() != Description::Direction::Inactive)
|
||||
++activeMediaCount;
|
||||
}},
|
||||
description.media(i));
|
||||
|
||||
if (activeMediaCount == 0)
|
||||
throw std::invalid_argument("Remote description has no active media");
|
||||
|
||||
if (auto local = localDescription(); local && local->iceUfrag() && local->icePwd())
|
||||
if (*description.iceUfrag() == *local->iceUfrag() &&
|
||||
*description.icePwd() == *local->icePwd())
|
||||
throw std::logic_error("Got the local description as remote description");
|
||||
|
||||
PLOG_VERBOSE << "Remote description looks valid";
|
||||
}
|
||||
|
||||
void PeerConnection::processLocalDescription(Description description) {
|
||||
int activeMediaCount = 0;
|
||||
|
||||
auto remote = remoteDescription();
|
||||
if (remote && remote->type() == Description::Type::Offer) {
|
||||
if (auto remote = remoteDescription()) {
|
||||
// Reciprocate remote description
|
||||
for (unsigned int i = 0; i < remote->mediaCount(); ++i)
|
||||
std::visit( // reciprocate each media
|
||||
@ -828,7 +957,6 @@ void PeerConnection::processLocalDescription(Description description) {
|
||||
auto reciprocated = app->reciprocate();
|
||||
reciprocated.hintSctpPort(DEFAULT_SCTP_PORT);
|
||||
reciprocated.setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE);
|
||||
++activeMediaCount;
|
||||
|
||||
PLOG_DEBUG << "Reciprocating application in local description, mid=\""
|
||||
<< reciprocated.mid() << "\"";
|
||||
@ -837,10 +965,7 @@ void PeerConnection::processLocalDescription(Description description) {
|
||||
},
|
||||
[&](Description::Media *media) {
|
||||
auto reciprocated = media->reciprocate();
|
||||
#if RTC_ENABLE_MEDIA
|
||||
if (reciprocated.direction() != Description::Direction::Inactive)
|
||||
++activeMediaCount;
|
||||
#else
|
||||
#if !RTC_ENABLE_MEDIA
|
||||
// No media support, mark as inactive
|
||||
reciprocated.setDirection(Description::Direction::Inactive);
|
||||
#endif
|
||||
@ -855,15 +980,17 @@ void PeerConnection::processLocalDescription(Description description) {
|
||||
},
|
||||
},
|
||||
remote->media(i));
|
||||
} else {
|
||||
}
|
||||
|
||||
if (description.type() == Description::Type::Offer) {
|
||||
// This is an offer, add locally created data channels and tracks
|
||||
// Add application for data channels
|
||||
{
|
||||
if (!description.hasApplication()) {
|
||||
std::shared_lock lock(mDataChannelsMutex);
|
||||
if (!mDataChannels.empty()) {
|
||||
Description::Application app("data");
|
||||
app.setSctpPort(DEFAULT_SCTP_PORT);
|
||||
app.setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE);
|
||||
++activeMediaCount;
|
||||
|
||||
PLOG_DEBUG << "Adding application to local description, mid=\"" << app.mid()
|
||||
<< "\"";
|
||||
@ -873,43 +1000,53 @@ void PeerConnection::processLocalDescription(Description description) {
|
||||
}
|
||||
|
||||
// Add media for local tracks
|
||||
{
|
||||
std::shared_lock lock(mTracksMutex);
|
||||
// for (auto it = mTracks.begin(); it != mTracks.end(); ++it) {
|
||||
for (auto ptr : mTrackLines) {
|
||||
if (auto track = ptr.lock()) {
|
||||
auto media = track->description();
|
||||
#if RTC_ENABLE_MEDIA
|
||||
if (media.direction() != Description::Direction::Inactive)
|
||||
++activeMediaCount;
|
||||
#else
|
||||
// No media support, mark as inactive
|
||||
media.setDirection(Description::Direction::Inactive);
|
||||
#endif
|
||||
PLOG_DEBUG << "Adding media to local description, mid=\"" << media.mid()
|
||||
<< "\", active=" << std::boolalpha
|
||||
<< (media.direction() != Description::Direction::Inactive);
|
||||
|
||||
description.addMedia(std::move(media));
|
||||
}
|
||||
std::shared_lock lock(mTracksMutex);
|
||||
for (auto it = mTracks.begin(); it != mTracks.end(); ++it) {
|
||||
if (description.hasMid(it->first))
|
||||
continue;
|
||||
|
||||
if (auto track = it->second.lock()) {
|
||||
auto media = track->description();
|
||||
#if !RTC_ENABLE_MEDIA
|
||||
// No media support, mark as inactive
|
||||
media.setDirection(Description::Direction::Inactive);
|
||||
#endif
|
||||
PLOG_DEBUG << "Adding media to local description, mid=\"" << media.mid()
|
||||
<< "\", active=" << std::boolalpha
|
||||
<< (media.direction() != Description::Direction::Inactive);
|
||||
|
||||
description.addMedia(std::move(media));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// There must be at least one active media to negociate
|
||||
// if (activeMediaCount == 0)
|
||||
// throw std::runtime_error("Nothing to negociate");
|
||||
|
||||
// Set local fingerprint (wait for certificate if necessary)
|
||||
description.setFingerprint(mCertificate.get()->fingerprint());
|
||||
|
||||
std::lock_guard lock(mLocalDescriptionMutex);
|
||||
mLocalDescription.emplace(std::move(description));
|
||||
{
|
||||
// Set as local description
|
||||
std::lock_guard lock(mLocalDescriptionMutex);
|
||||
|
||||
std::vector<Candidate> existingCandidates;
|
||||
if (mLocalDescription) {
|
||||
existingCandidates = mLocalDescription->extractCandidates();
|
||||
mCurrentLocalDescription.emplace(std::move(*mLocalDescription));
|
||||
}
|
||||
|
||||
mLocalDescription.emplace(std::move(description));
|
||||
mLocalDescription->addCandidates(std::move(existingCandidates));
|
||||
}
|
||||
|
||||
mProcessor->enqueue([this, description = *mLocalDescription]() {
|
||||
PLOG_VERBOSE << "Issuing local description: " << description;
|
||||
mLocalDescriptionCallback(std::move(description));
|
||||
});
|
||||
|
||||
// Reciprocated tracks might need to be open
|
||||
if (auto dtlsTransport = std::atomic_load(&mDtlsTransport);
|
||||
dtlsTransport && dtlsTransport->state() == Transport::State::Connected)
|
||||
mProcessor->enqueue(std::bind(&PeerConnection::openTracks, this));
|
||||
}
|
||||
|
||||
void PeerConnection::processLocalCandidate(Candidate candidate) {
|
||||
@ -917,6 +1054,7 @@ void PeerConnection::processLocalCandidate(Candidate candidate) {
|
||||
if (!mLocalDescription)
|
||||
throw std::logic_error("Got a local candidate without local description");
|
||||
|
||||
candidate.resolve(Candidate::ResolveMode::Simple); // for proper SDP generation later
|
||||
mLocalDescription->addCandidate(candidate);
|
||||
|
||||
mProcessor->enqueue([this, candidate = std::move(candidate)]() {
|
||||
@ -925,6 +1063,56 @@ void PeerConnection::processLocalCandidate(Candidate candidate) {
|
||||
});
|
||||
}
|
||||
|
||||
void PeerConnection::processRemoteDescription(Description description) {
|
||||
{
|
||||
// Set as remote description
|
||||
std::lock_guard lock(mRemoteDescriptionMutex);
|
||||
|
||||
std::vector<Candidate> existingCandidates;
|
||||
if (mRemoteDescription)
|
||||
existingCandidates = mRemoteDescription->extractCandidates();
|
||||
|
||||
mRemoteDescription.emplace(std::move(description));
|
||||
mRemoteDescription->addCandidates(std::move(existingCandidates));
|
||||
}
|
||||
|
||||
if (description.hasApplication()) {
|
||||
auto dtlsTransport = std::atomic_load(&mDtlsTransport);
|
||||
auto sctpTransport = std::atomic_load(&mSctpTransport);
|
||||
if (!sctpTransport && dtlsTransport &&
|
||||
dtlsTransport->state() == Transport::State::Connected)
|
||||
initSctpTransport();
|
||||
}
|
||||
}
|
||||
|
||||
void PeerConnection::processRemoteCandidate(Candidate candidate) {
|
||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||
if (!iceTransport)
|
||||
throw std::logic_error("Remote candidate set without remote description");
|
||||
|
||||
if (candidate.resolve(Candidate::ResolveMode::Simple)) {
|
||||
iceTransport->addRemoteCandidate(candidate);
|
||||
} else {
|
||||
// OK, we might need a lookup, do it asynchronously
|
||||
// We don't use the thread pool because we have no control on the timeout
|
||||
weak_ptr<IceTransport> weakIceTransport{iceTransport};
|
||||
std::thread t([weakIceTransport, candidate]() mutable {
|
||||
if (candidate.resolve(Candidate::ResolveMode::Lookup))
|
||||
if (auto iceTransport = weakIceTransport.lock())
|
||||
iceTransport->addRemoteCandidate(candidate);
|
||||
});
|
||||
t.detach();
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard lock(mRemoteDescriptionMutex);
|
||||
if (!mRemoteDescription)
|
||||
throw std::logic_error("Got a remote candidate without remote description");
|
||||
|
||||
mRemoteDescription->addCandidate(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerConnection::triggerDataChannel(weak_ptr<DataChannel> weakDataChannel) {
|
||||
auto dataChannel = weakDataChannel.lock();
|
||||
if (!dataChannel)
|
||||
@ -942,13 +1130,17 @@ bool PeerConnection::changeState(State state) {
|
||||
State current;
|
||||
do {
|
||||
current = mState.load();
|
||||
if (current == state)
|
||||
return true;
|
||||
if (current == State::Closed)
|
||||
return false;
|
||||
if (current == state)
|
||||
return false;
|
||||
|
||||
} while (!mState.compare_exchange_weak(current, state));
|
||||
|
||||
std::ostringstream s;
|
||||
s << state;
|
||||
PLOG_INFO << "Changed state to " << s.str();
|
||||
|
||||
if (state == State::Closed)
|
||||
// This is the last state change, so we may steal the callback
|
||||
mProcessor->enqueue([cb = std::move(mStateChangeCallback)]() { cb(State::Closed); });
|
||||
@ -959,8 +1151,24 @@ bool PeerConnection::changeState(State state) {
|
||||
}
|
||||
|
||||
bool PeerConnection::changeGatheringState(GatheringState state) {
|
||||
if (mGatheringState.exchange(state) != state)
|
||||
mProcessor->enqueue([this, state] { mGatheringStateChangeCallback(state); });
|
||||
if (mGatheringState.exchange(state) == state)
|
||||
return false;
|
||||
|
||||
std::ostringstream s;
|
||||
s << state;
|
||||
PLOG_INFO << "Changed gathering state to " << s.str();
|
||||
mProcessor->enqueue([this, state] { mGatheringStateChangeCallback(state); });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PeerConnection::changeSignalingState(SignalingState state) {
|
||||
if (mSignalingState.exchange(state) == state)
|
||||
return false;
|
||||
|
||||
std::ostringstream s;
|
||||
s << state;
|
||||
PLOG_INFO << "Changed signaling state to " << s.str();
|
||||
mProcessor->enqueue([this, state] { mSignalingStateChangeCallback(state); });
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -973,15 +1181,10 @@ void PeerConnection::resetCallbacks() {
|
||||
mGatheringStateChangeCallback = nullptr;
|
||||
}
|
||||
|
||||
bool PeerConnection::getSelectedCandidatePair([[maybe_unused]] CandidateInfo *local,
|
||||
[[maybe_unused]] CandidateInfo *remote) {
|
||||
#if USE_NICE
|
||||
bool PeerConnection::getSelectedCandidatePair([[maybe_unused]] Candidate *local,
|
||||
[[maybe_unused]] Candidate *remote) {
|
||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||
return iceTransport->getSelectedCandidatePair(local, remote);
|
||||
#else
|
||||
PLOG_WARNING << "getSelectedCandidatePair() is only implemented with libnice as ICE backend";
|
||||
return false;
|
||||
#endif
|
||||
return iceTransport ? iceTransport->getSelectedCandidatePair(local, remote) : false;
|
||||
}
|
||||
|
||||
void PeerConnection::clearStats() {
|
||||
@ -1008,15 +1211,14 @@ std::optional<std::chrono::milliseconds> PeerConnection::rtt() {
|
||||
auto sctpTransport = std::atomic_load(&mSctpTransport);
|
||||
if (sctpTransport)
|
||||
return sctpTransport->rtt();
|
||||
PLOG_WARNING << "Could not load sctpTransport";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::State &state) {
|
||||
std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::State state) {
|
||||
using State = rtc::PeerConnection::State;
|
||||
std::string str;
|
||||
const char *str;
|
||||
switch (state) {
|
||||
case State::New:
|
||||
str = "new";
|
||||
@ -1043,15 +1245,15 @@ std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::State &st
|
||||
return out << str;
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::GatheringState &state) {
|
||||
std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::GatheringState state) {
|
||||
using GatheringState = rtc::PeerConnection::GatheringState;
|
||||
std::string str;
|
||||
const char *str;
|
||||
switch (state) {
|
||||
case GatheringState::New:
|
||||
str = "new";
|
||||
break;
|
||||
case GatheringState::InProgress:
|
||||
str = "in_progress";
|
||||
str = "in-progress";
|
||||
break;
|
||||
case GatheringState::Complete:
|
||||
str = "complete";
|
||||
@ -1062,3 +1264,29 @@ std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::Gathering
|
||||
}
|
||||
return out << str;
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::SignalingState state) {
|
||||
using SignalingState = rtc::PeerConnection::SignalingState;
|
||||
const char *str;
|
||||
switch (state) {
|
||||
case SignalingState::Stable:
|
||||
str = "stable";
|
||||
break;
|
||||
case SignalingState::HaveLocalOffer:
|
||||
str = "have-local-offer";
|
||||
break;
|
||||
case SignalingState::HaveRemoteOffer:
|
||||
str = "have-remote-offer";
|
||||
break;
|
||||
case SignalingState::HaveLocalPranswer:
|
||||
str = "have-local-pranswer";
|
||||
break;
|
||||
case SignalingState::HaveRemotePranswer:
|
||||
str = "have-remote-pranswer";
|
||||
break;
|
||||
default:
|
||||
str = "unknown";
|
||||
break;
|
||||
}
|
||||
return out << str;
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ public:
|
||||
void join();
|
||||
|
||||
template <class F, class... Args>
|
||||
auto enqueue(F &&f, Args &&... args) -> invoke_future_t<F, Args...>;
|
||||
void enqueue(F &&f, Args &&... args);
|
||||
|
||||
protected:
|
||||
void schedule();
|
||||
@ -60,31 +60,20 @@ protected:
|
||||
std::condition_variable mCondition;
|
||||
};
|
||||
|
||||
template <class F, class... Args>
|
||||
auto Processor::enqueue(F &&f, Args &&... args) -> invoke_future_t<F, Args...> {
|
||||
template <class F, class... Args> void Processor::enqueue(F &&f, Args &&... args) {
|
||||
std::unique_lock lock(mMutex);
|
||||
using R = std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>;
|
||||
auto task = std::make_shared<std::packaged_task<R()>>(
|
||||
std::bind(std::forward<F>(f), std::forward<Args>(args)...));
|
||||
std::future<R> result = task->get_future();
|
||||
|
||||
auto bundle = [this, task = std::move(task)]() {
|
||||
try {
|
||||
(*task)();
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_WARNING << "Unhandled exception in task: " << e.what();
|
||||
}
|
||||
schedule(); // chain the next task
|
||||
auto bound = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
|
||||
auto task = [this, bound = std::move(bound)]() mutable {
|
||||
scope_guard guard(std::bind(&Processor::schedule, this)); // chain the next task
|
||||
return bound();
|
||||
};
|
||||
|
||||
if (!mPending) {
|
||||
ThreadPool::Instance().enqueue(std::move(bundle));
|
||||
ThreadPool::Instance().enqueue(std::move(task));
|
||||
mPending = true;
|
||||
} else {
|
||||
mTasks.emplace(std::move(bundle));
|
||||
mTasks.emplace(std::move(task));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
@ -106,7 +106,7 @@ void RtcpReceivingSession::pushRR(unsigned int lastSR_delay) {
|
||||
auto msg = rtc::make_message(RTCP_RR::sizeWithReportBlocks(1), rtc::Message::Type::Control);
|
||||
auto rr = reinterpret_cast<RTCP_RR *>(msg->data());
|
||||
rr->preparePacket(mSsrc, 1);
|
||||
rr->getReportBlock(0)->preparePacket(mSsrc, 0, 0, mGreatestSeqNo, 0, 0, mSyncNTPTS,
|
||||
rr->getReportBlock(0)->preparePacket(mSsrc, 0, 0, uint16_t(mGreatestSeqNo), 0, 0, mSyncNTPTS,
|
||||
lastSR_delay);
|
||||
rr->log();
|
||||
|
||||
|
@ -322,7 +322,7 @@ void SctpTransport::incoming(message_ptr message) {
|
||||
bool SctpTransport::trySendQueue() {
|
||||
// Requires mSendMutex to be locked
|
||||
while (auto next = mSendQueue.peek()) {
|
||||
auto message = *next;
|
||||
message_ptr message = std::move(*next);
|
||||
if (!trySendMessage(message))
|
||||
return false;
|
||||
mSendQueue.pop();
|
||||
@ -476,8 +476,6 @@ int SctpTransport::handleRecv(struct socket * /*sock*/, union sctp_sockstore /*a
|
||||
const byte *data, size_t len, struct sctp_rcvinfo info, int flags) {
|
||||
try {
|
||||
PLOG_VERBOSE << "Handle recv, len=" << len;
|
||||
if (!len)
|
||||
return 0; // Ignore
|
||||
|
||||
// SCTP_FRAGMENT_INTERLEAVE does not seem to work as expected for messages > 64KB,
|
||||
// therefore partial notifications and messages need to be handled separately.
|
||||
@ -497,7 +495,7 @@ int SctpTransport::handleRecv(struct socket * /*sock*/, union sctp_sockstore /*a
|
||||
if (flags & MSG_EOR) {
|
||||
// Message is complete, process it
|
||||
processData(std::move(mPartialMessage), info.rcv_sid,
|
||||
PayloadId(htonl(info.rcv_ppid)));
|
||||
PayloadId(ntohl(info.rcv_ppid)));
|
||||
mPartialMessage.clear();
|
||||
}
|
||||
}
|
||||
@ -702,8 +700,9 @@ std::optional<milliseconds> SctpTransport::rtt() {
|
||||
}
|
||||
|
||||
int SctpTransport::RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data,
|
||||
size_t len, struct sctp_rcvinfo recv_info, int flags, void *ptr) {
|
||||
auto *transport = static_cast<SctpTransport *>(ptr);
|
||||
size_t len, struct sctp_rcvinfo recv_info, int flags,
|
||||
void *ulp_info) {
|
||||
auto *transport = static_cast<SctpTransport *>(ulp_info);
|
||||
|
||||
std::shared_lock lock(InstancesMutex);
|
||||
if (Instances.find(transport) == Instances.end()) {
|
||||
@ -717,15 +716,8 @@ int SctpTransport::RecvCallback(struct socket *sock, union sctp_sockstore addr,
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SctpTransport::SendCallback(struct socket *sock, uint32_t sb_free) {
|
||||
struct sctp_paddrinfo paddrinfo = {};
|
||||
socklen_t len = sizeof(paddrinfo);
|
||||
if (usrsctp_getsockopt(sock, IPPROTO_SCTP, SCTP_GET_PEER_ADDR_INFO, &paddrinfo, &len))
|
||||
return -1;
|
||||
|
||||
auto sconn = reinterpret_cast<struct sockaddr_conn *>(&paddrinfo.spinfo_address);
|
||||
void *ptr = sconn->sconn_addr;
|
||||
auto *transport = static_cast<SctpTransport *>(ptr);
|
||||
int SctpTransport::SendCallback(struct socket *, uint32_t sb_free, void *ulp_info) {
|
||||
auto *transport = static_cast<SctpTransport *>(ulp_info);
|
||||
|
||||
std::shared_lock lock(InstancesMutex);
|
||||
if (Instances.find(transport) == Instances.end())
|
||||
|
@ -110,8 +110,8 @@ private:
|
||||
std::atomic<size_t> mBytesSent = 0, mBytesReceived = 0;
|
||||
|
||||
static int RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data, size_t len,
|
||||
struct sctp_rcvinfo recv_info, int flags, void *user_data);
|
||||
static int SendCallback(struct socket *sock, uint32_t sb_free);
|
||||
struct sctp_rcvinfo recv_info, int flags, void *ulp_info);
|
||||
static int SendCallback(struct socket *sock, uint32_t sb_free, void *ulp_info);
|
||||
static int WriteCallback(void *sctp_ptr, void *data, size_t len, uint8_t tos, uint8_t set_df);
|
||||
|
||||
static std::unordered_set<SctpTransport *> Instances;
|
||||
|
@ -271,7 +271,7 @@ void TcpTransport::close() {
|
||||
bool TcpTransport::trySendQueue() {
|
||||
// mSockMutex must be locked
|
||||
while (auto next = mSendQueue.peek()) {
|
||||
auto message = *next;
|
||||
message_ptr message = std::move(*next);
|
||||
if (!trySendMessage(message)) {
|
||||
mSendQueue.exchange(message);
|
||||
return false;
|
||||
|
@ -58,11 +58,7 @@ void ThreadPool::run() {
|
||||
|
||||
bool ThreadPool::runOne() {
|
||||
if (auto task = dequeue()) {
|
||||
try {
|
||||
task();
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_WARNING << "Unhandled exception in task: " << e.what();
|
||||
}
|
||||
task();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -73,8 +73,15 @@ template <class F, class... Args>
|
||||
auto ThreadPool::enqueue(F &&f, Args &&... args) -> invoke_future_t<F, Args...> {
|
||||
std::unique_lock lock(mMutex);
|
||||
using R = std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>;
|
||||
auto task = std::make_shared<std::packaged_task<R()>>(
|
||||
std::bind(std::forward<F>(f), std::forward<Args>(args)...));
|
||||
auto bound = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
|
||||
auto task = std::make_shared<std::packaged_task<R()>>([bound = std::move(bound)]() mutable {
|
||||
try {
|
||||
return bound();
|
||||
} catch (const std::exception &e) {
|
||||
PLOG_WARNING << e.what();
|
||||
throw;
|
||||
}
|
||||
});
|
||||
std::future<R> result = task->get_future();
|
||||
|
||||
mTasks.emplace([task = std::move(task), token = Init::Token()]() { return (*task)(); });
|
||||
|
@ -238,11 +238,9 @@ ssize_t TlsTransport::ReadCallback(gnutls_transport_ptr_t ptr, void *data, size_
|
||||
|
||||
int TlsTransport::TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms) {
|
||||
TlsTransport *t = static_cast<TlsTransport *>(ptr);
|
||||
if (ms != GNUTLS_INDEFINITE_TIMEOUT)
|
||||
t->mIncomingQueue.wait(milliseconds(ms));
|
||||
else
|
||||
t->mIncomingQueue.wait();
|
||||
return !t->mIncomingQueue.empty() ? 1 : 0;
|
||||
bool notEmpty = t->mIncomingQueue.wait(
|
||||
ms != GNUTLS_INDEFINITE_TIMEOUT ? std::make_optional(milliseconds(ms)) : nullopt);
|
||||
return notEmpty ? 1 : 0;
|
||||
}
|
||||
|
||||
#else // USE_GNUTLS==0
|
||||
@ -413,7 +411,7 @@ void TlsTransport::runRecvLoop() {
|
||||
if (!next)
|
||||
break;
|
||||
|
||||
message_ptr message = *next;
|
||||
message_ptr message = std::move(*next);
|
||||
if (message->size() > 0)
|
||||
BIO_write(mInBio, message->data(), int(message->size())); // Input
|
||||
else
|
||||
|
@ -49,8 +49,8 @@ bool Track::send(const byte *data, size_t size) {
|
||||
}
|
||||
|
||||
std::optional<message_variant> Track::receive() {
|
||||
if (!mRecvQueue.empty())
|
||||
return to_variant(std::move(**mRecvQueue.pop()));
|
||||
if (auto next = mRecvQueue.tryPop())
|
||||
return to_variant(std::move(**next));
|
||||
|
||||
return nullopt;
|
||||
}
|
||||
|
@ -51,31 +51,45 @@ WebSocket::~WebSocket() {
|
||||
WebSocket::State WebSocket::readyState() const { return mState; }
|
||||
|
||||
void WebSocket::open(const string &url) {
|
||||
PLOG_VERBOSE << "Opening WebSocket to URL: " << url;
|
||||
|
||||
if (mState != State::Closed)
|
||||
throw std::runtime_error("WebSocket must be closed before opening");
|
||||
throw std::logic_error("WebSocket must be closed before opening");
|
||||
|
||||
static const char *rs = R"(^(([^:\/?#]+):)?(//([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?)";
|
||||
static std::regex regex(rs, std::regex::extended);
|
||||
// Modified regex from RFC 3986, see https://tools.ietf.org/html/rfc3986#appendix-B
|
||||
static const char *rs =
|
||||
R"(^(([^:.@/?#]+):)?(/{0,2}((([^:@]*)(:([^@]*))?)@)?(([^:/?#]*)(:([^/?#]*))?))?([^?#]*)(\?([^#]*))?(#(.*))?)";
|
||||
|
||||
std::smatch match;
|
||||
if (!std::regex_match(url, match, regex))
|
||||
throw std::invalid_argument("Malformed WebSocket URL: " + url);
|
||||
static const std::regex r(rs, std::regex::extended);
|
||||
|
||||
mScheme = match[2];
|
||||
if (mScheme != "ws" && mScheme != "wss")
|
||||
std::smatch m;
|
||||
if (!std::regex_match(url, m, r) || m[10].length() == 0)
|
||||
throw std::invalid_argument("Invalid WebSocket URL: " + url);
|
||||
|
||||
mScheme = m[2];
|
||||
if (mScheme.empty())
|
||||
mScheme = "ws";
|
||||
else if (mScheme != "ws" && mScheme != "wss")
|
||||
throw std::invalid_argument("Invalid WebSocket scheme: " + mScheme);
|
||||
|
||||
mHost = match[4];
|
||||
if (auto pos = mHost.find(':'); pos != string::npos) {
|
||||
mHostname = mHost.substr(0, pos);
|
||||
mService = mHost.substr(pos + 1);
|
||||
} else {
|
||||
mHostname = mHost;
|
||||
mHostname = m[10];
|
||||
mService = m[12];
|
||||
if (mService.empty()) {
|
||||
mService = mScheme == "ws" ? "80" : "443";
|
||||
mHost = mHostname;
|
||||
} else {
|
||||
mHost = mHostname + ':' + mService;
|
||||
}
|
||||
|
||||
mPath = match[5];
|
||||
if (string query = match[7]; !query.empty())
|
||||
while (!mHostname.empty() && mHostname.front() == '[')
|
||||
mHostname.erase(mHostname.begin());
|
||||
while (!mHostname.empty() && mHostname.back() == ']')
|
||||
mHostname.pop_back();
|
||||
|
||||
mPath = m[13];
|
||||
if (mPath.empty())
|
||||
mPath += '/';
|
||||
if (string query = m[15]; !query.empty())
|
||||
mPath += "?" + query;
|
||||
|
||||
changeState(State::Connecting);
|
||||
@ -110,8 +124,8 @@ bool WebSocket::isClosed() const { return mState == State::Closed; }
|
||||
size_t WebSocket::maxMessageSize() const { return DEFAULT_MAX_MESSAGE_SIZE; }
|
||||
|
||||
std::optional<message_variant> WebSocket::receive() {
|
||||
while (!mRecvQueue.empty()) {
|
||||
auto message = *mRecvQueue.pop();
|
||||
while (auto next = mRecvQueue.tryPop()) {
|
||||
message_ptr message = std::move(*next);
|
||||
if (message->type != Message::Control)
|
||||
return to_variant(std::move(*message));
|
||||
}
|
||||
@ -133,6 +147,11 @@ bool WebSocket::outgoing(message_ptr message) {
|
||||
}
|
||||
|
||||
void WebSocket::incoming(message_ptr message) {
|
||||
if (!message) {
|
||||
remoteClose();
|
||||
return;
|
||||
}
|
||||
|
||||
if (message->type == Message::String || message->type == Message::Binary) {
|
||||
mRecvQueue.push(message);
|
||||
triggerAvailable(mRecvQueue.size());
|
||||
@ -140,6 +159,7 @@ void WebSocket::incoming(message_ptr message) {
|
||||
}
|
||||
|
||||
shared_ptr<TcpTransport> WebSocket::initTcpTransport() {
|
||||
PLOG_VERBOSE << "Starting TCP transport";
|
||||
using State = TcpTransport::State;
|
||||
try {
|
||||
std::lock_guard lock(mInitMutex);
|
||||
@ -186,6 +206,7 @@ shared_ptr<TcpTransport> WebSocket::initTcpTransport() {
|
||||
}
|
||||
|
||||
shared_ptr<TlsTransport> WebSocket::initTlsTransport() {
|
||||
PLOG_VERBOSE << "Starting TLS transport";
|
||||
using State = TlsTransport::State;
|
||||
try {
|
||||
std::lock_guard lock(mInitMutex);
|
||||
@ -243,6 +264,7 @@ shared_ptr<TlsTransport> WebSocket::initTlsTransport() {
|
||||
}
|
||||
|
||||
shared_ptr<WsTransport> WebSocket::initWsTransport() {
|
||||
PLOG_VERBOSE << "Starting WebSocket transport";
|
||||
using State = WsTransport::State;
|
||||
try {
|
||||
std::lock_guard lock(mInitMutex);
|
||||
@ -321,6 +343,6 @@ void WebSocket::closeTransports() {
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
} // namespace rtc
|
||||
|
||||
#endif
|
||||
|
@ -58,6 +58,12 @@ WsTransport::WsTransport(std::shared_ptr<Transport> lower, string host, string p
|
||||
onRecv(recvCallback);
|
||||
|
||||
PLOG_DEBUG << "Initializing WebSocket transport";
|
||||
|
||||
if (mHost.empty())
|
||||
throw std::invalid_argument("WebSocket HTTP host cannot be empty");
|
||||
|
||||
if (mPath.empty())
|
||||
throw std::invalid_argument("WebSocket HTTP path cannot be empty");
|
||||
}
|
||||
|
||||
WsTransport::~WsTransport() { stop(); }
|
||||
@ -147,7 +153,7 @@ void WsTransport::close() {
|
||||
}
|
||||
|
||||
bool WsTransport::sendHttpRequest() {
|
||||
PLOG_DEBUG << "Sending WebSocket HTTP request";
|
||||
PLOG_DEBUG << "Sending WebSocket HTTP request for path " << mPath;
|
||||
changeState(State::Connecting);
|
||||
|
||||
auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
|
||||
|
@ -155,7 +155,8 @@ size_t benchmark(milliseconds duration) {
|
||||
|
||||
endTime = steady_clock::now();
|
||||
|
||||
auto connectDuration = duration_cast<milliseconds>(openTime - startTime);
|
||||
auto connectDuration = duration_cast<milliseconds>(dc1->isOpen() ? openTime - startTime
|
||||
: steady_clock::duration(0));
|
||||
auto transferDuration = duration_cast<milliseconds>(endTime - receivedTime);
|
||||
|
||||
cout << "Test duration: " << duration.count() << " ms" << endl;
|
||||
|
@ -29,9 +29,12 @@ static void sleep(unsigned int secs) { Sleep(secs * 1000); }
|
||||
#include <unistd.h> // for sleep
|
||||
#endif
|
||||
|
||||
#define BUFFER_SIZE 4096
|
||||
|
||||
typedef struct {
|
||||
rtcState state;
|
||||
rtcGatheringState gatheringState;
|
||||
rtcSignalingState signalingState;
|
||||
int pc;
|
||||
int dc;
|
||||
bool connected;
|
||||
@ -40,33 +43,39 @@ typedef struct {
|
||||
static Peer *peer1 = NULL;
|
||||
static Peer *peer2 = NULL;
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
||||
static void descriptionCallback(int pc, const char *sdp, const char *type, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
printf("Description %d:\n%s\n", peer == peer1 ? 1 : 2, sdp);
|
||||
Peer *other = peer == peer1 ? peer2 : peer1;
|
||||
rtcSetRemoteDescription(other->pc, sdp, type);
|
||||
}
|
||||
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr) {
|
||||
static void candidateCallback(int pc, const char *cand, const char *mid, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
printf("Candidate %d: %s\n", peer == peer1 ? 1 : 2, cand);
|
||||
Peer *other = peer == peer1 ? peer2 : peer1;
|
||||
rtcAddRemoteCandidate(other->pc, cand, mid);
|
||||
}
|
||||
|
||||
static void stateChangeCallback(rtcState state, void *ptr) {
|
||||
static void stateChangeCallback(int pc, rtcState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->state = state;
|
||||
printf("State %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
|
||||
}
|
||||
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
|
||||
static void gatheringStateCallback(int pc, rtcGatheringState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->gatheringState = state;
|
||||
printf("Gathering state %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
|
||||
}
|
||||
|
||||
static void openCallback(void *ptr) {
|
||||
static void signalingStateCallback(int pc, rtcSignalingState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->signalingState = state;
|
||||
printf("Signaling state %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
|
||||
}
|
||||
|
||||
static void openCallback(int id, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = true;
|
||||
printf("DataChannel %d: Open\n", peer == peer1 ? 1 : 2);
|
||||
@ -75,12 +84,12 @@ static void openCallback(void *ptr) {
|
||||
rtcSendMessage(peer->dc, message, -1); // negative size indicates a null-terminated string
|
||||
}
|
||||
|
||||
static void closedCallback(void *ptr) {
|
||||
static void closedCallback(int id, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
}
|
||||
|
||||
static void messageCallback(const char *message, int size, void *ptr) {
|
||||
static void messageCallback(int id, const char *message, int size, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
if (size < 0) { // negative size indicates a null-terminated string
|
||||
printf("Message %d: %s\n", peer == peer1 ? 1 : 2, message);
|
||||
@ -89,7 +98,7 @@ static void messageCallback(const char *message, int size, void *ptr) {
|
||||
}
|
||||
}
|
||||
|
||||
static void dataChannelCallback(int dc, void *ptr) {
|
||||
static void dataChannelCallback(int pc, int dc, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->dc = dc;
|
||||
peer->connected = true;
|
||||
@ -178,20 +187,81 @@ int test_capi_connectivity_main() {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (peer1->signalingState != RTC_SIGNALING_STABLE ||
|
||||
peer2->signalingState != RTC_SIGNALING_STABLE) {
|
||||
fprintf(stderr, "Signaling state is not stable\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!peer1->connected || !peer2->connected) {
|
||||
fprintf(stderr, "DataChannel is not connected\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
char buffer[256];
|
||||
if (rtcGetLocalAddress(peer1->pc, buffer, 256) >= 0)
|
||||
printf("Local address 1: %s\n", buffer);
|
||||
if (rtcGetRemoteAddress(peer1->pc, buffer, 256) >= 0)
|
||||
printf("Remote address 1: %s\n", buffer);
|
||||
if (rtcGetLocalAddress(peer2->pc, buffer, 256) >= 0)
|
||||
printf("Local address 2: %s\n", buffer);
|
||||
if (rtcGetRemoteAddress(peer2->pc, buffer, 256) >= 0)
|
||||
printf("Remote address 2: %s\n", buffer);
|
||||
char buffer[BUFFER_SIZE];
|
||||
char buffer2[BUFFER_SIZE];
|
||||
|
||||
if (rtcGetLocalDescription(peer1->pc, buffer, BUFFER_SIZE) < 0) {
|
||||
fprintf(stderr, "rtcGetLocalDescription failed\n");
|
||||
goto error;
|
||||
}
|
||||
printf("Local description 1: %s\n", buffer);
|
||||
|
||||
if (rtcGetRemoteDescription(peer1->pc, buffer, BUFFER_SIZE) < 0) {
|
||||
fprintf(stderr, "rtcGetRemoteDescription failed\n");
|
||||
goto error;
|
||||
}
|
||||
printf("Remote description 1: %s\n", buffer);
|
||||
|
||||
if (rtcGetLocalDescription(peer2->pc, buffer, BUFFER_SIZE) < 0) {
|
||||
fprintf(stderr, "rtcGetLocalDescription failed\n");
|
||||
goto error;
|
||||
}
|
||||
printf("Local description 2: %s\n", buffer);
|
||||
|
||||
if (rtcGetRemoteDescription(peer2->pc, buffer, BUFFER_SIZE) < 0) {
|
||||
fprintf(stderr, "rtcGetRemoteDescription failed\n");
|
||||
goto error;
|
||||
}
|
||||
printf("Remote description 2: %s\n", buffer);
|
||||
|
||||
if (rtcGetLocalAddress(peer1->pc, buffer, BUFFER_SIZE) < 0) {
|
||||
fprintf(stderr, "rtcGetLocalAddress failed\n");
|
||||
goto error;
|
||||
}
|
||||
printf("Local address 1: %s\n", buffer);
|
||||
|
||||
if (rtcGetRemoteAddress(peer1->pc, buffer, BUFFER_SIZE) < 0) {
|
||||
fprintf(stderr, "rtcGetRemoteAddress failed\n");
|
||||
goto error;
|
||||
}
|
||||
printf("Remote address 1: %s\n", buffer);
|
||||
|
||||
if (rtcGetLocalAddress(peer2->pc, buffer, BUFFER_SIZE) < 0) {
|
||||
fprintf(stderr, "rtcGetLocalAddress failed\n");
|
||||
goto error;
|
||||
}
|
||||
printf("Local address 2: %s\n", buffer);
|
||||
|
||||
if (rtcGetRemoteAddress(peer2->pc, buffer, BUFFER_SIZE) < 0) {
|
||||
fprintf(stderr, "rtcGetRemoteAddress failed\n");
|
||||
goto error;
|
||||
}
|
||||
printf("Remote address 2: %s\n", buffer);
|
||||
|
||||
if (rtcGetSelectedCandidatePair(peer1->pc, buffer, BUFFER_SIZE, buffer2, BUFFER_SIZE) < 0) {
|
||||
fprintf(stderr, "rtcGetSelectedCandidatePair failed\n");
|
||||
goto error;
|
||||
}
|
||||
printf("Local candidate 1: %s\n", buffer);
|
||||
printf("Remote candidate 1: %s\n", buffer2);
|
||||
|
||||
if (rtcGetSelectedCandidatePair(peer2->pc, buffer, BUFFER_SIZE, buffer2, BUFFER_SIZE) < 0) {
|
||||
fprintf(stderr, "rtcGetSelectedCandidatePair failed\n");
|
||||
goto error;
|
||||
}
|
||||
printf("Local candidate 2: %s\n", buffer);
|
||||
printf("Remote candidate 2: %s\n", buffer2);
|
||||
|
||||
deletePeer(peer1);
|
||||
sleep(1);
|
||||
|
@ -43,44 +43,44 @@ static Peer *peer2 = NULL;
|
||||
static const char *mediaDescription = "video 9 UDP/TLS/RTP/SAVPF\r\n"
|
||||
"a=mid:video\r\n";
|
||||
|
||||
static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
|
||||
static void descriptionCallback(int pc, const char *sdp, const char *type, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
printf("Description %d:\n%s\n", peer == peer1 ? 1 : 2, sdp);
|
||||
Peer *other = peer == peer1 ? peer2 : peer1;
|
||||
rtcSetRemoteDescription(other->pc, sdp, type);
|
||||
}
|
||||
|
||||
static void candidateCallback(const char *cand, const char *mid, void *ptr) {
|
||||
static void candidateCallback(int pc, const char *cand, const char *mid, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
printf("Candidate %d: %s\n", peer == peer1 ? 1 : 2, cand);
|
||||
Peer *other = peer == peer1 ? peer2 : peer1;
|
||||
rtcAddRemoteCandidate(other->pc, cand, mid);
|
||||
}
|
||||
|
||||
static void stateChangeCallback(rtcState state, void *ptr) {
|
||||
static void stateChangeCallback(int pc, rtcState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->state = state;
|
||||
printf("State %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
|
||||
}
|
||||
|
||||
static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
|
||||
static void gatheringStateCallback(int pc, rtcGatheringState state, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->gatheringState = state;
|
||||
printf("Gathering state %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
|
||||
}
|
||||
|
||||
static void openCallback(void *ptr) {
|
||||
static void openCallback(int id, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = true;
|
||||
printf("Track %d: Open\n", peer == peer1 ? 1 : 2);
|
||||
}
|
||||
|
||||
static void closedCallback(void *ptr) {
|
||||
static void closedCallback(int id, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->connected = false;
|
||||
}
|
||||
|
||||
static void trackCallback(int tr, void *ptr) {
|
||||
static void trackCallback(int pc, int tr, void *ptr) {
|
||||
Peer *peer = (Peer *)ptr;
|
||||
peer->tr = tr;
|
||||
peer->connected = true;
|
||||
@ -156,7 +156,7 @@ int test_capi_track_main() {
|
||||
rtcSetClosedCallback(peer1->tr, closedCallback);
|
||||
|
||||
// Initiate the handshake
|
||||
rtcSetLocalDescription(peer1->pc);
|
||||
rtcSetLocalDescription(peer1->pc, NULL);
|
||||
|
||||
attempts = 10;
|
||||
while ((!peer2->connected || !peer1->connected) && attempts--)
|
||||
|
@ -69,6 +69,10 @@ void test_connectivity() {
|
||||
cout << "Gathering state 1: " << state << endl;
|
||||
});
|
||||
|
||||
pc1->onSignalingStateChange([](PeerConnection::SignalingState state) {
|
||||
cout << "Signaling state 1: " << state << endl;
|
||||
});
|
||||
|
||||
pc2->onLocalDescription([wpc1 = make_weak_ptr(pc1)](Description sdp) {
|
||||
auto pc1 = wpc1.lock();
|
||||
if (!pc1)
|
||||
@ -91,6 +95,10 @@ void test_connectivity() {
|
||||
cout << "Gathering state 2: " << state << endl;
|
||||
});
|
||||
|
||||
pc2->onSignalingStateChange([](PeerConnection::SignalingState state) {
|
||||
cout << "Signaling state 2: " << state << endl;
|
||||
});
|
||||
|
||||
shared_ptr<DataChannel> dc2;
|
||||
pc2->onDataChannel([&dc2](shared_ptr<DataChannel> dc) {
|
||||
cout << "DataChannel 2: Received with label \"" << dc->label() << "\"" << endl;
|
||||
@ -125,6 +133,7 @@ void test_connectivity() {
|
||||
}
|
||||
});
|
||||
|
||||
// Wait a bit
|
||||
int attempts = 10;
|
||||
shared_ptr<DataChannel> adc2;
|
||||
while ((!(adc2 = std::atomic_load(&dc2)) || !adc2->isOpen() || !dc1->isOpen()) && attempts--)
|
||||
@ -146,6 +155,59 @@ void test_connectivity() {
|
||||
if (auto addr = pc2->remoteAddress())
|
||||
cout << "Remote address 2: " << *addr << endl;
|
||||
|
||||
Candidate local, remote;
|
||||
if(pc1->getSelectedCandidatePair(&local, &remote)) {
|
||||
cout << "Local candidate 1: " << local << endl;
|
||||
cout << "Remote candidate 1: " << remote << endl;
|
||||
}
|
||||
if(pc2->getSelectedCandidatePair(&local, &remote)) {
|
||||
cout << "Local candidate 2: " << local << endl;
|
||||
cout << "Remote candidate 2: " << remote << endl;
|
||||
}
|
||||
|
||||
// Try to open a second data channel with another label
|
||||
shared_ptr<DataChannel> second2;
|
||||
pc2->onDataChannel([&second2](shared_ptr<DataChannel> dc) {
|
||||
cout << "Second DataChannel 2: Received with label \"" << dc->label() << "\"" << endl;
|
||||
if (dc->label() != "second") {
|
||||
cerr << "Wrong second DataChannel label" << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
dc->onMessage([](variant<binary, string> message) {
|
||||
if (holds_alternative<string>(message)) {
|
||||
cout << "Second Message 2: " << get<string>(message) << endl;
|
||||
}
|
||||
});
|
||||
|
||||
dc->send("Send hello from 2");
|
||||
|
||||
std::atomic_store(&second2, dc);
|
||||
});
|
||||
|
||||
auto second1 = pc1->createDataChannel("second");
|
||||
second1->onOpen([wsecond1 = make_weak_ptr(dc1)]() {
|
||||
auto second1 = wsecond1.lock();
|
||||
if (!second1)
|
||||
return;
|
||||
|
||||
cout << "Second DataChannel 1: Open" << endl;
|
||||
second1->send("Second hello from 1");
|
||||
});
|
||||
dc1->onMessage([](const variant<binary, string> &message) {
|
||||
if (holds_alternative<string>(message)) {
|
||||
cout << "Second Message 1: " << get<string>(message) << endl;
|
||||
}
|
||||
});
|
||||
|
||||
// Wait a bit
|
||||
attempts = 10;
|
||||
shared_ptr<DataChannel> asecond2;
|
||||
while (
|
||||
(!(asecond2 = std::atomic_load(&second2)) || !asecond2->isOpen() || !second1->isOpen()) &&
|
||||
attempts--)
|
||||
this_thread::sleep_for(1s);
|
||||
|
||||
// Delay close of peer 2 to check closing works properly
|
||||
pc1->close();
|
||||
this_thread::sleep_for(1s);
|
||||
|
@ -92,9 +92,10 @@ void test_track() {
|
||||
});
|
||||
|
||||
shared_ptr<Track> t2;
|
||||
pc2->onTrack([&t2](shared_ptr<Track> t) {
|
||||
string newTrackMid;
|
||||
pc2->onTrack([&t2, &newTrackMid](shared_ptr<Track> t) {
|
||||
cout << "Track 2: Received with mid \"" << t->mid() << "\"" << endl;
|
||||
if (t->mid() != "test") {
|
||||
if (t->mid() != newTrackMid) {
|
||||
cerr << "Wrong track mid" << endl;
|
||||
return;
|
||||
}
|
||||
@ -102,7 +103,9 @@ void test_track() {
|
||||
std::atomic_store(&t2, t);
|
||||
});
|
||||
|
||||
auto t1 = pc1->addTrack(Description::Video("test"));
|
||||
// Test opening a track
|
||||
newTrackMid = "test";
|
||||
auto t1 = pc1->addTrack(Description::Video(newTrackMid));
|
||||
|
||||
pc1->setLocalDescription();
|
||||
|
||||
@ -118,6 +121,20 @@ void test_track() {
|
||||
if (!at2 || !at2->isOpen() || !t1->isOpen())
|
||||
throw runtime_error("Track is not open");
|
||||
|
||||
// Test renegotiation
|
||||
newTrackMid = "added";
|
||||
t1 = pc1->addTrack(Description::Video(newTrackMid));
|
||||
|
||||
pc1->setLocalDescription();
|
||||
|
||||
attempts = 10;
|
||||
t2.reset();
|
||||
while ((!(at2 = std::atomic_load(&t2)) || !at2->isOpen() || !t1->isOpen()) && attempts--)
|
||||
this_thread::sleep_for(1s);
|
||||
|
||||
if (!at2 || !at2->isOpen() || !t1->isOpen())
|
||||
throw runtime_error("Renegociated track is not open");
|
||||
|
||||
// TODO: Test sending RTP packets in track
|
||||
|
||||
// Delay close of peer 2 to check closing works properly
|
||||
|
Reference in New Issue
Block a user