mirror of
https://github.com/mii443/libdatachannel.git
synced 2025-08-23 15:48:03 +00:00
Compare commits
83 Commits
Author | SHA1 | Date | |
---|---|---|---|
8ea828f1b0 | |||
35da6636aa | |||
363827594b | |||
cbc027f144 | |||
ebc6a4b65c | |||
37d47d28a8 | |||
46878519c0 | |||
84c298f4f8 | |||
23aed2b844 | |||
df62d6d51c | |||
26241f00b7 | |||
873d14c824 | |||
4953a112ad | |||
c31e1bf0be | |||
98ddba192f | |||
b02b30eea8 | |||
324d97a9b7 | |||
0a1dd4db01 | |||
b1de9acb20 | |||
960300a7cd | |||
3f084d7527 | |||
64096d599c | |||
552e443ef1 | |||
52cb8d68a0 | |||
372e2b7a1f | |||
a92e63720c | |||
8d121c086e | |||
b538e454aa | |||
60d09d5c6f | |||
266159fe41 | |||
458decb12d | |||
635c2e5513 | |||
adc4223617 | |||
326ae27ad1 | |||
6d33d19816 | |||
734efb391a | |||
cba864507f | |||
3432b233ff | |||
ef9bfe811b | |||
949e1de9cd | |||
57c52cf7ae | |||
eaac06546e | |||
9e38d08c0b | |||
47db28617a | |||
de8c4a55cf | |||
08d94e59c7 | |||
e8a6698abd | |||
7348b2b350 | |||
71e5d2bf43 | |||
7188bc9d8d | |||
49180e826e | |||
c6fbbb499f | |||
7b565f0f0e | |||
a7cc637aa2 | |||
824bb93b0a | |||
a99efd27d2 | |||
6266e16644 | |||
edd4fcfcc0 | |||
30dc229477 | |||
d288885a89 | |||
bd19d9d5b4 | |||
a284af2000 | |||
88e8d028b4 | |||
8cab2b9d87 | |||
14f8858d3d | |||
62f7642de9 | |||
98048a178e | |||
26eb9bf300 | |||
d4ff4a3e0d | |||
488e143e1f | |||
4d5c5ef404 | |||
628742c2f3 | |||
2492a42151 | |||
4a0b6e99d4 | |||
65946fe254 | |||
3286b5c05e | |||
ac8e02604d | |||
ddf71ffdac | |||
5dd57204dd | |||
9a622a5e44 | |||
42e79ce921 | |||
cdde6ad36e | |||
c03e615483 |
@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.7)
|
cmake_minimum_required(VERSION 3.7)
|
||||||
project(libdatachannel
|
project(libdatachannel
|
||||||
DESCRIPTION "WebRTC Data Channels Library"
|
DESCRIPTION "WebRTC Data Channels Library"
|
||||||
VERSION 0.9.0
|
VERSION 0.9.4
|
||||||
LANGUAGES CXX)
|
LANGUAGES CXX)
|
||||||
|
|
||||||
# Options
|
# Options
|
||||||
@ -11,6 +11,7 @@ option(NO_WEBSOCKET "Disable WebSocket support" OFF)
|
|||||||
option(NO_EXAMPLES "Disable examples" OFF)
|
option(NO_EXAMPLES "Disable examples" OFF)
|
||||||
option(NO_TESTS "Disable tests build" OFF)
|
option(NO_TESTS "Disable tests build" OFF)
|
||||||
option(WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
|
option(WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
|
||||||
|
option(CAPI_STDCALL "Set calling convention of C API callbacks stdcall" OFF)
|
||||||
|
|
||||||
if(USE_NICE)
|
if(USE_NICE)
|
||||||
option(USE_JUICE "Use libjuice" OFF)
|
option(USE_JUICE "Use libjuice" OFF)
|
||||||
@ -217,6 +218,11 @@ else()
|
|||||||
target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuiceStatic)
|
target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuiceStatic)
|
||||||
endif()
|
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::LibDataChannel ALIAS datachannel)
|
||||||
add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static)
|
add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static)
|
||||||
|
|
||||||
|
@ -202,5 +202,6 @@ ws->open("wss://my.websocket/service");
|
|||||||
## External resources
|
## External resources
|
||||||
- Rust wrapper for libdatachannel: [datachannel-rs](https://github.com/lerouxrgd/datachannel-rs)
|
- 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)
|
- 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)
|
- 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...2111295fe8
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)
|
cmake_policy(SET CMP0079 NEW)
|
||||||
endif()
|
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
|
set_target_properties(datachannel-client PROPERTIES
|
||||||
CXX_STANDARD 17
|
CXX_STANDARD 17
|
||||||
OUTPUT_NAME client)
|
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 Will Munn
|
||||||
* Copyright (c) 2020 Nico Chatzi
|
* Copyright (c) 2020 Nico Chatzi
|
||||||
* Copyright (c) 2020 Lara Mackey
|
* Copyright (c) 2020 Lara Mackey
|
||||||
|
* Copyright (c) 2020 Erik Cota-Robles
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@ -30,6 +31,7 @@
|
|||||||
#include <random>
|
#include <random>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include "parse_cl.h"
|
||||||
|
|
||||||
using namespace rtc;
|
using namespace rtc;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@ -43,20 +45,45 @@ unordered_map<string, shared_ptr<PeerConnection>> peerConnectionMap;
|
|||||||
unordered_map<string, shared_ptr<DataChannel>> dataChannelMap;
|
unordered_map<string, shared_ptr<DataChannel>> dataChannelMap;
|
||||||
|
|
||||||
string localId;
|
string localId;
|
||||||
|
bool echoDataChannelMessages = false;
|
||||||
|
|
||||||
shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
||||||
weak_ptr<WebSocket> wws, string id);
|
weak_ptr<WebSocket> wws, string id);
|
||||||
|
void confirmOnStdout(bool echoed, string id, string type, size_t length);
|
||||||
string randomId(size_t length);
|
string randomId(size_t length);
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
|
Cmdline *params = nullptr;
|
||||||
|
try {
|
||||||
|
params = new Cmdline(argc, argv);
|
||||||
|
} catch (const std::range_error&e) {
|
||||||
|
std::cout<< e.what() << '\n';
|
||||||
|
delete params;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
rtc::InitLogger(LogLevel::Debug);
|
rtc::InitLogger(LogLevel::Debug);
|
||||||
|
|
||||||
Configuration config;
|
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 suported." << 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);
|
localId = randomId(4);
|
||||||
cout << "The local ID is: " << localId << endl;
|
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>();
|
auto ws = make_shared<WebSocket>();
|
||||||
|
|
||||||
ws->onOpen([]() { cout << "WebSocket connected, signaling ready" << endl; });
|
ws->onOpen([]() { cout << "WebSocket connected, signaling ready" << endl; });
|
||||||
@ -101,7 +128,13 @@ int main(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const string url = "ws://localhost:8000/" + localId;
|
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);
|
ws->open(url);
|
||||||
|
|
||||||
cout << "Waiting for signaling to be connected..." << endl;
|
cout << "Waiting for signaling to be connected..." << endl;
|
||||||
@ -137,11 +170,20 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
|
dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
|
||||||
|
|
||||||
dc->onMessage([id](const variant<binary, string> &message) {
|
dc->onMessage([id, wdc = make_weak_ptr(dc)](const variant<binary, string> &message) {
|
||||||
if (!holds_alternative<string>(message))
|
static bool firstMessage = true;
|
||||||
return;
|
if (holds_alternative<string>(message) && (!echoDataChannelMessages || firstMessage)) {
|
||||||
|
cout << "Message from " << id << " received: " << get<string>(message) << endl;
|
||||||
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;
|
||||||
|
}
|
||||||
|
confirmOnStdout(echoed, id, (holds_alternative<string>(message) ? "text" : "binary"),
|
||||||
|
get<string>(message).length());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
dataChannelMap.emplace(id, dc);
|
dataChannelMap.emplace(id, dc);
|
||||||
@ -153,6 +195,7 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
dataChannelMap.clear();
|
dataChannelMap.clear();
|
||||||
peerConnectionMap.clear();
|
peerConnectionMap.clear();
|
||||||
|
delete params;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,11 +233,20 @@ shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
|||||||
|
|
||||||
dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
|
dc->onClosed([id]() { cout << "DataChannel from " << id << " closed" << endl; });
|
||||||
|
|
||||||
dc->onMessage([id](const variant<binary, string> &message) {
|
dc->onMessage([id, wdc = make_weak_ptr(dc)](const variant<binary, string> &message) {
|
||||||
if (!holds_alternative<string>(message))
|
static bool firstMessage = true;
|
||||||
return;
|
if (holds_alternative<string>(message) && (!echoDataChannelMessages || firstMessage)) {
|
||||||
|
cout << "Message from " << id << " received: " << get<string>(message) << endl;
|
||||||
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;
|
||||||
|
}
|
||||||
|
confirmOnStdout(echoed, id, (holds_alternative<string>(message) ? "text" : "binary"),
|
||||||
|
get<string>(message).length());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
dc->send("Hello from " + localId);
|
dc->send("Hello from " + localId);
|
||||||
@ -206,6 +258,19 @@ shared_ptr<PeerConnection> createPeerConnection(const Configuration &config,
|
|||||||
return pc;
|
return pc;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void confirmOnStdout(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 host " << 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
|
// Helper function to generate a random ID
|
||||||
string randomId(size_t length) {
|
string randomId(size_t length) {
|
||||||
static const string characters(
|
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
|
@ -73,7 +73,7 @@ int main() {
|
|||||||
track->onMessage(
|
track->onMessage(
|
||||||
[session, sock, addr](rtc::binary message) {
|
[session, sock, addr](rtc::binary message) {
|
||||||
// This is an RTP packet
|
// 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));
|
reinterpret_cast<const struct sockaddr *>(&addr), sizeof(addr));
|
||||||
},
|
},
|
||||||
nullptr);
|
nullptr);
|
||||||
|
@ -63,16 +63,20 @@ async def handle_websocket(websocket, path):
|
|||||||
print('Client {} disconnected'.format(client_id))
|
print('Client {} disconnected'.format(client_id))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
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
|
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:
|
if ssl_cert:
|
||||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||||
ssl_context.load_cert_chain(ssl_cert)
|
ssl_context.load_cert_chain(ssl_cert)
|
||||||
else:
|
else:
|
||||||
ssl_context = None
|
ssl_context = None
|
||||||
|
|
||||||
print('Listening on port {}'.format(port))
|
print('Listening on {}'.format(endpoint))
|
||||||
start_server = websockets.serve(handle_websocket, '127.0.0.1', port, ssl=ssl_context)
|
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_until_complete(start_server)
|
||||||
asyncio.get_event_loop().run_forever()
|
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"
|
checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libdatachannel_signaling_example"
|
name = "libdatachannel_signaling_server_example"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
|
@ -92,7 +92,9 @@ async fn handle(clients: ClientsMap, stream: TcpStream) {
|
|||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), std::io::Error> {
|
async fn main() -> Result<(), std::io::Error> {
|
||||||
let service = env::args().nth(1).unwrap_or("8000".to_string());
|
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)
|
let mut listener = TcpListener::bind(endpoint)
|
||||||
.await.expect("Listener binding failed");
|
.await.expect("Listener binding failed");
|
||||||
|
@ -98,8 +98,10 @@ wsServer.on('request', (req) => {
|
|||||||
clients[id] = conn;
|
clients[id] = conn;
|
||||||
});
|
});
|
||||||
|
|
||||||
const hostname = '127.0.0.1';
|
const endpoint = process.env.PORT || '8000';
|
||||||
const port = 8000;
|
const splitted = endpoint.split(':');
|
||||||
|
const port = splitted.pop();
|
||||||
|
const hostname = splitted.join(':') || '127.0.0.1';
|
||||||
|
|
||||||
httpServer.listen(port, hostname, () => {
|
httpServer.listen(port, hostname, () => {
|
||||||
console.log(`Server listening on ${hostname}:${port}`);
|
console.log(`Server listening on ${hostname}:${port}`);
|
||||||
|
@ -47,6 +47,8 @@ public:
|
|||||||
Role role() const;
|
Role role() const;
|
||||||
string roleString() const;
|
string roleString() const;
|
||||||
string bundleMid() const;
|
string bundleMid() const;
|
||||||
|
string iceUfrag() const;
|
||||||
|
string icePwd() const;
|
||||||
std::optional<string> fingerprint() const;
|
std::optional<string> fingerprint() const;
|
||||||
bool ended() const;
|
bool ended() const;
|
||||||
|
|
||||||
|
@ -76,11 +76,13 @@ public:
|
|||||||
const Configuration *config() const;
|
const Configuration *config() const;
|
||||||
State state() const;
|
State state() const;
|
||||||
GatheringState gatheringState() const;
|
GatheringState gatheringState() const;
|
||||||
|
bool hasLocalDescription() const;
|
||||||
|
bool hasRemoteDescription() const;
|
||||||
|
bool hasMedia() const;
|
||||||
std::optional<Description> localDescription() const;
|
std::optional<Description> localDescription() const;
|
||||||
std::optional<Description> remoteDescription() const;
|
std::optional<Description> remoteDescription() const;
|
||||||
std::optional<string> localAddress() const;
|
std::optional<string> localAddress() const;
|
||||||
std::optional<string> remoteAddress() const;
|
std::optional<string> remoteAddress() const;
|
||||||
bool hasMedia() const;
|
|
||||||
|
|
||||||
void setLocalDescription();
|
void setLocalDescription();
|
||||||
void setRemoteDescription(Description description);
|
void setRemoteDescription(Description description);
|
||||||
|
@ -38,17 +38,22 @@ public:
|
|||||||
~Queue();
|
~Queue();
|
||||||
|
|
||||||
void stop();
|
void stop();
|
||||||
|
bool running() const;
|
||||||
bool empty() const;
|
bool empty() const;
|
||||||
bool full() const;
|
bool full() const;
|
||||||
size_t size() const; // elements
|
size_t size() const; // elements
|
||||||
size_t amount() const; // amount
|
size_t amount() const; // amount
|
||||||
void push(T element);
|
void push(T element);
|
||||||
std::optional<T> pop();
|
std::optional<T> pop();
|
||||||
|
std::optional<T> tryPop();
|
||||||
std::optional<T> peek();
|
std::optional<T> peek();
|
||||||
std::optional<T> exchange(T element);
|
std::optional<T> exchange(T element);
|
||||||
bool wait(const std::optional<std::chrono::milliseconds> &duration = nullopt);
|
bool wait(const std::optional<std::chrono::milliseconds> &duration = nullopt);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void pushImpl(T element);
|
||||||
|
std::optional<T> popImpl();
|
||||||
|
|
||||||
const size_t mLimit;
|
const size_t mLimit;
|
||||||
size_t mAmount;
|
size_t mAmount;
|
||||||
std::queue<T> mQueue;
|
std::queue<T> mQueue;
|
||||||
@ -76,6 +81,11 @@ template <typename T> void Queue<T>::stop() {
|
|||||||
mPushCondition.notify_all();
|
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 {
|
template <typename T> bool Queue<T>::empty() const {
|
||||||
std::lock_guard lock(mMutex);
|
std::lock_guard lock(mMutex);
|
||||||
return mQueue.empty();
|
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) {
|
template <typename T> void Queue<T>::push(T element) {
|
||||||
std::unique_lock lock(mMutex);
|
std::unique_lock lock(mMutex);
|
||||||
mPushCondition.wait(lock, [this]() { return !mLimit || mQueue.size() < mLimit || mStopping; });
|
mPushCondition.wait(lock, [this]() { return !mLimit || mQueue.size() < mLimit || mStopping; });
|
||||||
if (!mStopping) {
|
pushImpl(std::move(element));
|
||||||
mAmount += mAmountFunction(element);
|
|
||||||
mQueue.emplace(std::move(element));
|
|
||||||
mPopCondition.notify_one();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> std::optional<T> Queue<T>::pop() {
|
template <typename T> std::optional<T> Queue<T>::pop() {
|
||||||
std::unique_lock lock(mMutex);
|
std::unique_lock lock(mMutex);
|
||||||
mPopCondition.wait(lock, [this]() { return !mQueue.empty() || mStopping; });
|
mPopCondition.wait(lock, [this]() { return !mQueue.empty() || mStopping; });
|
||||||
if (!mQueue.empty()) {
|
return popImpl();
|
||||||
mAmount -= mAmountFunction(mQueue.front());
|
}
|
||||||
std::optional<T> element{std::move(mQueue.front())};
|
|
||||||
mQueue.pop();
|
template <typename T> std::optional<T> Queue<T>::tryPop() {
|
||||||
return element;
|
std::unique_lock lock(mMutex);
|
||||||
} else {
|
return popImpl();
|
||||||
return nullopt;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> std::optional<T> Queue<T>::peek() {
|
template <typename T> std::optional<T> Queue<T>::peek() {
|
||||||
std::unique_lock lock(mMutex);
|
std::unique_lock lock(mMutex);
|
||||||
if (!mQueue.empty()) {
|
return !mQueue.empty() ? std::make_optional(mQueue.front()) : nullopt;
|
||||||
return std::optional<T>{mQueue.front()};
|
|
||||||
} else {
|
|
||||||
return nullopt;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> std::optional<T> Queue<T>::exchange(T element) {
|
template <typename T> std::optional<T> Queue<T>::exchange(T element) {
|
||||||
std::unique_lock lock(mMutex);
|
std::unique_lock lock(mMutex);
|
||||||
if (!mQueue.empty()) {
|
if (mQueue.empty())
|
||||||
std::swap(mQueue.front(), element);
|
|
||||||
return std::optional<T>{element};
|
|
||||||
} else {
|
|
||||||
return nullopt;
|
return nullopt;
|
||||||
}
|
|
||||||
|
std::swap(mQueue.front(), element);
|
||||||
|
return std::make_optional(std::move(element));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
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; });
|
mPopCondition.wait_for(lock, *duration, [this]() { return !mQueue.empty() || mStopping; });
|
||||||
else
|
else
|
||||||
mPopCondition.wait(lock, [this]() { return !mQueue.empty() || mStopping; });
|
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
|
} // namespace rtc
|
||||||
|
@ -29,6 +29,12 @@ extern "C" {
|
|||||||
#define RTC_EXPORT
|
#define RTC_EXPORT
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CAPI_STDCALL
|
||||||
|
#define RTC_API __stdcall
|
||||||
|
#else
|
||||||
|
#define RTC_API
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef RTC_ENABLE_WEBSOCKET
|
#ifndef RTC_ENABLE_WEBSOCKET
|
||||||
#define RTC_ENABLE_WEBSOCKET 1
|
#define RTC_ENABLE_WEBSOCKET 1
|
||||||
#endif
|
#endif
|
||||||
@ -81,22 +87,23 @@ typedef struct {
|
|||||||
unsigned int maxRetransmits; // ignored if reliable
|
unsigned int maxRetransmits; // ignored if reliable
|
||||||
} rtcReliability;
|
} rtcReliability;
|
||||||
|
|
||||||
typedef void (*rtcLogCallbackFunc)(rtcLogLevel level, const char *message);
|
typedef void (RTC_API *rtcLogCallbackFunc)(rtcLogLevel level, const char *message);
|
||||||
typedef void (*rtcDescriptionCallbackFunc)(const char *sdp, const char *type, void *ptr);
|
typedef void (RTC_API *rtcDescriptionCallbackFunc)(const char *sdp, const char *type, void *ptr);
|
||||||
typedef void (*rtcCandidateCallbackFunc)(const char *cand, const char *mid, void *ptr);
|
typedef void (RTC_API *rtcCandidateCallbackFunc)(const char *cand, const char *mid, void *ptr);
|
||||||
typedef void (*rtcStateChangeCallbackFunc)(rtcState state, void *ptr);
|
typedef void (RTC_API *rtcStateChangeCallbackFunc)(rtcState state, void *ptr);
|
||||||
typedef void (*rtcGatheringStateCallbackFunc)(rtcGatheringState state, void *ptr);
|
typedef void (RTC_API *rtcGatheringStateCallbackFunc)(rtcGatheringState state, void *ptr);
|
||||||
typedef void (*rtcDataChannelCallbackFunc)(int dc, void *ptr);
|
typedef void (RTC_API *rtcDataChannelCallbackFunc)(int dc, void *ptr);
|
||||||
typedef void (*rtcTrackCallbackFunc)(int tr, void *ptr);
|
typedef void (RTC_API *rtcTrackCallbackFunc)(int tr, void *ptr);
|
||||||
typedef void (*rtcOpenCallbackFunc)(void *ptr);
|
typedef void (RTC_API *rtcOpenCallbackFunc)(void *ptr);
|
||||||
typedef void (*rtcClosedCallbackFunc)(void *ptr);
|
typedef void (RTC_API *rtcClosedCallbackFunc)(void *ptr);
|
||||||
typedef void (*rtcErrorCallbackFunc)(const char *error, void *ptr);
|
typedef void (RTC_API *rtcErrorCallbackFunc)(const char *error, void *ptr);
|
||||||
typedef void (*rtcMessageCallbackFunc)(const char *message, int size, void *ptr);
|
typedef void (RTC_API *rtcMessageCallbackFunc)(const char *message, int size, void *ptr);
|
||||||
typedef void (*rtcBufferedAmountLowCallbackFunc)(void *ptr);
|
typedef void (RTC_API *rtcBufferedAmountLowCallbackFunc)(void *ptr);
|
||||||
typedef void (*rtcAvailableCallbackFunc)(void *ptr);
|
typedef void (RTC_API *rtcAvailableCallbackFunc)(void *ptr);
|
||||||
|
|
||||||
// Log
|
// 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
|
// User pointer
|
||||||
RTC_EXPORT void rtcSetUserPointer(int id, void *ptr);
|
RTC_EXPORT void rtcSetUserPointer(int id, void *ptr);
|
||||||
@ -114,6 +121,9 @@ RTC_EXPORT int rtcSetLocalDescription(int pc);
|
|||||||
RTC_EXPORT int rtcSetRemoteDescription(int pc, const char *sdp, 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 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 rtcGetLocalAddress(int pc, char *buffer, int size);
|
||||||
RTC_EXPORT int rtcGetRemoteAddress(int pc, char *buffer, int size);
|
RTC_EXPORT int rtcGetRemoteAddress(int pc, char *buffer, int size);
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ namespace rtc {
|
|||||||
|
|
||||||
Candidate::Candidate(string candidate, string mid) : mIsResolved(false) {
|
Candidate::Candidate(string candidate, string mid) : mIsResolved(false) {
|
||||||
const std::array prefixes{"a=", "candidate:"};
|
const std::array prefixes{"a=", "candidate:"};
|
||||||
for (string prefix : prefixes)
|
for (const string &prefix : prefixes)
|
||||||
if (hasprefix(candidate, prefix))
|
if (hasprefix(candidate, prefix))
|
||||||
candidate.erase(0, prefix.size());
|
candidate.erase(0, prefix.size());
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ bool Candidate::resolve(ResolveMode mode) {
|
|||||||
|
|
||||||
struct addrinfo *result = nullptr;
|
struct addrinfo *result = nullptr;
|
||||||
if (getaddrinfo(node.c_str(), service.c_str(), &hints, &result) == 0) {
|
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) {
|
if (p->ai_family == AF_INET || p->ai_family == AF_INET6) {
|
||||||
// Rewrite the candidate
|
// Rewrite the candidate
|
||||||
char nodebuffer[MAX_NUMERICNODE_LEN];
|
char nodebuffer[MAX_NUMERICNODE_LEN];
|
||||||
@ -113,9 +113,10 @@ bool Candidate::resolve(ResolveMode mode) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
freeaddrinfo(result);
|
freeaddrinfo(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mIsResolved;
|
return mIsResolved;
|
||||||
|
103
src/capi.cpp
103
src/capi.cpp
@ -195,12 +195,17 @@ template <typename F> int wrap(F func) {
|
|||||||
return RTC_ERR_SUCCESS; \
|
return RTC_ERR_SUCCESS; \
|
||||||
})
|
})
|
||||||
|
|
||||||
class plog_appender : public plog::IAppender {
|
class plogAppender : public plog::IAppender {
|
||||||
public:
|
public:
|
||||||
plog_appender(rtcLogCallbackFunc cb = nullptr) { set_callback(cb); }
|
plogAppender(rtcLogCallbackFunc cb = nullptr) { setCallback(cb); }
|
||||||
|
|
||||||
void set_callback(rtcLogCallbackFunc cb) {
|
plogAppender(plogAppender &&appender) : callback(nullptr) {
|
||||||
std::lock_guard lock(mutex);
|
std::lock_guard lock(appender.callbackMutex);
|
||||||
|
std::swap(appender.callback, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCallback(rtcLogCallbackFunc cb) {
|
||||||
|
std::lock_guard lock(callbackMutex);
|
||||||
callback = cb;
|
callback = cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,7 +220,7 @@ public:
|
|||||||
#else
|
#else
|
||||||
std::string str = formatted;
|
std::string str = formatted;
|
||||||
#endif
|
#endif
|
||||||
std::lock_guard lock(mutex);
|
std::lock_guard lock(callbackMutex);
|
||||||
if (callback)
|
if (callback)
|
||||||
callback(static_cast<rtcLogLevel>(record.getSeverity()), str.c_str());
|
callback(static_cast<rtcLogLevel>(record.getSeverity()), str.c_str());
|
||||||
else
|
else
|
||||||
@ -224,18 +229,24 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
rtcLogCallbackFunc callback;
|
rtcLogCallbackFunc callback;
|
||||||
|
std::mutex callbackMutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb) {
|
void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb) {
|
||||||
static std::optional<plog_appender> appender;
|
static std::optional<plogAppender> appender;
|
||||||
if (appender)
|
const auto severity = static_cast<plog::Severity>(level);
|
||||||
appender->set_callback(cb);
|
std::lock_guard lock(mutex);
|
||||||
else if (cb)
|
if (appender) {
|
||||||
appender.emplace(plog_appender(cb));
|
appender->setCallback(cb);
|
||||||
|
InitLogger(severity, nullptr); // change the severity
|
||||||
InitLogger(static_cast<plog::Severity>(level), appender ? &appender.value() : nullptr);
|
} 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); }
|
void rtcSetUserPointer(int i, void *ptr) { setUserPointer(i, ptr); }
|
||||||
@ -325,15 +336,17 @@ int rtcDeleteDataChannel(int dc) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int rtcAddTrack(int pc, const char *mediaDescriptionSdp) {
|
int rtcAddTrack(int pc, const char *mediaDescriptionSdp) {
|
||||||
if (!mediaDescriptionSdp)
|
return WRAP({
|
||||||
throw std::invalid_argument("Unexpected null pointer for track media description");
|
if (!mediaDescriptionSdp)
|
||||||
|
throw std::invalid_argument("Unexpected null pointer for track media description");
|
||||||
|
|
||||||
auto peerConnection = getPeerConnection(pc);
|
auto peerConnection = getPeerConnection(pc);
|
||||||
Description::Media media{string(mediaDescriptionSdp)};
|
Description::Media media{string(mediaDescriptionSdp)};
|
||||||
int tr = emplaceTrack(peerConnection->addTrack(std::move(media)));
|
int tr = emplaceTrack(peerConnection->addTrack(std::move(media)));
|
||||||
if (auto ptr = getUserPointer(pc))
|
if (auto ptr = getUserPointer(pc))
|
||||||
rtcSetUserPointer(tr, *ptr);
|
rtcSetUserPointer(tr, *ptr);
|
||||||
return tr;
|
return tr;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtcDeleteTrack(int tr) {
|
int rtcDeleteTrack(int tr) {
|
||||||
@ -516,6 +529,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) {
|
int rtcGetLocalAddress(int pc, char *buffer, int size) {
|
||||||
return WRAP({
|
return WRAP({
|
||||||
auto peerConnection = getPeerConnection(pc);
|
auto peerConnection = getPeerConnection(pc);
|
||||||
@ -533,6 +592,8 @@ int rtcGetLocalAddress(int pc, char *buffer, int size) {
|
|||||||
buffer[size] = '\0';
|
buffer[size] = '\0';
|
||||||
return size + 1;
|
return size + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return RTC_ERR_FAILURE;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -553,6 +614,8 @@ int rtcGetRemoteAddress(int pc, char *buffer, int size) {
|
|||||||
buffer[size] = '\0';
|
buffer[size] = '\0';
|
||||||
return int(size + 1);
|
return int(size + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return RTC_ERR_FAILURE;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,8 +122,8 @@ bool DataChannel::send(const byte *data, size_t size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::optional<message_variant> DataChannel::receive() {
|
std::optional<message_variant> DataChannel::receive() {
|
||||||
while (!mRecvQueue.empty()) {
|
while (auto next = mRecvQueue.tryPop()) {
|
||||||
auto message = *mRecvQueue.pop();
|
message_ptr message = std::move(*next);
|
||||||
if (message->type == Message::Control) {
|
if (message->type == Message::Control) {
|
||||||
auto raw = reinterpret_cast<const uint8_t *>(message->data());
|
auto raw = reinterpret_cast<const uint8_t *>(message->data());
|
||||||
if (!message->empty() && raw[0] == MESSAGE_CLOSE)
|
if (!message->empty() && raw[0] == MESSAGE_CLOSE)
|
||||||
@ -218,6 +218,8 @@ void DataChannel::incoming(message_ptr message) {
|
|||||||
|
|
||||||
switch (message->type) {
|
switch (message->type) {
|
||||||
case Message::Control: {
|
case Message::Control: {
|
||||||
|
if (message->size() == 0)
|
||||||
|
break; // Ignore
|
||||||
auto raw = reinterpret_cast<const uint8_t *>(message->data());
|
auto raw = reinterpret_cast<const uint8_t *>(message->data());
|
||||||
switch (raw[0]) {
|
switch (raw[0]) {
|
||||||
case MESSAGE_OPEN:
|
case MESSAGE_OPEN:
|
||||||
|
@ -79,13 +79,15 @@ Description::Description(const string &sdp, Type type, Role role)
|
|||||||
std::uniform_int_distribution<uint32_t> uniform;
|
std::uniform_int_distribution<uint32_t> uniform;
|
||||||
mSessionId = std::to_string(uniform(generator));
|
mSessionId = std::to_string(uniform(generator));
|
||||||
|
|
||||||
std::istringstream ss(sdp);
|
|
||||||
std::shared_ptr<Entry> current;
|
|
||||||
|
|
||||||
int index = -1;
|
int index = -1;
|
||||||
string line;
|
std::shared_ptr<Entry> current;
|
||||||
while (std::getline(ss, line) || !line.empty()) {
|
std::istringstream ss(sdp);
|
||||||
|
while (ss) {
|
||||||
|
string line;
|
||||||
|
std::getline(ss, line);
|
||||||
trim_end(line);
|
trim_end(line);
|
||||||
|
if (line.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
// Media description line (aka m-line)
|
// Media description line (aka m-line)
|
||||||
if (match_prefix(line, "m=")) {
|
if (match_prefix(line, "m=")) {
|
||||||
@ -130,7 +132,13 @@ Description::Description(const string &sdp, Type type, Role role)
|
|||||||
} else if (current) {
|
} else if (current) {
|
||||||
current->parseSdpLine(std::move(line));
|
current->parseSdpLine(std::move(line));
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
if (mIceUfrag.empty())
|
||||||
|
throw std::invalid_argument("Missing ice-ufrag parameter in SDP description");
|
||||||
|
|
||||||
|
if (mIcePwd.empty())
|
||||||
|
throw std::invalid_argument("Missing ice-pwd parameter in SDP description");
|
||||||
}
|
}
|
||||||
|
|
||||||
Description::Type Description::type() const { return mType; }
|
Description::Type Description::type() const { return mType; }
|
||||||
@ -146,6 +154,10 @@ string Description::bundleMid() const {
|
|||||||
return !mEntries.empty() ? mEntries[0]->mid() : "0";
|
return !mEntries.empty() ? mEntries[0]->mid() : "0";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string Description::iceUfrag() const { return mIceUfrag; }
|
||||||
|
|
||||||
|
string Description::icePwd() const { return mIcePwd; }
|
||||||
|
|
||||||
std::optional<string> Description::fingerprint() const { return mFingerprint; }
|
std::optional<string> Description::fingerprint() const { return mFingerprint; }
|
||||||
|
|
||||||
bool Description::ended() const { return mEnded; }
|
bool Description::ended() const { return mEnded; }
|
||||||
@ -488,9 +500,13 @@ void Description::Application::parseSdpLine(string_view line) {
|
|||||||
|
|
||||||
Description::Media::Media(const string &sdp) : Entry(sdp, "", Direction::Unknown) {
|
Description::Media::Media(const string &sdp) : Entry(sdp, "", Direction::Unknown) {
|
||||||
std::istringstream ss(sdp);
|
std::istringstream ss(sdp);
|
||||||
string line;
|
while (ss) {
|
||||||
while (std::getline(ss, line) || !line.empty()) {
|
string line;
|
||||||
|
std::getline(ss, line);
|
||||||
trim_end(line);
|
trim_end(line);
|
||||||
|
if (line.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
parseSdpLine(line);
|
parseSdpLine(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
ssize_t DtlsTransport::ReadCallback(gnutls_transport_ptr_t ptr, void *data, size_t maxlen) {
|
||||||
DtlsTransport *t = static_cast<DtlsTransport *>(ptr);
|
DtlsTransport *t = static_cast<DtlsTransport *>(ptr);
|
||||||
if (auto next = t->mIncomingQueue.pop()) {
|
if (auto next = t->mIncomingQueue.pop()) {
|
||||||
auto message = *next;
|
message_ptr message = std::move(*next);
|
||||||
ssize_t len = std::min(maxlen, message->size());
|
ssize_t len = std::min(maxlen, message->size());
|
||||||
std::memcpy(data, message->data(), len);
|
std::memcpy(data, message->data(), len);
|
||||||
gnutls_transport_set_errno(t->mSession, 0);
|
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) {
|
int DtlsTransport::TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms) {
|
||||||
DtlsTransport *t = static_cast<DtlsTransport *>(ptr);
|
DtlsTransport *t = static_cast<DtlsTransport *>(ptr);
|
||||||
t->mIncomingQueue.wait(ms != GNUTLS_INDEFINITE_TIMEOUT ? std::make_optional(milliseconds(ms))
|
bool notEmpty = t->mIncomingQueue.wait(
|
||||||
: nullopt);
|
ms != GNUTLS_INDEFINITE_TIMEOUT ? std::make_optional(milliseconds(ms)) : nullopt);
|
||||||
return !t->mIncomingQueue.empty() ? 1 : 0;
|
return notEmpty ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#else // USE_GNUTLS==0
|
#else // USE_GNUTLS==0
|
||||||
@ -435,10 +435,10 @@ void DtlsTransport::runRecvLoop() {
|
|||||||
|
|
||||||
const size_t bufferSize = maxMtu;
|
const size_t bufferSize = maxMtu;
|
||||||
byte buffer[bufferSize];
|
byte buffer[bufferSize];
|
||||||
while (true) {
|
while (mIncomingQueue.running()) {
|
||||||
// Process pending messages
|
// Process pending messages
|
||||||
while (!mIncomingQueue.empty()) {
|
while (auto next = mIncomingQueue.tryPop()) {
|
||||||
auto message = *mIncomingQueue.pop();
|
message_ptr message = std::move(*next);
|
||||||
BIO_write(mInBio, message->data(), int(message->size()));
|
BIO_write(mInBio, message->data(), int(message->size()));
|
||||||
|
|
||||||
if (state() == State::Connecting) {
|
if (state() == State::Connecting) {
|
||||||
@ -492,8 +492,7 @@ void DtlsTransport::runRecvLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mIncomingQueue.wait(duration))
|
mIncomingQueue.wait(duration);
|
||||||
break; // queue is stopped
|
|
||||||
}
|
}
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
PLOG_ERROR << "DTLS recv: " << e.what();
|
PLOG_ERROR << "DTLS recv: " << e.what();
|
||||||
|
@ -58,8 +58,32 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
|||||||
if (config.enableIceTcp) {
|
if (config.enableIceTcp) {
|
||||||
PLOG_WARNING << "ICE-TCP is not supported with libjuice";
|
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_handler(IceTransport::LogCallback);
|
||||||
juice_set_log_level(JUICE_LOG_LEVEL_VERBOSE);
|
juice_set_log_level(level);
|
||||||
|
|
||||||
juice_config_t jconfig = {};
|
juice_config_t jconfig = {};
|
||||||
jconfig.cb_state_changed = IceTransport::StateChangeCallback;
|
jconfig.cb_state_changed = IceTransport::StateChangeCallback;
|
||||||
@ -357,8 +381,11 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
|||||||
hints.ai_protocol = IPPROTO_UDP;
|
hints.ai_protocol = IPPROTO_UDP;
|
||||||
hints.ai_flags = AI_ADDRCONFIG;
|
hints.ai_flags = AI_ADDRCONFIG;
|
||||||
struct addrinfo *result = nullptr;
|
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;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto p = result; p; p = p->ai_next) {
|
for (auto p = result; p; p = p->ai_next) {
|
||||||
if (p->ai_family == AF_INET) {
|
if (p->ai_family == AF_INET) {
|
||||||
@ -400,8 +427,11 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
|
|||||||
server.relayType == IceServer::RelayType::TurnUdp ? IPPROTO_UDP : IPPROTO_TCP;
|
server.relayType == IceServer::RelayType::TurnUdp ? IPPROTO_UDP : IPPROTO_TCP;
|
||||||
hints.ai_flags = AI_ADDRCONFIG;
|
hints.ai_flags = AI_ADDRCONFIG;
|
||||||
struct addrinfo *result = nullptr;
|
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;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
if (p->ai_family == AF_INET || p->ai_family == AF_INET6) {
|
||||||
|
@ -24,6 +24,8 @@
|
|||||||
#include "plog/Log.h"
|
#include "plog/Log.h"
|
||||||
#include "plog/Logger.h"
|
#include "plog/Logger.h"
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
namespace rtc {
|
namespace rtc {
|
||||||
|
|
||||||
void InitLogger(LogLevel level) { InitLogger(static_cast<plog::Severity>(level)); }
|
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) {
|
void InitLogger(plog::Severity severity, plog::IAppender *appender) {
|
||||||
static plog::ColorConsoleAppender<plog::TxtFormatter> consoleAppender;
|
static plog::ColorConsoleAppender<plog::TxtFormatter> consoleAppender;
|
||||||
static plog::Logger<0> *logger = nullptr;
|
static plog::Logger<0> *logger = nullptr;
|
||||||
|
static std::mutex mutex;
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
if (!logger) {
|
if (!logger) {
|
||||||
logger = &plog::init(severity, appender ? appender : &consoleAppender);
|
logger = &plog::init(severity, appender ? appender : &consoleAppender);
|
||||||
PLOG_DEBUG << "Logger initialized";
|
PLOG_DEBUG << "Logger initialized";
|
||||||
|
@ -82,11 +82,27 @@ std::optional<Description> PeerConnection::remoteDescription() const {
|
|||||||
return mRemoteDescription;
|
return mRemoteDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PeerConnection::hasLocalDescription() const {
|
||||||
|
std::lock_guard lock(mLocalDescriptionMutex);
|
||||||
|
return bool(mLocalDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
void PeerConnection::setLocalDescription() {
|
||||||
PLOG_VERBOSE << "Setting local description";
|
PLOG_VERBOSE << "Setting local description";
|
||||||
|
|
||||||
if (std::atomic_load(&mIceTransport)) {
|
if (std::atomic_load(&mIceTransport)) {
|
||||||
PLOG_DEBUG << "Local description is already set";
|
PLOG_DEBUG << "Local description is already set, ignoring";
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of
|
// RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of
|
||||||
@ -101,6 +117,9 @@ void PeerConnection::setLocalDescription() {
|
|||||||
void PeerConnection::setRemoteDescription(Description description) {
|
void PeerConnection::setRemoteDescription(Description description) {
|
||||||
PLOG_VERBOSE << "Setting remote description: " << string(description);
|
PLOG_VERBOSE << "Setting remote description: " << string(description);
|
||||||
|
|
||||||
|
if (hasRemoteDescription())
|
||||||
|
throw std::logic_error("Remote description is already set");
|
||||||
|
|
||||||
if (description.mediaCount() == 0)
|
if (description.mediaCount() == 0)
|
||||||
throw std::invalid_argument("Remote description has no media line");
|
throw std::invalid_argument("Remote description has no media line");
|
||||||
|
|
||||||
@ -120,9 +139,27 @@ void PeerConnection::setRemoteDescription(Description description) {
|
|||||||
if (!description.fingerprint())
|
if (!description.fingerprint())
|
||||||
throw std::invalid_argument("Remote description has no fingerprint");
|
throw std::invalid_argument("Remote description has no fingerprint");
|
||||||
|
|
||||||
description.hintType(localDescription() ? Description::Type::Answer : Description::Type::Offer);
|
description.hintType(hasLocalDescription() ? Description::Type::Answer
|
||||||
auto type = description.type();
|
: Description::Type::Offer);
|
||||||
auto remoteCandidates = description.extractCandidates(); // Candidates will be added at the end
|
|
||||||
|
if (description.type() == Description::Type::Offer) {
|
||||||
|
if (hasLocalDescription()) {
|
||||||
|
PLOG_ERROR << "Got a remote offer description while an answer was expected";
|
||||||
|
throw std::logic_error("Got an unexpected remote offer description");
|
||||||
|
}
|
||||||
|
} else { // Answer
|
||||||
|
if (auto local = localDescription()) {
|
||||||
|
if (description.iceUfrag() == local->iceUfrag() &&
|
||||||
|
description.icePwd() == local->icePwd())
|
||||||
|
throw std::logic_error("Got the local description as remote description");
|
||||||
|
} else {
|
||||||
|
PLOG_ERROR << "Got a remote answer description while an offer was expected";
|
||||||
|
throw std::logic_error("Got an unexpected remote answer description");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Candidates will be added at the end, extract them for now
|
||||||
|
auto remoteCandidates = description.extractCandidates();
|
||||||
|
|
||||||
auto iceTransport = std::atomic_load(&mIceTransport);
|
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||||
if (!iceTransport)
|
if (!iceTransport)
|
||||||
@ -130,11 +167,12 @@ void PeerConnection::setRemoteDescription(Description description) {
|
|||||||
iceTransport->setRemoteDescription(description);
|
iceTransport->setRemoteDescription(description);
|
||||||
|
|
||||||
{
|
{
|
||||||
|
// Set as remote description
|
||||||
std::lock_guard lock(mRemoteDescriptionMutex);
|
std::lock_guard lock(mRemoteDescriptionMutex);
|
||||||
mRemoteDescription.emplace(std::move(description));
|
mRemoteDescription.emplace(std::move(description));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == Description::Type::Offer) {
|
if (description.type() == Description::Type::Offer) {
|
||||||
// This is an offer and we are the answerer.
|
// This is an offer and we are the answerer.
|
||||||
Description localDescription = iceTransport->getLocalDescription(Description::Type::Answer);
|
Description localDescription = iceTransport->getLocalDescription(Description::Type::Answer);
|
||||||
processLocalDescription(localDescription);
|
processLocalDescription(localDescription);
|
||||||
@ -161,7 +199,7 @@ void PeerConnection::setRemoteDescription(Description description) {
|
|||||||
|
|
||||||
for (const auto &candidate : remoteCandidates)
|
for (const auto &candidate : remoteCandidates)
|
||||||
addRemoteCandidate(candidate);
|
addRemoteCandidate(candidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerConnection::addRemoteCandidate(Candidate candidate) {
|
void PeerConnection::addRemoteCandidate(Candidate candidate) {
|
||||||
PLOG_VERBOSE << "Adding remote candidate: " << string(candidate);
|
PLOG_VERBOSE << "Adding remote candidate: " << string(candidate);
|
||||||
@ -250,13 +288,8 @@ void PeerConnection::onGatheringStateChange(std::function<void(GatheringState st
|
|||||||
mGatheringStateChangeCallback = callback;
|
mGatheringStateChangeCallback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PeerConnection::hasMedia() const {
|
|
||||||
auto local = localDescription();
|
|
||||||
return local && local->hasAudioOrVideo();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Track> PeerConnection::addTrack(Description::Media description) {
|
std::shared_ptr<Track> PeerConnection::addTrack(Description::Media description) {
|
||||||
if (localDescription())
|
if (hasLocalDescription())
|
||||||
throw std::logic_error("Tracks must be created before local description");
|
throw std::logic_error("Tracks must be created before local description");
|
||||||
|
|
||||||
if (auto it = mTracks.find(description.mid()); it != mTracks.end())
|
if (auto it = mTracks.find(description.mid()); it != mTracks.end())
|
||||||
@ -514,17 +547,17 @@ void PeerConnection::forwardMessage(message_ptr message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto channel = findDataChannel(uint16_t(message->stream));
|
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) {
|
if (!channel) {
|
||||||
|
auto iceTransport = std::atomic_load(&mIceTransport);
|
||||||
|
auto sctpTransport = std::atomic_load(&mSctpTransport);
|
||||||
|
if (!iceTransport || !sctpTransport)
|
||||||
|
return;
|
||||||
|
|
||||||
const byte dataChannelOpenMessage{0x03};
|
const byte dataChannelOpenMessage{0x03};
|
||||||
unsigned int remoteParity = (iceTransport->role() == Description::Role::Active) ? 1 : 0;
|
unsigned int remoteParity = (iceTransport->role() == Description::Role::Active) ? 1 : 0;
|
||||||
if (message->type == Message::Control && *message->data() == dataChannelOpenMessage &&
|
if (message->type == Message::Control && *message->data() == dataChannelOpenMessage &&
|
||||||
message->stream % 2 == remoteParity) {
|
message->stream % 2 == remoteParity) {
|
||||||
|
|
||||||
channel =
|
channel =
|
||||||
std::make_shared<DataChannel>(shared_from_this(), sctpTransport, message->stream);
|
std::make_shared<DataChannel>(shared_from_this(), sctpTransport, message->stream);
|
||||||
channel->onOpen(weak_bind(&PeerConnection::triggerDataChannel, this,
|
channel->onOpen(weak_bind(&PeerConnection::triggerDataChannel, this,
|
||||||
@ -699,6 +732,9 @@ void PeerConnection::openTracks() {
|
|||||||
void PeerConnection::processLocalDescription(Description description) {
|
void PeerConnection::processLocalDescription(Description description) {
|
||||||
int activeMediaCount = 0;
|
int activeMediaCount = 0;
|
||||||
|
|
||||||
|
if (hasLocalDescription())
|
||||||
|
throw std::logic_error("Local description is already set");
|
||||||
|
|
||||||
if (auto remote = remoteDescription()) {
|
if (auto remote = remoteDescription()) {
|
||||||
// Reciprocate remote description
|
// Reciprocate remote description
|
||||||
for (int i = 0; i < remote->mediaCount(); ++i)
|
for (int i = 0; i < remote->mediaCount(); ++i)
|
||||||
@ -782,8 +818,11 @@ void PeerConnection::processLocalDescription(Description description) {
|
|||||||
// Set local fingerprint (wait for certificate if necessary)
|
// Set local fingerprint (wait for certificate if necessary)
|
||||||
description.setFingerprint(mCertificate.get()->fingerprint());
|
description.setFingerprint(mCertificate.get()->fingerprint());
|
||||||
|
|
||||||
std::lock_guard lock(mLocalDescriptionMutex);
|
{
|
||||||
mLocalDescription.emplace(std::move(description));
|
// Set as local description
|
||||||
|
std::lock_guard lock(mLocalDescriptionMutex);
|
||||||
|
mLocalDescription.emplace(std::move(description));
|
||||||
|
}
|
||||||
|
|
||||||
mProcessor->enqueue([this, description = *mLocalDescription]() {
|
mProcessor->enqueue([this, description = *mLocalDescription]() {
|
||||||
PLOG_VERBOSE << "Issuing local description: " << description;
|
PLOG_VERBOSE << "Issuing local description: " << description;
|
||||||
|
56
src/rtcp.cpp
56
src/rtcp.cpp
@ -71,24 +71,24 @@ private:
|
|||||||
uint32_t _delaySinceLastReport;
|
uint32_t _delaySinceLastReport;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
inline void preparePacket(SSRC ssrc, [[maybe_unused]] unsigned int packetsLost,
|
inline void preparePacket(SSRC ssrc_, [[maybe_unused]] unsigned int packetsLost,
|
||||||
[[maybe_unused]] unsigned int totalPackets, uint16_t highestSeqNo,
|
[[maybe_unused]] unsigned int totalPackets, uint16_t highestSeqNo,
|
||||||
uint16_t seqNoCycles, uint32_t jitter, uint64_t lastSR_NTP,
|
uint16_t seqNoCycles, uint32_t jitter, uint64_t lastSR_NTP,
|
||||||
uint64_t lastSR_DELAY) {
|
uint64_t lastSR_DELAY) {
|
||||||
setSeqNo(highestSeqNo, seqNoCycles);
|
setSeqNo(highestSeqNo, seqNoCycles);
|
||||||
setJitter(jitter);
|
setJitter(jitter);
|
||||||
setSSRC(ssrc);
|
setSSRC(ssrc_);
|
||||||
|
|
||||||
// Middle 32 bits of NTP Timestamp
|
// Middle 32 bits of NTP Timestamp
|
||||||
// this->lastReport = lastSR_NTP >> 16u;
|
// _lastReport = lastSR_NTP >> 16u;
|
||||||
setNTPOfSR(uint32_t(lastSR_NTP));
|
setNTPOfSR(uint32_t(lastSR_NTP));
|
||||||
setDelaySinceSR(uint32_t(lastSR_DELAY));
|
setDelaySinceSR(uint32_t(lastSR_DELAY));
|
||||||
|
|
||||||
// The delay, expressed in units of 1/65536 seconds
|
// The delay, expressed in units of 1/65536 seconds
|
||||||
// this->delaySinceLastReport = lastSR_DELAY;
|
// _delaySinceLastReport = lastSR_DELAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void setSSRC(SSRC ssrc) { this->ssrc = htonl(ssrc); }
|
inline void setSSRC(SSRC ssrc_) { ssrc = htonl(ssrc_); }
|
||||||
inline SSRC getSSRC() const { return ntohl(ssrc); }
|
inline SSRC getSSRC() const { return ntohl(ssrc); }
|
||||||
|
|
||||||
inline void setPacketsLost([[maybe_unused]] unsigned int packetsLost,
|
inline void setPacketsLost([[maybe_unused]] unsigned int packetsLost,
|
||||||
@ -172,7 +172,7 @@ public:
|
|||||||
|
|
||||||
struct RTCP_SR {
|
struct RTCP_SR {
|
||||||
RTCP_HEADER header;
|
RTCP_HEADER header;
|
||||||
SSRC senderSSRC;
|
SSRC senderSsrc;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint64_t _ntpTimestamp;
|
uint64_t _ntpTimestamp;
|
||||||
@ -183,11 +183,11 @@ private:
|
|||||||
RTCP_ReportBlock _reportBlocks;
|
RTCP_ReportBlock _reportBlocks;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
inline void preparePacket(SSRC senderSSRC, uint8_t reportCount) {
|
inline void preparePacket(SSRC senderSsrc_, uint8_t reportCount) {
|
||||||
unsigned int length =
|
unsigned int length =
|
||||||
((sizeof(header) + 24 + reportCount * sizeof(RTCP_ReportBlock)) / 4) - 1;
|
((sizeof(header) + 24 + reportCount * sizeof(RTCP_ReportBlock)) / 4) - 1;
|
||||||
header.prepareHeader(200, reportCount, uint16_t(length));
|
header.prepareHeader(200, reportCount, uint16_t(length));
|
||||||
this->senderSSRC = htonl(senderSSRC);
|
senderSsrc = htonl(senderSsrc_);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline RTCP_ReportBlock *getReportBlock(int num) { return &_reportBlocks + num; }
|
inline RTCP_ReportBlock *getReportBlock(int num) { return &_reportBlocks + num; }
|
||||||
@ -209,7 +209,7 @@ public:
|
|||||||
inline void log() const {
|
inline void log() const {
|
||||||
header.log();
|
header.log();
|
||||||
PLOG_DEBUG << "RTCP SR: "
|
PLOG_DEBUG << "RTCP SR: "
|
||||||
<< " SSRC=" << ntohl(senderSSRC) << ", NTP_TS=" << ntpTimestamp()
|
<< " SSRC=" << ntohl(senderSsrc) << ", NTP_TS=" << ntpTimestamp()
|
||||||
<< ", RTP_TS=" << rtpTimestamp() << ", packetCount=" << packetCount()
|
<< ", RTP_TS=" << rtpTimestamp() << ", packetCount=" << packetCount()
|
||||||
<< ", octetCount=" << octetCount();
|
<< ", octetCount=" << octetCount();
|
||||||
|
|
||||||
@ -221,7 +221,7 @@ public:
|
|||||||
|
|
||||||
struct RTCP_RR {
|
struct RTCP_RR {
|
||||||
RTCP_HEADER header;
|
RTCP_HEADER header;
|
||||||
SSRC senderSSRC;
|
SSRC senderSsrc;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
RTCP_ReportBlock _reportBlocks;
|
RTCP_ReportBlock _reportBlocks;
|
||||||
@ -230,19 +230,19 @@ public:
|
|||||||
inline RTCP_ReportBlock *getReportBlock(int num) { return &_reportBlocks + num; }
|
inline RTCP_ReportBlock *getReportBlock(int num) { return &_reportBlocks + num; }
|
||||||
inline const RTCP_ReportBlock *getReportBlock(int num) const { return &_reportBlocks + num; }
|
inline const RTCP_ReportBlock *getReportBlock(int num) const { return &_reportBlocks + num; }
|
||||||
|
|
||||||
inline SSRC getSenderSSRC() const { return ntohl(senderSSRC); }
|
inline SSRC getSenderSSRC() const { return ntohl(senderSsrc); }
|
||||||
inline void setSenderSSRC(SSRC ssrc) { this->senderSSRC = htonl(ssrc); }
|
inline void setSenderSSRC(SSRC ssrc) { senderSsrc = htonl(ssrc); }
|
||||||
|
|
||||||
[[nodiscard]] inline size_t getSize() const {
|
[[nodiscard]] inline size_t getSize() const {
|
||||||
// "length" in packet is one less than the number of 32 bit words in the packet.
|
// "length" in packet is one less than the number of 32 bit words in the packet.
|
||||||
return sizeof(uint32_t) * (1 + size_t(header.length()));
|
return sizeof(uint32_t) * (1 + size_t(header.length()));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void preparePacket(SSRC senderSSRC, uint8_t reportCount) {
|
inline void preparePacket(SSRC ssrc, uint8_t reportCount) {
|
||||||
// "length" in packet is one less than the number of 32 bit words in the packet.
|
// "length" in packet is one less than the number of 32 bit words in the packet.
|
||||||
size_t length = (sizeWithReportBlocks(reportCount) / 4) - 1;
|
size_t length = (sizeWithReportBlocks(reportCount) / 4) - 1;
|
||||||
header.prepareHeader(201, reportCount, uint16_t(length));
|
header.prepareHeader(201, reportCount, uint16_t(length));
|
||||||
this->senderSSRC = htonl(senderSSRC);
|
senderSsrc = htonl(ssrc);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline static size_t sizeWithReportBlocks(uint8_t reportCount) {
|
inline static size_t sizeWithReportBlocks(uint8_t reportCount) {
|
||||||
@ -252,7 +252,7 @@ public:
|
|||||||
inline void log() const {
|
inline void log() const {
|
||||||
header.log();
|
header.log();
|
||||||
PLOG_DEBUG << "RTCP RR: "
|
PLOG_DEBUG << "RTCP RR: "
|
||||||
<< " SSRC=" << ntohl(senderSSRC);
|
<< " SSRC=" << ntohl(senderSsrc);
|
||||||
|
|
||||||
for (unsigned i = 0; i < unsigned(header.reportCount()); i++) {
|
for (unsigned i = 0; i < unsigned(header.reportCount()); i++) {
|
||||||
getReportBlock(i)->log();
|
getReportBlock(i)->log();
|
||||||
@ -262,7 +262,7 @@ public:
|
|||||||
|
|
||||||
struct RTCP_REMB {
|
struct RTCP_REMB {
|
||||||
RTCP_HEADER header;
|
RTCP_HEADER header;
|
||||||
SSRC senderSSRC;
|
SSRC senderSsrc;
|
||||||
SSRC mediaSourceSSRC;
|
SSRC mediaSourceSSRC;
|
||||||
|
|
||||||
// Unique identifier
|
// Unique identifier
|
||||||
@ -278,48 +278,48 @@ struct RTCP_REMB {
|
|||||||
return sizeof(uint32_t) * (1 + size_t(header.length()));
|
return sizeof(uint32_t) * (1 + size_t(header.length()));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void preparePacket(SSRC senderSSRC, unsigned int numSSRC, unsigned int bitrate) {
|
inline void preparePacket(SSRC senderSsrc_, unsigned int numSSRC, unsigned int br) {
|
||||||
// Report Count becomes the format here.
|
// Report Count becomes the format here.
|
||||||
header.prepareHeader(206, 15, 0);
|
header.prepareHeader(206, 15, 0);
|
||||||
|
|
||||||
// Always zero.
|
// Always zero.
|
||||||
mediaSourceSSRC = 0;
|
mediaSourceSSRC = 0;
|
||||||
|
|
||||||
this->senderSSRC = htonl(senderSSRC);
|
senderSsrc = htonl(senderSsrc_);
|
||||||
setBitrate(numSSRC, bitrate);
|
setBitrate(numSSRC, br);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void setBitrate(unsigned int numSSRC, unsigned int bitrate) {
|
inline void setBitrate(unsigned int numSSRC, unsigned int br) {
|
||||||
unsigned int exp = 0;
|
unsigned int exp = 0;
|
||||||
while (bitrate > pow(2, 18) - 1) {
|
while (br > pow(2, 18) - 1) {
|
||||||
exp++;
|
exp++;
|
||||||
bitrate /= 2;
|
br /= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// "length" in packet is one less than the number of 32 bit words in the packet.
|
// "length" in packet is one less than the number of 32 bit words in the packet.
|
||||||
header.setLength(uint16_t(((sizeof(header) + 4 * 2 + 4 + 4) / 4) - 1 + numSSRC));
|
header.setLength(uint16_t(((sizeof(header) + 4 * 2 + 4 + 4) / 4) - 1 + numSSRC));
|
||||||
|
|
||||||
this->bitrate = htonl((numSSRC << (32u - 8u)) | (exp << (32u - 8u - 6u)) | bitrate);
|
bitrate = htonl((numSSRC << (32u - 8u)) | (exp << (32u - 8u - 6u)) | br);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Make this work
|
// TODO Make this work
|
||||||
// uint64_t getBitrate() const{
|
// uint64_t getBitrate() const{
|
||||||
// uint32_t ntohed = ntohl(this->bitrate);
|
// uint32_t ntohed = ntohl(bitrate);
|
||||||
// uint64_t bitrate = ntohed & (unsigned int)(pow(2, 18)-1);
|
// uint64_t bitrate = ntohed & (unsigned int)(pow(2, 18)-1);
|
||||||
// unsigned int exp = ntohed & ((unsigned int)( (pow(2, 6)-1)) << (32u-8u-6u));
|
// unsigned int exp = ntohed & ((unsigned int)( (pow(2, 6)-1)) << (32u-8u-6u));
|
||||||
// return bitrate * pow(2,exp);
|
// return bitrate * pow(2,exp);
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// uint8_t getNumSSRCS() const {
|
// uint8_t getNumSSRCS() const {
|
||||||
// return ntohl(this->bitrate) & (((unsigned int) pow(2,8)-1) << (32u-8u));
|
// return ntohl(bitrate) & (((unsigned int) pow(2,8)-1) << (32u-8u));
|
||||||
// }
|
// }
|
||||||
|
|
||||||
inline void setSSRC(uint8_t iterator, SSRC ssrc) { this->ssrc[iterator] = htonl(ssrc); }
|
inline void setSSRC(uint8_t iterator, SSRC ssrc_) { ssrc[iterator] = htonl(ssrc_); }
|
||||||
|
|
||||||
inline void log() const {
|
inline void log() const {
|
||||||
header.log();
|
header.log();
|
||||||
PLOG_DEBUG << "RTCP REMB: "
|
PLOG_DEBUG << "RTCP REMB: "
|
||||||
<< " SSRC=" << ntohl(senderSSRC);
|
<< " SSRC=" << ntohl(senderSsrc);
|
||||||
}
|
}
|
||||||
|
|
||||||
static unsigned int sizeWithSSRCs(int numSSRC) {
|
static unsigned int sizeWithSSRCs(int numSSRC) {
|
||||||
@ -407,7 +407,7 @@ void RtcpSession::pushRR(unsigned int lastSR_delay) {
|
|||||||
auto msg = rtc::make_message(RTCP_RR::sizeWithReportBlocks(1), rtc::Message::Type::Control);
|
auto msg = rtc::make_message(RTCP_RR::sizeWithReportBlocks(1), rtc::Message::Type::Control);
|
||||||
auto rr = reinterpret_cast<RTCP_RR *>(msg->data());
|
auto rr = reinterpret_cast<RTCP_RR *>(msg->data());
|
||||||
rr->preparePacket(mSsrc, 1);
|
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);
|
lastSR_delay);
|
||||||
rr->log();
|
rr->log();
|
||||||
|
|
||||||
|
@ -322,7 +322,7 @@ void SctpTransport::incoming(message_ptr message) {
|
|||||||
bool SctpTransport::trySendQueue() {
|
bool SctpTransport::trySendQueue() {
|
||||||
// Requires mSendMutex to be locked
|
// Requires mSendMutex to be locked
|
||||||
while (auto next = mSendQueue.peek()) {
|
while (auto next = mSendQueue.peek()) {
|
||||||
auto message = *next;
|
message_ptr message = std::move(*next);
|
||||||
if (!trySendMessage(message))
|
if (!trySendMessage(message))
|
||||||
return false;
|
return false;
|
||||||
mSendQueue.pop();
|
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) {
|
const byte *data, size_t len, struct sctp_rcvinfo info, int flags) {
|
||||||
try {
|
try {
|
||||||
PLOG_VERBOSE << "Handle recv, len=" << len;
|
PLOG_VERBOSE << "Handle recv, len=" << len;
|
||||||
if (!len)
|
|
||||||
return 0; // Ignore
|
|
||||||
|
|
||||||
// SCTP_FRAGMENT_INTERLEAVE does not seem to work as expected for messages > 64KB,
|
// SCTP_FRAGMENT_INTERLEAVE does not seem to work as expected for messages > 64KB,
|
||||||
// therefore partial notifications and messages need to be handled separately.
|
// 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) {
|
if (flags & MSG_EOR) {
|
||||||
// Message is complete, process it
|
// Message is complete, process it
|
||||||
processData(std::move(mPartialMessage), info.rcv_sid,
|
processData(std::move(mPartialMessage), info.rcv_sid,
|
||||||
PayloadId(htonl(info.rcv_ppid)));
|
PayloadId(ntohl(info.rcv_ppid)));
|
||||||
mPartialMessage.clear();
|
mPartialMessage.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -702,8 +700,9 @@ std::optional<milliseconds> SctpTransport::rtt() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int SctpTransport::RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data,
|
int SctpTransport::RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data,
|
||||||
size_t len, struct sctp_rcvinfo recv_info, int flags, void *ptr) {
|
size_t len, struct sctp_rcvinfo recv_info, int flags,
|
||||||
auto *transport = static_cast<SctpTransport *>(ptr);
|
void *ulp_info) {
|
||||||
|
auto *transport = static_cast<SctpTransport *>(ulp_info);
|
||||||
|
|
||||||
std::shared_lock lock(InstancesMutex);
|
std::shared_lock lock(InstancesMutex);
|
||||||
if (Instances.find(transport) == Instances.end()) {
|
if (Instances.find(transport) == Instances.end()) {
|
||||||
@ -717,15 +716,8 @@ int SctpTransport::RecvCallback(struct socket *sock, union sctp_sockstore addr,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SctpTransport::SendCallback(struct socket *sock, uint32_t sb_free) {
|
int SctpTransport::SendCallback(struct socket *, uint32_t sb_free, void *ulp_info) {
|
||||||
struct sctp_paddrinfo paddrinfo = {};
|
auto *transport = static_cast<SctpTransport *>(ulp_info);
|
||||||
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);
|
|
||||||
|
|
||||||
std::shared_lock lock(InstancesMutex);
|
std::shared_lock lock(InstancesMutex);
|
||||||
if (Instances.find(transport) == Instances.end())
|
if (Instances.find(transport) == Instances.end())
|
||||||
|
@ -110,8 +110,8 @@ private:
|
|||||||
std::atomic<size_t> mBytesSent = 0, mBytesReceived = 0;
|
std::atomic<size_t> mBytesSent = 0, mBytesReceived = 0;
|
||||||
|
|
||||||
static int RecvCallback(struct socket *sock, union sctp_sockstore addr, void *data, size_t len,
|
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);
|
struct sctp_rcvinfo recv_info, int flags, void *ulp_info);
|
||||||
static int SendCallback(struct socket *sock, uint32_t sb_free);
|
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 int WriteCallback(void *sctp_ptr, void *data, size_t len, uint8_t tos, uint8_t set_df);
|
||||||
|
|
||||||
static std::unordered_set<SctpTransport *> Instances;
|
static std::unordered_set<SctpTransport *> Instances;
|
||||||
|
@ -271,7 +271,7 @@ void TcpTransport::close() {
|
|||||||
bool TcpTransport::trySendQueue() {
|
bool TcpTransport::trySendQueue() {
|
||||||
// mSockMutex must be locked
|
// mSockMutex must be locked
|
||||||
while (auto next = mSendQueue.peek()) {
|
while (auto next = mSendQueue.peek()) {
|
||||||
auto message = *next;
|
message_ptr message = std::move(*next);
|
||||||
if (!trySendMessage(message)) {
|
if (!trySendMessage(message)) {
|
||||||
mSendQueue.exchange(message);
|
mSendQueue.exchange(message);
|
||||||
return false;
|
return false;
|
||||||
|
@ -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) {
|
int TlsTransport::TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms) {
|
||||||
TlsTransport *t = static_cast<TlsTransport *>(ptr);
|
TlsTransport *t = static_cast<TlsTransport *>(ptr);
|
||||||
if (ms != GNUTLS_INDEFINITE_TIMEOUT)
|
bool notEmpty = t->mIncomingQueue.wait(
|
||||||
t->mIncomingQueue.wait(milliseconds(ms));
|
ms != GNUTLS_INDEFINITE_TIMEOUT ? std::make_optional(milliseconds(ms)) : nullopt);
|
||||||
else
|
return notEmpty ? 1 : 0;
|
||||||
t->mIncomingQueue.wait();
|
|
||||||
return !t->mIncomingQueue.empty() ? 1 : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#else // USE_GNUTLS==0
|
#else // USE_GNUTLS==0
|
||||||
@ -413,7 +411,7 @@ void TlsTransport::runRecvLoop() {
|
|||||||
if (!next)
|
if (!next)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
message_ptr message = *next;
|
message_ptr message = std::move(*next);
|
||||||
if (message->size() > 0)
|
if (message->size() > 0)
|
||||||
BIO_write(mInBio, message->data(), int(message->size())); // Input
|
BIO_write(mInBio, message->data(), int(message->size())); // Input
|
||||||
else
|
else
|
||||||
|
@ -45,8 +45,8 @@ bool Track::send(const byte *data, size_t size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::optional<message_variant> Track::receive() {
|
std::optional<message_variant> Track::receive() {
|
||||||
if (!mRecvQueue.empty())
|
if (auto next = mRecvQueue.tryPop())
|
||||||
return to_variant(std::move(**mRecvQueue.pop()));
|
return to_variant(std::move(**next));
|
||||||
|
|
||||||
return nullopt;
|
return nullopt;
|
||||||
}
|
}
|
||||||
|
@ -51,31 +51,45 @@ WebSocket::~WebSocket() {
|
|||||||
WebSocket::State WebSocket::readyState() const { return mState; }
|
WebSocket::State WebSocket::readyState() const { return mState; }
|
||||||
|
|
||||||
void WebSocket::open(const string &url) {
|
void WebSocket::open(const string &url) {
|
||||||
|
PLOG_VERBOSE << "Opening WebSocket to URL: " << url;
|
||||||
|
|
||||||
if (mState != State::Closed)
|
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"(^(([^:\/?#]+):)?(//([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?)";
|
// Modified regex from RFC 3986, see https://tools.ietf.org/html/rfc3986#appendix-B
|
||||||
static std::regex regex(rs, std::regex::extended);
|
static const char *rs =
|
||||||
|
R"(^(([^:.@/?#]+):)?(/{0,2}((([^:@]*)(:([^@]*))?)@)?(([^:/?#]*)(:([^/?#]*))?))?([^?#]*)(\?([^#]*))?(#(.*))?)";
|
||||||
|
|
||||||
std::smatch match;
|
static const std::regex r(rs, std::regex::extended);
|
||||||
if (!std::regex_match(url, match, regex))
|
|
||||||
throw std::invalid_argument("Malformed WebSocket URL: " + url);
|
|
||||||
|
|
||||||
mScheme = match[2];
|
std::smatch m;
|
||||||
if (mScheme != "ws" && mScheme != "wss")
|
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);
|
throw std::invalid_argument("Invalid WebSocket scheme: " + mScheme);
|
||||||
|
|
||||||
mHost = match[4];
|
mHostname = m[10];
|
||||||
if (auto pos = mHost.find(':'); pos != string::npos) {
|
mService = m[12];
|
||||||
mHostname = mHost.substr(0, pos);
|
if (mService.empty()) {
|
||||||
mService = mHost.substr(pos + 1);
|
|
||||||
} else {
|
|
||||||
mHostname = mHost;
|
|
||||||
mService = mScheme == "ws" ? "80" : "443";
|
mService = mScheme == "ws" ? "80" : "443";
|
||||||
|
mHost = mHostname;
|
||||||
|
} else {
|
||||||
|
mHost = mHostname + ':' + mService;
|
||||||
}
|
}
|
||||||
|
|
||||||
mPath = match[5];
|
while (!mHostname.empty() && mHostname.front() == '[')
|
||||||
if (string query = match[7]; !query.empty())
|
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;
|
mPath += "?" + query;
|
||||||
|
|
||||||
changeState(State::Connecting);
|
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; }
|
size_t WebSocket::maxMessageSize() const { return DEFAULT_MAX_MESSAGE_SIZE; }
|
||||||
|
|
||||||
std::optional<message_variant> WebSocket::receive() {
|
std::optional<message_variant> WebSocket::receive() {
|
||||||
while (!mRecvQueue.empty()) {
|
while (auto next = mRecvQueue.tryPop()) {
|
||||||
auto message = *mRecvQueue.pop();
|
message_ptr message = std::move(*next);
|
||||||
if (message->type != Message::Control)
|
if (message->type != Message::Control)
|
||||||
return to_variant(std::move(*message));
|
return to_variant(std::move(*message));
|
||||||
}
|
}
|
||||||
@ -133,6 +147,11 @@ bool WebSocket::outgoing(message_ptr message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WebSocket::incoming(message_ptr message) {
|
void WebSocket::incoming(message_ptr message) {
|
||||||
|
if (!message) {
|
||||||
|
remoteClose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (message->type == Message::String || message->type == Message::Binary) {
|
if (message->type == Message::String || message->type == Message::Binary) {
|
||||||
mRecvQueue.push(message);
|
mRecvQueue.push(message);
|
||||||
triggerAvailable(mRecvQueue.size());
|
triggerAvailable(mRecvQueue.size());
|
||||||
|
@ -58,6 +58,12 @@ WsTransport::WsTransport(std::shared_ptr<Transport> lower, string host, string p
|
|||||||
onRecv(recvCallback);
|
onRecv(recvCallback);
|
||||||
|
|
||||||
PLOG_DEBUG << "Initializing WebSocket transport";
|
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(); }
|
WsTransport::~WsTransport() { stop(); }
|
||||||
@ -147,7 +153,7 @@ void WsTransport::close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool WsTransport::sendHttpRequest() {
|
bool WsTransport::sendHttpRequest() {
|
||||||
PLOG_DEBUG << "Sending WebSocket HTTP request";
|
PLOG_DEBUG << "Sending WebSocket HTTP request for path " << mPath;
|
||||||
changeState(State::Connecting);
|
changeState(State::Connecting);
|
||||||
|
|
||||||
auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
|
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();
|
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);
|
auto transferDuration = duration_cast<milliseconds>(endTime - receivedTime);
|
||||||
|
|
||||||
cout << "Test duration: " << duration.count() << " ms" << endl;
|
cout << "Test duration: " << duration.count() << " ms" << endl;
|
||||||
|
@ -29,6 +29,8 @@ static void sleep(unsigned int secs) { Sleep(secs * 1000); }
|
|||||||
#include <unistd.h> // for sleep
|
#include <unistd.h> // for sleep
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define BUFFER_SIZE 4096
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
rtcState state;
|
rtcState state;
|
||||||
rtcGatheringState gatheringState;
|
rtcGatheringState gatheringState;
|
||||||
@ -183,15 +185,55 @@ int test_capi_connectivity_main() {
|
|||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
char buffer[256];
|
char buffer[BUFFER_SIZE];
|
||||||
if (rtcGetLocalAddress(peer1->pc, buffer, 256) >= 0)
|
|
||||||
printf("Local address 1: %s\n", buffer);
|
if (rtcGetLocalDescription(peer1->pc, buffer, BUFFER_SIZE) < 0) {
|
||||||
if (rtcGetRemoteAddress(peer1->pc, buffer, 256) >= 0)
|
fprintf(stderr, "rtcGetLocalDescription failed\n");
|
||||||
printf("Remote address 1: %s\n", buffer);
|
goto error;
|
||||||
if (rtcGetLocalAddress(peer2->pc, buffer, 256) >= 0)
|
}
|
||||||
printf("Local address 2: %s\n", buffer);
|
printf("Local description 1: %s\n", buffer);
|
||||||
if (rtcGetRemoteAddress(peer2->pc, buffer, 256) >= 0)
|
|
||||||
printf("Remote address 2: %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);
|
||||||
|
|
||||||
deletePeer(peer1);
|
deletePeer(peer1);
|
||||||
sleep(1);
|
sleep(1);
|
||||||
|
@ -125,6 +125,7 @@ void test_connectivity() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Wait a bit
|
||||||
int attempts = 10;
|
int attempts = 10;
|
||||||
shared_ptr<DataChannel> adc2;
|
shared_ptr<DataChannel> adc2;
|
||||||
while ((!(adc2 = std::atomic_load(&dc2)) || !adc2->isOpen() || !dc1->isOpen()) && attempts--)
|
while ((!(adc2 = std::atomic_load(&dc2)) || !adc2->isOpen() || !dc1->isOpen()) && attempts--)
|
||||||
@ -146,6 +147,49 @@ void test_connectivity() {
|
|||||||
if (auto addr = pc2->remoteAddress())
|
if (auto addr = pc2->remoteAddress())
|
||||||
cout << "Remote address 2: " << *addr << endl;
|
cout << "Remote address 2: " << *addr << 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
|
// Delay close of peer 2 to check closing works properly
|
||||||
pc1->close();
|
pc1->close();
|
||||||
this_thread::sleep_for(1s);
|
this_thread::sleep_for(1s);
|
||||||
|
Reference in New Issue
Block a user