Use sourcecode for libjuice

This commit is contained in:
dijunkun
2023-07-13 16:58:20 +08:00
parent ef6a04dc97
commit 8cd87a2646
76 changed files with 14877 additions and 179 deletions

7
thirdparty/libjuice/.clang-format vendored Normal file
View File

@@ -0,0 +1,7 @@
---
BasedOnStyle: LLVM
IndentWidth: 4
TabWidth: 4
UseTab: ForIndentation
ColumnLimit: 100

45
thirdparty/libjuice/.clang-tidy vendored Normal file
View File

@@ -0,0 +1,45 @@
---
Checks: 'clang-diagnostic-*,clang-analyzer-*,-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,-clang-analyzer-security.insecureAPI.strcpy'
WarningsAsErrors: ''
HeaderFilterRegex: ''
AnalyzeTemporaryDtors: false
FormatStyle: none
CheckOptions:
- key: llvm-else-after-return.WarnOnConditionVariables
value: 'false'
- key: modernize-loop-convert.MinConfidence
value: reasonable
- key: modernize-replace-auto-ptr.IncludeStyle
value: llvm
- key: cert-str34-c.DiagnoseSignedUnsignedCharComparisons
value: 'false'
- key: google-readability-namespace-comments.ShortNamespaceLines
value: '10'
- key: cert-err33-c.CheckedFunctions
value: '::aligned_alloc;::asctime_s;::at_quick_exit;::atexit;::bsearch;::bsearch_s;::btowc;::c16rtomb;::c32rtomb;::calloc;::clock;::cnd_broadcast;::cnd_init;::cnd_signal;::cnd_timedwait;::cnd_wait;::ctime_s;::fclose;::fflush;::fgetc;::fgetpos;::fgets;::fgetwc;::fopen;::fopen_s;::fprintf;::fprintf_s;::fputc;::fputs;::fputwc;::fputws;::fread;::freopen;::freopen_s;::fscanf;::fscanf_s;::fseek;::fsetpos;::ftell;::fwprintf;::fwprintf_s;::fwrite;::fwscanf;::fwscanf_s;::getc;::getchar;::getenv;::getenv_s;::gets_s;::getwc;::getwchar;::gmtime;::gmtime_s;::localtime;::localtime_s;::malloc;::mbrtoc16;::mbrtoc32;::mbsrtowcs;::mbsrtowcs_s;::mbstowcs;::mbstowcs_s;::memchr;::mktime;::mtx_init;::mtx_lock;::mtx_timedlock;::mtx_trylock;::mtx_unlock;::printf_s;::putc;::putwc;::raise;::realloc;::remove;::rename;::scanf;::scanf_s;::setlocale;::setvbuf;::signal;::snprintf;::snprintf_s;::sprintf;::sprintf_s;::sscanf;::sscanf_s;::strchr;::strerror_s;::strftime;::strpbrk;::strrchr;::strstr;::strtod;::strtof;::strtoimax;::strtok;::strtok_s;::strtol;::strtold;::strtoll;::strtoul;::strtoull;::strtoumax;::strxfrm;::swprintf;::swprintf_s;::swscanf;::swscanf_s;::thrd_create;::thrd_detach;::thrd_join;::thrd_sleep;::time;::timespec_get;::tmpfile;::tmpfile_s;::tmpnam;::tmpnam_s;::tss_create;::tss_get;::tss_set;::ungetc;::ungetwc;::vfprintf;::vfprintf_s;::vfscanf;::vfscanf_s;::vfwprintf;::vfwprintf_s;::vfwscanf;::vfwscanf_s;::vprintf_s;::vscanf;::vscanf_s;::vsnprintf;::vsnprintf_s;::vsprintf;::vsprintf_s;::vsscanf;::vsscanf_s;::vswprintf;::vswprintf_s;::vswscanf;::vswscanf_s;::vwprintf_s;::vwscanf;::vwscanf_s;::wcrtomb;::wcschr;::wcsftime;::wcspbrk;::wcsrchr;::wcsrtombs;::wcsrtombs_s;::wcsstr;::wcstod;::wcstof;::wcstoimax;::wcstok;::wcstok_s;::wcstol;::wcstold;::wcstoll;::wcstombs;::wcstombs_s;::wcstoul;::wcstoull;::wcstoumax;::wcsxfrm;::wctob;::wctrans;::wctype;::wmemchr;::wprintf_s;::wscanf;::wscanf_s;'
- key: cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField
value: 'false'
- key: cert-dcl16-c.NewSuffixes
value: 'L;LL;LU;LLU'
- key: google-readability-braces-around-statements.ShortStatementLines
value: '1'
- key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic
value: 'true'
- key: google-readability-namespace-comments.SpacesBeforeComments
value: '2'
- key: modernize-loop-convert.MaxCopySize
value: '16'
- key: modernize-pass-by-value.IncludeStyle
value: llvm
- key: modernize-use-nullptr.NullMacros
value: 'NULL'
- key: llvm-qualified-auto.AddConstToQualified
value: 'false'
- key: modernize-loop-convert.NamingStyle
value: CamelCase
- key: llvm-else-after-return.WarnOnUnfixable
value: 'false'
- key: google-readability-function-size.StatementThreshold
value: '800'
...

11
thirdparty/libjuice/.editorconfig vendored Normal file
View File

@@ -0,0 +1,11 @@
# EditorConfig is awesome: https://EditorConfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = tab
indent_size = 4

View File

@@ -0,0 +1,3 @@
github: ['paullouisageneau']
custom: ['https://paypal.me/paullouisageneau']

View File

@@ -0,0 +1,43 @@
name: Build and test
on:
push:
branches:
- master
pull_request:
jobs:
build-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: install packages
run: sudo apt update && sudo apt install nettle-dev clang-tidy
- name: cmake
run: cmake -B build -DUSE_NETTLE=1 -DWARNINGS_AS_ERRORS=1 -DCLANG_TIDY=ON
- name: make
run: (cd build; make)
- name: test
run: ./build/tests
build-macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: cmake
run: cmake -B build -DWARNINGS_AS_ERRORS=1 -DENABLE_LOCAL_ADDRESS_TRANSLATION=1
- name: make
run: (cd build; make)
- name: test
run: ./build/tests
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- uses: ilammy/msvc-dev-cmd@v1
- name: cmake
run: cmake -B build -G "NMake Makefiles" -DWARNINGS_AS_ERRORS=1
- name: nmake
run: |
cd build
nmake
- name: test
run: build/tests.exe

8
thirdparty/libjuice/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
build/
*.d
*.o
*.a
*.so
compile_commands.json
tests

249
thirdparty/libjuice/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,249 @@
cmake_minimum_required(VERSION 3.7)
project(libjuice
VERSION 1.2.3
LANGUAGES C)
set(PROJECT_DESCRIPTION "UDP Interactive Connectivity Establishment (ICE) library")
option(USE_NETTLE "Use Nettle for hash functions" OFF)
option(NO_SERVER "Disable server support" OFF)
option(NO_TESTS "Disable tests build" OFF)
option(NO_EXPORT_HEADER "Disable export header" OFF)
option(WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
option(FUZZER "Enable oss-fuzz fuzzing" OFF)
option(CLANG_TIDY "Enable clang-tidy" OFF)
# Mitigations
option(DISABLE_CONSENT_FRESHNESS "Disable RFC 7675 Consent Freshness" OFF)
option(ENABLE_LOCALHOST_ADDRESS "List localhost addresses in candidates" OFF)
option(ENABLE_LOCAL_ADDRESS_TRANSLATION "Translate local addresses to localhost" OFF)
set(C_STANDARD 11)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
if(WIN32)
add_definitions(-DWIN32_LEAN_AND_MEAN)
if(MSVC)
add_definitions(-DNOMINMAX)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
endif()
endif()
if(CLANG_TIDY)
set(CMAKE_C_CLANG_TIDY clang-tidy)
endif()
set(LIBJUICE_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/addr.c
${CMAKE_CURRENT_SOURCE_DIR}/src/agent.c
${CMAKE_CURRENT_SOURCE_DIR}/src/crc32.c
${CMAKE_CURRENT_SOURCE_DIR}/src/const_time.c
${CMAKE_CURRENT_SOURCE_DIR}/src/conn.c
${CMAKE_CURRENT_SOURCE_DIR}/src/conn_poll.c
${CMAKE_CURRENT_SOURCE_DIR}/src/conn_thread.c
${CMAKE_CURRENT_SOURCE_DIR}/src/conn_mux.c
${CMAKE_CURRENT_SOURCE_DIR}/src/base64.c
${CMAKE_CURRENT_SOURCE_DIR}/src/hash.c
${CMAKE_CURRENT_SOURCE_DIR}/src/hmac.c
${CMAKE_CURRENT_SOURCE_DIR}/src/ice.c
${CMAKE_CURRENT_SOURCE_DIR}/src/juice.c
${CMAKE_CURRENT_SOURCE_DIR}/src/log.c
${CMAKE_CURRENT_SOURCE_DIR}/src/random.c
${CMAKE_CURRENT_SOURCE_DIR}/src/server.c
${CMAKE_CURRENT_SOURCE_DIR}/src/stun.c
${CMAKE_CURRENT_SOURCE_DIR}/src/timestamp.c
${CMAKE_CURRENT_SOURCE_DIR}/src/turn.c
${CMAKE_CURRENT_SOURCE_DIR}/src/udp.c
)
source_group("Source Files" FILES "${LIBJUICE_SOURCES}")
set(LIBJUICE_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/include/juice/juice.h
)
source_group("Header Files" FILES "${LIBJUICE_HEADERS}")
set(TESTS_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/test/main.c
${CMAKE_CURRENT_SOURCE_DIR}/test/crc32.c
${CMAKE_CURRENT_SOURCE_DIR}/test/base64.c
${CMAKE_CURRENT_SOURCE_DIR}/test/stun.c
${CMAKE_CURRENT_SOURCE_DIR}/test/gathering.c
${CMAKE_CURRENT_SOURCE_DIR}/test/connectivity.c
${CMAKE_CURRENT_SOURCE_DIR}/test/turn.c
${CMAKE_CURRENT_SOURCE_DIR}/test/thread.c
${CMAKE_CURRENT_SOURCE_DIR}/test/mux.c
${CMAKE_CURRENT_SOURCE_DIR}/test/notrickle.c
${CMAKE_CURRENT_SOURCE_DIR}/test/server.c
${CMAKE_CURRENT_SOURCE_DIR}/test/conflict.c
${CMAKE_CURRENT_SOURCE_DIR}/test/bind.c
)
source_group("Test Files" FILES "${TESTS_SOURCES}")
set(FUZZER_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/fuzzer/fuzzer.c
)
source_group("Fuzzer Files" FILES "${FUZZER_SOURCES}")
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
add_library(juice SHARED EXCLUDE_FROM_ALL ${LIBJUICE_SOURCES})
set_target_properties(juice PROPERTIES VERSION ${PROJECT_VERSION})
target_include_directories(juice PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
target_include_directories(juice PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/juice)
target_include_directories(juice PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_compile_definitions(juice PRIVATE $<$<CONFIG:Release>:RELEASE=1>)
target_link_libraries(juice PRIVATE Threads::Threads)
add_library(juice-static STATIC ${LIBJUICE_SOURCES})
set_target_properties(juice-static PROPERTIES VERSION ${PROJECT_VERSION})
target_include_directories(juice-static PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
target_include_directories(juice-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/juice)
target_include_directories(juice-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_compile_definitions(juice-static PRIVATE $<$<CONFIG:Release>:RELEASE=1>)
target_compile_definitions(juice-static PUBLIC JUICE_STATIC)
target_link_libraries(juice-static PRIVATE Threads::Threads)
if(WIN32)
target_link_libraries(juice PRIVATE
ws2_32 # winsock2
bcrypt)
target_link_libraries(juice-static PRIVATE
ws2_32 # winsock2
bcrypt)
endif()
if(USE_NETTLE)
find_package(Nettle REQUIRED)
target_compile_definitions(juice PRIVATE USE_NETTLE=1)
target_link_libraries(juice PRIVATE Nettle::Nettle)
target_compile_definitions(juice-static PRIVATE USE_NETTLE=1)
target_link_libraries(juice-static PRIVATE Nettle::Nettle)
else()
target_compile_definitions(juice PRIVATE USE_NETTLE=0)
target_compile_definitions(juice-static PRIVATE USE_NETTLE=0)
endif()
if(NO_SERVER)
target_compile_definitions(juice PRIVATE NO_SERVER)
target_compile_definitions(juice-static PRIVATE NO_SERVER)
endif()
if(APPLE)
# This seems to be necessary on MacOS
target_include_directories(juice PRIVATE /usr/local/include)
target_include_directories(juice-static PRIVATE /usr/local/include)
endif()
set_target_properties(juice PROPERTIES EXPORT_NAME LibJuice)
add_library(LibJuice::LibJuice ALIAS juice)
set_target_properties(juice-static PROPERTIES EXPORT_NAME LibJuiceStatic)
add_library(LibJuice::LibJuiceStatic ALIAS juice-static)
install(TARGETS juice-static EXPORT LibJuiceTargets
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
install(FILES ${LIBJUICE_HEADERS} DESTINATION include/juice)
# Export Targets
install(
EXPORT LibJuiceTargets
FILE LibJuiceTargets.cmake
NAMESPACE LibJuice::
DESTINATION lib/cmake/LibJuice
)
# Export config
install(
FILES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/LibJuiceConfig.cmake
DESTINATION lib/cmake/LibJuice
)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
${CMAKE_BINARY_DIR}/LibJuiceConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion)
install(FILES ${CMAKE_BINARY_DIR}/LibJuiceConfigVersion.cmake
DESTINATION lib/cmake/LibJuice)
if(NOT NO_EXPORT_HEADER AND CMAKE_VERSION VERSION_GREATER_EQUAL "3.12")
include(GenerateExportHeader)
generate_export_header(juice
EXPORT_MACRO_NAME JUICE_EXPORT
NO_EXPORT_MACRO_NAME JUICE_NO_EXPORT
DEPRECATED_MACRO_NAME JUICE_DEPRECATED
STATIC_DEFINE JUICE_STATIC)
target_include_directories(juice PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>)
target_compile_definitions(juice PUBLIC -DJUICE_HAS_EXPORT_HEADER)
set_target_properties(juice PROPERTIES C_VISIBILITY_PRESET hidden)
install(FILES ${PROJECT_BINARY_DIR}/juice_export.h DESTINATION include/juice)
else()
target_compile_definitions(juice PRIVATE JUICE_EXPORTS)
target_compile_definitions(juice-static PRIVATE JUICE_EXPORTS)
endif()
if(NOT MSVC)
target_compile_options(juice PRIVATE -Wall -Wextra)
target_compile_options(juice-static PRIVATE -Wall -Wextra)
endif()
if(WARNINGS_AS_ERRORS)
if(MSVC)
target_compile_options(juice PRIVATE /WX)
target_compile_options(juice-static PRIVATE /WX)
else()
target_compile_options(juice PRIVATE -Werror)
target_compile_options(juice-static PRIVATE -Werror)
endif()
endif()
if(DISABLE_CONSENT_FRESHNESS)
target_compile_definitions(juice PRIVATE JUICE_DISABLE_CONSENT_FRESHNESS=1)
target_compile_definitions(juice-static PRIVATE JUICE_DISABLE_CONSENT_FRESHNESS=1)
endif()
if(ENABLE_LOCALHOST_ADDRESS)
target_compile_definitions(juice PRIVATE JUICE_ENABLE_LOCALHOST_ADDRESS=1)
target_compile_definitions(juice-static PRIVATE JUICE_ENABLE_LOCALHOST_ADDRESS=1)
endif()
if(ENABLE_LOCAL_ADDRESS_TRANSLATION)
target_compile_definitions(juice PRIVATE JUICE_ENABLE_LOCAL_ADDRESS_TRANSLATION=1)
target_compile_definitions(juice-static PRIVATE JUICE_ENABLE_LOCAL_ADDRESS_TRANSLATION=1)
endif()
# Tests
if(NOT NO_TESTS)
add_executable(juice-tests ${TESTS_SOURCES})
target_include_directories(juice-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_include_directories(juice-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/juice)
set_target_properties(juice-tests PROPERTIES
VERSION ${PROJECT_VERSION}
OUTPUT_NAME tests)
set_target_properties(juice-tests PROPERTIES
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.github.paullouisageneau.libjuice.tests)
target_link_libraries(juice-tests juice Threads::Threads)
endif()
# Fuzzer
if(FUZZER)
add_executable(stun-fuzzer ${FUZZER_SOURCES})
target_include_directories(stun-fuzzer PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_include_directories(stun-fuzzer PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/juice)
set_target_properties(stun-fuzzer PROPERTIES OUTPUT_NAME fuzzer)
target_link_libraries(stun-fuzzer juice-static ${LIB_FUZZING_ENGINE})
endif()

373
thirdparty/libjuice/LICENSE vendored Normal file
View File

@@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

77
thirdparty/libjuice/Makefile vendored Normal file
View File

@@ -0,0 +1,77 @@
# libjuice
NAME=libjuice
CC=$(CROSS)gcc
AR=$(CROSS)ar
RM=rm -f
CFLAGS=-O2 -pthread -fPIC -Wno-address-of-packed-member
LDFLAGS=-pthread
LIBS=
INCLUDES=-Iinclude/juice
LDLIBS=
USE_NETTLE ?= 0
ifneq ($(USE_NETTLE), 0)
CFLAGS+=-DUSE_NETTLE=1
LIBS+=nettle
else
CFLAGS+=-DUSE_NETTLE=0
endif
NO_SERVER ?= 0
ifneq ($(NO_SERVER), 0)
CFLAGS+=-DNO_SERVER
endif
FORCE_M32 ?= 0
ifneq ($(FORCE_M32), 0)
CFLAGS+= -m32
LDFLAGS+= -m32
endif
CFLAGS+=-DJUICE_EXPORTS
ifneq ($(LIBS), "")
INCLUDES+=$(if $(LIBS),$(shell pkg-config --cflags $(LIBS)),)
LDLIBS+=$(if $(LIBS), $(shell pkg-config --libs $(LIBS)),)
endif
SRCS=$(shell printf "%s " src/*.c)
OBJS=$(subst .c,.o,$(SRCS))
TEST_SRCS=$(shell printf "%s " test/*.c)
TEST_OBJS=$(subst .c,.o,$(TEST_SRCS))
all: $(NAME).a $(NAME).so tests
src/%.o: src/%.c
$(CC) $(CFLAGS) $(INCLUDES) -MMD -MP -o $@ -c $<
test/%.o: test/%.c
$(CC) $(CFLAGS) $(INCLUDES) -Iinclude -Isrc -MMD -MP -o $@ -c $<
-include $(subst .c,.d,$(SRCS))
$(NAME).a: $(OBJS)
$(AR) crf $@ $(OBJS)
$(NAME).so: $(OBJS)
$(CC) $(LDFLAGS) -shared -o $@ $(OBJS) $(LDLIBS)
tests: $(NAME).a $(TEST_OBJS)
$(CC) $(LDFLAGS) -o $@ $(TEST_OBJS) $(LDLIBS) $(NAME).a
clean:
-$(RM) include/juice/*.d *.d
-$(RM) src/*.o src/*.d
-$(RM) test/*.o test/*.d
dist-clean: clean
-$(RM) $(NAME).a
-$(RM) $(NAME).so
-$(RM) tests
-$(RM) include/*~
-$(RM) src/*~
-$(RM) test/*~

103
thirdparty/libjuice/README.md vendored Normal file
View File

@@ -0,0 +1,103 @@
# libjuice - UDP Interactive Connectivity Establishment
[![License: MPL 2.0](https://img.shields.io/badge/License-MPL_2.0-blue.svg)](https://www.mozilla.org/en-US/MPL/2.0/)
[![Build and test](https://github.com/paullouisageneau/libjuice/actions/workflows/build.yml/badge.svg)](https://github.com/paullouisageneau/libjuice/actions/workflows/build.yml)
[![AUR package](https://repology.org/badge/version-for-repo/aur/libjuice.svg)](https://repology.org/project/libjuice/versions)
[![Vcpkg package](https://repology.org/badge/version-for-repo/vcpkg/libjuice.svg)](https://repology.org/project/libjuice/versions)
[![Gitter](https://badges.gitter.im/libjuice/community.svg)](https://gitter.im/libjuice/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Discord](https://img.shields.io/discord/903257095539925006?logo=discord)](https://discord.gg/jXAP8jp3Nn)
libjuice :lemon::sweat_drops: (_JUICE is a UDP Interactive Connectivity Establishment library_) allows to open bidirectionnal User Datagram Protocol (UDP) streams with Network Address Translator (NAT) traversal.
The library is a simplified implementation of the Interactive Connectivity Establishment (ICE) protocol, client-side and server-side, written in C without dependencies for POSIX platforms (including GNU/Linux, Android, Apple macOS and iOS) and Microsoft Windows. The client supports only a single component over UDP per session in a standard single-gateway network topology, as this should be sufficient for the majority of use cases nowadays.
libjuice is licensed under MPL 2.0, see [LICENSE](https://github.com/paullouisageneau/libjuice/blob/master/LICENSE).
libjuice is available on [AUR](https://aur.archlinux.org/packages/libjuice/) and [vcpkg](https://vcpkg.info/port/libjuice). Bindings are available for [Rust](https://github.com/VollmondT/juice-rs).
For a STUN/TURN server application based on libjuice, see [Violet](https://github.com/paullouisageneau/violet).
## Compatibility
The library implements a simplified but fully compatible ICE agent ([RFC5245](https://www.rfc-editor.org/rfc/rfc5245.html) then [RFC8445](https://www.rfc-editor.org/rfc/rfc8445.html)) featuring:
- STUN protocol ([RFC5389](https://www.rfc-editor.org/rfc/rfc5389.html) then [RFC8489](https://www.rfc-editor.org/rfc/rfc8489.html))
- TURN relaying ([RFC5766](https://www.rfc-editor.org/rfc/rfc5766.html) then [RFC8656](https://www.rfc-editor.org/rfc/rfc8656.html))
- Consent freshness ([RFC7675](https://www.rfc-editor.org/rfc/rfc7675.html))
- SDP-based interface ([RFC8839](https://www.rfc-editor.org/rfc/rfc8839.html))
- IPv4 and IPv6 dual-stack support
- Optional multiplexing on a single UDP port
The limitations compared to a fully-featured ICE agent are:
- Only UDP is supported as transport protocol and other protocols are ignored.
- Only one component is supported, which is sufficient for WebRTC Data Channels and multiplexed RTP+RTCP.
- Candidates are gathered without binding to each network interface, which behaves identically to the full implementation on most client systems.
It also implements a lightweight STUN/TURN server ([RFC8489](https://www.rfc-editor.org/rfc/rfc8489.html) and [RFC8656](https://www.rfc-editor.org/rfc/rfc8656.html)). The server can be disabled at compile-time with the `NO_SERVER` flag.
## Dependencies
None!
Optionally, [Nettle](https://www.lysator.liu.se/~nisse/nettle/) can provide SHA1 and SHA256 algorithms instead of the internal implementation.
## Building
### Clone repository
```bash
$ git clone https://github.com/paullouisageneau/libjuice.git
$ cd libjuice
```
### Build with CMake
The CMake library targets `libjuice` and `libjuice-static` respectively correspond to the shared and static libraries. The default target will build the library and tests. It exports the targets with namespace `LibJuice::LibJuice` and `LibJuice::LibJuiceStatic` to link the library from another CMake project.
#### POSIX-compliant operating systems (including Linux and Apple macOS)
```bash
$ cmake -B build
$ cd build
$ make -j2
```
The option `USE_NETTLE` allows to use the Nettle library instead of the internal implementation for HMAC-SHA1:
```bash
$ cmake -B build -DUSE_NETTLE=1
$ cd build
$ make -j2
```
#### Microsoft Windows with MinGW cross-compilation
```bash
$ cmake -B build -DCMAKE_TOOLCHAIN_FILE=/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake # replace with your toolchain file
$ cd build
$ make -j2
```
#### Microsoft Windows with Microsoft Visual C++
```bash
$ cmake -B build -G "NMake Makefiles"
$ cd build
$ nmake
```
### Build directly with Make (Linux only)
```bash
$ make
```
The option `USE_NETTLE` allows to use the Nettle library instead of the internal implementation for HMAC-SHA1:
```bash
$ make USE_NETTLE=1
```
## Example
See [test/connectivity.c](https://github.com/paullouisageneau/libjuice/blob/master/test/connectivity.c) for a complete local connection example.
See [test/server.c](https://github.com/paullouisageneau/libjuice/blob/master/test/server.c) for a server example.

View File

@@ -0,0 +1,2 @@
include("${CMAKE_CURRENT_LIST_DIR}/LibJuiceTargets.cmake")

View File

@@ -0,0 +1,142 @@
# Copyright (C) 2020 Dieter Baron and Thomas Klausner
#
# The authors can be contacted at <libzip@nih.at>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. The names of the authors may not be used to endorse or promote
# products derived from this software without specific prior
# written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#[=======================================================================[.rst:
FindNettle
-------
Finds the Nettle library.
Imported Targets
^^^^^^^^^^^^^^^^
This module provides the following imported targets, if found:
``Nettle::Nettle``
The Nettle library
Result Variables
^^^^^^^^^^^^^^^^
This will define the following variables:
``Nettle_FOUND``
True if the system has the Nettle library.
``Nettle_VERSION``
The version of the Nettle library which was found.
``Nettle_INCLUDE_DIRS``
Include directories needed to use Nettle.
``Nettle_LIBRARIES``
Libraries needed to link to Nettle.
Cache Variables
^^^^^^^^^^^^^^^
The following cache variables may also be set:
``Nettle_INCLUDE_DIR``
The directory containing ``nettle/aes.h``.
``Nettle_LIBRARY``
The path to the Nettle library.
#]=======================================================================]
find_package(PkgConfig)
pkg_check_modules(PC_Nettle QUIET nettle)
find_path(Nettle_INCLUDE_DIR
NAMES nettle/aes.h nettle/md5.h nettle/pbkdf2.h nettle/ripemd160.h nettle/sha.h
PATHS ${PC_Nettle_INCLUDE_DIRS}
)
find_library(Nettle_LIBRARY
NAMES nettle
PATHS ${PC_Nettle_LIBRARY_DIRS}
)
# Extract version information from the header file
if(Nettle_INCLUDE_DIR)
# This file only exists in nettle>=3.0
if(EXISTS ${Nettle_INCLUDE_DIR}/nettle/version.h)
file(STRINGS ${Nettle_INCLUDE_DIR}/nettle/version.h _ver_major_line
REGEX "^#define NETTLE_VERSION_MAJOR *[0-9]+"
LIMIT_COUNT 1)
string(REGEX MATCH "[0-9]+"
Nettle_MAJOR_VERSION "${_ver_major_line}")
file(STRINGS ${Nettle_INCLUDE_DIR}/nettle/version.h _ver_minor_line
REGEX "^#define NETTLE_VERSION_MINOR *[0-9]+"
LIMIT_COUNT 1)
string(REGEX MATCH "[0-9]+"
Nettle_MINOR_VERSION "${_ver_minor_line}")
set(Nettle_VERSION "${Nettle_MAJOR_VERSION}.${Nettle_MINOR_VERSION}")
unset(_ver_major_line)
unset(_ver_minor_line)
else()
if(PC_Nettle_VERSION)
set(Nettle_VERSION ${PC_Nettle_VERSION})
else()
set(Nettle_VERSION "1.0")
endif()
endif()
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Nettle
FOUND_VAR Nettle_FOUND
REQUIRED_VARS
Nettle_LIBRARY
Nettle_INCLUDE_DIR
VERSION_VAR Nettle_VERSION
)
if(Nettle_FOUND)
set(Nettle_LIBRARIES ${Nettle_LIBRARY})
set(Nettle_INCLUDE_DIRS ${Nettle_INCLUDE_DIR})
set(Nettle_DEFINITIONS ${PC_Nettle_CFLAGS_OTHER})
endif()
if(Nettle_FOUND AND NOT TARGET Nettle::Nettle)
add_library(Nettle::Nettle UNKNOWN IMPORTED)
set_target_properties(Nettle::Nettle PROPERTIES
IMPORTED_LOCATION "${Nettle_LIBRARY}"
INTERFACE_COMPILE_OPTIONS "${PC_Nettle_CFLAGS_OTHER}"
INTERFACE_INCLUDE_DIRECTORIES "${Nettle_INCLUDE_DIR}"
)
endif()
mark_as_advanced(
Nettle_INCLUDE_DIR
Nettle_LIBRARY
)
# compatibility variables
set(Nettle_VERSION_STRING ${Nettle_VERSION})

26
thirdparty/libjuice/fuzzer/README.md vendored Normal file
View File

@@ -0,0 +1,26 @@
## Fuzzer
### Export Symbols
```
export CC=clang
export CXX=clang++
export CFLAGS=-fsanitize=fuzzer-no-link,address
export LIB_FUZZING_ENGINE=-fsanitize=fuzzer
export LDFLAGS=-fsanitize=address
```
### Build
```
$ mkdir build
$ cd build
$ cmake -DCMAKE_BUILD_TYPE=Debug -DFUZZER=ON -DCMAKE_C_COMPILER=$CC \
-DCMAKE_C_FLAGS=$CFLAGS -DCMAKE_EXE_LINKER_FLAGS=$CFLAGS \
-DLIB_FUZZING_ENGINE=$LIB_FUZZING_ENGINE \
../
```
### Run
```
$ mkdir coverage
$ ./fuzzer coverage/ ../fuzzer/input/
```

41
thirdparty/libjuice/fuzzer/fuzzer.c vendored Normal file
View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2022 0x34d (https://github.com/0x34d)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <stdint.h>
#include <string.h>
#include "stun.h"
#define kMinInputLength 5
#define kMaxInputLength 2048
extern int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
if (Size < kMinInputLength || Size > kMaxInputLength) {
return 0;
}
stun_message_t msg;
memset(&msg, 0, sizeof(msg));
_juice_is_stun_datagram((void *)Data, Size);
_juice_stun_read((void *)Data, Size, &msg);
_juice_stun_check_integrity((void *)Data, Size, &msg, "VOkJxbRl1RmTxUk/WvJxBt");
return 0;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

310
thirdparty/libjuice/src/addr.c vendored Normal file
View File

@@ -0,0 +1,310 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "addr.h"
#include "log.h"
#include <stdio.h>
#include <string.h>
socklen_t addr_get_len(const struct sockaddr *sa) {
switch (sa->sa_family) {
case AF_INET:
return sizeof(struct sockaddr_in);
case AF_INET6:
return sizeof(struct sockaddr_in6);
default:
JLOG_WARN("Unknown address family %hu", sa->sa_family);
return 0;
}
}
uint16_t addr_get_port(const struct sockaddr *sa) {
switch (sa->sa_family) {
case AF_INET:
return ntohs(((struct sockaddr_in *)sa)->sin_port);
case AF_INET6:
return ntohs(((struct sockaddr_in6 *)sa)->sin6_port);
default:
JLOG_WARN("Unknown address family %hu", sa->sa_family);
return 0;
}
}
int addr_set_port(struct sockaddr *sa, uint16_t port) {
switch (sa->sa_family) {
case AF_INET:
((struct sockaddr_in *)sa)->sin_port = htons(port);
return 0;
case AF_INET6:
((struct sockaddr_in6 *)sa)->sin6_port = htons(port);
return 0;
default:
JLOG_WARN("Unknown address family %hu", sa->sa_family);
return -1;
}
}
bool addr_is_any(const struct sockaddr *sa) {
switch (sa->sa_family) {
case AF_INET: {
const struct sockaddr_in *sin = (const struct sockaddr_in *)sa;
const uint8_t *b = (const uint8_t *)&sin->sin_addr;
for (int i = 0; i < 4; ++i)
if (b[i] != 0)
return false;
return true;
}
case AF_INET6: {
const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sa;
if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
const uint8_t *b = (const uint8_t *)&sin6->sin6_addr + 12;
for (int i = 0; i < 4; ++i)
if (b[i] != 0)
return false;
} else {
const uint8_t *b = (const uint8_t *)&sin6->sin6_addr;
for (int i = 0; i < 16; ++i)
if (b[i] != 0)
return false;
}
return true;
}
default:
return false;
}
}
bool addr_is_local(const struct sockaddr *sa) {
switch (sa->sa_family) {
case AF_INET: {
const struct sockaddr_in *sin = (const struct sockaddr_in *)sa;
const uint8_t *b = (const uint8_t *)&sin->sin_addr;
if (b[0] == 127) // loopback
return true;
if (b[0] == 169 && b[1] == 254) // link-local
return true;
return false;
}
case AF_INET6: {
const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sa;
if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) {
return true;
}
if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
return true;
}
if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
const uint8_t *b = (const uint8_t *)&sin6->sin6_addr + 12;
if (b[0] == 127) // loopback
return true;
if (b[0] == 169 && b[1] == 254) // link-local
return true;
return false;
}
return false;
}
default:
return false;
}
}
bool addr_unmap_inet6_v4mapped(struct sockaddr *sa, socklen_t *len) {
if (sa->sa_family != AF_INET6)
return false;
const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sa;
if (!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
return false;
struct sockaddr_in6 copy = *sin6;
sin6 = &copy;
struct sockaddr_in *sin = (struct sockaddr_in *)sa;
memset(sin, 0, sizeof(*sin));
sin->sin_family = AF_INET;
sin->sin_port = sin6->sin6_port;
memcpy(&sin->sin_addr, ((const uint8_t *)&sin6->sin6_addr) + 12, 4);
*len = sizeof(*sin);
return true;
}
bool addr_map_inet6_v4mapped(struct sockaddr_storage *ss, socklen_t *len) {
if (ss->ss_family != AF_INET)
return false;
const struct sockaddr_in *sin = (const struct sockaddr_in *)ss;
struct sockaddr_in copy = *sin;
sin = &copy;
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ss;
memset(sin6, 0, sizeof(*sin6));
sin6->sin6_family = AF_INET6;
sin6->sin6_port = sin->sin_port;
uint8_t *b = (uint8_t *)&sin6->sin6_addr;
memset(b, 0, 10);
memset(b + 10, 0xFF, 2);
memcpy(b + 12, (const uint8_t *)&sin->sin_addr, 4);
*len = sizeof(*sin6);
return true;
}
bool addr_is_equal(const struct sockaddr *a, const struct sockaddr *b, bool compare_ports) {
if (a->sa_family != b->sa_family)
return false;
switch (a->sa_family) {
case AF_INET: {
const struct sockaddr_in *ain = (const struct sockaddr_in *)a;
const struct sockaddr_in *bin = (const struct sockaddr_in *)b;
if (memcmp(&ain->sin_addr, &bin->sin_addr, 4) != 0)
return false;
if (compare_ports && ain->sin_port != bin->sin_port)
return false;
break;
}
case AF_INET6: {
const struct sockaddr_in6 *ain6 = (const struct sockaddr_in6 *)a;
const struct sockaddr_in6 *bin6 = (const struct sockaddr_in6 *)b;
if (memcmp(&ain6->sin6_addr, &bin6->sin6_addr, 16) != 0)
return false;
if (compare_ports && ain6->sin6_port != bin6->sin6_port)
return false;
break;
}
default:
return false;
}
return true;
}
int addr_to_string(const struct sockaddr *sa, char *buffer, size_t size) {
socklen_t salen = addr_get_len(sa);
if (salen == 0)
goto error;
char host[ADDR_MAX_NUMERICHOST_LEN];
char service[ADDR_MAX_NUMERICSERV_LEN];
if (getnameinfo(sa, salen, host, ADDR_MAX_NUMERICHOST_LEN, service, ADDR_MAX_NUMERICSERV_LEN,
NI_NUMERICHOST | NI_NUMERICSERV | NI_DGRAM)) {
JLOG_ERROR("getnameinfo failed, errno=%d", sockerrno);
goto error;
}
int len = snprintf(buffer, size, "%s:%s", host, service);
if (len < 0 || (size_t)len >= size)
goto error;
return len;
error:
// Make sure we still write a valid null-terminated string
snprintf(buffer, size, "?");
return -1;
}
// djb2 hash function
#define DJB2_INIT 5381
static void djb2(unsigned long *hash, int i) {
*hash = ((*hash << 5) + *hash) + i; // hash * 33 + i
}
unsigned long addr_hash(const struct sockaddr *sa, bool with_port) {
unsigned long hash = DJB2_INIT;
djb2(&hash, sa->sa_family);
switch (sa->sa_family) {
case AF_INET: {
const struct sockaddr_in *sin = (const struct sockaddr_in *)sa;
const uint8_t *b = (const uint8_t *)&sin->sin_addr;
for (int i = 0; i < 4; ++i)
djb2(&hash, b[i]);
if (with_port) {
djb2(&hash, sin->sin_port >> 8);
djb2(&hash, sin->sin_port & 0xFF);
}
break;
}
case AF_INET6: {
const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sa;
const uint8_t *b = (const uint8_t *)&sin6->sin6_addr;
for (int i = 0; i < 16; ++i)
djb2(&hash, b[i]);
if (with_port) {
djb2(&hash, sin6->sin6_port >> 8);
djb2(&hash, sin6->sin6_port & 0xFF);
}
break;
}
default:
break;
}
return hash;
}
int addr_resolve(const char *hostname, const char *service, addr_record_t *records, size_t count) {
addr_record_t *end = records + count;
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_ADDRCONFIG;
struct addrinfo *ai_list = NULL;
if (getaddrinfo(hostname, service, &hints, &ai_list)) {
JLOG_WARN("Address resolution failed for %s:%s", hostname, service);
return -1;
}
int ret = 0;
for (struct addrinfo *ai = ai_list; ai; ai = ai->ai_next) {
if (ai->ai_family == AF_INET || ai->ai_family == AF_INET6) {
++ret;
if (records != end) {
memcpy(&records->addr, ai->ai_addr, ai->ai_addrlen);
records->len = (socklen_t)ai->ai_addrlen;
++records;
}
}
}
freeaddrinfo(ai_list);
return ret;
}
bool addr_is_numeric_hostname(const char *hostname) {
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_NUMERICHOST|AI_NUMERICSERV;
struct addrinfo *ai_list = NULL;
if (getaddrinfo(hostname, "9", &hints, &ai_list))
return false;
freeaddrinfo(ai_list);
return true;
}
bool addr_record_is_equal(const addr_record_t *a, const addr_record_t *b, bool compare_ports) {
return addr_is_equal((const struct sockaddr *)&a->addr, (const struct sockaddr *)&b->addr,
compare_ports);
}
int addr_record_to_string(const addr_record_t *record, char *buffer, size_t size) {
return addr_to_string((const struct sockaddr *)&record->addr, buffer, size);
}
unsigned long addr_record_hash(const addr_record_t *record, bool with_port) {
return addr_hash((const struct sockaddr *)&record->addr, with_port);
}

45
thirdparty/libjuice/src/addr.h vendored Normal file
View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_ADDR_H
#define JUICE_ADDR_H
#include "socket.h"
#include <stdbool.h>
#include <stdint.h>
// IPv6 max representation length is 45 plus 4 for potential zone index
#define ADDR_MAX_NUMERICHOST_LEN 56 // 45 + 4 + 1 rounded up
#define ADDR_MAX_NUMERICSERV_LEN 8 // 5 + 1 rounded up
#define ADDR_MAX_STRING_LEN 64
socklen_t addr_get_len(const struct sockaddr *sa);
uint16_t addr_get_port(const struct sockaddr *sa);
int addr_set_port(struct sockaddr *sa, uint16_t port);
bool addr_is_any(const struct sockaddr *sa);
bool addr_is_local(const struct sockaddr *sa);
bool addr_unmap_inet6_v4mapped(struct sockaddr *sa, socklen_t *len);
bool addr_map_inet6_v4mapped(struct sockaddr_storage *ss, socklen_t *len);
bool addr_is_equal(const struct sockaddr *a, const struct sockaddr *b, bool compare_ports);
int addr_to_string(const struct sockaddr *sa, char *buffer, size_t size);
unsigned long addr_hash(const struct sockaddr *sa, bool with_port);
typedef struct addr_record {
struct sockaddr_storage addr;
socklen_t len;
} addr_record_t;
int addr_resolve(const char *hostname, const char *service, addr_record_t *records, size_t count);
bool addr_is_numeric_hostname(const char *hostname);
bool addr_record_is_equal(const addr_record_t *a, const addr_record_t *b, bool compare_ports);
int addr_record_to_string(const addr_record_t *record, char *buffer, size_t size);
unsigned long addr_record_hash(const addr_record_t *record, bool with_port);
#endif // JUICE_ADDR_H

2514
thirdparty/libjuice/src/agent.c vendored Normal file

File diff suppressed because it is too large Load Diff

227
thirdparty/libjuice/src/agent.h vendored Normal file
View File

@@ -0,0 +1,227 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_AGENT_H
#define JUICE_AGENT_H
#include "addr.h"
#include "conn.h"
#include "ice.h"
#include "juice.h"
#include "stun.h"
#include "thread.h"
#include "timestamp.h"
#include "turn.h"
#include <stdbool.h>
#include <stdint.h>
// RFC 8445: Agents MUST NOT use an RTO value smaller than 500 ms.
#define MIN_STUN_RETRANSMISSION_TIMEOUT 500 // msecs
#define MAX_STUN_RETRANSMISSION_COUNT 5 // count (exponential backoff, will give ~30s)
// RFC 8445: ICE agents SHOULD use a default Ta value, 50 ms, but MAY use
// another value based on the characteristics of the associated data.
#define STUN_PACING_TIME 50 // msecs
// RFC 8445: Agents SHOULD use a Tr value of 15 seconds. Agents MAY use a bigger value but MUST NOT
// use a value smaller than 15 seconds.
#define STUN_KEEPALIVE_PERIOD 15000 // msecs
// Consent freshness
// RFC 7675: Consent expires after 30 seconds.
#define CONSENT_TIMEOUT 30000 // msecs
// RFC 7675: To prevent synchronization of consent checks, each interval MUST be randomized from
// between 0.8 and 1.2 times the basic period. Implementations SHOULD set a default interval of 5
// seconds, resulting in a period between checks of 4 to 6 seconds. Implementations MUST NOT set the
// period between checks to less than 4 seconds.
#define MIN_CONSENT_CHECK_PERIOD 4000 // msecs
#define MAX_CONSENT_CHECK_PERIOD 6000 // msecs
// TURN refresh period
#define TURN_LIFETIME 600000 // msecs, 10 min
#define TURN_REFRESH_PERIOD (TURN_LIFETIME - 60000) // msecs, lifetime - 1 min
// ICE trickling timeout
#define ICE_FAIL_TIMEOUT 30000 // msecs
// Max STUN and TURN server entries
#define MAX_SERVER_ENTRIES_COUNT 2 // max STUN server entries
#define MAX_RELAY_ENTRIES_COUNT 2 // max TURN server entries
// Max TURN redirections for ALTERNATE-SERVER mechanism
#define MAX_TURN_REDIRECTIONS 1
// Compute max candidates and entries count
#define MAX_STUN_SERVER_RECORDS_COUNT MAX_SERVER_ENTRIES_COUNT
#define MAX_HOST_CANDIDATES_COUNT ((ICE_MAX_CANDIDATES_COUNT - MAX_STUN_SERVER_RECORDS_COUNT) / 2)
#define MAX_PEER_REFLEXIVE_CANDIDATES_COUNT MAX_HOST_CANDIDATES_COUNT
#define MAX_CANDIDATE_PAIRS_COUNT (ICE_MAX_CANDIDATES_COUNT * (1 + MAX_RELAY_ENTRIES_COUNT))
#define MAX_STUN_ENTRIES_COUNT (MAX_CANDIDATE_PAIRS_COUNT + MAX_STUN_SERVER_RECORDS_COUNT)
#define AGENT_TURN_MAP_SIZE ICE_MAX_CANDIDATES_COUNT
typedef enum agent_mode {
AGENT_MODE_UNKNOWN,
AGENT_MODE_CONTROLLED,
AGENT_MODE_CONTROLLING
} agent_mode_t;
typedef enum agent_stun_entry_type {
AGENT_STUN_ENTRY_TYPE_EMPTY,
AGENT_STUN_ENTRY_TYPE_SERVER,
AGENT_STUN_ENTRY_TYPE_RELAY,
AGENT_STUN_ENTRY_TYPE_CHECK
} agent_stun_entry_type_t;
typedef enum agent_stun_entry_state {
AGENT_STUN_ENTRY_STATE_PENDING,
AGENT_STUN_ENTRY_STATE_CANCELLED,
AGENT_STUN_ENTRY_STATE_FAILED,
AGENT_STUN_ENTRY_STATE_SUCCEEDED,
AGENT_STUN_ENTRY_STATE_SUCCEEDED_KEEPALIVE,
AGENT_STUN_ENTRY_STATE_IDLE
} agent_stun_entry_state_t;
typedef struct agent_turn_state {
turn_map_t map;
stun_credentials_t credentials;
const char *password;
} agent_turn_state_t;
typedef struct agent_stun_entry {
agent_stun_entry_type_t type;
agent_stun_entry_state_t state;
agent_mode_t mode;
ice_candidate_pair_t *pair;
addr_record_t record;
addr_record_t relayed;
uint8_t transaction_id[STUN_TRANSACTION_ID_SIZE];
timestamp_t next_transmission;
timediff_t retransmission_timeout;
int retransmissions;
// TURN
agent_turn_state_t *turn;
unsigned int turn_redirections;
struct agent_stun_entry *relay_entry;
} agent_stun_entry_t;
struct juice_agent {
juice_config_t config;
juice_state_t state;
agent_mode_t mode;
ice_description_t local;
ice_description_t remote;
ice_candidate_pair_t candidate_pairs[MAX_CANDIDATE_PAIRS_COUNT];
ice_candidate_pair_t *ordered_pairs[MAX_CANDIDATE_PAIRS_COUNT];
ice_candidate_pair_t *selected_pair;
int candidate_pairs_count;
agent_stun_entry_t entries[MAX_STUN_ENTRIES_COUNT];
int entries_count;
atomic_ptr(agent_stun_entry_t) selected_entry;
uint64_t ice_tiebreaker;
timestamp_t fail_timestamp;
bool gathering_done;
int conn_index;
void *conn_impl;
thread_t resolver_thread;
bool resolver_thread_started;
};
juice_agent_t *agent_create(const juice_config_t *config);
void agent_destroy(juice_agent_t *agent);
int agent_gather_candidates(juice_agent_t *agent);
int agent_resolve_servers(juice_agent_t *agent);
int agent_get_local_description(juice_agent_t *agent, char *buffer, size_t size);
int agent_set_remote_description(juice_agent_t *agent, const char *sdp);
int agent_add_remote_candidate(juice_agent_t *agent, const char *sdp);
int agent_set_remote_gathering_done(juice_agent_t *agent);
int agent_send(juice_agent_t *agent, const char *data, size_t size, int ds);
int agent_direct_send(juice_agent_t *agent, const addr_record_t *dst, const char *data, size_t size,
int ds);
int agent_relay_send(juice_agent_t *agent, agent_stun_entry_t *entry, const addr_record_t *dst,
const char *data, size_t size, int ds);
int agent_channel_send(juice_agent_t *agent, agent_stun_entry_t *entry, const addr_record_t *dst,
const char *data, size_t size, int ds);
juice_state_t agent_get_state(juice_agent_t *agent);
int agent_get_selected_candidate_pair(juice_agent_t *agent, ice_candidate_t *local,
ice_candidate_t *remote);
int agent_conn_recv(juice_agent_t *agent, char *buf, size_t len, const addr_record_t *src);
int agent_conn_update(juice_agent_t *agent, timestamp_t *next_timestamp);
int agent_conn_fail(juice_agent_t *agent);
int agent_input(juice_agent_t *agent, char *buf, size_t len, const addr_record_t *src,
const addr_record_t *relayed); // relayed may be NULL
int agent_bookkeeping(juice_agent_t *agent, timestamp_t *next_timestamp);
void agent_change_state(juice_agent_t *agent, juice_state_t state);
int agent_verify_stun_binding(juice_agent_t *agent, void *buf, size_t size,
const stun_message_t *msg);
int agent_verify_credentials(juice_agent_t *agent, const agent_stun_entry_t *entry, void *buf,
size_t size, stun_message_t *msg);
int agent_dispatch_stun(juice_agent_t *agent, void *buf, size_t size, stun_message_t *msg,
const addr_record_t *src,
const addr_record_t *relayed); // relayed may be NULL
int agent_process_stun_binding(juice_agent_t *agent, const stun_message_t *msg,
agent_stun_entry_t *entry, const addr_record_t *src,
const addr_record_t *relayed); // relayed may be NULL
int agent_send_stun_binding(juice_agent_t *agent, agent_stun_entry_t *entry, stun_class_t msg_class,
unsigned int error_code, const uint8_t *transaction_id,
const addr_record_t *mapped);
int agent_process_turn_allocate(juice_agent_t *agent, const stun_message_t *msg,
agent_stun_entry_t *entry);
int agent_send_turn_allocate_request(juice_agent_t *agent, const agent_stun_entry_t *entry,
stun_method_t method);
int agent_process_turn_create_permission(juice_agent_t *agent, const stun_message_t *msg,
agent_stun_entry_t *entry);
int agent_send_turn_create_permission_request(juice_agent_t *agent, agent_stun_entry_t *entry,
const addr_record_t *record, int ds);
int agent_process_turn_channel_bind(juice_agent_t *agent, const stun_message_t *msg,
agent_stun_entry_t *entry);
int agent_send_turn_channel_bind_request(juice_agent_t *agent, agent_stun_entry_t *entry,
const addr_record_t *record, int ds,
uint16_t *out_channel); // out_channel may be NULL
int agent_process_turn_data(juice_agent_t *agent, const stun_message_t *msg,
agent_stun_entry_t *entry);
int agent_process_channel_data(juice_agent_t *agent, agent_stun_entry_t *entry, char *buf,
size_t len);
int agent_add_local_relayed_candidate(juice_agent_t *agent, const addr_record_t *record);
int agent_add_local_reflexive_candidate(juice_agent_t *agent, ice_candidate_type_t type,
const addr_record_t *record);
int agent_add_remote_reflexive_candidate(juice_agent_t *agent, ice_candidate_type_t type,
uint32_t priority, const addr_record_t *record);
int agent_add_candidate_pair(juice_agent_t *agent, ice_candidate_t *local,
ice_candidate_t *remote); // local may be NULL
int agent_add_candidate_pairs_for_remote(juice_agent_t *agent, ice_candidate_t *remote);
int agent_unfreeze_candidate_pair(juice_agent_t *agent, ice_candidate_pair_t *pair);
void agent_arm_keepalive(juice_agent_t *agent, agent_stun_entry_t *entry);
void agent_arm_transmission(juice_agent_t *agent, agent_stun_entry_t *entry, timediff_t delay);
void agent_update_gathering_done(juice_agent_t *agent);
void agent_update_candidate_pairs(juice_agent_t *agent);
void agent_update_ordered_pairs(juice_agent_t *agent);
agent_stun_entry_t *agent_find_entry_from_transaction_id(juice_agent_t *agent,
const uint8_t *transaction_id);
agent_stun_entry_t *
agent_find_entry_from_record(juice_agent_t *agent, const addr_record_t *record,
const addr_record_t *relayed); // relayed may be NULL
void agent_translate_host_candidate_entry(juice_agent_t *agent, agent_stun_entry_t *entry);
#endif

90
thirdparty/libjuice/src/base64.c vendored Normal file
View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2021 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "base64.h"
#include <string.h>
#include <ctype.h>
int juice_base64_encode(const void *data, size_t size, char *out, size_t out_size) {
static const char tab[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
if (out_size < 4 * ((size + 2) / 3) + 1)
return -1;
const uint8_t *in = (const uint8_t *)data;
char *w = out;
while (size >= 3) {
*w++ = tab[*in >> 2];
*w++ = tab[((*in & 0x03) << 4) | (*(in + 1) >> 4)];
*w++ = tab[((*(in + 1) & 0x0F) << 2) | (*(in + 2) >> 6)];
*w++ = tab[*(in + 2) & 0x3F];
in += 3;
size -= 3;
}
if (size) {
*w++ = tab[*in >> 2];
if (size == 1) {
*w++ = tab[(*in & 0x03) << 4];
*w++ = '=';
} else { // size == 2
*w++ = tab[((*in & 0x03) << 4) | (*(in + 1) >> 4)];
*w++ = tab[(*(in + 1) & 0x0F) << 2];
}
*w++ = '=';
}
*w = '\0';
return (int)(w - out);
}
int juice_base64_decode(const char *str, void *out, size_t out_size) {
const uint8_t *in = (const uint8_t *)str;
uint8_t *w = (uint8_t *)out;
while (*in && *in != '=') {
uint8_t tab[4] = {0, 0, 0, 0};
size_t size = 0;
while (*in && size < 4) {
uint8_t c = *in++;
if (isspace(c))
continue;
if (c == '=')
break;
if ('A' <= c && c <= 'Z')
tab[size++] = c - 'A';
else if ('a' <= c && c <= 'z')
tab[size++] = c + 26 - 'a';
else if ('0' <= c && c <= '9')
tab[size++] = c + 52 - '0';
else if (c == '+' || c == '-')
tab[size++] = 62;
else if (c == '/' || c == '_')
tab[size++] = 63;
else
return -1; // Invalid character
}
if (size > 0) {
if (out_size < size - 1)
return -1;
out_size -= size - 1;
*w++ = (tab[0] << 2) | (tab[1] >> 4);
if (size > 1) {
*w++ = (tab[1] << 4) | (tab[2] >> 2);
if (size > 2)
*w++ = (tab[2] << 6) | tab[3];
}
}
}
return (int)(w - (uint8_t *)out);
}

24
thirdparty/libjuice/src/base64.h vendored Normal file
View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_BASE64_H
#define JUICE_BASE64_H
#include "juice.h"
#include <stdint.h>
#include <stdlib.h>
// RFC4648-compliant base64 encoder and decoder
JUICE_EXPORT int juice_base64_encode(const void *data, size_t size, char *out, size_t out_size);
JUICE_EXPORT int juice_base64_decode(const char *str, void *out, size_t out_size);
#define BASE64_ENCODE(data, size, out, out_size) juice_base64_encode(data, size, out, out_size)
#define BASE64_DECODE(str, out, out_size) juice_base64_decode(str, out, out_size)
#endif // JUICE_BASE64_H

249
thirdparty/libjuice/src/conn.c vendored Normal file
View File

@@ -0,0 +1,249 @@
/**
* Copyright (c) 2022 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "conn.h"
#include "agent.h"
#include "conn_mux.h"
#include "conn_poll.h"
#include "conn_thread.h"
#include "log.h"
#include <assert.h>
#include <string.h>
#define INITIAL_REGISTRY_SIZE 16
typedef struct conn_mode_entry {
int (*registry_init_func)(conn_registry_t *registry, udp_socket_config_t *config);
void (*registry_cleanup_func)(conn_registry_t *registry);
int (*init_func)(juice_agent_t *agent, struct conn_registry *registry,
udp_socket_config_t *config);
void (*cleanup_func)(juice_agent_t *agent);
void (*lock_func)(juice_agent_t *agent);
void (*unlock_func)(juice_agent_t *agent);
int (*interrupt_func)(juice_agent_t *agent);
int (*send_func)(juice_agent_t *agent, const addr_record_t *dst, const char *data, size_t size,
int ds);
int (*get_addrs_func)(juice_agent_t *agent, addr_record_t *records, size_t size);
mutex_t mutex;
conn_registry_t *registry;
} conn_mode_entry_t;
#define MODE_ENTRIES_SIZE 3
static conn_mode_entry_t mode_entries[MODE_ENTRIES_SIZE] = {
{conn_poll_registry_init, conn_poll_registry_cleanup, conn_poll_init, conn_poll_cleanup,
conn_poll_lock, conn_poll_unlock, conn_poll_interrupt, conn_poll_send, conn_poll_get_addrs,
MUTEX_INITIALIZER, NULL},
{conn_mux_registry_init, conn_mux_registry_cleanup, conn_mux_init, conn_mux_cleanup,
conn_mux_lock, conn_mux_unlock, conn_mux_interrupt, conn_mux_send, conn_mux_get_addrs,
MUTEX_INITIALIZER, NULL},
{NULL, NULL, conn_thread_init, conn_thread_cleanup, conn_thread_lock, conn_thread_unlock,
conn_thread_interrupt, conn_thread_send, conn_thread_get_addrs, MUTEX_INITIALIZER, NULL}};
static conn_mode_entry_t *get_mode_entry(juice_agent_t *agent) {
juice_concurrency_mode_t mode = agent->config.concurrency_mode;
assert(mode >= 0 && mode < MODE_ENTRIES_SIZE);
return mode_entries + (int)mode;
}
static conn_registry_t *acquire_registry(conn_mode_entry_t *entry, udp_socket_config_t *config) {
// entry must be locked
conn_registry_t *registry = entry->registry;
if (!registry) {
if (!entry->registry_init_func)
return NULL;
JLOG_DEBUG("Creating connections registry");
registry = calloc(1, sizeof(conn_registry_t));
if (!registry) {
JLOG_FATAL("Memory allocation failed for connections registry");
return NULL;
}
registry->agents = malloc(INITIAL_REGISTRY_SIZE * sizeof(juice_agent_t *));
if (!registry->agents) {
JLOG_FATAL("Memory allocation failed for connections array");
free(registry);
return NULL;
}
registry->agents_size = INITIAL_REGISTRY_SIZE;
registry->agents_count = 0;
memset(registry->agents, 0, INITIAL_REGISTRY_SIZE * sizeof(juice_agent_t *));
mutex_init(&registry->mutex, MUTEX_RECURSIVE);
mutex_lock(&registry->mutex);
if (entry->registry_init_func(registry, config)) {
mutex_unlock(&registry->mutex);
free(registry->agents);
free(registry);
return NULL;
}
entry->registry = registry;
} else {
mutex_lock(&registry->mutex);
}
// registry is locked
return registry;
}
static void release_registry(conn_mode_entry_t *entry) {
// entry must be locked
conn_registry_t *registry = entry->registry;
if (!registry)
return;
// registry must be locked
if (registry->agents_count == 0) {
JLOG_DEBUG("No connection left, destroying connections registry");
mutex_unlock(&registry->mutex);
if (entry->registry_cleanup_func)
entry->registry_cleanup_func(registry);
free(registry->agents);
free(registry);
entry->registry = NULL;
return;
}
JLOG_VERBOSE("%d connection%s left", registry->agents_count,
registry->agents_count >= 2 ? "s" : "");
mutex_unlock(&registry->mutex);
}
int conn_create(juice_agent_t *agent, udp_socket_config_t *config) {
conn_mode_entry_t *entry = get_mode_entry(agent);
mutex_lock(&entry->mutex);
conn_registry_t *registry = acquire_registry(entry, config); // locks the registry if created
mutex_unlock(&entry->mutex);
JLOG_DEBUG("Creating connection");
if (registry) {
int i = 0;
while (i < registry->agents_size && registry->agents[i])
++i;
if (i == registry->agents_size) {
int new_size = registry->agents_size * 2;
JLOG_DEBUG("Reallocating connections array, new_size=%d", new_size);
assert(new_size > 0);
juice_agent_t **new_agents =
realloc(registry->agents, new_size * sizeof(juice_agent_t *));
if (!new_agents) {
JLOG_FATAL("Memory reallocation failed for connections array");
mutex_unlock(&registry->mutex);
return -1;
}
registry->agents = new_agents;
registry->agents_size = new_size;
memset(registry->agents + i, 0, (new_size - i) * sizeof(juice_agent_t *));
}
if (get_mode_entry(agent)->init_func(agent, registry, config)) {
mutex_unlock(&registry->mutex);
return -1;
}
registry->agents[i] = agent;
agent->conn_index = i;
++registry->agents_count;
mutex_unlock(&registry->mutex);
} else {
if (get_mode_entry(agent)->init_func(agent, NULL, config)) {
mutex_unlock(&registry->mutex);
return -1;
}
agent->conn_index = -1;
}
conn_interrupt(agent);
return 0;
}
void conn_destroy(juice_agent_t *agent) {
conn_mode_entry_t *entry = get_mode_entry(agent);
mutex_lock(&entry->mutex);
JLOG_DEBUG("Destroying connection");
conn_registry_t *registry = entry->registry;
if (registry) {
mutex_lock(&registry->mutex);
entry->cleanup_func(agent);
if (agent->conn_index >= 0) {
int i = agent->conn_index;
assert(registry->agents[i] == agent);
registry->agents[i] = NULL;
agent->conn_index = -1;
}
assert(registry->agents_count > 0);
--registry->agents_count;
release_registry(entry); // unlocks the registry
} else {
entry->cleanup_func(agent);
assert(agent->conn_index < 0);
}
mutex_unlock(&entry->mutex);
}
void conn_lock(juice_agent_t *agent) {
if (!agent->conn_impl)
return;
get_mode_entry(agent)->lock_func(agent);
}
void conn_unlock(juice_agent_t *agent) {
if (!agent->conn_impl)
return;
get_mode_entry(agent)->unlock_func(agent);
}
int conn_interrupt(juice_agent_t *agent) {
if (!agent->conn_impl)
return -1;
return get_mode_entry(agent)->interrupt_func(agent);
}
int conn_send(juice_agent_t *agent, const addr_record_t *dst, const char *data, size_t size,
int ds) {
if (!agent->conn_impl)
return -1;
return get_mode_entry(agent)->send_func(agent, dst, data, size, ds);
}
int conn_get_addrs(juice_agent_t *agent, addr_record_t *records, size_t size) {
if (!agent->conn_impl)
return -1;
return get_mode_entry(agent)->get_addrs_func(agent, records, size);
}

44
thirdparty/libjuice/src/conn.h vendored Normal file
View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2022 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_CONN_H
#define JUICE_CONN_H
#include "addr.h"
#include "juice.h"
#include "thread.h"
#include "timestamp.h"
#include "udp.h"
#include <stdbool.h>
#include <stdint.h>
typedef struct juice_agent juice_agent_t;
// Generic connection interface for agents
// This interface abstracts sockets and polling to allow for different concurrency modes.
// See include/juice/juice.h for implemented concurrency modes
typedef struct conn_registry {
void *impl;
mutex_t mutex;
juice_agent_t **agents;
int agents_size;
int agents_count;
} conn_registry_t;
int conn_create(juice_agent_t *agent, udp_socket_config_t *config);
void conn_destroy(juice_agent_t *agent);
void conn_lock(juice_agent_t *agent);
void conn_unlock(juice_agent_t *agent);
int conn_interrupt(juice_agent_t *agent);
int conn_send(juice_agent_t *agent, const addr_record_t *dst, const char *data, size_t size,
int ds);
int conn_get_addrs(juice_agent_t *agent, addr_record_t *records, size_t size);
#endif

540
thirdparty/libjuice/src/conn_mux.c vendored Normal file
View File

@@ -0,0 +1,540 @@
/**
* Copyright (c) 2022 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "conn_mux.h"
#include "agent.h"
#include "log.h"
#include "socket.h"
#include "stun.h"
#include "thread.h"
#include "udp.h"
#include <assert.h>
#include <string.h>
#define BUFFER_SIZE 4096
#define INITIAL_MAP_SIZE 16
typedef enum map_entry_type {
MAP_ENTRY_TYPE_EMPTY = 0,
MAP_ENTRY_TYPE_DELETED,
MAP_ENTRY_TYPE_FULL
} map_entry_type_t;
typedef struct map_entry {
map_entry_type_t type;
juice_agent_t *agent;
addr_record_t record;
} map_entry_t;
typedef struct registry_impl {
thread_t thread;
socket_t sock;
mutex_t send_mutex;
int send_ds;
map_entry_t *map;
int map_size;
int map_count;
} registry_impl_t;
typedef struct conn_impl {
conn_registry_t *registry;
timestamp_t next_timestamp;
bool finished;
} conn_impl_t;
static bool is_ready(const juice_agent_t *agent) {
if (!agent)
return false;
conn_impl_t *conn_impl = agent->conn_impl;
if (!conn_impl || conn_impl->finished)
return false;
return true;
}
static map_entry_t *find_map_entry(registry_impl_t *impl, const addr_record_t *record,
bool allow_deleted);
static int insert_map_entry(registry_impl_t *impl, const addr_record_t *record,
juice_agent_t *agent);
static int remove_map_entries(registry_impl_t *impl, juice_agent_t *agent);
static int grow_map(registry_impl_t *impl, int new_size);
static map_entry_t *find_map_entry(registry_impl_t *impl, const addr_record_t *record,
bool allow_deleted) {
unsigned long key = addr_record_hash(record, false) % impl->map_size;
unsigned long pos = key;
while (true) {
map_entry_t *entry = impl->map + pos;
if (entry->type == MAP_ENTRY_TYPE_EMPTY ||
addr_record_is_equal(&entry->record, record, true)) // compare ports
break;
if (entry->type == MAP_ENTRY_TYPE_DELETED && allow_deleted)
break;
pos = (pos + 1) % impl->map_size;
if (pos == key)
return NULL;
}
return impl->map + pos;
}
static int insert_map_entry(registry_impl_t *impl, const addr_record_t *record,
juice_agent_t *agent) {
map_entry_t *entry = find_map_entry(impl, record, true); // allow deleted
if (!entry || (entry->type != MAP_ENTRY_TYPE_FULL && impl->map_count * 2 >= impl->map_size)) {
grow_map(impl, impl->map_size * 2);
return insert_map_entry(impl, record, agent);
}
if (entry->type != MAP_ENTRY_TYPE_FULL)
++impl->map_count;
entry->type = MAP_ENTRY_TYPE_FULL;
entry->agent = agent;
entry->record = *record;
JLOG_VERBOSE("Added map entry, count=%d", impl->map_count);
return 0;
}
static int remove_map_entries(registry_impl_t *impl, juice_agent_t *agent) {
int count = 0;
for (int i = 0; i < impl->map_size; ++i) {
map_entry_t *entry = impl->map + i;
if (entry->type == MAP_ENTRY_TYPE_FULL && entry->agent == agent) {
entry->type = MAP_ENTRY_TYPE_DELETED;
entry->agent = NULL;
++count;
}
}
assert(impl->map_count >= count);
impl->map_count -= count;
JLOG_VERBOSE("Removed %d map entries, count=%d", count, impl->map_count);
return 0;
}
static int grow_map(registry_impl_t *impl, int new_size) {
if (new_size <= impl->map_size)
return 0;
JLOG_DEBUG("Growing map, new_size=%d", new_size);
map_entry_t *new_map = calloc(1, new_size * sizeof(map_entry_t));
if (!new_map) {
JLOG_FATAL("Memory allocation failed for map");
return -1;
}
map_entry_t *old_map = impl->map;
int old_size = impl->map_size;
impl->map = new_map;
impl->map_size = new_size;
impl->map_count = 0;
for (int i = 0; i < old_size; ++i) {
map_entry_t *old_entry = old_map + i;
if (old_entry->type == MAP_ENTRY_TYPE_FULL)
insert_map_entry(impl, &old_entry->record, old_entry->agent);
}
free(old_map);
return 0;
}
int conn_mux_prepare(conn_registry_t *registry, struct pollfd *pfd, timestamp_t *next_timestamp);
int conn_mux_process(conn_registry_t *registry, struct pollfd *pfd);
int conn_mux_recv(conn_registry_t *registry, char *buffer, size_t size, addr_record_t *src);
void conn_mux_fail(conn_registry_t *registry);
int conn_mux_run(conn_registry_t *registry);
static thread_return_t THREAD_CALL conn_mux_entry(void *arg) {
conn_registry_t *registry = (conn_registry_t *)arg;
conn_mux_run(registry);
return (thread_return_t)0;
}
int conn_mux_registry_init(conn_registry_t *registry, udp_socket_config_t *config) {
(void)config;
registry_impl_t *registry_impl = calloc(1, sizeof(registry_impl_t));
if (!registry_impl) {
JLOG_FATAL("Memory allocation failed for connections registry impl");
return -1;
}
registry_impl->map = calloc(INITIAL_MAP_SIZE, sizeof(map_entry_t));
if (!registry_impl->map) {
JLOG_FATAL("Memory allocation failed for map");
free(registry_impl);
return -1;
}
registry_impl->map_size = INITIAL_MAP_SIZE;
registry_impl->map_count = 0;
registry_impl->sock = udp_create_socket(config);
if (registry_impl->sock == INVALID_SOCKET) {
JLOG_FATAL("UDP socket creation failed");
free(registry_impl->map);
free(registry_impl);
return -1;
}
mutex_init(&registry_impl->send_mutex, 0);
registry->impl = registry_impl;
JLOG_DEBUG("Starting connections thread");
int ret = thread_init(&registry_impl->thread, conn_mux_entry, registry);
if (ret) {
JLOG_FATAL("Thread creation failed, error=%d", ret);
goto error;
}
return 0;
error:
mutex_destroy(&registry_impl->send_mutex);
closesocket(registry_impl->sock);
free(registry_impl->map);
free(registry_impl);
registry->impl = NULL;
return -1;
}
void conn_mux_registry_cleanup(conn_registry_t *registry) {
registry_impl_t *registry_impl = registry->impl;
JLOG_VERBOSE("Waiting for connections thread");
thread_join(registry_impl->thread, NULL);
mutex_destroy(&registry_impl->send_mutex);
closesocket(registry_impl->sock);
free(registry_impl->map);
free(registry->impl);
registry->impl = NULL;
}
int conn_mux_prepare(conn_registry_t *registry, struct pollfd *pfd, timestamp_t *next_timestamp) {
timestamp_t now = current_timestamp();
*next_timestamp = now + 60000;
mutex_lock(&registry->mutex);
registry_impl_t *registry_impl = registry->impl;
pfd->fd = registry_impl->sock;
pfd->events = POLLIN;
for (int i = 0; i < registry->agents_size; ++i) {
juice_agent_t *agent = registry->agents[i];
if (is_ready(agent)) {
conn_impl_t *conn_impl = agent->conn_impl;
if (*next_timestamp > conn_impl->next_timestamp)
*next_timestamp = conn_impl->next_timestamp;
}
}
int count = registry->agents_count;
mutex_unlock(&registry->mutex);
return count;
}
static juice_agent_t *lookup_agent(conn_registry_t *registry, char *buf, size_t len,
const addr_record_t *src) {
JLOG_VERBOSE("Looking up agent from address");
registry_impl_t *registry_impl = registry->impl;
map_entry_t *entry = find_map_entry(registry_impl, src, false);
juice_agent_t *agent = entry && entry->type == MAP_ENTRY_TYPE_FULL ? entry->agent : NULL;
if (agent) {
JLOG_DEBUG("Found agent from address");
return agent;
}
if (!is_stun_datagram(buf, len)) {
JLOG_INFO("Got non-STUN message from unknown source address");
return NULL;
}
JLOG_VERBOSE("Looking up agent from STUN message content");
stun_message_t msg;
if (stun_read(buf, len, &msg) < 0) {
JLOG_ERROR("STUN message reading failed");
return NULL;
}
if (msg.msg_class == STUN_CLASS_REQUEST && msg.msg_method == STUN_METHOD_BINDING &&
msg.has_integrity) {
// Binding request from peer
char username[STUN_MAX_USERNAME_LEN];
strcpy(username, msg.credentials.username);
char *separator = strchr(username, ':');
if (!separator) {
JLOG_WARN("STUN username invalid, username=\"%s\"", username);
return NULL;
}
*separator = '\0';
const char *local_ufrag = username;
for (int i = 0; i < registry->agents_size; ++i) {
agent = registry->agents[i];
if (is_ready(agent)) {
if (strcmp(local_ufrag, agent->local.ice_ufrag) == 0) {
JLOG_DEBUG("Found agent from ICE ufrag");
insert_map_entry(registry_impl, src, agent);
return agent;
}
}
}
} else {
if (!STUN_IS_RESPONSE(msg.msg_class)) {
JLOG_INFO("Got unexpected STUN message from unknown source address");
return NULL;
}
for (int i = 0; i < registry->agents_size; ++i) {
agent = registry->agents[i];
if (is_ready(agent)) {
if (agent_find_entry_from_transaction_id(agent, msg.transaction_id)) {
JLOG_DEBUG("Found agent from transaction ID");
return agent;
}
}
}
}
return NULL;
}
int conn_mux_process(conn_registry_t *registry, struct pollfd *pfd) {
mutex_lock(&registry->mutex);
if (pfd->revents & POLLNVAL || pfd->revents & POLLERR) {
JLOG_ERROR("Error when polling socket");
conn_mux_fail(registry);
mutex_unlock(&registry->mutex);
return -1;
}
if (pfd->revents & POLLIN) {
char buffer[BUFFER_SIZE];
addr_record_t src;
int ret;
while ((ret = conn_mux_recv(registry, buffer, BUFFER_SIZE, &src)) > 0) {
if (JLOG_DEBUG_ENABLED) {
char src_str[ADDR_MAX_STRING_LEN];
addr_record_to_string(&src, src_str, ADDR_MAX_STRING_LEN);
JLOG_DEBUG("Demultiplexing incoming datagram from %s", src_str);
}
juice_agent_t *agent = lookup_agent(registry, buffer, (size_t)ret, &src);
if (!agent || !is_ready(agent)) {
JLOG_DEBUG("Agent not found for incoming datagram, dropping");
continue;
}
conn_impl_t *conn_impl = agent->conn_impl;
if (agent_conn_recv(agent, buffer, (size_t)ret, &src) != 0) {
JLOG_WARN("Agent receive failed");
conn_impl->finished = true;
continue;
}
conn_impl->next_timestamp = current_timestamp();
}
if (ret < 0) {
conn_mux_fail(registry);
mutex_unlock(&registry->mutex);
return -1;
}
}
for (int i = 0; i < registry->agents_size; ++i) {
juice_agent_t *agent = registry->agents[i];
if (is_ready(agent)) {
conn_impl_t *conn_impl = agent->conn_impl;
if (conn_impl->next_timestamp <= current_timestamp()) {
if (agent_conn_update(agent, &conn_impl->next_timestamp) != 0) {
JLOG_WARN("Agent update failed");
conn_impl->finished = true;
continue;
}
}
}
}
mutex_unlock(&registry->mutex);
return 0;
}
int conn_mux_recv(conn_registry_t *registry, char *buffer, size_t size, addr_record_t *src) {
JLOG_VERBOSE("Receiving datagram");
registry_impl_t *registry_impl = registry->impl;
int len;
while ((len = udp_recvfrom(registry_impl->sock, buffer, size, src)) == 0) {
// Empty datagram (used to interrupt)
}
if (len < 0) {
if (sockerrno == SEAGAIN || sockerrno == SEWOULDBLOCK) {
JLOG_VERBOSE("No more datagrams to receive");
return 0;
}
JLOG_ERROR("recvfrom failed, errno=%d", sockerrno);
return -1;
}
addr_unmap_inet6_v4mapped((struct sockaddr *)&src->addr, &src->len);
return len; // len > 0
}
void conn_mux_fail(conn_registry_t *registry) {
for (int i = 0; i < registry->agents_size; ++i) {
juice_agent_t *agent = registry->agents[i];
if (is_ready(agent)) {
conn_impl_t *conn_impl = agent->conn_impl;
agent_conn_fail(agent);
conn_impl->finished = true;
}
}
}
int conn_mux_run(conn_registry_t *registry) {
struct pollfd pfd[1];
timestamp_t next_timestamp;
while (conn_mux_prepare(registry, pfd, &next_timestamp) > 0) {
timediff_t timediff = next_timestamp - current_timestamp();
if (timediff < 0)
timediff = 0;
JLOG_VERBOSE("Entering poll for %d ms", (int)timediff);
int ret = poll(pfd, 1, (int)timediff);
JLOG_VERBOSE("Leaving poll");
if (ret < 0) {
if (sockerrno == SEINTR || sockerrno == SEAGAIN) {
JLOG_VERBOSE("poll interrupted");
continue;
} else {
JLOG_FATAL("poll failed, errno=%d", sockerrno);
break;
}
}
if (conn_mux_process(registry, pfd) < 0)
break;
}
JLOG_DEBUG("Leaving connections thread");
return 0;
}
int conn_mux_init(juice_agent_t *agent, conn_registry_t *registry, udp_socket_config_t *config) {
(void)config; // ignored, only the config from the first connection is used
conn_impl_t *conn_impl = calloc(1, sizeof(conn_impl_t));
if (!conn_impl) {
JLOG_FATAL("Memory allocation failed for connection impl");
return -1;
}
conn_impl->registry = registry;
agent->conn_impl = conn_impl;
return 0;
}
void conn_mux_cleanup(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
conn_registry_t *registry = conn_impl->registry;
mutex_lock(&registry->mutex);
registry_impl_t *registry_impl = registry->impl;
remove_map_entries(registry_impl, agent);
mutex_unlock(&registry->mutex);
conn_mux_interrupt(agent);
free(agent->conn_impl);
agent->conn_impl = NULL;
}
void conn_mux_lock(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
conn_registry_t *registry = conn_impl->registry;
mutex_lock(&registry->mutex);
}
void conn_mux_unlock(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
conn_registry_t *registry = conn_impl->registry;
mutex_unlock(&registry->mutex);
}
int conn_mux_interrupt(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
conn_registry_t *registry = conn_impl->registry;
mutex_lock(&registry->mutex);
conn_impl->next_timestamp = current_timestamp();
mutex_unlock(&registry->mutex);
JLOG_VERBOSE("Interrupting connections thread");
registry_impl_t *registry_impl = registry->impl;
mutex_lock(&registry_impl->send_mutex);
if (udp_sendto_self(registry_impl->sock, NULL, 0) < 0) {
if (sockerrno != SEAGAIN && sockerrno != SEWOULDBLOCK) {
JLOG_WARN("Failed to interrupt poll by triggering socket, errno=%d", sockerrno);
}
mutex_unlock(&registry_impl->send_mutex);
return -1;
}
mutex_unlock(&registry_impl->send_mutex);
return 0;
}
int conn_mux_send(juice_agent_t *agent, const addr_record_t *dst, const char *data, size_t size,
int ds) {
conn_impl_t *conn_impl = agent->conn_impl;
registry_impl_t *registry_impl = conn_impl->registry->impl;
mutex_lock(&registry_impl->send_mutex);
if (registry_impl->send_ds >= 0 && registry_impl->send_ds != ds) {
JLOG_VERBOSE("Setting Differentiated Services field to 0x%X", ds);
if (udp_set_diffserv(registry_impl->sock, ds) == 0)
registry_impl->send_ds = ds;
else
registry_impl->send_ds = -1; // disable for next time
}
JLOG_VERBOSE("Sending datagram, size=%d", size);
int ret = udp_sendto(registry_impl->sock, data, size, dst);
if (ret < 0) {
if (sockerrno == SEAGAIN || sockerrno == SEWOULDBLOCK)
JLOG_INFO("Send failed, buffer is full");
else if (sockerrno == SEMSGSIZE)
JLOG_WARN("Send failed, datagram is too large");
else
JLOG_WARN("Send failed, errno=%d", sockerrno);
}
mutex_unlock(&registry_impl->send_mutex);
return ret;
}
int conn_mux_get_addrs(juice_agent_t *agent, addr_record_t *records, size_t size) {
conn_impl_t *conn_impl = agent->conn_impl;
registry_impl_t *registry_impl = conn_impl->registry->impl;
return udp_get_addrs(registry_impl->sock, records, size);
}

32
thirdparty/libjuice/src/conn_mux.h vendored Normal file
View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2022 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_CONN_MUX_H
#define JUICE_CONN_MUX_H
#include "addr.h"
#include "conn.h"
#include "thread.h"
#include "timestamp.h"
#include <stdbool.h>
#include <stdint.h>
int conn_mux_registry_init(conn_registry_t *registry, udp_socket_config_t *config);
void conn_mux_registry_cleanup(conn_registry_t *registry);
int conn_mux_init(juice_agent_t *agent, conn_registry_t *registry, udp_socket_config_t *config);
void conn_mux_cleanup(juice_agent_t *agent);
void conn_mux_lock(juice_agent_t *agent);
void conn_mux_unlock(juice_agent_t *agent);
int conn_mux_interrupt(juice_agent_t *agent);
int conn_mux_send(juice_agent_t *agent, const addr_record_t *dst, const char *data, size_t size,
int ds);
int conn_mux_get_addrs(juice_agent_t *agent, addr_record_t *records, size_t size);
#endif

433
thirdparty/libjuice/src/conn_poll.c vendored Normal file
View File

@@ -0,0 +1,433 @@
/**
* Copyright (c) 2022 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "conn_poll.h"
#include "agent.h"
#include "log.h"
#include "socket.h"
#include "thread.h"
#include "udp.h"
#include <assert.h>
#include <string.h>
#define BUFFER_SIZE 4096
typedef struct registry_impl {
thread_t thread;
#ifdef _WIN32
socket_t interrupt_sock;
#else
int interrupt_pipe_out;
int interrupt_pipe_in;
#endif
} registry_impl_t;
typedef enum conn_state { CONN_STATE_NEW = 0, CONN_STATE_READY, CONN_STATE_FINISHED } conn_state_t;
typedef struct conn_impl {
conn_registry_t *registry;
conn_state_t state;
socket_t sock;
mutex_t send_mutex;
int send_ds;
timestamp_t next_timestamp;
} conn_impl_t;
typedef struct pfds_record {
struct pollfd *pfds;
nfds_t size;
} pfds_record_t;
int conn_poll_prepare(conn_registry_t *registry, pfds_record_t *pfds, timestamp_t *next_timestamp);
int conn_poll_process(conn_registry_t *registry, pfds_record_t *pfds);
int conn_poll_recv(socket_t sock, char *buffer, size_t size, addr_record_t *src);
int conn_poll_run(conn_registry_t *registry);
static thread_return_t THREAD_CALL conn_thread_entry(void *arg) {
conn_registry_t *registry = (conn_registry_t *)arg;
conn_poll_run(registry);
return (thread_return_t)0;
}
int conn_poll_registry_init(conn_registry_t *registry, udp_socket_config_t *config) {
(void)config;
registry_impl_t *registry_impl = calloc(1, sizeof(registry_impl_t));
if (!registry_impl) {
JLOG_FATAL("Memory allocation failed for connections registry impl");
return -1;
}
#ifdef _WIN32
udp_socket_config_t interrupt_config;
memset(&interrupt_config, 0, sizeof(interrupt_config));
interrupt_config.bind_address = "localhost";
registry_impl->interrupt_sock = udp_create_socket(&interrupt_config);
if (registry_impl->interrupt_sock == INVALID_SOCKET) {
JLOG_FATAL("Dummy socket creation failed");
free(registry_impl);
return -1;
}
#else
int pipefds[2];
if (pipe(pipefds)) {
JLOG_FATAL("Pipe creation failed");
free(registry_impl);
return -1;
}
fcntl(pipefds[0], F_SETFL, O_NONBLOCK);
fcntl(pipefds[1], F_SETFL, O_NONBLOCK);
registry_impl->interrupt_pipe_out = pipefds[1]; // read
registry_impl->interrupt_pipe_in = pipefds[0]; // write
#endif
registry->impl = registry_impl;
JLOG_DEBUG("Starting connections thread");
int ret = thread_init(&registry_impl->thread, conn_thread_entry, registry);
if (ret) {
JLOG_FATAL("Thread creation failed, error=%d", ret);
goto error;
}
return 0;
error:
#ifndef _WIN32
close(registry_impl->interrupt_pipe_out);
close(registry_impl->interrupt_pipe_in);
#endif
free(registry_impl);
registry->impl = NULL;
return -1;
}
void conn_poll_registry_cleanup(conn_registry_t *registry) {
registry_impl_t *registry_impl = registry->impl;
JLOG_VERBOSE("Waiting for connections thread");
thread_join(registry_impl->thread, NULL);
#ifdef _WIN32
closesocket(registry_impl->interrupt_sock);
#else
close(registry_impl->interrupt_pipe_out);
close(registry_impl->interrupt_pipe_in);
#endif
free(registry->impl);
registry->impl = NULL;
}
int conn_poll_prepare(conn_registry_t *registry, pfds_record_t *pfds, timestamp_t *next_timestamp) {
timestamp_t now = current_timestamp();
*next_timestamp = now + 60000;
mutex_lock(&registry->mutex);
nfds_t size = (nfds_t)(1 + registry->agents_size);
if (pfds->size != size) {
struct pollfd *new_pfds = realloc(pfds->pfds, sizeof(struct pollfd) * size);
if (!new_pfds) {
JLOG_FATAL("Memory allocation for poll file descriptors failed");
goto error;
}
pfds->pfds = new_pfds;
pfds->size = size;
}
registry_impl_t *registry_impl = registry->impl;
struct pollfd *interrupt_pfd = pfds->pfds;
assert(interrupt_pfd);
#ifdef _WIN32
interrupt_pfd->fd = registry_impl->interrupt_sock;
#else
interrupt_pfd->fd = registry_impl->interrupt_pipe_in;
#endif
interrupt_pfd->events = POLLIN;
for (nfds_t i = 1; i < pfds->size; ++i) {
struct pollfd *pfd = pfds->pfds + i;
juice_agent_t *agent = registry->agents[i - 1];
if (!agent) {
pfd->fd = INVALID_SOCKET;
pfd->events = 0;
continue;
}
conn_impl_t *conn_impl = agent->conn_impl;
if (!conn_impl ||
(conn_impl->state != CONN_STATE_NEW && conn_impl->state != CONN_STATE_READY)) {
pfd->fd = INVALID_SOCKET;
pfd->events = 0;
continue;
}
if (conn_impl->state == CONN_STATE_NEW)
conn_impl->state = CONN_STATE_READY;
if (*next_timestamp > conn_impl->next_timestamp)
*next_timestamp = conn_impl->next_timestamp;
pfd->fd = conn_impl->sock;
pfd->events = POLLIN;
}
int count = registry->agents_count;
mutex_unlock(&registry->mutex);
return count;
error:
mutex_unlock(&registry->mutex);
return -1;
}
int conn_poll_recv(socket_t sock, char *buffer, size_t size, addr_record_t *src) {
JLOG_VERBOSE("Receiving datagram");
int len;
while ((len = udp_recvfrom(sock, buffer, size, src)) == 0) {
// Empty datagram, ignore
}
if (len < 0) {
if (sockerrno == SEAGAIN || sockerrno == SEWOULDBLOCK) {
JLOG_VERBOSE("No more datagrams to receive");
return 0;
}
JLOG_ERROR("recvfrom failed, errno=%d", sockerrno);
return -1;
}
addr_unmap_inet6_v4mapped((struct sockaddr *)&src->addr, &src->len);
return len; // len > 0
}
int conn_poll_process(conn_registry_t *registry, pfds_record_t *pfds) {
struct pollfd *interrupt_pfd = pfds->pfds;
if (interrupt_pfd->revents & POLLIN) {
#ifdef _WIN32
char dummy;
addr_record_t src;
while (udp_recvfrom(interrupt_pfd->fd, &dummy, 1, &src) >= 0) {
// Ignore
}
#else
char dummy;
while (read(interrupt_pfd->fd, &dummy, 1) > 0) {
// Ignore
}
#endif
}
for (nfds_t i = 1; i < pfds->size; ++i) {
struct pollfd *pfd = pfds->pfds + i;
if (pfd->fd == INVALID_SOCKET)
continue;
mutex_lock(&registry->mutex);
juice_agent_t *agent = registry->agents[i - 1];
if (!agent)
goto end;
conn_impl_t *conn_impl = agent->conn_impl;
if (!conn_impl || conn_impl->sock != pfd->fd || conn_impl->state != CONN_STATE_READY)
goto end;
if (pfd->revents & POLLNVAL || pfd->revents & POLLERR) {
JLOG_WARN("Error when polling socket");
agent_conn_fail(agent);
conn_impl->state = CONN_STATE_FINISHED;
goto end;
}
if (pfd->revents & POLLIN) {
char buffer[BUFFER_SIZE];
addr_record_t src;
int ret = 0;
int left = 1000; // limit for fairness between sockets
while (left-- &&
(ret = conn_poll_recv(conn_impl->sock, buffer, BUFFER_SIZE, &src)) > 0) {
if (agent_conn_recv(agent, buffer, (size_t)ret, &src) != 0) {
JLOG_WARN("Agent receive failed");
conn_impl->state = CONN_STATE_FINISHED;
break;
}
}
if (conn_impl->state == CONN_STATE_FINISHED)
goto end;
if (ret < 0) {
agent_conn_fail(agent);
conn_impl->state = CONN_STATE_FINISHED;
goto end;
}
if (agent_conn_update(agent, &conn_impl->next_timestamp) != 0) {
JLOG_WARN("Agent update failed");
conn_impl->state = CONN_STATE_FINISHED;
goto end;
}
} else if (conn_impl->next_timestamp <= current_timestamp()) {
if (agent_conn_update(agent, &conn_impl->next_timestamp) != 0) {
JLOG_WARN("Agent update failed");
conn_impl->state = CONN_STATE_FINISHED;
goto end;
}
}
end:
mutex_unlock(&registry->mutex);
}
return 0;
}
int conn_poll_run(conn_registry_t *registry) {
pfds_record_t pfds;
pfds.pfds = NULL;
pfds.size = 0;
timestamp_t next_timestamp = 0;
int count;
while ((count = conn_poll_prepare(registry, &pfds, &next_timestamp)) > 0) {
timediff_t timediff = next_timestamp - current_timestamp();
if (timediff < 0)
timediff = 0;
JLOG_VERBOSE("Entering poll on %d sockets for %d ms", count, (int)timediff);
int ret = poll(pfds.pfds, pfds.size, (int)timediff);
JLOG_VERBOSE("Leaving poll");
if (ret < 0) {
#ifdef _WIN32
if (ret == WSAENOTSOCK)
continue; // prepare again as the fd has been removed
#endif
if (sockerrno == SEINTR || sockerrno == SEAGAIN) {
JLOG_VERBOSE("poll interrupted");
continue;
} else {
JLOG_FATAL("poll failed, errno=%d", sockerrno);
break;
}
}
if (conn_poll_process(registry, &pfds) < 0)
break;
}
JLOG_DEBUG("Leaving connections thread");
free(pfds.pfds);
return 0;
}
int conn_poll_init(juice_agent_t *agent, conn_registry_t *registry, udp_socket_config_t *config) {
conn_impl_t *conn_impl = calloc(1, sizeof(conn_impl_t));
if (!conn_impl) {
JLOG_FATAL("Memory allocation failed for connection impl");
return -1;
}
conn_impl->sock = udp_create_socket(config);
if (conn_impl->sock == INVALID_SOCKET) {
JLOG_ERROR("UDP socket creation failed");
free(conn_impl);
return -1;
}
mutex_init(&conn_impl->send_mutex, 0);
conn_impl->registry = registry;
agent->conn_impl = conn_impl;
return 0;
}
void conn_poll_cleanup(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
conn_poll_interrupt(agent);
mutex_destroy(&conn_impl->send_mutex);
closesocket(conn_impl->sock);
free(agent->conn_impl);
agent->conn_impl = NULL;
}
void conn_poll_lock(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
conn_registry_t *registry = conn_impl->registry;
mutex_lock(&registry->mutex);
}
void conn_poll_unlock(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
conn_registry_t *registry = conn_impl->registry;
mutex_unlock(&registry->mutex);
}
int conn_poll_interrupt(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
conn_registry_t *registry = conn_impl->registry;
registry_impl_t *registry_impl = registry->impl;
mutex_lock(&registry->mutex);
conn_impl->next_timestamp = current_timestamp();
mutex_unlock(&registry->mutex);
JLOG_VERBOSE("Interrupting connections thread");
#ifdef _WIN32
if (udp_sendto_self(registry_impl->interrupt_sock, NULL, 0) < 0) {
if (sockerrno != SEAGAIN && sockerrno != SEWOULDBLOCK) {
JLOG_WARN("Failed to interrupt poll by triggering socket, errno=%d", sockerrno);
}
return -1;
}
#else
char dummy = 0;
if (write(registry_impl->interrupt_pipe_out, &dummy, 1) < 0 && errno != EAGAIN &&
errno != EWOULDBLOCK) {
JLOG_WARN("Failed to interrupt poll by writing to pipe, errno=%d", errno);
}
#endif
return 0;
}
int conn_poll_send(juice_agent_t *agent, const addr_record_t *dst, const char *data, size_t size,
int ds) {
conn_impl_t *conn_impl = agent->conn_impl;
mutex_lock(&conn_impl->send_mutex);
if (conn_impl->send_ds >= 0 && conn_impl->send_ds != ds) {
JLOG_VERBOSE("Setting Differentiated Services field to 0x%X", ds);
if (udp_set_diffserv(conn_impl->sock, ds) == 0)
conn_impl->send_ds = ds;
else
conn_impl->send_ds = -1; // disable for next time
}
JLOG_VERBOSE("Sending datagram, size=%d", size);
int ret = udp_sendto(conn_impl->sock, data, size, dst);
if (ret < 0) {
if (sockerrno == SEAGAIN || sockerrno == SEWOULDBLOCK)
JLOG_INFO("Send failed, buffer is full");
else if (sockerrno == SEMSGSIZE)
JLOG_WARN("Send failed, datagram is too large");
else
JLOG_WARN("Send failed, errno=%d", sockerrno);
}
mutex_unlock(&conn_impl->send_mutex);
return ret;
}
int conn_poll_get_addrs(juice_agent_t *agent, addr_record_t *records, size_t size) {
conn_impl_t *conn_impl = agent->conn_impl;
return udp_get_addrs(conn_impl->sock, records, size);
}

32
thirdparty/libjuice/src/conn_poll.h vendored Normal file
View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2022 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_CONN_POLL_H
#define JUICE_CONN_POLL_H
#include "addr.h"
#include "conn.h"
#include "thread.h"
#include "timestamp.h"
#include <stdbool.h>
#include <stdint.h>
int conn_poll_registry_init(conn_registry_t *registry, udp_socket_config_t *config);
void conn_poll_registry_cleanup(conn_registry_t *registry);
int conn_poll_init(juice_agent_t *agent, conn_registry_t *registry, udp_socket_config_t *config);
void conn_poll_cleanup(juice_agent_t *agent);
void conn_poll_lock(juice_agent_t *agent);
void conn_poll_unlock(juice_agent_t *agent);
int conn_poll_interrupt(juice_agent_t *agent);
int conn_poll_send(juice_agent_t *agent, const addr_record_t *dst, const char *data, size_t size,
int ds);
int conn_poll_get_addrs(juice_agent_t *agent, addr_record_t *records, size_t size);
#endif

278
thirdparty/libjuice/src/conn_thread.c vendored Normal file
View File

@@ -0,0 +1,278 @@
/**
* Copyright (c) 2022 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "conn_thread.h"
#include "agent.h"
#include "log.h"
#include "socket.h"
#include "thread.h"
#include "udp.h"
#include <assert.h>
#include <string.h>
#define BUFFER_SIZE 4096
typedef struct conn_impl {
thread_t thread;
socket_t sock;
mutex_t mutex;
mutex_t send_mutex;
int send_ds;
timestamp_t next_timestamp;
bool stopped;
} conn_impl_t;
int conn_thread_run(juice_agent_t *agent);
int conn_thread_prepare(juice_agent_t *agent, struct pollfd *pfd, timestamp_t *next_timestamp);
int conn_thread_process(juice_agent_t *agent, struct pollfd *pfd);
int conn_thread_recv(socket_t sock, char *buffer, size_t size, addr_record_t *src);
static thread_return_t THREAD_CALL conn_thread_entry(void *arg) {
juice_agent_t *agent = (juice_agent_t *)arg;
conn_thread_run(agent);
return (thread_return_t)0;
}
int conn_thread_prepare(juice_agent_t *agent, struct pollfd *pfd, timestamp_t *next_timestamp) {
conn_impl_t *conn_impl = agent->conn_impl;
mutex_lock(&conn_impl->mutex);
if (conn_impl->stopped) {
mutex_unlock(&conn_impl->mutex);
return 0;
}
pfd->fd = conn_impl->sock;
pfd->events = POLLIN;
*next_timestamp = conn_impl->next_timestamp;
mutex_unlock(&conn_impl->mutex);
return 1;
}
int conn_thread_process(juice_agent_t *agent, struct pollfd *pfd) {
conn_impl_t *conn_impl = agent->conn_impl;
mutex_lock(&conn_impl->mutex);
if (conn_impl->stopped) {
mutex_unlock(&conn_impl->mutex);
return -1;
}
if (pfd->revents & POLLNVAL || pfd->revents & POLLERR) {
JLOG_ERROR("Error when polling socket");
agent_conn_fail(agent);
mutex_unlock(&conn_impl->mutex);
return -1;
}
if (pfd->revents & POLLIN) {
char buffer[BUFFER_SIZE];
addr_record_t src;
int ret;
while ((ret = conn_thread_recv(conn_impl->sock, buffer, BUFFER_SIZE, &src)) > 0) {
if (agent_conn_recv(agent, buffer, (size_t)ret, &src) != 0) {
JLOG_WARN("Agent receive failed");
mutex_unlock(&conn_impl->mutex);
return -1;
}
}
if (ret < 0) {
agent_conn_fail(agent);
mutex_unlock(&conn_impl->mutex);
return -1;
}
if (agent_conn_update(agent, &conn_impl->next_timestamp) != 0) {
JLOG_WARN("Agent update failed");
mutex_unlock(&conn_impl->mutex);
return -1;
}
} else if (conn_impl->next_timestamp <= current_timestamp()) {
if (agent_conn_update(agent, &conn_impl->next_timestamp) != 0) {
JLOG_WARN("Agent update failed");
mutex_unlock(&conn_impl->mutex);
return -1;
}
}
mutex_unlock(&conn_impl->mutex);
return 0;
}
int conn_thread_recv(socket_t sock, char *buffer, size_t size, addr_record_t *src) {
JLOG_VERBOSE("Receiving datagram");
int len;
while ((len = udp_recvfrom(sock, buffer, size, src)) == 0) {
// Empty datagram (used to interrupt)
}
if (len < 0) {
if (sockerrno == SEAGAIN || sockerrno == SEWOULDBLOCK) {
JLOG_VERBOSE("No more datagrams to receive");
return 0;
}
JLOG_ERROR("recvfrom failed, errno=%d", sockerrno);
return -1;
}
addr_unmap_inet6_v4mapped((struct sockaddr *)&src->addr, &src->len);
return len; // len > 0
}
int conn_thread_run(juice_agent_t *agent) {
struct pollfd pfd[1];
timestamp_t next_timestamp;
while (conn_thread_prepare(agent, pfd, &next_timestamp) > 0) {
timediff_t timediff = next_timestamp - current_timestamp();
if (timediff < 0)
timediff = 0;
JLOG_VERBOSE("Entering poll for %d ms", (int)timediff);
int ret = poll(pfd, 1, (int)timediff);
JLOG_VERBOSE("Leaving poll");
if (ret < 0) {
if (sockerrno == SEINTR || sockerrno == SEAGAIN) {
JLOG_VERBOSE("poll interrupted");
continue;
} else {
JLOG_FATAL("poll failed, errno=%d", sockerrno);
break;
}
}
if (conn_thread_process(agent, pfd) < 0)
break;
}
JLOG_DEBUG("Leaving connection thread");
return 0;
}
int conn_thread_init(juice_agent_t *agent, conn_registry_t *registry, udp_socket_config_t *config) {
(void)registry;
conn_impl_t *conn_impl = calloc(1, sizeof(conn_impl_t));
if (!conn_impl) {
JLOG_FATAL("Memory allocation failed for connection impl");
return -1;
}
conn_impl->sock = udp_create_socket(config);
if (conn_impl->sock == INVALID_SOCKET) {
JLOG_ERROR("UDP socket creation failed");
free(conn_impl);
return -1;
}
mutex_init(&conn_impl->mutex, 0);
mutex_init(&conn_impl->send_mutex, 0);
agent->conn_impl = conn_impl;
JLOG_DEBUG("Starting connection thread");
int ret = thread_init(&conn_impl->thread, conn_thread_entry, agent);
if (ret) {
JLOG_FATAL("Thread creation failed, error=%d", ret);
free(conn_impl);
agent->conn_impl = NULL;
return -1;
}
return 0;
}
void conn_thread_cleanup(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
mutex_lock(&conn_impl->mutex);
conn_impl->stopped = true;
mutex_unlock(&conn_impl->mutex);
conn_thread_interrupt(agent);
JLOG_VERBOSE("Waiting for connection thread");
thread_join(conn_impl->thread, NULL);
closesocket(conn_impl->sock);
mutex_destroy(&conn_impl->mutex);
mutex_destroy(&conn_impl->send_mutex);
free(agent->conn_impl);
agent->conn_impl = NULL;
}
void conn_thread_lock(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
mutex_lock(&conn_impl->mutex);
}
void conn_thread_unlock(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
mutex_unlock(&conn_impl->mutex);
}
int conn_thread_interrupt(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
mutex_lock(&conn_impl->mutex);
conn_impl->next_timestamp = current_timestamp();
mutex_unlock(&conn_impl->mutex);
JLOG_VERBOSE("Interrupting connection thread");
mutex_lock(&conn_impl->send_mutex);
if (udp_sendto_self(conn_impl->sock, NULL, 0) < 0) {
if (sockerrno != SEAGAIN && sockerrno != SEWOULDBLOCK) {
JLOG_WARN("Failed to interrupt poll by triggering socket, errno=%d", sockerrno);
}
mutex_unlock(&conn_impl->send_mutex);
return -1;
}
mutex_unlock(&conn_impl->send_mutex);
return 0;
}
int conn_thread_send(juice_agent_t *agent, const addr_record_t *dst, const char *data, size_t size,
int ds) {
conn_impl_t *conn_impl = agent->conn_impl;
mutex_lock(&conn_impl->send_mutex);
if (conn_impl->send_ds >= 0 && conn_impl->send_ds != ds) {
JLOG_VERBOSE("Setting Differentiated Services field to 0x%X", ds);
if (udp_set_diffserv(conn_impl->sock, ds) == 0)
conn_impl->send_ds = ds;
else
conn_impl->send_ds = -1; // disable for next time
}
JLOG_VERBOSE("Sending datagram, size=%d", size);
int ret = udp_sendto(conn_impl->sock, data, size, dst);
if (ret < 0) {
if (sockerrno == SEAGAIN || sockerrno == SEWOULDBLOCK)
JLOG_INFO("Send failed, buffer is full");
else if (sockerrno == SEMSGSIZE)
JLOG_WARN("Send failed, datagram is too large");
else
JLOG_WARN("Send failed, errno=%d", sockerrno);
}
mutex_unlock(&conn_impl->send_mutex);
return ret;
}
int conn_thread_get_addrs(juice_agent_t *agent, addr_record_t *records, size_t size) {
conn_impl_t *conn_impl = agent->conn_impl;
return udp_get_addrs(conn_impl->sock, records, size);
}

32
thirdparty/libjuice/src/conn_thread.h vendored Normal file
View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2022 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_CONN_THREAD_H
#define JUICE_CONN_THREAD_H
#include "addr.h"
#include "conn.h"
#include "thread.h"
#include "timestamp.h"
#include <stdbool.h>
#include <stdint.h>
int conn_thread_registry_init(conn_registry_t *registry, udp_socket_config_t *config);
void conn_thread_registry_cleanup(conn_registry_t *registry);
int conn_thread_init(juice_agent_t *agent, conn_registry_t *registry, udp_socket_config_t *config);
void conn_thread_cleanup(juice_agent_t *agent);
void conn_thread_lock(juice_agent_t *agent);
void conn_thread_unlock(juice_agent_t *agent);
int conn_thread_interrupt(juice_agent_t *agent);
int conn_thread_send(juice_agent_t *agent, const addr_record_t *dst, const char *data, size_t size,
int ds);
int conn_thread_get_addrs(juice_agent_t *agent, addr_record_t *records, size_t size);
#endif

34
thirdparty/libjuice/src/const_time.c vendored Normal file
View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2021 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "const_time.h"
int const_time_memcmp(const void *a, const void *b, size_t len) {
const unsigned char *ca = a;
const unsigned char *cb = b;
unsigned char x = 0;
for (size_t i = 0; i < len; i++)
x |= ca[i] ^ cb[i];
return x;
}
int const_time_strcmp(const void *a, const void *b) {
const unsigned char *ca = a;
const unsigned char *cb = b;
unsigned char x = 0;
size_t i = 0;
for(;;) {
x |= ca[i] ^ cb[i];
if (!ca[i] || !cb[i])
break;
++i;
}
return x;
}

18
thirdparty/libjuice/src/const_time.h vendored Normal file
View File

@@ -0,0 +1,18 @@
/**
* Copyright (c) 2021 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_CONST_TIME_H
#define JUICE_CONST_TIME_H
#include <stdint.h>
#include <stdlib.h>
int const_time_memcmp(const void *a, const void *b, size_t len);
int const_time_strcmp(const void *a, const void *b);
#endif

38
thirdparty/libjuice/src/crc32.c vendored Normal file
View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "crc32.h"
#define CRC32_REVERSED_POLY 0xEDB88320
#define CRC32_INIT 0xFFFFFFFF
#define CRC32_XOR 0xFFFFFFFF
static uint32_t crc32_byte(uint32_t crc) {
for (int i = 0; i < 8; ++i)
if (crc & 1)
crc = (crc >> 1) ^ CRC32_REVERSED_POLY;
else
crc = (crc >> 1);
return crc;
}
static uint32_t crc32_table(const uint8_t *p, size_t size, uint32_t *table) {
uint32_t crc = CRC32_INIT;
while (size--)
crc = table[(uint8_t)(crc & 0xFF) ^ *p++] ^ (crc >> 8);
return crc ^ CRC32_XOR;
}
JUICE_EXPORT uint32_t juice_crc32(const void *data, size_t size) {
static uint32_t table[256] = {0};
if (table[0] == 0)
for (uint32_t i = 0; i < 256; ++i)
table[i] = crc32_byte(i);
return crc32_table(data, size, table);
}

21
thirdparty/libjuice/src/crc32.h vendored Normal file
View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_CRC32_H
#define JUICE_CRC32_H
#include "juice.h"
#include <stdint.h>
#include <stdlib.h>
JUICE_EXPORT uint32_t juice_crc32(const void *data, size_t size);
#define CRC32(data, size) juice_crc32(data, size)
#endif // JUICE_CRC32_H

59
thirdparty/libjuice/src/hash.c vendored Normal file
View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "hash.h"
#if USE_NETTLE
#include <nettle/md5.h>
#include <nettle/sha1.h>
#include <nettle/sha2.h>
#else
#include "picohash.h"
#endif
void hash_md5(const void *message, size_t size, void *digest) {
#if USE_NETTLE
struct md5_ctx ctx;
md5_init(&ctx);
md5_update(&ctx, size, message);
md5_digest(&ctx, HASH_MD5_SIZE, digest);
#else
picohash_ctx_t ctx;
picohash_init_md5(&ctx);
picohash_update(&ctx, message, size);
picohash_final(&ctx, digest);
#endif
}
void hash_sha1(const void *message, size_t size, void *digest) {
#if USE_NETTLE
struct sha1_ctx ctx;
sha1_init(&ctx);
sha1_update(&ctx, size, message);
sha1_digest(&ctx, HASH_SHA1_SIZE, digest);
#else
picohash_ctx_t ctx;
picohash_init_sha1(&ctx);
picohash_update(&ctx, message, size);
picohash_final(&ctx, digest);
#endif
}
void hash_sha256(const void *message, size_t size, void *digest) {
#if USE_NETTLE
struct sha256_ctx ctx;
sha256_init(&ctx);
sha256_update(&ctx, size, message);
sha256_digest(&ctx, HASH_SHA256_SIZE, digest);
#else
picohash_ctx_t ctx;
picohash_init_sha256(&ctx);
picohash_update(&ctx, message, size);
picohash_final(&ctx, digest);
#endif
}

23
thirdparty/libjuice/src/hash.h vendored Normal file
View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_HASH_H
#define JUICE_HASH_H
#include <stdint.h>
#include <stdlib.h>
#define HASH_MD5_SIZE 16
#define HASH_SHA1_SIZE 24
#define HASH_SHA256_SIZE 32
void hash_md5(const void *message, size_t size, void *digest);
void hash_sha1(const void *message, size_t size, void *digest);
void hash_sha256(const void *message, size_t size, void *digest);
#endif

43
thirdparty/libjuice/src/hmac.c vendored Normal file
View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "hmac.h"
#if USE_NETTLE
#include <nettle/hmac.h>
#else
#include "picohash.h"
#endif
void hmac_sha1(const void *message, size_t size, const void *key, size_t key_size, void *digest) {
#if USE_NETTLE
struct hmac_sha1_ctx ctx;
hmac_sha1_set_key(&ctx, key_size, key);
hmac_sha1_update(&ctx, size, message);
hmac_sha1_digest(&ctx, HMAC_SHA1_SIZE, digest);
#else
picohash_ctx_t ctx;
picohash_init_hmac(&ctx, picohash_init_sha1, key, key_size);
picohash_update(&ctx, message, size);
picohash_final(&ctx, digest);
#endif
}
void hmac_sha256(const void *message, size_t size, const void *key, size_t key_size, void *digest) {
#if USE_NETTLE
struct hmac_sha256_ctx ctx;
hmac_sha256_set_key(&ctx, key_size, key);
hmac_sha256_update(&ctx, size, message);
hmac_sha256_digest(&ctx, HMAC_SHA256_SIZE, digest);
#else
picohash_ctx_t ctx;
picohash_init_hmac(&ctx, picohash_init_sha256, key, key_size);
picohash_update(&ctx, message, size);
picohash_final(&ctx, digest);
#endif
}

21
thirdparty/libjuice/src/hmac.h vendored Normal file
View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_HMAC_H
#define JUICE_HMAC_H
#include <stdint.h>
#include <stdlib.h>
#define HMAC_SHA1_SIZE 20
#define HMAC_SHA256_SIZE 32
void hmac_sha1(const void *message, size_t size, const void *key, size_t key_size, void *digest);
void hmac_sha256(const void *message, size_t size, const void *key, size_t key_size, void *digest);
#endif

408
thirdparty/libjuice/src/ice.c vendored Normal file
View File

@@ -0,0 +1,408 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "ice.h"
#include "log.h"
#include "random.h"
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_SIZE 1024
#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
// See RFC4566 for SDP format: https://www.rfc-editor.org/rfc/rfc4566.html
static const char *skip_prefix(const char *str, const char *prefix) {
size_t len = strlen(prefix);
return strncmp(str, prefix, len) == 0 ? str + len : str;
}
static bool match_prefix(const char *str, const char *prefix, const char **end) {
*end = skip_prefix(str, prefix);
return *end != str || !*prefix;
}
static int parse_sdp_line(const char *line, ice_description_t *description) {
const char *arg;
if (match_prefix(line, "a=ice-ufrag:", &arg)) {
sscanf(arg, "%256s", description->ice_ufrag);
return 0;
}
if (match_prefix(line, "a=ice-pwd:", &arg)) {
sscanf(arg, "%256s", description->ice_pwd);
return 0;
}
if (match_prefix(line, "a=end-of-candidates:", &arg)) {
description->finished = true;
return 0;
}
ice_candidate_t candidate;
if (ice_parse_candidate_sdp(line, &candidate) == 0) {
ice_add_candidate(&candidate, description);
return 0;
}
return ICE_PARSE_IGNORED;
}
static int parse_sdp_candidate(const char *line, ice_candidate_t *candidate) {
memset(candidate, 0, sizeof(*candidate));
line = skip_prefix(line, "a=");
line = skip_prefix(line, "candidate:");
char transport[32 + 1];
char type[32 + 1];
if (sscanf(line, "%32s %d %32s %u %256s %32s typ %32s", candidate->foundation,
&candidate->component, transport, &candidate->priority, candidate->hostname,
candidate->service, type) != 7) {
JLOG_WARN("Failed to parse candidate: %s", line);
return ICE_PARSE_ERROR;
}
for (int i = 0; transport[i]; ++i)
transport[i] = toupper((unsigned char)transport[i]);
for (int i = 0; type[i]; ++i)
type[i] = tolower((unsigned char)type[i]);
if (strcmp(type, "host") == 0)
candidate->type = ICE_CANDIDATE_TYPE_HOST;
else if (strcmp(type, "srflx") == 0)
candidate->type = ICE_CANDIDATE_TYPE_SERVER_REFLEXIVE;
else if (strcmp(type, "relay") == 0)
candidate->type = ICE_CANDIDATE_TYPE_RELAYED;
else {
JLOG_WARN("Ignoring candidate with unknown type \"%s\"", type);
return ICE_PARSE_IGNORED;
}
if (strcmp(transport, "UDP") != 0) {
JLOG_WARN("Ignoring candidate with transport %s", transport);
return ICE_PARSE_IGNORED;
}
return 0;
}
int ice_parse_sdp(const char *sdp, ice_description_t *description) {
memset(description, 0, sizeof(*description));
description->candidates_count = 0;
description->finished = false;
char buffer[BUFFER_SIZE];
size_t size = 0;
while (*sdp) {
if (*sdp == '\n') {
if (size) {
buffer[size++] = '\0';
if(parse_sdp_line(buffer, description) == ICE_PARSE_ERROR)
return ICE_PARSE_ERROR;
size = 0;
}
} else if (*sdp != '\r' && size + 1 < BUFFER_SIZE) {
buffer[size++] = *sdp;
}
++sdp;
}
ice_sort_candidates(description);
JLOG_DEBUG("Parsed remote description: ufrag=\"%s\", pwd=\"%s\", candidates=%d",
description->ice_ufrag, description->ice_pwd, description->candidates_count);
if (*description->ice_ufrag == '\0')
return ICE_PARSE_MISSING_UFRAG;
if (*description->ice_pwd == '\0')
return ICE_PARSE_MISSING_PWD;
return 0;
}
int ice_parse_candidate_sdp(const char *line, ice_candidate_t *candidate) {
const char *arg;
if (match_prefix(line, "a=candidate:", &arg)) {
int ret = parse_sdp_candidate(line, candidate);
if (ret < 0)
return ret;
ice_resolve_candidate(candidate, ICE_RESOLVE_MODE_SIMPLE);
return 0;
}
return ICE_PARSE_ERROR;
}
int ice_create_local_description(ice_description_t *description) {
memset(description, 0, sizeof(*description));
juice_random_str64(description->ice_ufrag, 4 + 1);
juice_random_str64(description->ice_pwd, 22 + 1);
description->candidates_count = 0;
description->finished = false;
JLOG_DEBUG("Created local description: ufrag=\"%s\", pwd=\"%s\"", description->ice_ufrag,
description->ice_pwd);
return 0;
}
int ice_create_local_candidate(ice_candidate_type_t type, int component, int index,
const addr_record_t *record, ice_candidate_t *candidate) {
memset(candidate, 0, sizeof(*candidate));
candidate->type = type;
candidate->component = component;
candidate->resolved = *record;
strcpy(candidate->foundation, "-");
candidate->priority = ice_compute_priority(candidate->type, candidate->resolved.addr.ss_family,
candidate->component, index);
if (getnameinfo((struct sockaddr *)&record->addr, record->len, candidate->hostname, 256,
candidate->service, 32, NI_NUMERICHOST | NI_NUMERICSERV | NI_DGRAM)) {
JLOG_ERROR("getnameinfo failed, errno=%d", sockerrno);
return -1;
}
return 0;
}
int ice_resolve_candidate(ice_candidate_t *candidate, ice_resolve_mode_t mode) {
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_ADDRCONFIG;
if (mode != ICE_RESOLVE_MODE_LOOKUP)
hints.ai_flags |= AI_NUMERICHOST | AI_NUMERICSERV;
struct addrinfo *ai_list = NULL;
if (getaddrinfo(candidate->hostname, candidate->service, &hints, &ai_list)) {
JLOG_INFO("Failed to resolve address: %s:%s", candidate->hostname, candidate->service);
candidate->resolved.len = 0;
return -1;
}
for (struct addrinfo *ai = ai_list; ai; ai = ai->ai_next) {
if (ai->ai_family == AF_INET || ai->ai_family == AF_INET6) {
candidate->resolved.len = (socklen_t)ai->ai_addrlen;
memcpy(&candidate->resolved.addr, ai->ai_addr, ai->ai_addrlen);
break;
}
}
freeaddrinfo(ai_list);
return 0;
}
int ice_add_candidate(ice_candidate_t *candidate, ice_description_t *description) {
if (candidate->type == ICE_CANDIDATE_TYPE_UNKNOWN)
return -1;
if (description->candidates_count >= ICE_MAX_CANDIDATES_COUNT) {
JLOG_WARN("Description already has the maximum number of candidates");
return -1;
}
if (strcmp(candidate->foundation, "-") == 0)
snprintf(candidate->foundation, 32, "%u",
(unsigned int)(description->candidates_count + 1));
ice_candidate_t *pos = description->candidates + description->candidates_count;
*pos = *candidate;
++description->candidates_count;
return 0;
}
void ice_sort_candidates(ice_description_t *description) {
// In-place insertion sort
ice_candidate_t *begin = description->candidates;
ice_candidate_t *end = begin + description->candidates_count;
ice_candidate_t *cur = begin;
while (++cur < end) {
uint32_t priority = cur->priority;
ice_candidate_t *prev = cur;
ice_candidate_t tmp = *prev;
while (--prev >= begin && prev->priority < priority) {
*(prev + 1) = *prev;
}
if (prev + 1 != cur)
*(prev + 1) = tmp;
}
}
ice_candidate_t *ice_find_candidate_from_addr(ice_description_t *description,
const addr_record_t *record,
ice_candidate_type_t type) {
ice_candidate_t *cur = description->candidates;
ice_candidate_t *end = cur + description->candidates_count;
while (cur != end) {
if ((type == ICE_CANDIDATE_TYPE_UNKNOWN || cur->type == type) &&
addr_is_equal((struct sockaddr *)&record->addr, (struct sockaddr *)&cur->resolved.addr,
true))
return cur;
++cur;
}
return NULL;
}
int ice_generate_sdp(const ice_description_t *description, char *buffer, size_t size) {
if (!*description->ice_ufrag || !*description->ice_pwd)
return -1;
int len = 0;
char *begin = buffer;
char *end = begin + size;
// Round 0: description
// Round i with i>0 and i<count+1: candidate i-1
// Round count + 1: end-of-candidates and ice-options lines
for (int i = 0; i < description->candidates_count + 2; ++i) {
int ret;
if (i == 0) {
ret = snprintf(begin, end - begin, "a=ice-ufrag:%s\r\na=ice-pwd:%s\r\n",
description->ice_ufrag, description->ice_pwd);
} else if (i < description->candidates_count + 1) {
const ice_candidate_t *candidate = description->candidates + i - 1;
if (candidate->type == ICE_CANDIDATE_TYPE_UNKNOWN ||
candidate->type == ICE_CANDIDATE_TYPE_PEER_REFLEXIVE)
continue;
char tmp[BUFFER_SIZE];
if (ice_generate_candidate_sdp(candidate, tmp, BUFFER_SIZE) < 0)
continue;
ret = snprintf(begin, end - begin, "%s\r\n", tmp);
} else { // i == description->candidates_count + 1
// RFC 8445 10. ICE Option: An agent compliant to this specification MUST inform the
// peer about the compliance using the 'ice2' option.
if (description->finished)
ret = snprintf(begin, end - begin, "a=end-of-candidates\r\na=ice-options:ice2\r\n");
else
ret = snprintf(begin, end - begin, "a=ice-options:ice2,trickle\r\n");
}
if (ret < 0)
return -1;
len += ret;
if (begin < end)
begin += ret >= end - begin ? end - begin - 1 : ret;
}
return len;
}
int ice_generate_candidate_sdp(const ice_candidate_t *candidate, char *buffer, size_t size) {
const char *type = NULL;
const char *suffix = NULL;
switch (candidate->type) {
case ICE_CANDIDATE_TYPE_HOST:
type = "host";
break;
case ICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
type = "prflx";
break;
case ICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
type = "srflx";
suffix = "raddr 0.0.0.0 rport 0"; // This is needed for compatibility with Firefox
break;
case ICE_CANDIDATE_TYPE_RELAYED:
type = "relay";
suffix = "raddr 0.0.0.0 rport 0"; // This is needed for compatibility with Firefox
break;
default:
JLOG_ERROR("Unknown candidate type");
return -1;
}
return snprintf(buffer, size, "a=candidate:%s %u UDP %u %s %s typ %s%s%s",
candidate->foundation, candidate->component, candidate->priority,
candidate->hostname, candidate->service, type, suffix ? " " : "",
suffix ? suffix : "");
}
int ice_create_candidate_pair(ice_candidate_t *local, ice_candidate_t *remote, bool is_controlling,
ice_candidate_pair_t *pair) { // local or remote might be NULL
if (local && remote && local->resolved.addr.ss_family != remote->resolved.addr.ss_family) {
JLOG_ERROR("Mismatching candidates address families");
return -1;
}
memset(pair, 0, sizeof(*pair));
pair->local = local;
pair->remote = remote;
pair->state = ICE_CANDIDATE_PAIR_STATE_FROZEN;
return ice_update_candidate_pair(pair, is_controlling);
}
int ice_update_candidate_pair(ice_candidate_pair_t *pair, bool is_controlling) {
// Compute pair priority according to RFC 8445, extended to support generic pairs missing local
// or remote See https://www.rfc-editor.org/rfc/rfc8445.html#section-6.1.2.3
if (!pair->local && !pair->remote)
return 0;
uint64_t local_priority =
pair->local
? pair->local->priority
: ice_compute_priority(ICE_CANDIDATE_TYPE_HOST, pair->remote->resolved.addr.ss_family,
pair->remote->component, 0);
uint64_t remote_priority =
pair->remote
? pair->remote->priority
: ice_compute_priority(ICE_CANDIDATE_TYPE_HOST, pair->local->resolved.addr.ss_family,
pair->local->component, 0);
uint64_t g = is_controlling ? local_priority : remote_priority;
uint64_t d = is_controlling ? remote_priority : local_priority;
uint64_t min = g < d ? g : d;
uint64_t max = g > d ? g : d;
pair->priority = (min << 32) + (max << 1) + (g > d ? 1 : 0);
return 0;
}
int ice_candidates_count(const ice_description_t *description, ice_candidate_type_t type) {
int count = 0;
for (int i = 0; i < description->candidates_count; ++i) {
const ice_candidate_t *candidate = description->candidates + i;
if (candidate->type == type)
++count;
}
return count;
}
uint32_t ice_compute_priority(ice_candidate_type_t type, int family, int component, int index) {
// Compute candidate priority according to RFC 8445
// See https://www.rfc-editor.org/rfc/rfc8445.html#section-5.1.2.1
uint32_t p = 0;
switch (type) {
case ICE_CANDIDATE_TYPE_HOST:
p += ICE_CANDIDATE_PREF_HOST;
break;
case ICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
p += ICE_CANDIDATE_PREF_PEER_REFLEXIVE;
break;
case ICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
p += ICE_CANDIDATE_PREF_SERVER_REFLEXIVE;
break;
case ICE_CANDIDATE_TYPE_RELAYED:
p += ICE_CANDIDATE_PREF_RELAYED;
break;
default:
break;
}
p <<= 16;
switch (family) {
case AF_INET:
p += 32767;
break;
case AF_INET6:
p += 65535;
break;
default:
break;
}
p -= CLAMP(index, 0, 32767);
p <<= 8;
p += 256 - CLAMP(component, 1, 256);
return p;
}

103
thirdparty/libjuice/src/ice.h vendored Normal file
View File

@@ -0,0 +1,103 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_ICE_H
#define JUICE_ICE_H
#include "addr.h"
#include "juice.h"
#include "timestamp.h"
#include <stdbool.h>
#include <stdint.h>
#define ICE_MAX_CANDIDATES_COUNT 20 // ~ 500B * 20 = 10KB
typedef enum ice_candidate_type {
ICE_CANDIDATE_TYPE_UNKNOWN,
ICE_CANDIDATE_TYPE_HOST,
ICE_CANDIDATE_TYPE_SERVER_REFLEXIVE,
ICE_CANDIDATE_TYPE_PEER_REFLEXIVE,
ICE_CANDIDATE_TYPE_RELAYED,
} ice_candidate_type_t;
// RFC 8445: The RECOMMENDED values for type preferences are 126 for host candidates, 110 for
// peer-reflexive candidates, 100 for server-reflexive candidates, and 0 for relayed candidates.
#define ICE_CANDIDATE_PREF_HOST 126
#define ICE_CANDIDATE_PREF_PEER_REFLEXIVE 110
#define ICE_CANDIDATE_PREF_SERVER_REFLEXIVE 100
#define ICE_CANDIDATE_PREF_RELAYED 0
typedef struct ice_candidate {
ice_candidate_type_t type;
uint32_t priority;
int component;
char foundation[32 + 1]; // 1 to 32 characters
char transport[32 + 1];
char hostname[256 + 1];
char service[32 + 1];
addr_record_t resolved;
} ice_candidate_t;
typedef struct ice_description {
char ice_ufrag[256 + 1]; // 4 to 256 characters
char ice_pwd[256 + 1]; // 22 to 256 characters
ice_candidate_t candidates[ICE_MAX_CANDIDATES_COUNT];
int candidates_count;
bool finished;
} ice_description_t;
typedef enum ice_candidate_pair_state {
ICE_CANDIDATE_PAIR_STATE_PENDING,
ICE_CANDIDATE_PAIR_STATE_SUCCEEDED,
ICE_CANDIDATE_PAIR_STATE_FAILED,
ICE_CANDIDATE_PAIR_STATE_FROZEN,
} ice_candidate_pair_state_t;
typedef struct ice_candidate_pair {
ice_candidate_t *local;
ice_candidate_t *remote;
uint64_t priority;
ice_candidate_pair_state_t state;
bool nominated;
bool nomination_requested;
timestamp_t consent_expiry;
} ice_candidate_pair_t;
typedef enum ice_resolve_mode {
ICE_RESOLVE_MODE_SIMPLE,
ICE_RESOLVE_MODE_LOOKUP,
} ice_resolve_mode_t;
#define ICE_PARSE_ERROR -1
#define ICE_PARSE_IGNORED -2
#define ICE_PARSE_MISSING_UFRAG -3
#define ICE_PARSE_MISSING_PWD -4
int ice_parse_sdp(const char *sdp, ice_description_t *description);
int ice_parse_candidate_sdp(const char *line, ice_candidate_t *candidate);
int ice_create_local_description(ice_description_t *description);
int ice_create_local_candidate(ice_candidate_type_t type, int component, int index,
const addr_record_t *record, ice_candidate_t *candidate);
int ice_resolve_candidate(ice_candidate_t *candidate, ice_resolve_mode_t mode);
int ice_add_candidate(ice_candidate_t *candidate, ice_description_t *description);
void ice_sort_candidates(ice_description_t *description);
ice_candidate_t *ice_find_candidate_from_addr(ice_description_t *description,
const addr_record_t *record,
ice_candidate_type_t type);
int ice_generate_sdp(const ice_description_t *description, char *buffer, size_t size);
int ice_generate_candidate_sdp(const ice_candidate_t *candidate, char *buffer, size_t size);
int ice_create_candidate_pair(ice_candidate_t *local, ice_candidate_t *remote, bool is_controlling,
ice_candidate_pair_t *pair); // local or remote might be NULL
int ice_update_candidate_pair(ice_candidate_pair_t *pair, bool is_controlling);
int ice_candidates_count(const ice_description_t *description, ice_candidate_type_t type);
uint32_t ice_compute_priority(ice_candidate_type_t type, int family, int component, int index);
#endif

207
thirdparty/libjuice/src/juice.c vendored Normal file
View File

@@ -0,0 +1,207 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "juice.h"
#include "addr.h"
#include "agent.h"
#include "ice.h"
#ifndef NO_SERVER
#include "server.h"
#endif
#include <stdio.h>
JUICE_EXPORT juice_agent_t *juice_create(const juice_config_t *config) {
if (!config)
return NULL;
return agent_create(config);
}
JUICE_EXPORT void juice_destroy(juice_agent_t *agent) {
if (agent)
agent_destroy(agent);
}
JUICE_EXPORT int juice_gather_candidates(juice_agent_t *agent) {
if (!agent)
return JUICE_ERR_INVALID;
if (agent_gather_candidates(agent) < 0)
return JUICE_ERR_FAILED;
return JUICE_ERR_SUCCESS;
}
JUICE_EXPORT int juice_get_local_description(juice_agent_t *agent, char *buffer, size_t size) {
if (!agent || (!buffer && size))
return JUICE_ERR_INVALID;
if (agent_get_local_description(agent, buffer, size) < 0)
return JUICE_ERR_FAILED;
return JUICE_ERR_SUCCESS;
}
JUICE_EXPORT int juice_set_remote_description(juice_agent_t *agent, const char *sdp) {
if (!agent || !sdp)
return JUICE_ERR_INVALID;
if (agent_set_remote_description(agent, sdp) < 0)
return JUICE_ERR_FAILED;
return JUICE_ERR_SUCCESS;
}
JUICE_EXPORT int juice_add_remote_candidate(juice_agent_t *agent, const char *sdp) {
if (!agent || !sdp)
return JUICE_ERR_INVALID;
if (agent_add_remote_candidate(agent, sdp) < 0)
return JUICE_ERR_FAILED;
return JUICE_ERR_SUCCESS;
}
JUICE_EXPORT int juice_set_remote_gathering_done(juice_agent_t *agent) {
if (!agent)
return JUICE_ERR_INVALID;
if (agent_set_remote_gathering_done(agent) < 0)
return JUICE_ERR_FAILED;
return JUICE_ERR_SUCCESS;
}
JUICE_EXPORT int juice_send(juice_agent_t *agent, const char *data, size_t size) {
if (!agent || (!data && size))
return JUICE_ERR_INVALID;
if (agent_send(agent, data, size, 0) < 0)
return JUICE_ERR_FAILED;
return JUICE_ERR_SUCCESS;
}
JUICE_EXPORT int juice_send_diffserv(juice_agent_t *agent, const char *data, size_t size, int ds) {
if (!agent || (!data && size))
return JUICE_ERR_INVALID;
if (agent_send(agent, data, size, ds) < 0)
return JUICE_ERR_FAILED;
return JUICE_ERR_SUCCESS;
}
JUICE_EXPORT juice_state_t juice_get_state(juice_agent_t *agent) { return agent_get_state(agent); }
JUICE_EXPORT int juice_get_selected_candidates(juice_agent_t *agent, char *local, size_t local_size,
char *remote, size_t remote_size) {
if (!agent || (!local && local_size) || (!remote && remote_size))
return JUICE_ERR_INVALID;
ice_candidate_t local_cand, remote_cand;
if (agent_get_selected_candidate_pair(agent, &local_cand, &remote_cand))
return JUICE_ERR_NOT_AVAIL;
if (local_size && ice_generate_candidate_sdp(&local_cand, local, local_size) < 0)
return JUICE_ERR_FAILED;
if (remote_size && ice_generate_candidate_sdp(&remote_cand, remote, remote_size) < 0)
return JUICE_ERR_FAILED;
return JUICE_ERR_SUCCESS;
}
JUICE_EXPORT int juice_get_selected_addresses(juice_agent_t *agent, char *local, size_t local_size,
char *remote, size_t remote_size) {
if (!agent || (!local && local_size) || (!remote && remote_size))
return JUICE_ERR_INVALID;
ice_candidate_t local_cand, remote_cand;
if (agent_get_selected_candidate_pair(agent, &local_cand, &remote_cand))
return JUICE_ERR_NOT_AVAIL;
if (local_size && addr_record_to_string(&local_cand.resolved, local, local_size) < 0)
return JUICE_ERR_FAILED;
if (remote_size && addr_record_to_string(&remote_cand.resolved, remote, remote_size) < 0)
return JUICE_ERR_FAILED;
return JUICE_ERR_SUCCESS;
}
JUICE_EXPORT const char *juice_state_to_string(juice_state_t state) {
switch (state) {
case JUICE_STATE_DISCONNECTED:
return "disconnected";
case JUICE_STATE_GATHERING:
return "gathering";
case JUICE_STATE_CONNECTING:
return "connecting";
case JUICE_STATE_CONNECTED:
return "connected";
case JUICE_STATE_COMPLETED:
return "completed";
case JUICE_STATE_FAILED:
return "failed";
default:
return "unknown";
}
}
JUICE_EXPORT juice_server_t *juice_server_create(const juice_server_config_t *config) {
#ifndef NO_SERVER
if (!config)
return NULL;
return server_create(config);
#else
(void)config;
JLOG_FATAL("The library was compiled without server support");
return NULL;
#endif
}
JUICE_EXPORT void juice_server_destroy(juice_server_t *server) {
#ifndef NO_SERVER
if (server)
server_destroy(server);
#else
(void)server;
#endif
}
JUICE_EXPORT uint16_t juice_server_get_port(juice_server_t *server) {
#ifndef NO_SERVER
return server ? server_get_port(server) : 0;
#else
(void)server;
return 0;
#endif
}
JUICE_EXPORT int juice_server_add_credentials(juice_server_t *server,
const juice_server_credentials_t *credentials,
unsigned long lifetime_ms) {
#ifndef NO_SERVER
if (!server || !credentials)
return JUICE_ERR_INVALID;
if (server_add_credentials(server, credentials, (timediff_t)lifetime_ms) < 0)
return JUICE_ERR_FAILED;
return JUICE_ERR_SUCCESS;
#else
(void)server;
(void)credentials;
(void)lifetime_ms;
return JUICE_ERR_INVALID;
#endif
}

129
thirdparty/libjuice/src/log.c vendored Normal file
View File

@@ -0,0 +1,129 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "log.h"
#include "thread.h" // for mutexes and atomics
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#ifndef _WIN32
#include <unistd.h>
#endif
#define BUFFER_SIZE 4096
static const char *log_level_names[] = {"VERBOSE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"};
static const char *log_level_colors[] = {
"\x1B[90m", // grey
"\x1B[96m", // cyan
"\x1B[39m", // default foreground
"\x1B[93m", // yellow
"\x1B[91m", // red
"\x1B[97m\x1B[41m" // white on red
};
static mutex_t log_mutex = MUTEX_INITIALIZER;
static volatile juice_log_cb_t log_cb = NULL;
static atomic(juice_log_level_t) log_level = ATOMIC_VAR_INIT(JUICE_LOG_LEVEL_WARN);
static bool use_color(void) {
#ifdef _WIN32
return false;
#else
return isatty(fileno(stdout)) != 0;
#endif
}
static int get_localtime(const time_t *t, struct tm *buf) {
#ifdef _WIN32
// Windows does not have POSIX localtime_r...
return localtime_s(buf, t) == 0 ? 0 : -1;
#else // POSIX
return localtime_r(t, buf) != NULL ? 0 : -1;
#endif
}
JUICE_EXPORT void juice_set_log_level(juice_log_level_t level) { atomic_store(&log_level, level); }
JUICE_EXPORT void juice_set_log_handler(juice_log_cb_t cb) {
mutex_lock(&log_mutex);
log_cb = cb;
mutex_unlock(&log_mutex);
}
bool juice_log_is_enabled(juice_log_level_t level) {
return level != JUICE_LOG_LEVEL_NONE && level >= atomic_load(&log_level);
}
void juice_log_write(juice_log_level_t level, const char *file, int line, const char *fmt, ...) {
if (!juice_log_is_enabled(level))
return;
mutex_lock(&log_mutex);
#if !RELEASE
const char *filename = file + strlen(file);
while (filename != file && *filename != '/' && *filename != '\\')
--filename;
if (filename != file)
++filename;
#else
(void)file;
(void)line;
#endif
if (log_cb) {
char message[BUFFER_SIZE];
int len = 0;
#if !RELEASE
len = snprintf(message, BUFFER_SIZE, "%s:%d: ", filename, line);
if (len < 0)
return;
#endif
if (len < BUFFER_SIZE) {
va_list args;
va_start(args, fmt);
vsnprintf(message + len, BUFFER_SIZE - len, fmt, args);
va_end(args);
}
log_cb(level, message);
} else {
time_t t = time(NULL);
struct tm lt;
char buffer[16];
if (get_localtime(&t, &lt) != 0 || strftime(buffer, 16, "%H:%M:%S", &lt) == 0)
buffer[0] = '\0';
if (use_color())
fprintf(stdout, "%s", log_level_colors[level]);
fprintf(stdout, "%s %-7s ", buffer, log_level_names[level]);
#if !RELEASE
fprintf(stdout, "%s:%d: ", filename, line);
#endif
va_list args;
va_start(args, fmt);
vfprintf(stdout, fmt, args);
va_end(args);
if (use_color())
fprintf(stdout, "%s", "\x1B[0m\x1B[0K");
fprintf(stdout, "\n");
fflush(stdout);
}
mutex_unlock(&log_mutex);
}

33
thirdparty/libjuice/src/log.h vendored Normal file
View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_LOG_H
#define JUICE_LOG_H
#include "juice.h"
#include <stdarg.h>
bool juice_log_is_enabled(juice_log_level_t level);
void juice_log_write(juice_log_level_t level, const char *file, int line, const char *fmt, ...);
#define JLOG_VERBOSE(...) juice_log_write(JUICE_LOG_LEVEL_VERBOSE, __FILE__, __LINE__, __VA_ARGS__)
#define JLOG_DEBUG(...) juice_log_write(JUICE_LOG_LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
#define JLOG_INFO(...) juice_log_write(JUICE_LOG_LEVEL_INFO, __FILE__, __LINE__, __VA_ARGS__)
#define JLOG_WARN(...) juice_log_write(JUICE_LOG_LEVEL_WARN, __FILE__, __LINE__, __VA_ARGS__)
#define JLOG_ERROR(...) juice_log_write(JUICE_LOG_LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__)
#define JLOG_FATAL(...) juice_log_write(JUICE_LOG_LEVEL_FATAL, __FILE__, __LINE__, __VA_ARGS__)
#define JLOG_VERBOSE_ENABLED juice_log_is_enabled(JUICE_LOG_LEVEL_VERBOSE)
#define JLOG_DEBUG_ENABLED juice_log_is_enabled(JUICE_LOG_LEVEL_DEBUG)
#define JLOG_INFO_ENABLED juice_log_is_enabled(JUICE_LOG_LEVEL_INFO)
#define JLOG_WARN_ENABLED juice_log_is_enabled(JUICE_LOG_LEVEL_WARN)
#define JLOG_ERROR_ENABLED juice_log_is_enabled(JUICE_LOG_LEVEL_ERROR)
#define JLOG_FATAL_ENABLED juice_log_is_enabled(JUICE_LOG_LEVEL_FATAL)
#endif // JUICE_LOG_H

741
thirdparty/libjuice/src/picohash.h vendored Normal file
View File

@@ -0,0 +1,741 @@
/**
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef _picohash_h_
#define _picohash_h_
#include <assert.h>
#include <inttypes.h>
#include <string.h>
#ifdef _WIN32
/* assume Windows is little endian */
#elif defined __BIG_ENDIAN__
#define _PICOHASH_BIG_ENDIAN
#elif defined __LITTLE_ENDIAN__
/* override */
#elif defined __BYTE_ORDER
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define _PICOHASH_BIG_ENDIAN
#endif
#else // ! defined __LITTLE_ENDIAN__
#include <endian.h> // machine/endian.h
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define _PICOHASH_BIG_ENDIAN
#endif
#endif
#define PICOHASH_MD5_BLOCK_LENGTH 64
#define PICOHASH_MD5_DIGEST_LENGTH 16
typedef struct {
uint_fast32_t lo, hi;
uint_fast32_t a, b, c, d;
unsigned char buffer[64];
uint_fast32_t block[PICOHASH_MD5_DIGEST_LENGTH];
} _picohash_md5_ctx_t;
static void _picohash_md5_init(_picohash_md5_ctx_t *ctx);
static void _picohash_md5_update(_picohash_md5_ctx_t *ctx, const void *data, size_t size);
static void _picohash_md5_final(_picohash_md5_ctx_t *ctx, void *digest);
#define PICOHASH_SHA1_BLOCK_LENGTH 64
#define PICOHASH_SHA1_DIGEST_LENGTH 20
typedef struct {
uint32_t buffer[PICOHASH_SHA1_BLOCK_LENGTH / 4];
uint32_t state[PICOHASH_SHA1_DIGEST_LENGTH / 4];
uint64_t byteCount;
uint8_t bufferOffset;
} _picohash_sha1_ctx_t;
static void _picohash_sha1_init(_picohash_sha1_ctx_t *ctx);
static void _picohash_sha1_update(_picohash_sha1_ctx_t *ctx, const void *input, size_t len);
static void _picohash_sha1_final(_picohash_sha1_ctx_t *ctx, void *digest);
#define PICOHASH_SHA256_BLOCK_LENGTH 64
#define PICOHASH_SHA256_DIGEST_LENGTH 32
#define PICOHASH_SHA224_BLOCK_LENGTH PICOHASH_SHA256_BLOCK_LENGTH
#define PICOHASH_SHA224_DIGEST_LENGTH 28
typedef struct {
uint64_t length;
uint32_t state[PICOHASH_SHA256_DIGEST_LENGTH / 4];
uint32_t curlen;
unsigned char buf[PICOHASH_SHA256_BLOCK_LENGTH];
} _picohash_sha256_ctx_t;
static void _picohash_sha256_init(_picohash_sha256_ctx_t *ctx);
static void _picohash_sha256_update(_picohash_sha256_ctx_t *ctx, const void *data, size_t len);
static void _picohash_sha256_final(_picohash_sha256_ctx_t *ctx, void *digest);
static void _picohash_sha224_init(_picohash_sha256_ctx_t *ctx);
static void _picohash_sha224_final(_picohash_sha256_ctx_t *ctx, void *digest);
#define PICOHASH_MAX_BLOCK_LENGTH 64
#define PICOHASH_MAX_DIGEST_LENGTH 32
typedef struct {
union {
_picohash_md5_ctx_t _md5;
_picohash_sha1_ctx_t _sha1;
_picohash_sha256_ctx_t _sha256;
};
size_t block_length;
size_t digest_length;
void (*_reset)(void *ctx);
void (*_update)(void *ctx, const void *input, size_t len);
void (*_final)(void *ctx, void *digest);
struct {
unsigned char key[PICOHASH_MAX_BLOCK_LENGTH];
void (*hash_reset)(void *ctx);
void (*hash_final)(void *ctx, void *digest);
} _hmac;
} picohash_ctx_t;
static void picohash_init_md5(picohash_ctx_t *ctx);
static void picohash_init_sha1(picohash_ctx_t *ctx);
static void picohash_init_sha224(picohash_ctx_t *ctx);
static void picohash_init_sha256(picohash_ctx_t *ctx);
static void picohash_update(picohash_ctx_t *ctx, const void *input, size_t len);
static void picohash_final(picohash_ctx_t *ctx, void *digest);
static void picohash_reset(picohash_ctx_t *ctx);
static void picohash_init_hmac(picohash_ctx_t *ctx, void (*initf)(picohash_ctx_t *), const void *key, size_t key_len);
/* following are private definitions */
/*
* The basic MD5 functions.
*
* F is optimized compared to its RFC 1321 definition just like in Colin
* Plumb's implementation.
*/
#define _PICOHASH_MD5_F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
#define _PICOHASH_MD5_G(x, y, z) ((y) ^ ((z) & ((x) ^ (y))))
#define _PICOHASH_MD5_H(x, y, z) ((x) ^ (y) ^ (z))
#define _PICOHASH_MD5_I(x, y, z) ((y) ^ ((x) | ~(z)))
/*
* The MD5 transformation for all four rounds.
*/
#define _PICOHASH_MD5_STEP(f, a, b, c, d, x, t, s) \
(a) += f((b), (c), (d)) + (x) + (t); \
(a) = (((a) << (s)) | (((a)&0xffffffff) >> (32 - (s)))); \
(a) += (b);
/*
* SET reads 4 input bytes in little-endian byte order and stores them
* in a properly aligned word in host byte order.
*
* Paul-Louis Ageneau: Removed optimization for little-endian architectures
* as it resulted in incorrect behavior when compiling with gcc optimizations.
*/
#define _PICOHASH_MD5_SET(n) \
(ctx->block[(n)] = (uint_fast32_t)ptr[(n)*4] | ((uint_fast32_t)ptr[(n)*4 + 1] << 8) | ((uint_fast32_t)ptr[(n)*4 + 2] << 16) | \
((uint_fast32_t)ptr[(n)*4 + 3] << 24))
#define _PICOHASH_MD5_GET(n) (ctx->block[(n)])
/*
* This processes one or more 64-byte data blocks, but does NOT update
* the bit counters. There're no alignment requirements.
*/
static const void *_picohash_md5_body(_picohash_md5_ctx_t *ctx, const void *data, size_t size)
{
const unsigned char *ptr;
uint_fast32_t a, b, c, d;
uint_fast32_t saved_a, saved_b, saved_c, saved_d;
ptr = data;
a = ctx->a;
b = ctx->b;
c = ctx->c;
d = ctx->d;
do {
saved_a = a;
saved_b = b;
saved_c = c;
saved_d = d;
/* Round 1 */
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, a, b, c, d, _PICOHASH_MD5_SET(0), 0xd76aa478, 7)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, d, a, b, c, _PICOHASH_MD5_SET(1), 0xe8c7b756, 12)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, c, d, a, b, _PICOHASH_MD5_SET(2), 0x242070db, 17)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, b, c, d, a, _PICOHASH_MD5_SET(3), 0xc1bdceee, 22)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, a, b, c, d, _PICOHASH_MD5_SET(4), 0xf57c0faf, 7)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, d, a, b, c, _PICOHASH_MD5_SET(5), 0x4787c62a, 12)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, c, d, a, b, _PICOHASH_MD5_SET(6), 0xa8304613, 17)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, b, c, d, a, _PICOHASH_MD5_SET(7), 0xfd469501, 22)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, a, b, c, d, _PICOHASH_MD5_SET(8), 0x698098d8, 7)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, d, a, b, c, _PICOHASH_MD5_SET(9), 0x8b44f7af, 12)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, c, d, a, b, _PICOHASH_MD5_SET(10), 0xffff5bb1, 17)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, b, c, d, a, _PICOHASH_MD5_SET(11), 0x895cd7be, 22)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, a, b, c, d, _PICOHASH_MD5_SET(12), 0x6b901122, 7)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, d, a, b, c, _PICOHASH_MD5_SET(13), 0xfd987193, 12)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, c, d, a, b, _PICOHASH_MD5_SET(14), 0xa679438e, 17)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, b, c, d, a, _PICOHASH_MD5_SET(15), 0x49b40821, 22)
/* Round 2 */
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, a, b, c, d, _PICOHASH_MD5_GET(1), 0xf61e2562, 5)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, d, a, b, c, _PICOHASH_MD5_GET(6), 0xc040b340, 9)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, c, d, a, b, _PICOHASH_MD5_GET(11), 0x265e5a51, 14)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, b, c, d, a, _PICOHASH_MD5_GET(0), 0xe9b6c7aa, 20)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, a, b, c, d, _PICOHASH_MD5_GET(5), 0xd62f105d, 5)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, d, a, b, c, _PICOHASH_MD5_GET(10), 0x02441453, 9)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, c, d, a, b, _PICOHASH_MD5_GET(15), 0xd8a1e681, 14)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, b, c, d, a, _PICOHASH_MD5_GET(4), 0xe7d3fbc8, 20)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, a, b, c, d, _PICOHASH_MD5_GET(9), 0x21e1cde6, 5)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, d, a, b, c, _PICOHASH_MD5_GET(14), 0xc33707d6, 9)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, c, d, a, b, _PICOHASH_MD5_GET(3), 0xf4d50d87, 14)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, b, c, d, a, _PICOHASH_MD5_GET(8), 0x455a14ed, 20)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, a, b, c, d, _PICOHASH_MD5_GET(13), 0xa9e3e905, 5)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, d, a, b, c, _PICOHASH_MD5_GET(2), 0xfcefa3f8, 9)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, c, d, a, b, _PICOHASH_MD5_GET(7), 0x676f02d9, 14)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, b, c, d, a, _PICOHASH_MD5_GET(12), 0x8d2a4c8a, 20)
/* Round 3 */
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, a, b, c, d, _PICOHASH_MD5_GET(5), 0xfffa3942, 4)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, d, a, b, c, _PICOHASH_MD5_GET(8), 0x8771f681, 11)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, c, d, a, b, _PICOHASH_MD5_GET(11), 0x6d9d6122, 16)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, b, c, d, a, _PICOHASH_MD5_GET(14), 0xfde5380c, 23)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, a, b, c, d, _PICOHASH_MD5_GET(1), 0xa4beea44, 4)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, d, a, b, c, _PICOHASH_MD5_GET(4), 0x4bdecfa9, 11)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, c, d, a, b, _PICOHASH_MD5_GET(7), 0xf6bb4b60, 16)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, b, c, d, a, _PICOHASH_MD5_GET(10), 0xbebfbc70, 23)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, a, b, c, d, _PICOHASH_MD5_GET(13), 0x289b7ec6, 4)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, d, a, b, c, _PICOHASH_MD5_GET(0), 0xeaa127fa, 11)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, c, d, a, b, _PICOHASH_MD5_GET(3), 0xd4ef3085, 16)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, b, c, d, a, _PICOHASH_MD5_GET(6), 0x04881d05, 23)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, a, b, c, d, _PICOHASH_MD5_GET(9), 0xd9d4d039, 4)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, d, a, b, c, _PICOHASH_MD5_GET(12), 0xe6db99e5, 11)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, c, d, a, b, _PICOHASH_MD5_GET(15), 0x1fa27cf8, 16)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, b, c, d, a, _PICOHASH_MD5_GET(2), 0xc4ac5665, 23)
/* Round 4 */
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, a, b, c, d, _PICOHASH_MD5_GET(0), 0xf4292244, 6)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, d, a, b, c, _PICOHASH_MD5_GET(7), 0x432aff97, 10)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, c, d, a, b, _PICOHASH_MD5_GET(14), 0xab9423a7, 15)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, b, c, d, a, _PICOHASH_MD5_GET(5), 0xfc93a039, 21)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, a, b, c, d, _PICOHASH_MD5_GET(12), 0x655b59c3, 6)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, d, a, b, c, _PICOHASH_MD5_GET(3), 0x8f0ccc92, 10)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, c, d, a, b, _PICOHASH_MD5_GET(10), 0xffeff47d, 15)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, b, c, d, a, _PICOHASH_MD5_GET(1), 0x85845dd1, 21)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, a, b, c, d, _PICOHASH_MD5_GET(8), 0x6fa87e4f, 6)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, d, a, b, c, _PICOHASH_MD5_GET(15), 0xfe2ce6e0, 10)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, c, d, a, b, _PICOHASH_MD5_GET(6), 0xa3014314, 15)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, b, c, d, a, _PICOHASH_MD5_GET(13), 0x4e0811a1, 21)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, a, b, c, d, _PICOHASH_MD5_GET(4), 0xf7537e82, 6)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, d, a, b, c, _PICOHASH_MD5_GET(11), 0xbd3af235, 10)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, c, d, a, b, _PICOHASH_MD5_GET(2), 0x2ad7d2bb, 15)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, b, c, d, a, _PICOHASH_MD5_GET(9), 0xeb86d391, 21)
a += saved_a;
b += saved_b;
c += saved_c;
d += saved_d;
ptr += 64;
} while (size -= 64);
ctx->a = a;
ctx->b = b;
ctx->c = c;
ctx->d = d;
return ptr;
}
inline void _picohash_md5_init(_picohash_md5_ctx_t *ctx)
{
ctx->a = 0x67452301;
ctx->b = 0xefcdab89;
ctx->c = 0x98badcfe;
ctx->d = 0x10325476;
ctx->lo = 0;
ctx->hi = 0;
}
inline void _picohash_md5_update(_picohash_md5_ctx_t *ctx, const void *data, size_t size)
{
uint_fast32_t saved_lo;
unsigned long used, free;
saved_lo = ctx->lo;
if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo)
ctx->hi++;
ctx->hi += (uint_fast32_t)(size >> 29);
used = saved_lo & 0x3f;
if (used) {
free = 64 - used;
if (size < free) {
memcpy(&ctx->buffer[used], data, size);
return;
}
memcpy(&ctx->buffer[used], data, free);
data = (const unsigned char *)data + free;
size -= free;
_picohash_md5_body(ctx, ctx->buffer, 64);
}
if (size >= 64) {
data = _picohash_md5_body(ctx, data, size & ~(unsigned long)0x3f);
size &= 0x3f;
}
memcpy(ctx->buffer, data, size);
}
inline void _picohash_md5_final(_picohash_md5_ctx_t *ctx, void *_digest)
{
unsigned char *digest = _digest;
unsigned long used, free;
used = ctx->lo & 0x3f;
ctx->buffer[used++] = 0x80;
free = 64 - used;
if (free < 8) {
memset(&ctx->buffer[used], 0, free);
_picohash_md5_body(ctx, ctx->buffer, 64);
used = 0;
free = 64;
}
memset(&ctx->buffer[used], 0, free - 8);
ctx->lo <<= 3;
ctx->buffer[56] = ctx->lo;
ctx->buffer[57] = ctx->lo >> 8;
ctx->buffer[58] = ctx->lo >> 16;
ctx->buffer[59] = ctx->lo >> 24;
ctx->buffer[60] = ctx->hi;
ctx->buffer[61] = ctx->hi >> 8;
ctx->buffer[62] = ctx->hi >> 16;
ctx->buffer[63] = ctx->hi >> 24;
_picohash_md5_body(ctx, ctx->buffer, 64);
digest[0] = ctx->a;
digest[1] = ctx->a >> 8;
digest[2] = ctx->a >> 16;
digest[3] = ctx->a >> 24;
digest[4] = ctx->b;
digest[5] = ctx->b >> 8;
digest[6] = ctx->b >> 16;
digest[7] = ctx->b >> 24;
digest[8] = ctx->c;
digest[9] = ctx->c >> 8;
digest[10] = ctx->c >> 16;
digest[11] = ctx->c >> 24;
digest[12] = ctx->d;
digest[13] = ctx->d >> 8;
digest[14] = ctx->d >> 16;
digest[15] = ctx->d >> 24;
memset(ctx, 0, sizeof(*ctx));
}
#define _PICOHASH_SHA1_K0 0x5a827999
#define _PICOHASH_SHA1_K20 0x6ed9eba1
#define _PICOHASH_SHA1_K40 0x8f1bbcdc
#define _PICOHASH_SHA1_K60 0xca62c1d6
static inline uint32_t _picohash_sha1_rol32(uint32_t number, uint8_t bits)
{
return ((number << bits) | (number >> (32 - bits)));
}
static inline void _picohash_sha1_hash_block(_picohash_sha1_ctx_t *s)
{
uint8_t i;
uint32_t a, b, c, d, e, t;
a = s->state[0];
b = s->state[1];
c = s->state[2];
d = s->state[3];
e = s->state[4];
for (i = 0; i < 80; i++) {
if (i >= 16) {
t = s->buffer[(i + 13) & 15] ^ s->buffer[(i + 8) & 15] ^ s->buffer[(i + 2) & 15] ^ s->buffer[i & 15];
s->buffer[i & 15] = _picohash_sha1_rol32(t, 1);
}
if (i < 20) {
t = (d ^ (b & (c ^ d))) + _PICOHASH_SHA1_K0;
} else if (i < 40) {
t = (b ^ c ^ d) + _PICOHASH_SHA1_K20;
} else if (i < 60) {
t = ((b & c) | (d & (b | c))) + _PICOHASH_SHA1_K40;
} else {
t = (b ^ c ^ d) + _PICOHASH_SHA1_K60;
}
t += _picohash_sha1_rol32(a, 5) + e + s->buffer[i & 15];
e = d;
d = c;
c = _picohash_sha1_rol32(b, 30);
b = a;
a = t;
}
s->state[0] += a;
s->state[1] += b;
s->state[2] += c;
s->state[3] += d;
s->state[4] += e;
}
static inline void _picohash_sha1_add_uncounted(_picohash_sha1_ctx_t *s, uint8_t data)
{
uint8_t *const b = (uint8_t *)s->buffer;
#ifdef _PICOHASH_BIG_ENDIAN
b[s->bufferOffset] = data;
#else
b[s->bufferOffset ^ 3] = data;
#endif
s->bufferOffset++;
if (s->bufferOffset == PICOHASH_SHA1_BLOCK_LENGTH) {
_picohash_sha1_hash_block(s);
s->bufferOffset = 0;
}
}
inline void _picohash_sha1_init(_picohash_sha1_ctx_t *s)
{
s->state[0] = 0x67452301;
s->state[1] = 0xefcdab89;
s->state[2] = 0x98badcfe;
s->state[3] = 0x10325476;
s->state[4] = 0xc3d2e1f0;
s->byteCount = 0;
s->bufferOffset = 0;
}
inline void _picohash_sha1_update(_picohash_sha1_ctx_t *s, const void *_data, size_t len)
{
const uint8_t *data = _data;
for (; len != 0; --len) {
++s->byteCount;
_picohash_sha1_add_uncounted(s, *data++);
}
}
inline void _picohash_sha1_final(_picohash_sha1_ctx_t *s, void *digest)
{
// Pad with 0x80 followed by 0x00 until the end of the block
_picohash_sha1_add_uncounted(s, 0x80);
while (s->bufferOffset != 56)
_picohash_sha1_add_uncounted(s, 0x00);
// Append length in the last 8 bytes
_picohash_sha1_add_uncounted(s, (uint8_t)(s->byteCount >> 53)); // Shifting to multiply by 8
_picohash_sha1_add_uncounted(s, (uint8_t)(s->byteCount >> 45)); // as SHA-1 supports bitstreams
_picohash_sha1_add_uncounted(s, (uint8_t)(s->byteCount >> 37)); // as well as byte.
_picohash_sha1_add_uncounted(s, (uint8_t)(s->byteCount >> 29));
_picohash_sha1_add_uncounted(s, (uint8_t)(s->byteCount >> 21));
_picohash_sha1_add_uncounted(s, (uint8_t)(s->byteCount >> 13));
_picohash_sha1_add_uncounted(s, (uint8_t)(s->byteCount >> 5));
_picohash_sha1_add_uncounted(s, (uint8_t)(s->byteCount << 3));
#ifndef SHA_BIG_ENDIAN
{ // Swap byte order back
int i;
for (i = 0; i < 5; i++) {
s->state[i] = (((s->state[i]) << 24) & 0xff000000) | (((s->state[i]) << 8) & 0x00ff0000) |
(((s->state[i]) >> 8) & 0x0000ff00) | (((s->state[i]) >> 24) & 0x000000ff);
}
}
#endif
memcpy(digest, s->state, sizeof(s->state));
}
#define _picohash_sha256_ch(x, y, z) (z ^ (x & (y ^ z)))
#define _picohash_sha256_maj(x, y, z) (((x | y) & z) | (x & y))
#define _picohash_sha256_s(x, y) \
(((((uint32_t)(x)&0xFFFFFFFFUL) >> (uint32_t)((y)&31)) | ((uint32_t)(x) << (uint32_t)(32 - ((y)&31)))) & 0xFFFFFFFFUL)
#define _picohash_sha256_r(x, n) (((x)&0xFFFFFFFFUL) >> (n))
#define _picohash_sha256_sigma0(x) (_picohash_sha256_s(x, 2) ^ _picohash_sha256_s(x, 13) ^ _picohash_sha256_s(x, 22))
#define _picohash_sha256_sigma1(x) (_picohash_sha256_s(x, 6) ^ _picohash_sha256_s(x, 11) ^ _picohash_sha256_s(x, 25))
#define _picohash_sha256_gamma0(x) (_picohash_sha256_s(x, 7) ^ _picohash_sha256_s(x, 18) ^ _picohash_sha256_r(x, 3))
#define _picohash_sha256_gamma1(x) (_picohash_sha256_s(x, 17) ^ _picohash_sha256_s(x, 19) ^ _picohash_sha256_r(x, 10))
#define _picohash_sha256_rnd(a, b, c, d, e, f, g, h, i) \
t0 = h + _picohash_sha256_sigma1(e) + _picohash_sha256_ch(e, f, g) + K[i] + W[i]; \
t1 = _picohash_sha256_sigma0(a) + _picohash_sha256_maj(a, b, c); \
d += t0; \
h = t0 + t1;
static inline void _picohash_sha256_compress(_picohash_sha256_ctx_t *ctx, unsigned char *buf)
{
static const uint32_t K[64] = {
0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, 0x3956c25bUL, 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL,
0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL, 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL,
0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL,
0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, 0xc6e00bf3UL, 0xd5a79147UL, 0x06ca6351UL, 0x14292967UL,
0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL, 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL,
0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL,
0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL, 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL,
0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL};
uint32_t S[8], W[64], t, t0, t1;
int i;
/* copy state into S */
for (i = 0; i < 8; i++)
S[i] = ctx->state[i];
/* copy the state into 512-bits into W[0..15] */
for (i = 0; i < 16; i++)
W[i] =
(uint32_t)buf[4 * i] << 24 | (uint32_t)buf[4 * i + 1] << 16 | (uint32_t)buf[4 * i + 2] << 8 | (uint32_t)buf[4 * i + 3];
/* fill W[16..63] */
for (i = 16; i < 64; i++)
W[i] = _picohash_sha256_gamma1(W[i - 2]) + W[i - 7] + _picohash_sha256_gamma0(W[i - 15]) + W[i - 16];
/* Compress */
for (i = 0; i < 64; ++i) {
_picohash_sha256_rnd(S[0], S[1], S[2], S[3], S[4], S[5], S[6], S[7], i);
t = S[7];
S[7] = S[6];
S[6] = S[5];
S[5] = S[4];
S[4] = S[3];
S[3] = S[2];
S[2] = S[1];
S[1] = S[0];
S[0] = t;
}
/* feedback */
for (i = 0; i < 8; i++)
ctx->state[i] = ctx->state[i] + S[i];
}
static inline void _picohash_sha256_do_final(_picohash_sha256_ctx_t *ctx, void *digest, size_t len)
{
unsigned char *out = digest;
size_t i;
/* increase the length of the message */
ctx->length += ctx->curlen * 8;
/* append the '1' bit */
ctx->buf[ctx->curlen++] = (unsigned char)0x80;
/* if the length is currently above 56 bytes we append zeros
* then compress. Then we can fall back to padding zeros and length
* encoding like normal.
*/
if (ctx->curlen > 56) {
while (ctx->curlen < 64) {
ctx->buf[ctx->curlen++] = (unsigned char)0;
}
_picohash_sha256_compress(ctx, ctx->buf);
ctx->curlen = 0;
}
/* pad upto 56 bytes of zeroes */
while (ctx->curlen < 56) {
ctx->buf[ctx->curlen++] = (unsigned char)0;
}
/* store length */
for (i = 0; i != 8; ++i)
ctx->buf[56 + i] = (unsigned char)(ctx->length >> (56 - 8 * i));
_picohash_sha256_compress(ctx, ctx->buf);
/* copy output */
for (i = 0; i != len / 4; ++i) {
out[i * 4] = ctx->state[i] >> 24;
out[i * 4 + 1] = ctx->state[i] >> 16;
out[i * 4 + 2] = ctx->state[i] >> 8;
out[i * 4 + 3] = ctx->state[i];
}
}
inline void _picohash_sha256_init(_picohash_sha256_ctx_t *ctx)
{
ctx->curlen = 0;
ctx->length = 0;
ctx->state[0] = 0x6A09E667UL;
ctx->state[1] = 0xBB67AE85UL;
ctx->state[2] = 0x3C6EF372UL;
ctx->state[3] = 0xA54FF53AUL;
ctx->state[4] = 0x510E527FUL;
ctx->state[5] = 0x9B05688CUL;
ctx->state[6] = 0x1F83D9ABUL;
ctx->state[7] = 0x5BE0CD19UL;
}
inline void _picohash_sha256_update(_picohash_sha256_ctx_t *ctx, const void *data, size_t len)
{
const unsigned char *in = data;
size_t n;
while (len > 0) {
if (ctx->curlen == 0 && len >= PICOHASH_SHA256_BLOCK_LENGTH) {
_picohash_sha256_compress(ctx, (unsigned char *)in);
ctx->length += PICOHASH_SHA256_BLOCK_LENGTH * 8;
in += PICOHASH_SHA256_BLOCK_LENGTH;
len -= PICOHASH_SHA256_BLOCK_LENGTH;
} else {
n = PICOHASH_SHA256_BLOCK_LENGTH - ctx->curlen;
if (n > len)
n = len;
memcpy(ctx->buf + ctx->curlen, in, n);
ctx->curlen += (uint32_t)n;
in += n;
len -= n;
if (ctx->curlen == 64) {
_picohash_sha256_compress(ctx, ctx->buf);
ctx->length += 8 * PICOHASH_SHA256_BLOCK_LENGTH;
ctx->curlen = 0;
}
}
}
}
inline void _picohash_sha256_final(_picohash_sha256_ctx_t *ctx, void *digest)
{
_picohash_sha256_do_final(ctx, digest, PICOHASH_SHA256_DIGEST_LENGTH);
}
inline void _picohash_sha224_init(_picohash_sha256_ctx_t *ctx)
{
ctx->curlen = 0;
ctx->length = 0;
ctx->state[0] = 0xc1059ed8UL;
ctx->state[1] = 0x367cd507UL;
ctx->state[2] = 0x3070dd17UL;
ctx->state[3] = 0xf70e5939UL;
ctx->state[4] = 0xffc00b31UL;
ctx->state[5] = 0x68581511UL;
ctx->state[6] = 0x64f98fa7UL;
ctx->state[7] = 0xbefa4fa4UL;
}
inline void _picohash_sha224_final(_picohash_sha256_ctx_t *ctx, void *digest)
{
_picohash_sha256_do_final(ctx, digest, PICOHASH_SHA224_DIGEST_LENGTH);
}
inline void picohash_init_md5(picohash_ctx_t *ctx)
{
ctx->block_length = PICOHASH_MD5_BLOCK_LENGTH;
ctx->digest_length = PICOHASH_MD5_DIGEST_LENGTH;
ctx->_reset = (void *)_picohash_md5_init;
ctx->_update = (void *)_picohash_md5_update;
ctx->_final = (void *)_picohash_md5_final;
_picohash_md5_init(&ctx->_md5);
}
inline void picohash_init_sha1(picohash_ctx_t *ctx)
{
ctx->block_length = PICOHASH_SHA1_BLOCK_LENGTH;
ctx->digest_length = PICOHASH_SHA1_DIGEST_LENGTH;
ctx->_reset = (void *)_picohash_sha1_init;
ctx->_update = (void *)_picohash_sha1_update;
ctx->_final = (void *)_picohash_sha1_final;
_picohash_sha1_init(&ctx->_sha1);
}
inline void picohash_init_sha224(picohash_ctx_t *ctx)
{
ctx->block_length = PICOHASH_SHA224_BLOCK_LENGTH;
ctx->digest_length = PICOHASH_SHA224_DIGEST_LENGTH;
ctx->_reset = (void *)_picohash_sha224_init;
ctx->_update = (void *)_picohash_sha256_update;
ctx->_final = (void *)_picohash_sha224_final;
_picohash_sha224_init(&ctx->_sha256);
}
inline void picohash_init_sha256(picohash_ctx_t *ctx)
{
ctx->block_length = PICOHASH_SHA256_BLOCK_LENGTH;
ctx->digest_length = PICOHASH_SHA256_DIGEST_LENGTH;
ctx->_reset = (void *)_picohash_sha256_init;
ctx->_update = (void *)_picohash_sha256_update;
ctx->_final = (void *)_picohash_sha256_final;
_picohash_sha256_init(&ctx->_sha256);
}
inline void picohash_update(picohash_ctx_t *ctx, const void *input, size_t len)
{
ctx->_update(ctx, input, len);
}
inline void picohash_final(picohash_ctx_t *ctx, void *digest)
{
ctx->_final(ctx, digest);
}
inline void picohash_reset(picohash_ctx_t *ctx)
{
ctx->_reset(ctx);
}
static inline void _picohash_hmac_apply_key(picohash_ctx_t *ctx, unsigned char delta)
{
size_t i;
for (i = 0; i != ctx->block_length; ++i)
ctx->_hmac.key[i] ^= delta;
picohash_update(ctx, ctx->_hmac.key, ctx->block_length);
for (i = 0; i != ctx->block_length; ++i)
ctx->_hmac.key[i] ^= delta;
}
static void _picohash_hmac_final(picohash_ctx_t *ctx, void *digest)
{
unsigned char inner_digest[PICOHASH_MAX_DIGEST_LENGTH];
ctx->_hmac.hash_final(ctx, inner_digest);
ctx->_hmac.hash_reset(ctx);
_picohash_hmac_apply_key(ctx, 0x5c);
picohash_update(ctx, inner_digest, ctx->digest_length);
memset(inner_digest, 0, ctx->digest_length);
ctx->_hmac.hash_final(ctx, digest);
}
static inline void _picohash_hmac_reset(picohash_ctx_t *ctx)
{
ctx->_hmac.hash_reset(ctx);
_picohash_hmac_apply_key(ctx, 0x36);
}
inline void picohash_init_hmac(picohash_ctx_t *ctx, void (*initf)(picohash_ctx_t *), const void *key, size_t key_len)
{
initf(ctx);
memset(ctx->_hmac.key, 0, ctx->block_length);
if (key_len > ctx->block_length) {
/* hash the key if it is too long */
picohash_update(ctx, key, key_len);
picohash_final(ctx, ctx->_hmac.key);
ctx->_hmac.hash_reset(ctx);
} else {
memcpy(ctx->_hmac.key, key, key_len);
}
/* replace reset and final function */
ctx->_hmac.hash_reset = ctx->_reset;
ctx->_hmac.hash_final = ctx->_final;
ctx->_reset = (void *)_picohash_hmac_reset;
ctx->_final = (void *)_picohash_hmac_final;
/* start calculating the inner hash */
_picohash_hmac_apply_key(ctx, 0x36);
}
#endif

126
thirdparty/libjuice/src/random.c vendored Normal file
View File

@@ -0,0 +1,126 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "random.h"
#include "log.h"
#include "thread.h" // for mutexes
#include <math.h>
#include <stdbool.h>
#include <time.h>
// getrandom() is not available in Android NDK API < 28 and needs glibc >= 2.25
#if defined(__linux__) && !defined(__ANDROID__) && (!defined(__GLIBC__) || __GLIBC__ > 2 || __GLIBC_MINOR__ >= 25)
#include <errno.h>
#include <sys/random.h>
static int random_bytes(void *buf, size_t size) {
ssize_t ret = getrandom(buf, size, 0);
if (ret < 0) {
JLOG_WARN("getrandom failed, errno=%d", errno);
return -1;
}
if ((size_t)ret < size) {
JLOG_WARN("getrandom returned too few bytes, size=%zu, returned=%zu", size, (size_t)ret);
return -1;
}
return 0;
}
#elif defined(_WIN32)
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0601 // Windows 7
#endif
#include <windows.h>
//
#include <bcrypt.h>
static int random_bytes(void *buf, size_t size) {
// Requires Windows 7 or later
NTSTATUS status = BCryptGenRandom(NULL, (PUCHAR)buf, (ULONG)size, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
return !status ? 0 : -1;
}
#else
static int random_bytes(void *buf, size_t size) {
(void)buf;
(void)size;
return -1;
}
#endif
static unsigned int generate_seed() {
#ifdef _WIN32
return (unsigned int)GetTickCount();
#else
struct timespec ts;
if (clock_gettime(CLOCK_REALTIME, &ts) == 0)
return (unsigned int)(ts.tv_sec ^ ts.tv_nsec);
else
return (unsigned int)time(NULL);
#endif
}
void juice_random(void *buf, size_t size) {
if (random_bytes(buf, size) == 0)
return;
// rand() is not thread-safe
static mutex_t rand_mutex = MUTEX_INITIALIZER;
mutex_lock(&rand_mutex);
static bool srandom_called = false;
#if defined(__linux__) || defined(__unix__) || defined(__APPLE__)
#define random_func random
#define srandom_func srandom
if (!srandom_called)
JLOG_DEBUG("Using random() for random bytes");
#else
#define random_func rand
#define srandom_func srand
if (!srandom_called)
JLOG_WARN("Falling back on rand() for random bytes");
#endif
if (!srandom_called) {
srandom_func(generate_seed());
srandom_called = true;
}
// RAND_MAX is guaranteed to be at least 2^15 - 1
uint8_t *bytes = buf;
for (size_t i = 0; i < size; ++i)
bytes[i] = (uint8_t)((random_func() & 0x7f80) >> 7);
mutex_unlock(&rand_mutex);
}
void juice_random_str64(char *buf, size_t size) {
static const char chars64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
size_t i = 0;
for (i = 0; i + 1 < size; ++i) {
uint8_t byte = 0;
juice_random(&byte, 1);
buf[i] = chars64[byte & 0x3F];
}
buf[i] = '\0';
}
uint32_t juice_rand32(void) {
uint32_t r = 0;
juice_random(&r, sizeof(r));
return r;
}
uint64_t juice_rand64(void) {
uint64_t r = 0;
juice_random(&r, sizeof(r));
return r;
}

21
thirdparty/libjuice/src/random.h vendored Normal file
View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_RANDOM_H
#define JUICE_RANDOM_H
#include <stdint.h>
#include <stdlib.h>
void juice_random(void *buf, size_t size);
void juice_random_str64(char *buf, size_t size);
uint32_t juice_rand32(void);
uint64_t juice_rand64(void);
#endif // JUICE_RANDOM_H

1143
thirdparty/libjuice/src/server.c vendored Normal file

File diff suppressed because it is too large Load Diff

123
thirdparty/libjuice/src/server.h vendored Normal file
View File

@@ -0,0 +1,123 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_SERVER_H
#define JUICE_SERVER_H
#ifndef NO_SERVER
#include "addr.h"
#include "juice.h"
#include "socket.h"
#include "stun.h"
#include "thread.h"
#include "timestamp.h"
#include "turn.h"
#include <stdbool.h>
#include <stdint.h>
#define SERVER_DEFAULT_REALM "libjuice"
#define SERVER_DEFAULT_MAX_ALLOCATIONS 1000 // should be 1024-1 or less to be safe for poll()
#define SERVER_DEFAULT_MAX_PEERS 16
#define SERVER_NONCE_KEY_SIZE 32
// RFC 8656: The server [...] SHOULD expire the nonce at least once every hour during the lifetime
// of the allocation
#define SERVER_NONCE_KEY_LIFETIME 600 * 1000 // 10 min
typedef enum server_turn_alloc_state {
SERVER_TURN_ALLOC_EMPTY,
SERVER_TURN_ALLOC_DELETED,
SERVER_TURN_ALLOC_FULL
} server_turn_alloc_state_t;
typedef struct server_turn_alloc {
server_turn_alloc_state_t state;
addr_record_t record;
juice_server_credentials_t *credentials;
uint8_t transaction_id[STUN_TRANSACTION_ID_SIZE];
timestamp_t timestamp;
socket_t sock;
turn_map_t map;
} server_turn_alloc_t;
typedef struct juice_credentials_list {
struct juice_credentials_list *next;
juice_server_credentials_t credentials;
uint8_t userhash[USERHASH_SIZE];
timestamp_t timestamp;
} juice_credentials_list_t;
typedef struct juice_server {
juice_server_config_t config; // Note config.credentials will be empty
juice_credentials_list_t *credentials; // Credentials are stored in this list
uint8_t nonce_key[SERVER_NONCE_KEY_SIZE];
timestamp_t nonce_key_timestamp;
socket_t sock;
thread_t thread;
mutex_t mutex;
bool thread_stopped;
server_turn_alloc_t *allocs;
int allocs_count;
} juice_server_t;
juice_server_t *server_create(const juice_server_config_t *config);
void server_do_destroy(juice_server_t *server);
void server_destroy(juice_server_t *server);
uint16_t server_get_port(juice_server_t *server);
int server_add_credentials(juice_server_t *server, const juice_server_credentials_t *credentials,
timediff_t lifetime);
juice_credentials_list_t *server_do_add_credentials(juice_server_t *server,
const juice_server_credentials_t *credentials,
timediff_t lifetime); // internal
void server_run(juice_server_t *server);
int server_send(juice_server_t *agent, const addr_record_t *dst, const char *data, size_t size);
int server_stun_send(juice_server_t *server, const addr_record_t *dst, const stun_message_t *msg,
const char *password // password may be NULL
);
int server_recv(juice_server_t *server);
int server_forward(juice_server_t *server, server_turn_alloc_t *alloc);
int server_input(juice_server_t *agent, char *buf, size_t len, const addr_record_t *src);
int server_interrupt(juice_server_t *server);
int server_bookkeeping(juice_server_t *agent, timestamp_t *next_timestamp);
void server_get_nonce(juice_server_t *server, const addr_record_t *src, char *nonce);
void server_prepare_credentials(juice_server_t *server, const addr_record_t *src,
const juice_server_credentials_t *credentials, stun_message_t *msg);
int server_dispatch_stun(juice_server_t *server, void *buf, size_t size, stun_message_t *msg,
const addr_record_t *src);
int server_answer_stun_binding(juice_server_t *server, const uint8_t *transaction_id,
const addr_record_t *src);
int server_answer_stun_error(juice_server_t *server, const uint8_t *transaction_id,
const addr_record_t *src, stun_method_t method, unsigned int code,
const juice_server_credentials_t *credentials);
int server_process_stun_binding(juice_server_t *server, const stun_message_t *msg,
const addr_record_t *src);
int server_process_turn_allocate(juice_server_t *server, const stun_message_t *msg,
const addr_record_t *src, juice_server_credentials_t *credentials);
int server_process_turn_create_permission(juice_server_t *server, const stun_message_t *msg,
const addr_record_t *src,
const juice_server_credentials_t *credentials);
int server_process_turn_channel_bind(juice_server_t *server, const stun_message_t *msg,
const addr_record_t *src,
const juice_server_credentials_t *credentials);
int server_process_turn_send(juice_server_t *server, const stun_message_t *msg,
const addr_record_t *src);
int server_process_channel_data(juice_server_t *server, char *buf, size_t len,
const addr_record_t *src);
#endif // ifndef NO_SERVER
#endif

132
thirdparty/libjuice/src/socket.h vendored Normal file
View File

@@ -0,0 +1,132 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_SOCKET_H
#define JUICE_SOCKET_H
#ifdef _WIN32
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0601 // Windows 7
#endif
#ifndef __MSVCRT_VERSION__
#define __MSVCRT_VERSION__ 0x0601
#endif
#include <winsock2.h>
#include <ws2tcpip.h>
//
#include <iphlpapi.h>
#include <windows.h>
#ifdef __MINGW32__
#include <sys/stat.h>
#include <sys/time.h>
#ifndef IPV6_V6ONLY
#define IPV6_V6ONLY 27
#endif
#endif
#define NO_IFADDRS
#define NO_PMTUDISC
typedef SOCKET socket_t;
typedef SOCKADDR sockaddr;
typedef ULONG ctl_t;
typedef DWORD sockopt_t;
#define sockerrno ((int)WSAGetLastError())
#define IP_DONTFRAG IP_DONTFRAGMENT
#define HOST_NAME_MAX 256
#define poll WSAPoll
typedef ULONG nfds_t;
#define SEADDRINUSE WSAEADDRINUSE
#define SEINTR WSAEINTR
#define SEAGAIN WSAEWOULDBLOCK
#define SEACCES WSAEACCES
#define SEWOULDBLOCK WSAEWOULDBLOCK
#define SEINPROGRESS WSAEINPROGRESS
#define SECONNREFUSED WSAECONNREFUSED
#define SECONNRESET WSAECONNRESET
#define SENETRESET WSAENETRESET
#define SEMSGSIZE WSAEMSGSIZE
#else // assume POSIX
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#ifndef __linux__
#define NO_PMTUDISC
#endif
#ifdef __ANDROID__
#define NO_IFADDRS
#else
#include <ifaddrs.h>
#endif
typedef int socket_t;
typedef int ctl_t;
typedef int sockopt_t;
#define sockerrno errno
#define INVALID_SOCKET -1
#define ioctlsocket ioctl
#define closesocket close
#define SEADDRINUSE EADDRINUSE
#define SEINTR EINTR
#define SEAGAIN EAGAIN
#define SEACCES EACCES
#define SEWOULDBLOCK EWOULDBLOCK
#define SEINPROGRESS EINPROGRESS
#define SECONNREFUSED ECONNREFUSED
#define SECONNRESET ECONNRESET
#define SENETRESET ENETRESET
#define SEMSGSIZE EMSGSIZE
#endif // _WIN32
#ifndef IN6_IS_ADDR_LOOPBACK
#define IN6_IS_ADDR_LOOPBACK(a) \
(((const uint32_t *)(a))[0] == 0 && ((const uint32_t *)(a))[1] == 0 && \
((const uint32_t *)(a))[2] == 0 && ((const uint32_t *)(a))[3] == htonl(1))
#endif
#ifndef IN6_IS_ADDR_LINKLOCAL
#define IN6_IS_ADDR_LINKLOCAL(a) \
((((const uint32_t *)(a))[0] & htonl(0xffc00000)) == htonl(0xfe800000))
#endif
#ifndef IN6_IS_ADDR_SITELOCAL
#define IN6_IS_ADDR_SITELOCAL(a) \
((((const uint32_t *)(a))[0] & htonl(0xffc00000)) == htonl(0xfec00000))
#endif
#ifndef IN6_IS_ADDR_V4MAPPED
#define IN6_IS_ADDR_V4MAPPED(a) \
((((const uint32_t *)(a))[0] == 0) && (((const uint32_t *)(a))[1] == 0) && \
(((const uint32_t *)(a))[2] == htonl(0xFFFF)))
#endif
#endif // JUICE_SOCKET_H

1236
thirdparty/libjuice/src/stun.c vendored Normal file

File diff suppressed because it is too large Load Diff

376
thirdparty/libjuice/src/stun.h vendored Normal file
View File

@@ -0,0 +1,376 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_STUN_H
#define JUICE_STUN_H
#include "juice.h"
#include "addr.h"
#include "hash.h"
#include "hmac.h"
#include <stdbool.h>
#include <stdint.h>
#pragma pack(push, 1)
/*
* STUN message header (20 bytes)
* See https://www.rfc-editor.org/rfc/rfc8489.html#section-5
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |0 0| STUN Message Type | Message Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Magic Cookie = 0x2112A442 |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* | Transaction ID (96 bits) |
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
#define STUN_TRANSACTION_ID_SIZE 12
struct stun_header {
uint16_t type;
uint16_t length;
uint32_t magic;
uint8_t transaction_id[STUN_TRANSACTION_ID_SIZE];
};
/*
* Format of STUN Message Type Field
*
* 0 1
* 2 3 4 5 6 7 8 9 0 1 2 3 4 5
* +--+--+-+-+-+-+-+-+-+-+-+-+-+-+
* |M |M |M|M|M|C|M|M|M|C|M|M|M|M|
* |11|10|9|8|7|1|6|5|4|0|3|2|1|0|
* +--+--+-+-+-+-+-+-+-+-+-+-+-+-+
* Request: C=b00
* Indication: C=b01
* Response: C=b10 (success)
* C=b11 (error)
*/
#define STUN_CLASS_MASK 0x0110
typedef enum stun_class {
STUN_CLASS_REQUEST = 0x0000,
STUN_CLASS_INDICATION = 0x0010,
STUN_CLASS_RESP_SUCCESS = 0x0100,
STUN_CLASS_RESP_ERROR = 0x0110
} stun_class_t;
typedef enum stun_method {
STUN_METHOD_BINDING = 0x0001,
// Methods for TURN
// See https://www.rfc-editor.org/rfc/rfc8656.html#section-17
STUN_METHOD_ALLOCATE = 0x003,
STUN_METHOD_REFRESH = 0x004,
STUN_METHOD_SEND = 0x006,
STUN_METHOD_DATA = 0x007,
STUN_METHOD_CREATE_PERMISSION = 0x008,
STUN_METHOD_CHANNEL_BIND = 0x009
} stun_method_t;
#define STUN_IS_RESPONSE(msg_class) (msg_class & 0x0100)
/*
* STUN attribute header
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Type | Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Value (variable) ...
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
struct stun_attr {
uint16_t type;
uint16_t length;
uint8_t value[];
};
typedef enum stun_attr_type {
// Comprehension-required
STUN_ATTR_MAPPED_ADDRESS = 0x0001,
STUN_ATTR_USERNAME = 0x0006,
STUN_ATTR_MESSAGE_INTEGRITY = 0x0008,
STUN_ATTR_ERROR_CODE = 0x0009,
STUN_ATTR_UNKNOWN_ATTRIBUTES = 0x000A,
STUN_ATTR_REALM = 0x0014,
STUN_ATTR_NONCE = 0x0015,
STUN_ATTR_MESSAGE_INTEGRITY_SHA256 = 0x001C,
STUN_ATTR_PASSWORD_ALGORITHM = 0x001D,
STUN_ATTR_USERHASH = 0x001E,
STUN_ATTR_XOR_MAPPED_ADDRESS = 0x0020,
STUN_ATTR_PRIORITY = 0x0024,
STUN_ATTR_USE_CANDIDATE = 0x0025,
// Comprehension-optional
STUN_ATTR_PASSWORD_ALGORITHMS = 0x8002,
STUN_ATTR_ALTERNATE_DOMAIN = 0x8003,
STUN_ATTR_SOFTWARE = 0x8022,
STUN_ATTR_ALTERNATE_SERVER = 0x8023,
STUN_ATTR_FINGERPRINT = 0x8028,
STUN_ATTR_ICE_CONTROLLED = 0x8029,
STUN_ATTR_ICE_CONTROLLING = 0x802A,
// Attributes for TURN
// See https://www.rfc-editor.org/rfc/rfc8656.html#section-18
STUN_ATTR_CHANNEL_NUMBER = 0x000C,
STUN_ATTR_LIFETIME = 0x000D,
STUN_ATTR_XOR_PEER_ADDRESS = 0x0012,
STUN_ATTR_DATA = 0x0013,
STUN_ATTR_XOR_RELAYED_ADDRESS = 0x0016,
STUN_ATTR_EVEN_PORT = 0x0018,
STUN_ATTR_REQUESTED_TRANSPORT = 0x0019,
STUN_ATTR_DONT_FRAGMENT = 0x001A,
STUN_ATTR_RESERVATION_TOKEN = 0x0022
} stun_attr_type_t;
#define STUN_IS_OPTIONAL_ATTR(attr_type) (attr_type & 0x8000)
/*
* STUN attribute value for MAPPED-ADDRESS or XOR-MAPPED-ADDRESS
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |X X X X X X X X| Family | Port or X-Port |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* | Address or X-Address (32 bits or 128 bits) |
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
struct stun_value_mapped_address {
uint8_t padding;
uint8_t family;
uint16_t port;
uint8_t address[];
};
typedef enum stun_address_family {
STUN_ADDRESS_FAMILY_IPV4 = 0x01,
STUN_ADDRESS_FAMILY_IPV6 = 0x02,
} stun_address_family_t;
/*
* STUN attribute value for ERROR-CODE
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Reserved, should be 0 |Class| Number |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Reason Phrase (variable) ...
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
struct stun_value_error_code {
uint16_t reserved;
uint8_t code_class; // lower 3 bits only, higher bits are reserved
uint8_t code_number;
uint8_t reason[];
};
#define STUN_ERROR_INTERNAL_VALIDATION_FAILED 599
/*
* STUN attribute for CHANNEL-NUMBER
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Channel Number | RFFU = 0 |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
struct stun_value_channel_number {
uint16_t channel_number;
uint16_t reserved;
};
/*
* STUN attribute for EVEN-PORT
*
* 0
* 0 1 2 3 4 5 6 7
* +-+-+-+-+-+-+-+-+
* |R| RFFU |
* +-+-+-+-+-+-+-+-+
*/
struct stun_value_even_port {
uint8_t r;
};
/*
* STUN attribute for REQUESTED-TRANSPORT
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Protocol | RFFU |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
struct stun_value_requested_transport {
uint8_t protocol;
uint8_t reserved1;
uint16_t reserved2;
};
/*
* STUN attribute value for PASSWORD-ALGORITHM and PASSWORD-ALGORITHMS
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Algorithm 1 | Algorithm 1 Parameters Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Algorithm 1 Parameters (variable)
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Algorithm 2 | Algorithm 2 Parameters Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Algorithm 2 Parameters (variable)
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | ...
*/
struct stun_value_password_algorithm {
uint16_t algorithm;
uint16_t parameters_length;
uint8_t parameters[];
};
typedef enum stun_password_algorithm {
STUN_PASSWORD_ALGORITHM_UNSET = 0x0000,
STUN_PASSWORD_ALGORITHM_MD5 = 0x0001,
STUN_PASSWORD_ALGORITHM_SHA256 = 0x0002,
} stun_password_algorithm_t;
#pragma pack(pop)
// The value of USERNAME is a variable-length value. It MUST contain a UTF-8 [RFC3629] encoded
// sequence of less than 513 bytes [...]
#define STUN_MAX_USERNAME_LEN 513 + 1
// The REALM attribute [...] MUST be a UTF-8 [RFC3629] encoded sequence of less than 128 characters
// (which can be as long as 763 bytes)
#define STUN_MAX_REALM_LEN 763 + 1
// The NONCE attribute may be present in requests and responses. It [...] MUST be less than 128
// characters (which can be as long as 763 bytes)
#define STUN_MAX_NONCE_LEN 763 + 1
// The value of SOFTWARE is variable length. It MUST be a UTF-8 [RFC3629] encoded sequence of less
// than 128 characters (which can be as long as 763 bytes)
#define STUN_MAX_SOFTWARE_LEN 763 + 1
// The reason phrase MUST be a UTF-8-encoded [RFC3629] sequence of fewer than 128 characters (which
// can be as long as 509 bytes when encoding them or 763 bytes when decoding them).
#define STUN_MAX_ERROR_REASON_LEN 763 + 1
#define STUN_MAX_PASSWORD_LEN STUN_MAX_USERNAME_LEN
// Nonce cookie prefix as specified in https://www.rfc-editor.org/rfc/rfc8489.html#section-9.2
#define STUN_NONCE_COOKIE "obMatJos2"
#define STUN_NONCE_COOKIE_LEN 9
// USERHASH is a SHA256 digest
#define USERHASH_SIZE HASH_SHA256_SIZE
// STUN Security Feature bits as defined in https://www.rfc-editor.org/rfc/rfc8489.html#section-18.1
// See errata about bit order: https://www.rfc-editor.org/errata_search.php?rfc=8489
// Bits are assigned starting from the least significant side of the bit set, so Bit 0 is the rightmost bit, and Bit 23 is the leftmost bit.
// Bit 0: Password algorithms
// Bit 1: Username anonymity
// Bit 2-23: Unassigned
#define STUN_SECURITY_PASSWORD_ALGORITHMS_BIT 0x01
#define STUN_SECURITY_USERNAME_ANONYMITY_BIT 0x02
#define STUN_MAX_PASSWORD_ALGORITHMS_VALUE_SIZE 256
typedef struct stun_credentials {
char username[STUN_MAX_USERNAME_LEN];
char realm[STUN_MAX_REALM_LEN];
char nonce[STUN_MAX_NONCE_LEN];
uint8_t userhash[USERHASH_SIZE];
bool enable_userhash;
stun_password_algorithm_t password_algorithm;
uint8_t password_algorithms_value[STUN_MAX_PASSWORD_ALGORITHMS_VALUE_SIZE];
size_t password_algorithms_value_size;
} stun_credentials_t;
typedef struct stun_message {
stun_class_t msg_class;
stun_method_t msg_method;
uint8_t transaction_id[STUN_TRANSACTION_ID_SIZE];
unsigned int error_code;
uint32_t priority;
uint64_t ice_controlling;
uint64_t ice_controlled;
bool use_candidate;
addr_record_t mapped;
stun_credentials_t credentials;
// Only for reading
bool has_integrity;
bool has_fingerprint;
// TURN
addr_record_t peer;
addr_record_t relayed;
addr_record_t alternate_server;
const char *data;
size_t data_size;
uint32_t lifetime;
uint16_t channel_number;
bool lifetime_set;
bool even_port;
bool next_port;
bool dont_fragment;
bool requested_transport;
uint64_t reservation_token;
} stun_message_t;
int stun_write(void *buf, size_t size, const stun_message_t *msg,
const char *password); // password may be NULL
int stun_write_header(void *buf, size_t size, stun_class_t class, stun_method_t method,
const uint8_t *transaction_id);
size_t stun_update_header_length(void *buf, size_t length);
int stun_write_attr(void *buf, size_t size, uint16_t type, const void *value, size_t length);
int stun_write_value_mapped_address(void *buf, size_t size, const struct sockaddr *addr,
socklen_t addrlen, const uint8_t *mask);
bool is_stun_datagram(const void *data, size_t size);
int stun_read(void *data, size_t size, stun_message_t *msg);
int stun_read_attr(const void *data, size_t size, stun_message_t *msg, uint8_t *begin,
uint8_t *attr_begin, uint32_t *security_bits);
int stun_read_value_mapped_address(const void *data, size_t size, addr_record_t *mapped,
const uint8_t *mask);
bool stun_check_integrity(void *buf, size_t size, const stun_message_t *msg, const char *password);
void stun_compute_userhash(const char *username, const char *realm, uint8_t *out);
void stun_prepend_nonce_cookie(char *nonce);
void stun_process_credentials(const stun_credentials_t *credentials, stun_credentials_t *dst);
const char *stun_get_error_reason(unsigned int code);
// Export for tests
JUICE_EXPORT bool _juice_is_stun_datagram(const void *data, size_t size);
JUICE_EXPORT int _juice_stun_read(void *data, size_t size, stun_message_t *msg);
JUICE_EXPORT bool _juice_stun_check_integrity(void *buf, size_t size, const stun_message_t *msg,
const char *password);
#endif

116
thirdparty/libjuice/src/thread.h vendored Normal file
View File

@@ -0,0 +1,116 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_THREAD_H
#define JUICE_THREAD_H
#ifdef _WIN32
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0601 // Windows 7
#endif
#ifndef __MSVCRT_VERSION__
#define __MSVCRT_VERSION__ 0x0601
#endif
#include <windows.h>
typedef HANDLE mutex_t;
typedef HANDLE thread_t;
typedef DWORD thread_return_t;
#define THREAD_CALL __stdcall
#define MUTEX_INITIALIZER NULL
#define MUTEX_PLAIN 0x0
#define MUTEX_RECURSIVE 0x0 // mutexes are recursive on Windows
static inline int mutex_init_impl(mutex_t *m) {
return ((*(m) = CreateMutex(NULL, FALSE, NULL)) != NULL ? 0 : (int)GetLastError());
}
static inline int mutex_lock_impl(volatile mutex_t *m) {
// Atomically initialize the mutex on first lock
if (*(m) == NULL) {
HANDLE cm = CreateMutex(NULL, FALSE, NULL);
if (cm == NULL)
return (int)GetLastError();
if (InterlockedCompareExchangePointer(m, cm, NULL) != NULL)
CloseHandle(cm);
}
return WaitForSingleObject(*m, INFINITE) != WAIT_FAILED ? 0 : (int)GetLastError();
}
#define mutex_init(m, flags) mutex_init_impl(m)
#define mutex_lock(m) mutex_lock_impl(m)
#define mutex_unlock(m) (void)ReleaseMutex(*(m))
#define mutex_destroy(m) (void)CloseHandle(*(m))
static inline void thread_join_impl(thread_t t, thread_return_t *res) {
WaitForSingleObject(t, INFINITE);
if (res)
GetExitCodeThread(t, res);
CloseHandle(t);
}
#define thread_init(t, func, arg) \
((*(t) = CreateThread(NULL, 0, func, arg, 0, NULL)) != NULL ? 0 : (int)GetLastError())
#define thread_join(t, res) thread_join_impl(t, res)
#else // POSIX
#include <pthread.h>
typedef pthread_mutex_t mutex_t;
typedef pthread_t thread_t;
typedef void *thread_return_t;
#define THREAD_CALL
#define MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER
#define MUTEX_PLAIN PTHREAD_MUTEX_NORMAL
#define MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE
static inline int mutex_init_impl(mutex_t *m, int flags) {
pthread_mutexattr_t mutexattr;
pthread_mutexattr_init(&mutexattr);
pthread_mutexattr_settype(&mutexattr, flags);
int ret = pthread_mutex_init(m, &mutexattr);
pthread_mutexattr_destroy(&mutexattr);
return ret;
}
#define mutex_init(m, flags) mutex_init_impl(m, flags)
#define mutex_lock(m) pthread_mutex_lock(m)
#define mutex_unlock(m) (void)pthread_mutex_unlock(m)
#define mutex_destroy(m) (void)pthread_mutex_destroy(m)
#define thread_init(t, func, arg) pthread_create(t, NULL, func, arg)
#define thread_join(t, res) (void)pthread_join(t, res)
#endif // ifdef _WIN32
#if __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__)
#include <stdatomic.h>
#define atomic(T) _Atomic(T)
#define atomic_ptr(T) _Atomic(T*)
#else // no atomics
// Since we don't need compare-and-swap, just assume store and load are atomic
#define atomic(T) volatile T
#define atomic_ptr(T) T* volatile
#define atomic_store(a, v) (void)(*(a) = (v))
#define atomic_load(a) (*(a))
#define ATOMIC_VAR_INIT(v) (v)
#endif // if atomics
#endif // JUICE_THREAD_H

45
thirdparty/libjuice/src/timestamp.c vendored Normal file
View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "timestamp.h"
#ifdef _WIN32
#include <windows.h>
#else
#include <time.h>
// clock_gettime() is not implemented on older versions of OS X (< 10.12)
#if defined(__APPLE__) && !defined(CLOCK_MONOTONIC)
#include <sys/time.h>
#define CLOCK_MONOTONIC 0
int clock_gettime(int clk_id, struct timespec *t) {
(void)clk_id;
// gettimeofday() does not return monotonic time but it should be good enough.
struct timeval now;
if (gettimeofday(&now, NULL))
return -1;
t->tv_sec = now.tv_sec;
t->tv_nsec = now.tv_usec * 1000;
return 0;
}
#endif // defined(__APPLE__) && !defined(CLOCK_MONOTONIC)
#endif
timestamp_t current_timestamp() {
#ifdef _WIN32
return (timestamp_t)GetTickCount();
#else // POSIX
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts))
return 0;
return (timestamp_t)ts.tv_sec * 1000 + (timestamp_t)ts.tv_nsec / 1000000;
#endif
}

20
thirdparty/libjuice/src/timestamp.h vendored Normal file
View File

@@ -0,0 +1,20 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_TIMESTAMP_H
#define JUICE_TIMESTAMP_H
#include <stdint.h>
#include <stdlib.h>
typedef int64_t timestamp_t;
typedef timestamp_t timediff_t;
timestamp_t current_timestamp();
#endif

495
thirdparty/libjuice/src/turn.c vendored Normal file
View File

@@ -0,0 +1,495 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "turn.h"
#include "log.h"
#include "random.h"
#include "socket.h"
#include <string.h>
static bool memory_is_zero(const void *data, size_t size) {
const char *d = data;
for (size_t i = 0; i < size; ++i)
if (d[i])
return false;
return true;
}
static uint16_t random_channel_number() {
/*
* RFC 8656 12. Channels
* The ChannelData message (see Section 12.4) starts with a two-byte
* field that carries the channel number. The values of this field are
* allocated as follows:
*
* +------------------------+--------------------------------------+
* | 0x0000 through 0x3FFF: | These values can never be used for |
* | | channel numbers. |
* +------------------------+--------------------------------------+
* | 0x4000 through 0x4FFF: | These values are the allowed channel |
* | | numbers (4096 possible values). |
* +------------------------+--------------------------------------+
* | 0x5000 through 0xFFFF: | Reserved (For DTLS-SRTP multiplexing |
* | | collision avoidance, see [RFC7983]). |
* +------------------------+--------------------------------------+
*/
uint16_t r;
juice_random(&r, 2);
return 0x4000 | (r & 0x0FFF);
}
bool is_channel_data(const void *data, size_t size) {
// According RFC 8656, first byte in [64..79] is TURN Channel
if (size == 0)
return false;
uint8_t b = *((const uint8_t *)data);
return b >= 64 && b <= 79;
}
bool is_valid_channel(uint16_t channel) { return channel >= 0x4000; }
int turn_wrap_channel_data(char *buffer, size_t size, const char *data, size_t data_size,
uint16_t channel) {
if (!is_valid_channel(channel)) {
JLOG_WARN("Invalid channel number: 0x%hX", channel);
return -1;
}
if (data_size >= 65536) {
JLOG_WARN("ChannelData is too long, size=%zu", size);
return -1;
}
if (size < sizeof(struct channel_data_header) + data_size) {
JLOG_WARN("Buffer is too small to add ChannelData header, size=%zu, needed=%zu", size,
sizeof(struct channel_data_header) + data_size);
return -1;
}
memmove(buffer + sizeof(struct channel_data_header), data, data_size);
struct channel_data_header *header = (struct channel_data_header *)buffer;
header->channel_number = htons((uint16_t)channel);
header->length = htons((uint16_t)data_size);
return (int)(sizeof(struct channel_data_header) + data_size);
}
static int find_ordered_channel_rec(turn_entry_t *const ordered_channels[], uint16_t channel,
int begin, int end) {
int d = end - begin;
if (d <= 0)
return begin;
int pivot = begin + d / 2;
const turn_entry_t *entry = ordered_channels[pivot];
if (channel < entry->channel)
return find_ordered_channel_rec(ordered_channels, channel, begin, pivot);
else if (channel > entry->channel)
return find_ordered_channel_rec(ordered_channels, channel, pivot + 1, end);
else
return pivot;
}
static int find_ordered_channel(const turn_map_t *map, uint16_t channel) {
return find_ordered_channel_rec(map->ordered_channels, channel, 0, map->channels_count);
}
static int find_ordered_transaction_id_rec(turn_entry_t *const ordered_transaction_ids[],
const uint8_t *transaction_id, int begin, int end) {
int d = end - begin;
if (d <= 0)
return begin;
int pivot = begin + d / 2;
const turn_entry_t *entry = ordered_transaction_ids[pivot];
int ret = memcmp(transaction_id, entry->transaction_id, STUN_TRANSACTION_ID_SIZE);
if (ret < 0)
return find_ordered_transaction_id_rec(ordered_transaction_ids, transaction_id, begin,
pivot);
else if (ret > 0)
return find_ordered_transaction_id_rec(ordered_transaction_ids, transaction_id, pivot + 1,
end);
else
return pivot;
}
static int find_ordered_transaction_id(const turn_map_t *map, const uint8_t *transaction_id) {
return find_ordered_transaction_id_rec(map->ordered_transaction_ids, transaction_id, 0,
map->transaction_ids_count);
}
static void remove_ordered_transaction_id(turn_map_t *map, const uint8_t *transaction_id) {
int pos = find_ordered_transaction_id(map, transaction_id);
if (pos < map->transaction_ids_count) {
memmove(map->ordered_transaction_ids + pos, map->ordered_transaction_ids + pos + 1,
(map->transaction_ids_count - (pos + 1)) * sizeof(turn_entry_t *));
map->transaction_ids_count--;
}
}
/*
static void remove_ordered_channel(turn_map_t *map, uint16_t channel) {
int pos = find_ordered_channel(map, channel);
if (pos < map->channels_count) {
memmove(map->ordered_channels + pos, map->ordered_channels + pos + 1,
(map->channels_count - (pos + 1)) * sizeof(turn_entry_t *));
map->channels_count--;
}
}
static void delete_entry(turn_map_t *map, turn_entry_t *entry) {
if (entry->type == TURN_ENTRY_TYPE_EMPTY || entry->type == TURN_ENTRY_TYPE_DELETED)
return;
if (!memory_is_zero(entry->transaction_id, STUN_TRANSACTION_ID_SIZE))
remove_ordered_transaction_id(map, entry->transaction_id);
if (entry->type == TURN_ENTRY_TYPE_CHANNEL && entry->channel)
remove_ordered_channel(map, entry->channel);
memset(entry, 0, sizeof(*entry));
entry->type = TURN_ENTRY_TYPE_DELETED;
}
*/
static turn_entry_t *find_entry(turn_map_t *map, const addr_record_t *record,
turn_entry_type_t type, bool allow_deleted) {
unsigned long key = (addr_record_hash(record, false) + (int)type) % map->map_size;
unsigned long pos = key;
while (true) {
turn_entry_t *entry = map->map + pos;
if (entry->type == TURN_ENTRY_TYPE_EMPTY ||
(entry->type == type && addr_record_is_equal(&entry->record, record, false)))
break;
if (allow_deleted && entry->type == TURN_ENTRY_TYPE_DELETED)
break;
pos = (pos + 1) % map->map_size;
if (pos == key) {
JLOG_VERBOSE("TURN map is full");
return NULL;
}
}
return map->map + pos;
}
static bool update_timestamp(turn_map_t *map, turn_entry_type_t type, const uint8_t *transaction_id,
const addr_record_t *record, timediff_t duration) {
turn_entry_t *entry;
if (record) {
entry = find_entry(map, record, type, true);
if (!entry)
return false;
if (entry->type == type) {
if (memcmp(entry->transaction_id, transaction_id, STUN_TRANSACTION_ID_SIZE) == 0)
return true;
} else {
entry->type = type;
entry->record = *record;
}
if (!memory_is_zero(entry->transaction_id, STUN_TRANSACTION_ID_SIZE))
remove_ordered_transaction_id(map, entry->transaction_id);
memcpy(entry->transaction_id, transaction_id, STUN_TRANSACTION_ID_SIZE);
} else {
int pos = find_ordered_transaction_id(map, transaction_id);
if (pos == map->transaction_ids_count)
return false;
entry = map->ordered_transaction_ids[pos];
if (entry->type != type ||
memcmp(entry->transaction_id, transaction_id, STUN_TRANSACTION_ID_SIZE) != 0)
return false;
}
entry->timestamp = current_timestamp() + duration;
entry->fresh_transaction_id = false;
return true;
}
int turn_init_map(turn_map_t *map, int size) {
memset(map, 0, sizeof(*map));
map->map_size = size * 2;
map->channels_count = 0;
map->transaction_ids_count = 0;
map->map = calloc(map->map_size, sizeof(turn_entry_t));
map->ordered_channels = calloc(map->map_size, sizeof(turn_entry_t *));
map->ordered_transaction_ids = calloc(map->map_size, sizeof(turn_entry_t *));
if (!map->map || !map->ordered_channels || !map->ordered_transaction_ids) {
JLOG_ERROR("Failed to allocate TURN map of size %d", size);
turn_destroy_map(map);
return -1;
}
return 0;
}
void turn_destroy_map(turn_map_t *map) {
free(map->map);
free(map->ordered_channels);
free(map->ordered_transaction_ids);
}
bool turn_set_permission(turn_map_t *map, const uint8_t *transaction_id,
const addr_record_t *record, timediff_t duration) {
return update_timestamp(map, TURN_ENTRY_TYPE_PERMISSION, transaction_id, record, duration);
}
bool turn_has_permission(turn_map_t *map, const addr_record_t *record) {
turn_entry_t *entry = find_entry(map, record, TURN_ENTRY_TYPE_PERMISSION, false);
if (!entry || entry->type != TURN_ENTRY_TYPE_PERMISSION)
return false;
return current_timestamp() < entry->timestamp;
}
bool turn_bind_channel(turn_map_t *map, const addr_record_t *record, const uint8_t *transaction_id,
uint16_t channel, timediff_t duration) {
if (!is_valid_channel(channel)) {
JLOG_ERROR("Invalid channel number: 0x%hX", channel);
return false;
}
turn_entry_t *entry = find_entry(map, record, TURN_ENTRY_TYPE_CHANNEL, true);
if (!entry)
return false;
if (entry->type == TURN_ENTRY_TYPE_CHANNEL && entry->channel) {
if (entry->channel != channel) {
JLOG_WARN("The record is already bound to a channel");
return false;
}
entry->timestamp = current_timestamp() + duration;
return true;
}
int pos = find_ordered_channel(map, channel);
if (pos < map->channels_count) {
const turn_entry_t *other_entry = map->ordered_channels[pos];
if (other_entry->channel == channel) {
JLOG_WARN("The channel is already bound to a record");
return false;
}
}
if (entry->type != TURN_ENTRY_TYPE_CHANNEL) {
entry->type = TURN_ENTRY_TYPE_CHANNEL;
entry->record = *record;
}
memmove(map->ordered_channels + pos + 1, map->ordered_channels + pos,
(map->channels_count - pos) * sizeof(turn_entry_t *));
map->ordered_channels[pos] = entry;
map->channels_count++;
entry->channel = channel;
entry->timestamp = current_timestamp() + duration;
if (transaction_id) {
memcpy(entry->transaction_id, transaction_id, STUN_TRANSACTION_ID_SIZE);
entry->fresh_transaction_id = true;
}
return true;
}
bool turn_bind_random_channel(turn_map_t *map, const addr_record_t *record, uint16_t *channel,
timediff_t duration) {
uint16_t c;
do {
c = random_channel_number();
} while (turn_find_channel(map, c, NULL));
if (!turn_bind_channel(map, record, NULL, c, duration))
return false;
if (channel)
*channel = c;
return true;
}
bool turn_bind_current_channel(turn_map_t *map, const uint8_t *transaction_id,
const addr_record_t *record, timediff_t duration) {
return update_timestamp(map, TURN_ENTRY_TYPE_CHANNEL, transaction_id, record, duration);
}
bool turn_get_channel(turn_map_t *map, const addr_record_t *record, uint16_t *channel) {
turn_entry_t *entry = find_entry(map, record, TURN_ENTRY_TYPE_CHANNEL, false);
if (!entry || entry->type != TURN_ENTRY_TYPE_CHANNEL)
return false;
if (channel)
*channel = entry->channel;
return true;
}
bool turn_get_bound_channel(turn_map_t *map, const addr_record_t *record, uint16_t *channel) {
turn_entry_t *entry = find_entry(map, record, TURN_ENTRY_TYPE_CHANNEL, false);
if (!entry || entry->type != TURN_ENTRY_TYPE_CHANNEL)
return false;
if (!entry->channel || current_timestamp() >= entry->timestamp)
return false;
if (channel)
*channel = entry->channel;
return true;
}
bool turn_find_channel(turn_map_t *map, uint16_t channel, addr_record_t *record) {
if (!is_valid_channel(channel)) {
JLOG_WARN("Invalid channel number: 0x%hX", channel);
return false;
}
int pos = find_ordered_channel(map, channel);
if (pos == map->channels_count)
return false;
const turn_entry_t *entry = map->ordered_channels[pos];
if (entry->channel != channel)
return false;
if (record)
*record = entry->record;
return true;
}
bool turn_find_bound_channel(turn_map_t *map, uint16_t channel, addr_record_t *record) {
if (!is_valid_channel(channel)) {
JLOG_WARN("Invalid channel number: 0x%hX", channel);
return false;
}
int pos = find_ordered_channel(map, channel);
if (pos == map->channels_count)
return false;
const turn_entry_t *entry = map->ordered_channels[pos];
if (entry->channel != channel || current_timestamp() >= entry->timestamp)
return false;
if (record)
*record = entry->record;
return true;
}
static bool set_transaction_id(turn_map_t *map, turn_entry_type_t type, const addr_record_t *record,
const uint8_t *transaction_id) {
if (type != TURN_ENTRY_TYPE_PERMISSION && type != TURN_ENTRY_TYPE_CHANNEL)
return false;
turn_entry_t *entry = find_entry(map, record, type, true);
if (!entry)
return false;
if (entry->type == type && !memory_is_zero(entry->transaction_id, STUN_TRANSACTION_ID_SIZE))
remove_ordered_transaction_id(map, entry->transaction_id);
int pos = find_ordered_transaction_id(map, transaction_id);
memmove(map->ordered_transaction_ids + pos + 1, map->ordered_transaction_ids + pos,
(map->transaction_ids_count - pos) * sizeof(turn_entry_t *));
map->ordered_transaction_ids[pos] = entry;
map->transaction_ids_count++;
if (entry->type != type) {
entry->type = type;
entry->record = *record;
}
memcpy(entry->transaction_id, transaction_id, STUN_TRANSACTION_ID_SIZE);
entry->fresh_transaction_id = true;
return true;
}
static bool find_transaction_id(turn_map_t *map, const uint8_t *transaction_id,
addr_record_t *record) {
int pos = find_ordered_transaction_id(map, transaction_id);
if (pos == map->transaction_ids_count)
return false;
const turn_entry_t *entry = map->ordered_transaction_ids[pos];
if (memcmp(entry->transaction_id, transaction_id, STUN_TRANSACTION_ID_SIZE) != 0)
return false;
if (record)
*record = entry->record;
return true;
}
static bool set_random_transaction_id(turn_map_t *map, turn_entry_type_t type,
const addr_record_t *record, uint8_t *transaction_id) {
turn_entry_t *entry = find_entry(map, record, type, false);
if (entry && entry->fresh_transaction_id) {
if (transaction_id)
memcpy(transaction_id, entry->transaction_id, STUN_TRANSACTION_ID_SIZE);
return true;
}
uint8_t tid[STUN_TRANSACTION_ID_SIZE];
do {
juice_random(tid, STUN_TRANSACTION_ID_SIZE);
} while (find_transaction_id(map, tid, NULL));
if (!set_transaction_id(map, type, record, tid))
return false;
if (transaction_id)
memcpy(transaction_id, tid, STUN_TRANSACTION_ID_SIZE);
return true;
}
bool turn_set_permission_transaction_id(turn_map_t *map, const addr_record_t *record,
const uint8_t *transaction_id) {
return set_transaction_id(map, TURN_ENTRY_TYPE_PERMISSION, record, transaction_id);
}
bool turn_set_channel_transaction_id(turn_map_t *map, const addr_record_t *record,
const uint8_t *transaction_id) {
return set_transaction_id(map, TURN_ENTRY_TYPE_CHANNEL, record, transaction_id);
}
bool turn_set_random_permission_transaction_id(turn_map_t *map, const addr_record_t *record,
uint8_t *transaction_id) {
return set_random_transaction_id(map, TURN_ENTRY_TYPE_PERMISSION, record, transaction_id);
}
bool turn_set_random_channel_transaction_id(turn_map_t *map, const addr_record_t *record,
uint8_t *transaction_id) {
return set_random_transaction_id(map, TURN_ENTRY_TYPE_CHANNEL, record, transaction_id);
}
bool turn_retrieve_transaction_id(turn_map_t *map, const uint8_t *transaction_id,
addr_record_t *record) {
int pos = find_ordered_transaction_id(map, transaction_id);
if (pos == map->transaction_ids_count)
return false;
turn_entry_t *entry = map->ordered_transaction_ids[pos];
if (memcmp(entry->transaction_id, transaction_id, STUN_TRANSACTION_ID_SIZE) != 0)
return false;
if (record)
*record = entry->record;
entry->fresh_transaction_id = false;
return true;
}

111
thirdparty/libjuice/src/turn.h vendored Normal file
View File

@@ -0,0 +1,111 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_TURN_H
#define JUICE_TURN_H
#include "addr.h"
#include "ice.h"
#include "juice.h"
#include "log.h"
#include "stun.h"
#include "timestamp.h"
#include <stdint.h>
#pragma pack(push, 1)
/*
* TURN ChannelData Message
* See https://www.rfc-editor.org/rfc/rfc8656.html#section-12.4
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Channel Number | Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* / Application Data /
* / /
* | |
* | +-------------------------------+
* | |
* +-------------------------------+
*/
struct channel_data_header {
uint16_t channel_number;
uint16_t length;
};
#pragma pack(pop)
bool is_channel_data(const void *data, size_t size);
bool is_valid_channel(uint16_t channel);
int turn_wrap_channel_data(char *buffer, size_t size, const char *data, size_t data_size,
uint16_t channel);
// TURN state map
typedef enum turn_entry_type {
TURN_ENTRY_TYPE_EMPTY = 0,
TURN_ENTRY_TYPE_DELETED,
TURN_ENTRY_TYPE_PERMISSION,
TURN_ENTRY_TYPE_CHANNEL
} turn_entry_type_t;
typedef struct turn_entry {
turn_entry_type_t type;
timestamp_t timestamp;
addr_record_t record;
uint8_t transaction_id[STUN_TRANSACTION_ID_SIZE];
uint16_t channel;
bool fresh_transaction_id;
} turn_entry_t;
typedef struct turn_map {
turn_entry_t *map;
turn_entry_t **ordered_channels;
turn_entry_t **ordered_transaction_ids;
int map_size;
int channels_count;
int transaction_ids_count;
} turn_map_t;
int turn_init_map(turn_map_t *map, int size);
void turn_destroy_map(turn_map_t *map);
bool turn_set_permission(turn_map_t *map, const uint8_t *transaction_id,
const addr_record_t *record, // record may be NULL
timediff_t duration);
bool turn_has_permission(turn_map_t *map, const addr_record_t *record);
bool turn_bind_channel(turn_map_t *map, const addr_record_t *record,
const uint8_t *transaction_id, // transaction_id may be NULL
uint16_t channel, timediff_t duration);
bool turn_bind_random_channel(turn_map_t *map, const addr_record_t *record, uint16_t *channel,
timediff_t duration);
bool turn_bind_current_channel(turn_map_t *map, const uint8_t *transaction_id,
const addr_record_t *record, // record may be NULL
timediff_t duration);
bool turn_get_channel(turn_map_t *map, const addr_record_t *record, uint16_t *channel);
bool turn_get_bound_channel(turn_map_t *map, const addr_record_t *record, uint16_t *channel);
bool turn_find_channel(turn_map_t *map, uint16_t channel, addr_record_t *record);
bool turn_find_bound_channel(turn_map_t *map, uint16_t channel, addr_record_t *record);
bool turn_set_permission_transaction_id(turn_map_t *map, const addr_record_t *record,
const uint8_t *transaction_id);
bool turn_set_channel_transaction_id(turn_map_t *map, const addr_record_t *record,
const uint8_t *transaction_id);
bool turn_set_random_permission_transaction_id(turn_map_t *map, const addr_record_t *record,
uint8_t *transaction_id);
bool turn_set_random_channel_transaction_id(turn_map_t *map, const addr_record_t *record,
uint8_t *transaction_id);
bool turn_retrieve_transaction_id(turn_map_t *map, const uint8_t *transaction_id,
addr_record_t *record);
#endif

604
thirdparty/libjuice/src/udp.c vendored Normal file
View File

@@ -0,0 +1,604 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "udp.h"
#include "addr.h"
#include "log.h"
#include "random.h"
#include "thread.h" // for mutexes
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
static struct addrinfo *find_family(struct addrinfo *ai_list, int family) {
struct addrinfo *ai = ai_list;
while (ai && ai->ai_family != family)
ai = ai->ai_next;
return ai;
}
static uint16_t get_next_port_in_range(uint16_t begin, uint16_t end) {
if (begin == 0)
begin = 1024;
if (end == 0)
end = 0xFFFF;
if (begin == end)
return begin;
static volatile uint32_t count = 0;
if (count == 0)
count = juice_rand32();
static mutex_t mutex = MUTEX_INITIALIZER;
mutex_lock(&mutex);
uint32_t diff = end > begin ? end - begin : 0;
uint16_t next = begin + count++ % (diff + 1);
mutex_unlock(&mutex);
return next;
}
socket_t udp_create_socket(const udp_socket_config_t *config) {
socket_t sock = INVALID_SOCKET;
// Obtain local Address
struct addrinfo *ai_list = NULL;
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
if (getaddrinfo(config->bind_address, "0", &hints, &ai_list) != 0) {
JLOG_ERROR("getaddrinfo for binding address failed, errno=%d", sockerrno);
return INVALID_SOCKET;
}
// Create socket
struct addrinfo *ai = NULL;
const int families[2] = {AF_INET6, AF_INET}; // Prefer IPv6
const char *names[2] = {"IPv6", "IPv4"};
for (int i = 0; i < 2; ++i) {
ai = find_family(ai_list, families[i]);
if (!ai)
continue;
sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (sock == INVALID_SOCKET) {
JLOG_WARN("UDP socket creation for %s family failed, errno=%d", names[i], sockerrno);
continue;
}
break;
}
if (sock == INVALID_SOCKET) {
JLOG_ERROR("UDP socket creation failed: no suitable address family");
goto error;
}
assert(ai != NULL);
// Listen on both IPv6 and IPv4
const sockopt_t disabled = 0;
if (ai->ai_family == AF_INET6)
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&disabled, sizeof(disabled));
// Set DF flag
#ifndef NO_PMTUDISC
const sockopt_t val = IP_PMTUDISC_DO;
setsockopt(sock, IPPROTO_IP, IP_MTU_DISCOVER, (const char *)&val, sizeof(val));
#ifdef IPV6_MTU_DISCOVER
if (ai->ai_family == AF_INET6)
setsockopt(sock, IPPROTO_IPV6, IPV6_MTU_DISCOVER, (const char *)&val, sizeof(val));
#endif
#else
// It seems Mac OS lacks a way to set the DF flag...
const sockopt_t enabled = 1;
#ifdef IP_DONTFRAG
setsockopt(sock, IPPROTO_IP, IP_DONTFRAG, (const char *)&enabled, sizeof(enabled));
#endif
#ifdef IPV6_DONTFRAG
if (ai->ai_family == AF_INET6)
setsockopt(sock, IPPROTO_IPV6, IPV6_DONTFRAG, (const char *)&enabled, sizeof(enabled));
#endif
#endif
// Set buffer size up to 1 MiB for performance
const sockopt_t buffer_size = 1 * 1024 * 1024;
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (const char *)&buffer_size, sizeof(buffer_size));
setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (const char *)&buffer_size, sizeof(buffer_size));
ctl_t nbio = 1;
if (ioctlsocket(sock, FIONBIO, &nbio)) {
JLOG_ERROR("Setting non-blocking mode on UDP socket failed, errno=%d", sockerrno);
goto error;
}
// Bind it
if (config->port_begin == 0 && config->port_end == 0) {
if (bind(sock, ai->ai_addr, (socklen_t)ai->ai_addrlen) == 0) {
JLOG_DEBUG("UDP socket bound to %s:%hu",
config->bind_address ? config->bind_address : "any", udp_get_port(sock));
freeaddrinfo(ai_list);
return sock;
}
JLOG_ERROR("UDP socket binding failed, errno=%d", sockerrno);
} else if (config->port_begin == config->port_end) {
uint16_t port = config->port_begin;
struct sockaddr_storage addr;
socklen_t addrlen = (socklen_t)ai->ai_addrlen;
memcpy(&addr, ai->ai_addr, addrlen);
addr_set_port((struct sockaddr *)&addr, port);
if (bind(sock, (struct sockaddr *)&addr, addrlen) == 0) {
JLOG_DEBUG("UDP socket bound to %s:%hu",
config->bind_address ? config->bind_address : "any", port);
freeaddrinfo(ai_list);
return sock;
}
JLOG_ERROR("UDP socket binding failed on port %hu, errno=%d", port, sockerrno);
} else {
struct sockaddr_storage addr;
socklen_t addrlen = (socklen_t)ai->ai_addrlen;
memcpy(&addr, ai->ai_addr, addrlen);
int retries = config->port_end - config->port_begin;
do {
uint16_t port = get_next_port_in_range(config->port_begin, config->port_end);
addr_set_port((struct sockaddr *)&addr, port);
if (bind(sock, (struct sockaddr *)&addr, addrlen) == 0) {
JLOG_DEBUG("UDP socket bound to %s:%hu",
config->bind_address ? config->bind_address : "any", port);
freeaddrinfo(ai_list);
return sock;
}
} while ((sockerrno == SEADDRINUSE || sockerrno == SEACCES) && retries-- > 0);
JLOG_ERROR("UDP socket binding failed on port range %s:[%hu,%hu], errno=%d",
config->bind_address ? config->bind_address : "any", config->port_begin,
config->port_end, sockerrno);
}
error:
freeaddrinfo(ai_list);
if (sock != INVALID_SOCKET)
closesocket(sock);
return INVALID_SOCKET;
}
int udp_recvfrom(socket_t sock, char *buffer, size_t size, addr_record_t *src) {
while (true) {
src->len = sizeof(src->addr);
int len =
recvfrom(sock, buffer, (socklen_t)size, 0, (struct sockaddr *)&src->addr, &src->len);
if (len >= 0) {
addr_unmap_inet6_v4mapped((struct sockaddr *)&src->addr, &src->len);
} else if (sockerrno == SECONNRESET || sockerrno == SENETRESET ||
sockerrno == SECONNREFUSED) {
// On Windows, if a UDP socket receives an ICMP port unreachable response after
// sending a datagram, this error is stored, and the next call to recvfrom() returns
// WSAECONNRESET (port unreachable) or WSAENETRESET (TTL expired).
// Therefore, it may be ignored.
JLOG_DEBUG("Ignoring %s returned by recvfrom",
sockerrno == SECONNRESET
? "ECONNRESET"
: (sockerrno == SENETRESET ? "ENETRESET" : "ECONNREFUSED"));
continue;
}
return len;
}
}
int udp_sendto(socket_t sock, const char *data, size_t size, const addr_record_t *dst) {
#ifndef __linux__
addr_record_t tmp = *dst;
addr_record_t name;
name.len = sizeof(name.addr);
if (getsockname(sock, (struct sockaddr *)&name.addr, &name.len) == 0) {
if (name.addr.ss_family == AF_INET6)
addr_map_inet6_v4mapped(&tmp.addr, &tmp.len);
} else {
JLOG_WARN("getsockname failed, errno=%d", sockerrno);
}
return sendto(sock, data, (socklen_t)size, 0, (const struct sockaddr *)&tmp.addr, tmp.len);
#else
return sendto(sock, data, size, 0, (const struct sockaddr *)&dst->addr, dst->len);
#endif
}
int udp_sendto_self(socket_t sock, const char *data, size_t size) {
addr_record_t local;
if (udp_get_local_addr(sock, AF_UNSPEC, &local) < 0)
return -1;
int ret;
#ifndef __linux__
// We know local has the same address family as sock here
ret = sendto(sock, data, (socklen_t)size, 0, (const struct sockaddr *)&local.addr, local.len);
#else
ret = sendto(sock, data, size, 0, (const struct sockaddr *)&local.addr, local.len);
#endif
if (ret >= 0 || local.addr.ss_family != AF_INET6)
return ret;
// Fallback as IPv6 may be disabled on the loopback interface
if (udp_get_local_addr(sock, AF_INET, &local) < 0)
return -1;
#ifndef __linux__
addr_map_inet6_v4mapped(&local.addr, &local.len);
return sendto(sock, data, (socklen_t)size, 0, (const struct sockaddr *)&local.addr, local.len);
#else
return sendto(sock, data, size, 0, (const struct sockaddr *)&local.addr, local.len);
#endif
}
int udp_set_diffserv(socket_t sock, int ds) {
#ifdef _WIN32
// IP_TOS has been intentionally broken on Windows in favor of a convoluted proprietary
// mechanism called qWave. Thank you Microsoft!
// TODO: Investigate if DSCP can be still set directly without administrator flow configuration.
(void)sock;
(void)ds;
JLOG_INFO("IP Differentiated Services are not supported on Windows");
return -1;
#else
addr_record_t name;
name.len = sizeof(name.addr);
if (getsockname(sock, (struct sockaddr *)&name.addr, &name.len) < 0) {
JLOG_WARN("getsockname failed, errno=%d", sockerrno);
return -1;
}
switch (name.addr.ss_family) {
case AF_INET:
#ifdef IP_TOS
if (setsockopt(sock, IPPROTO_IP, IP_TOS, &ds, sizeof(ds)) < 0) {
JLOG_WARN("Setting IP ToS failed, errno=%d", sockerrno);
return -1;
}
return 0;
#else
JLOG_INFO("Setting IP ToS is not supported");
return -1;
#endif
case AF_INET6:
#ifdef IPV6_TCLASS
if (setsockopt(sock, IPPROTO_IPV6, IPV6_TCLASS, &ds, sizeof(ds)) < 0) {
JLOG_WARN("Setting IPv6 traffic class failed, errno=%d", sockerrno);
return -1;
}
#ifdef IP_TOS
// Attempt to also set IP_TOS for IPv4, in case the system requires it
setsockopt(sock, IPPROTO_IP, IP_TOS, &ds, sizeof(ds));
#endif
return 0;
#else
JLOG_INFO("Setting IPv6 traffic class is not supported");
return -1;
#endif
default:
return -1;
}
#endif
}
uint16_t udp_get_port(socket_t sock) {
addr_record_t record;
if (udp_get_bound_addr(sock, &record) < 0)
return 0;
return addr_get_port((struct sockaddr *)&record.addr);
}
int udp_get_bound_addr(socket_t sock, addr_record_t *record) {
record->len = sizeof(record->addr);
if (getsockname(sock, (struct sockaddr *)&record->addr, &record->len)) {
JLOG_WARN("getsockname failed, errno=%d", sockerrno);
return -1;
}
return 0;
}
int udp_get_local_addr(socket_t sock, int family_hint, addr_record_t *record) {
if (udp_get_bound_addr(sock, record) < 0)
return -1;
// If the socket is bound to a particular address, return it
if (!addr_is_any((struct sockaddr *)&record->addr)) {
if (record->addr.ss_family == AF_INET && family_hint == AF_INET6)
addr_map_inet6_v4mapped(&record->addr, &record->len);
return 0;
}
if (record->addr.ss_family == AF_INET6 && family_hint == AF_INET) {
// Generate an IPv4 instead (socket is listening to any IPv4 or IPv6)
uint16_t port = addr_get_port((struct sockaddr *)&record->addr);
if (port == 0)
return -1;
struct sockaddr_in *sin = (struct sockaddr_in *)&record->addr;
memset(sin, 0, sizeof(*sin));
sin->sin_family = AF_INET;
sin->sin_port = htons(port);
record->len = sizeof(*sin);
}
switch (record->addr.ss_family) {
case AF_INET: {
struct sockaddr_in *sin = (struct sockaddr_in *)&record->addr;
const uint8_t localhost[4] = {127, 0, 0, 1};
memcpy(&sin->sin_addr, localhost, 4);
break;
}
case AF_INET6: {
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&record->addr;
uint8_t *b = (uint8_t *)&sin6->sin6_addr;
memset(b, 0, 15);
b[15] = 0x01; // localhost
break;
}
default:
// Ignore
break;
}
if (record->addr.ss_family == AF_INET && family_hint == AF_INET6)
addr_map_inet6_v4mapped(&record->addr, &record->len);
return 0;
}
// Helper function to check if a similar address already exists in records
// This function ignores the port
static int has_duplicate_addr(struct sockaddr *addr, const addr_record_t *records, size_t count) {
for (size_t i = 0; i < count; ++i) {
const addr_record_t *record = records + i;
if (record->addr.ss_family == addr->sa_family) {
switch (addr->sa_family) {
case AF_INET: {
// For IPv4, compare the whole address
const struct sockaddr_in *rsin = (const struct sockaddr_in *)&record->addr;
const struct sockaddr_in *asin = (const struct sockaddr_in *)addr;
if (memcmp(&rsin->sin_addr, &asin->sin_addr, 4) == 0)
return true;
break;
}
case AF_INET6: {
// For IPv6, compare the network part only
const struct sockaddr_in6 *rsin6 = (const struct sockaddr_in6 *)&record->addr;
const struct sockaddr_in6 *asin6 = (const struct sockaddr_in6 *)addr;
if (memcmp(&rsin6->sin6_addr, &asin6->sin6_addr, 8) == 0) // compare first 64 bits
return true;
break;
}
}
}
}
return false;
}
#if !defined(_WIN32) && defined(NO_IFADDRS)
// Helper function to get the IPv6 address of the default interface
static int get_local_default_inet6(uint16_t port, struct sockaddr_in6 *result) {
const char *dummy_host = "2001:db8::1"; // dummy public unreachable address
const uint16_t dummy_port = 9; // discard port
struct sockaddr_in6 sin6;
memset(&sin6, 0, sizeof(sin6));
sin6.sin6_family = AF_INET6;
sin6.sin6_port = htons(dummy_port);
if (inet_pton(AF_INET6, dummy_host, &sin6.sin6_addr) != 1)
return -1;
socket_t sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
if (sock == INVALID_SOCKET)
return -1;
if (connect(sock, (const struct sockaddr *)&sin6, sizeof(sin6)))
goto error;
socklen_t result_len = sizeof(*result);
if (getsockname(sock, (struct sockaddr *)result, &result_len))
goto error;
if (result_len != sizeof(*result))
goto error;
addr_set_port((struct sockaddr *)result, port);
closesocket(sock);
return 0;
error:
closesocket(sock);
return -1;
}
#endif
int udp_get_addrs(socket_t sock, addr_record_t *records, size_t count) {
addr_record_t bound;
if (udp_get_bound_addr(sock, &bound) < 0) {
JLOG_ERROR("Getting UDP bound address failed");
return -1;
}
if (!addr_is_any((struct sockaddr *)&bound.addr)) {
if (count > 0)
records[0] = bound;
return 1;
}
uint16_t port = addr_get_port((struct sockaddr *)&bound.addr);
// RFC 8445 5.1.1.1. Host Candidates:
// Addresses from a loopback interface MUST NOT be included in the candidate addresses.
// [...]
// If gathering one or more host candidates that correspond to an IPv6 address that was
// generated using a mechanism that prevents location tracking [RFC7721], host candidates
// that correspond to IPv6 addresses that do allow location tracking, are configured on the
// same interface, and are part of the same network prefix MUST NOT be gathered. Similarly,
// when host candidates corresponding to an IPv6 address generated using a mechanism that
// prevents location tracking are gathered, then host candidates corresponding to IPv6
// link-local addresses [RFC4291] MUST NOT be gathered. The IPv6 default address selection
// specification [RFC6724] specifies that temporary addresses [RFC4941] are to be preferred
// over permanent addresses.
// IPv6 IIDs generated by modern systems are opaque so there is no way to reliably differentiate
// privacy-enabled IPv6 addresses here. Therefore, we hope the preferred addresses are listed
// first, and we never list link-local addresses.
addr_record_t *current = records;
addr_record_t *end = records + count;
int ret = 0;
#if JUICE_ENABLE_LOCALHOST_ADDRESS
// Add localhost for test purposes
addr_record_t local;
if (bound.addr.ss_family == AF_INET6 && udp_get_local_addr(sock, AF_INET6, &local) == 0) {
++ret;
if (current != end) {
*current = local;
++current;
}
}
if (udp_get_local_addr(sock, AF_INET, &local) == 0) {
++ret;
if (current != end) {
*current = local;
++current;
}
}
#endif
#ifdef _WIN32
char buf[4096];
DWORD len = 0;
if (WSAIoctl(sock, SIO_ADDRESS_LIST_QUERY, NULL, 0, buf, sizeof(buf), &len, NULL, NULL)) {
JLOG_ERROR("WSAIoctl with SIO_ADDRESS_LIST_QUERY failed, errno=%d", WSAGetLastError());
return -1;
}
SOCKET_ADDRESS_LIST *list = (SOCKET_ADDRESS_LIST *)buf;
for (int i = 0; i < list->iAddressCount; ++i) {
struct sockaddr *sa = list->Address[i].lpSockaddr;
socklen_t len = list->Address[i].iSockaddrLength;
if ((sa->sa_family == AF_INET ||
(sa->sa_family == AF_INET6 && bound.addr.ss_family == AF_INET6)) &&
!addr_is_local(sa)) {
if (!has_duplicate_addr(sa, records, current - records)) {
++ret;
if (current != end) {
memcpy(&current->addr, sa, len);
current->len = len;
addr_unmap_inet6_v4mapped((struct sockaddr *)&current->addr, &current->len);
addr_set_port((struct sockaddr *)&current->addr, port);
++current;
}
}
}
}
#else // POSIX
#ifndef NO_IFADDRS
struct ifaddrs *ifas;
if (getifaddrs(&ifas)) {
JLOG_ERROR("getifaddrs failed, errno=%d", sockerrno);
return -1;
}
for (struct ifaddrs *ifa = ifas; ifa; ifa = ifa->ifa_next) {
unsigned int flags = ifa->ifa_flags;
if (!(flags & IFF_UP) || (flags & IFF_LOOPBACK))
continue;
if (strcmp(ifa->ifa_name, "docker0") == 0)
continue;
struct sockaddr *sa = ifa->ifa_addr;
socklen_t len;
if (sa &&
(sa->sa_family == AF_INET ||
(sa->sa_family == AF_INET6 && bound.addr.ss_family == AF_INET6)) &&
!addr_is_local(sa) && (len = addr_get_len(sa)) > 0) {
if (!has_duplicate_addr(sa, records, current - records)) {
++ret;
if (current != end) {
memcpy(&current->addr, sa, len);
current->len = len;
addr_set_port((struct sockaddr *)&current->addr, port);
++current;
}
}
}
}
freeifaddrs(ifas);
#else // NO_IFADDRS defined
char buf[4096];
struct ifconf ifc;
memset(&ifc, 0, sizeof(ifc));
ifc.ifc_len = sizeof(buf);
ifc.ifc_buf = buf;
if (ioctlsocket(sock, SIOCGIFCONF, &ifc)) {
JLOG_ERROR("ioctl for SIOCGIFCONF failed, errno=%d", sockerrno);
return -1;
}
bool ifconf_has_inet6 = false;
int n = ifc.ifc_len / sizeof(struct ifreq);
for (int i = 0; i < n; ++i) {
struct ifreq *ifr = ifc.ifc_req + i;
struct sockaddr *sa = &ifr->ifr_addr;
if (sa->sa_family == AF_INET6)
ifconf_has_inet6 = true;
socklen_t len;
if ((sa->sa_family == AF_INET ||
(sa->sa_family == AF_INET6 && bound.addr.ss_family == AF_INET6)) &&
!addr_is_local(sa) && (len = addr_get_len(sa)) > 0) {
if (!has_duplicate_addr(sa, records, current - records)) {
++ret;
if (current != end) {
memcpy(&current->addr, sa, len);
current->len = len;
addr_set_port((struct sockaddr *)&current->addr, port);
++current;
}
}
}
}
if (!ifconf_has_inet6 && bound.addr.ss_family == AF_INET6) {
struct sockaddr_in6 sin6;
if (get_local_default_inet6(port, &sin6) == 0) {
if (!addr_is_local((const struct sockaddr *)&sin6)) {
++ret;
if (current != end) {
memcpy(&current->addr, &sin6, sizeof(sin6));
current->len = sizeof(sin6);
++current;
}
}
}
}
#endif
#endif
return ret;
}

33
thirdparty/libjuice/src/udp.h vendored Normal file
View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_UDP_H
#define JUICE_UDP_H
#include "addr.h"
#include "socket.h"
#include <stdint.h>
typedef struct udp_socket_config {
const char *bind_address;
uint16_t port_begin;
uint16_t port_end;
} udp_socket_config_t;
socket_t udp_create_socket(const udp_socket_config_t *config);
int udp_recvfrom(socket_t sock, char *buffer, size_t size, addr_record_t *src);
int udp_sendto(socket_t sock, const char *data, size_t size, const addr_record_t *dst);
int udp_sendto_self(socket_t sock, const char *data, size_t size);
int udp_set_diffserv(socket_t sock, int ds);
uint16_t udp_get_port(socket_t sock);
int udp_get_bound_addr(socket_t sock, addr_record_t *record);
int udp_get_local_addr(socket_t sock, int family, addr_record_t *record); // family may be AF_UNSPEC
int udp_get_addrs(socket_t sock, addr_record_t *records, size_t count);
#endif // JUICE_UDP_H

45
thirdparty/libjuice/test/base64.c vendored Normal file
View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "base64.h"
#include <stdint.h>
#include <string.h>
#define BUFFER_SIZE 1024
int test_base64(void) {
const char *str = "Man is distinguished, not only by his reason, but by this singular passion "
"from other animals, which is a lust of the mind, that by a perseverance of "
"delight in the continued and indefatigable generation of knowledge, exceeds "
"the short vehemence of any carnal pleasure.";
const char *expected =
"TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIH"
"Bhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBw"
"ZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb2"
"4gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4"
"=";
char buffer1[BUFFER_SIZE];
if (BASE64_ENCODE(str, strlen(str), buffer1, BUFFER_SIZE) <= 0)
return -1;
if (strcmp(buffer1, expected) != 0)
return -1;
char buffer2[BUFFER_SIZE];
int len = BASE64_DECODE(buffer1, buffer2, BUFFER_SIZE);
if (len <= 0)
return -1;
buffer2[len] = '\0';
if (strcmp(buffer2, str) != 0)
return -1;
return 0;
}

225
thirdparty/libjuice/test/bind.c vendored Normal file
View File

@@ -0,0 +1,225 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "juice/juice.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
static void sleep(unsigned int secs) { Sleep(secs * 1000); }
#else
#include <unistd.h> // for sleep
#endif
#define BUFFER_SIZE 4096
#define BIND_ADDRESS "127.0.0.1"
static juice_agent_t *agent1;
static juice_agent_t *agent2;
static void on_state_changed1(juice_agent_t *agent, juice_state_t state, void *user_ptr);
static void on_state_changed2(juice_agent_t *agent, juice_state_t state, void *user_ptr);
static void on_candidate1(juice_agent_t *agent, const char *sdp, void *user_ptr);
static void on_candidate2(juice_agent_t *agent, const char *sdp, void *user_ptr);
static void on_gathering_done1(juice_agent_t *agent, void *user_ptr);
static void on_gathering_done2(juice_agent_t *agent, void *user_ptr);
static void on_recv1(juice_agent_t *agent, const char *data, size_t size, void *user_ptr);
static void on_recv2(juice_agent_t *agent, const char *data, size_t size, void *user_ptr);
int test_bind() {
juice_set_log_level(JUICE_LOG_LEVEL_DEBUG);
// Agent 1: Create agent
juice_config_t config1;
memset(&config1, 0, sizeof(config1));
config1.bind_address = BIND_ADDRESS;
config1.cb_state_changed = on_state_changed1;
config1.cb_candidate = on_candidate1;
config1.cb_gathering_done = on_gathering_done1;
config1.cb_recv = on_recv1;
config1.user_ptr = NULL;
agent1 = juice_create(&config1);
// Agent 2: Create agent
juice_config_t config2;
memset(&config2, 0, sizeof(config2));
config2.bind_address = BIND_ADDRESS;
config2.cb_state_changed = on_state_changed2;
config2.cb_candidate = on_candidate2;
config2.cb_gathering_done = on_gathering_done2;
config2.cb_recv = on_recv2;
config2.user_ptr = NULL;
agent2 = juice_create(&config2);
// Agent 1: Generate local description
char sdp1[JUICE_MAX_SDP_STRING_LEN];
juice_get_local_description(agent1, sdp1, JUICE_MAX_SDP_STRING_LEN);
printf("Local description 1:\n%s\n", sdp1);
// Agent 2: Receive description from agent 1
juice_set_remote_description(agent2, sdp1);
// Agent 2: Generate local description
char sdp2[JUICE_MAX_SDP_STRING_LEN];
juice_get_local_description(agent2, sdp2, JUICE_MAX_SDP_STRING_LEN);
printf("Local description 2:\n%s\n", sdp2);
// Agent 1: Receive description from agent 2
juice_set_remote_description(agent1, sdp2);
// Agent 1: Gather candidates (and send them to agent 2)
juice_gather_candidates(agent1);
sleep(2);
// Agent 2: Gather candidates (and send them to agent 1)
juice_gather_candidates(agent2);
sleep(2);
// -- Connection should be finished --
bool success = true;
/*
// Check states
juice_state_t state1 = juice_get_state(agent1);
juice_state_t state2 = juice_get_state(agent2);
bool success = (state1 == JUICE_STATE_COMPLETED && state2 == JUICE_STATE_COMPLETED);
*/
// Retrieve candidates
char local[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
char remote[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
if (success &=
(juice_get_selected_candidates(agent1, local, JUICE_MAX_CANDIDATE_SDP_STRING_LEN, remote,
JUICE_MAX_CANDIDATE_SDP_STRING_LEN) == 0)) {
printf("Local candidate 1: %s\n", local);
printf("Remote candidate 1: %s\n", remote);
}
if (success &=
(juice_get_selected_candidates(agent2, local, JUICE_MAX_CANDIDATE_SDP_STRING_LEN, remote,
JUICE_MAX_CANDIDATE_SDP_STRING_LEN) == 0)) {
printf("Local candidate 2: %s\n", local);
printf("Remote candidate 2: %s\n", remote);
}
// Retrieve addresses
char localAddr[JUICE_MAX_ADDRESS_STRING_LEN];
char remoteAddr[JUICE_MAX_ADDRESS_STRING_LEN];
if (success &= (juice_get_selected_addresses(agent1, localAddr, JUICE_MAX_ADDRESS_STRING_LEN,
remoteAddr, JUICE_MAX_ADDRESS_STRING_LEN) == 0)) {
printf("Local address 1: %s\n", localAddr);
printf("Remote address 1: %s\n", remoteAddr);
}
if (success &= (juice_get_selected_addresses(agent2, localAddr, JUICE_MAX_ADDRESS_STRING_LEN,
remoteAddr, JUICE_MAX_ADDRESS_STRING_LEN) == 0)) {
printf("Local address 2: %s\n", localAddr);
printf("Remote address 2: %s\n", remoteAddr);
}
// Agent 1: destroy
juice_destroy(agent1);
// Agent 2: destroy
juice_destroy(agent2);
if (success) {
printf("Success\n");
return 0;
} else {
printf("Failure\n");
return -1;
}
}
// Agent 1: on state changed
static void on_state_changed1(juice_agent_t *agent, juice_state_t state, void *user_ptr) {
printf("State 1: %s\n", juice_state_to_string(state));
if (state == JUICE_STATE_CONNECTED) {
// Agent 1: on connected, send a message
const char *message = "Hello from 1";
juice_send(agent, message, strlen(message));
}
}
// Agent 2: on state changed
static void on_state_changed2(juice_agent_t *agent, juice_state_t state, void *user_ptr) {
printf("State 2: %s\n", juice_state_to_string(state));
if (state == JUICE_STATE_CONNECTED) {
// Agent 2: on connected, send a message
const char *message = "Hello from 2";
juice_send(agent, message, strlen(message));
}
}
// Agent 1: on local candidate gathered
static void on_candidate1(juice_agent_t *agent, const char *sdp, void *user_ptr) {
printf("Candidate 1: %s\n", sdp);
// Filter host candidates for the bind address
if(!strstr(sdp, "host") || !strstr(sdp, BIND_ADDRESS))
return;
// Agent 2: Receive it from agent 1
juice_add_remote_candidate(agent2, sdp);
}
// Agent 2: on local candidate gathered
static void on_candidate2(juice_agent_t *agent, const char *sdp, void *user_ptr) {
printf("Candidate 2: %s\n", sdp);
// Filter host candidates for the bind address
if(!strstr(sdp, "host") || !strstr(sdp, BIND_ADDRESS))
return;
// Agent 1: Receive it from agent 2
juice_add_remote_candidate(agent1, sdp);
}
// Agent 1: on local candidates gathering done
static void on_gathering_done1(juice_agent_t *agent, void *user_ptr) {
printf("Gathering done 1\n");
juice_set_remote_gathering_done(agent2); // optional
}
// Agent 2: on local candidates gathering done
static void on_gathering_done2(juice_agent_t *agent, void *user_ptr) {
printf("Gathering done 2\n");
juice_set_remote_gathering_done(agent1); // optional
}
// Agent 1: on message received
static void on_recv1(juice_agent_t *agent, const char *data, size_t size, void *user_ptr) {
char buffer[BUFFER_SIZE];
if (size > BUFFER_SIZE - 1)
size = BUFFER_SIZE - 1;
memcpy(buffer, data, size);
buffer[size] = '\0';
printf("Received 1: %s\n", buffer);
}
// Agent 2: on message received
static void on_recv2(juice_agent_t *agent, const char *data, size_t size, void *user_ptr) {
char buffer[BUFFER_SIZE];
if (size > BUFFER_SIZE - 1)
size = BUFFER_SIZE - 1;
memcpy(buffer, data, size);
buffer[size] = '\0';
printf("Received 2: %s\n", buffer);
}

203
thirdparty/libjuice/test/conflict.c vendored Normal file
View File

@@ -0,0 +1,203 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "juice/juice.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
static void sleep(unsigned int secs) { Sleep(secs * 1000); }
#else
#include <unistd.h> // for sleep
#endif
#define BUFFER_SIZE 4096
static juice_agent_t *agent1;
static juice_agent_t *agent2;
static void on_state_changed1(juice_agent_t *agent, juice_state_t state, void *user_ptr);
static void on_state_changed2(juice_agent_t *agent, juice_state_t state, void *user_ptr);
static void on_candidate1(juice_agent_t *agent, const char *sdp, void *user_ptr);
static void on_candidate2(juice_agent_t *agent, const char *sdp, void *user_ptr);
static void on_gathering_done1(juice_agent_t *agent, void *user_ptr);
static void on_gathering_done2(juice_agent_t *agent, void *user_ptr);
static void on_recv1(juice_agent_t *agent, const char *data, size_t size, void *user_ptr);
static void on_recv2(juice_agent_t *agent, const char *data, size_t size, void *user_ptr);
int test_conflict() {
juice_set_log_level(JUICE_LOG_LEVEL_DEBUG);
// Agent 1: Create agent
juice_config_t config1;
memset(&config1, 0, sizeof(config1));
config1.cb_state_changed = on_state_changed1;
config1.cb_candidate = on_candidate1;
config1.cb_gathering_done = on_gathering_done1;
config1.cb_recv = on_recv1;
config1.user_ptr = NULL;
agent1 = juice_create(&config1);
// Agent 2: Create agent
juice_config_t config2;
memset(&config2, 0, sizeof(config2));
config2.cb_state_changed = on_state_changed2;
config2.cb_candidate = on_candidate2;
config2.cb_gathering_done = on_gathering_done2;
config2.cb_recv = on_recv2;
config2.user_ptr = NULL;
agent2 = juice_create(&config2);
// Agent 1: Generate local description
char sdp1[JUICE_MAX_SDP_STRING_LEN];
juice_get_local_description(agent1, sdp1, JUICE_MAX_SDP_STRING_LEN);
printf("Local description 1:\n%s\n", sdp1);
// Agent 2: Generate local description
char sdp2[JUICE_MAX_SDP_STRING_LEN];
juice_get_local_description(agent2, sdp2, JUICE_MAX_SDP_STRING_LEN);
printf("Local description 2:\n%s\n", sdp2);
// Setting the remote description now on both agents will hint them both into controlling mode,
// creating an ICE role conflict
// Agent 1: Receive description from agent 2
juice_set_remote_description(agent1, sdp2);
// Agent 2: Receive description from agent 1
juice_set_remote_description(agent2, sdp1);
// Agent 1: Gather candidates (and send them to agent 2)
juice_gather_candidates(agent1);
sleep(2);
// Agent 2: Gather candidates (and send them to agent 1)
juice_gather_candidates(agent2);
sleep(2);
// -- Connection should be finished --
// Check states
juice_state_t state1 = juice_get_state(agent1);
juice_state_t state2 = juice_get_state(agent2);
bool success = (state1 == JUICE_STATE_COMPLETED && state2 == JUICE_STATE_COMPLETED);
// Retrieve candidates
char local[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
char remote[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
if (success &=
(juice_get_selected_candidates(agent1, local, JUICE_MAX_CANDIDATE_SDP_STRING_LEN, remote,
JUICE_MAX_CANDIDATE_SDP_STRING_LEN) == 0)) {
printf("Local candidate 1: %s\n", local);
printf("Remote candidate 1: %s\n", remote);
if ((!strstr(local, "typ host") && !strstr(local, "typ prflx")) ||
(!strstr(remote, "typ host") && !strstr(remote, "typ prflx")))
success = false; // local connection should be possible
}
if (success &=
(juice_get_selected_candidates(agent2, local, JUICE_MAX_CANDIDATE_SDP_STRING_LEN, remote,
JUICE_MAX_CANDIDATE_SDP_STRING_LEN) == 0)) {
printf("Local candidate 2: %s\n", local);
printf("Remote candidate 2: %s\n", remote);
if ((!strstr(local, "typ host") && !strstr(local, "typ prflx")) ||
(!strstr(remote, "typ host") && !strstr(remote, "typ prflx")))
success = false; // local connection should be possible
}
// Agent 1: destroy
juice_destroy(agent1);
// Agent 2: destroy
juice_destroy(agent2);
if (success) {
printf("Success\n");
return 0;
} else {
printf("Failure\n");
return -1;
}
}
// Agent 1: on state changed
static void on_state_changed1(juice_agent_t *agent, juice_state_t state, void *user_ptr) {
printf("State 1: %s\n", juice_state_to_string(state));
if (state == JUICE_STATE_CONNECTED) {
// Agent 1: on connected, send a message
const char *message = "Hello from 1";
juice_send(agent, message, strlen(message));
}
}
// Agent 2: on state changed
static void on_state_changed2(juice_agent_t *agent, juice_state_t state, void *user_ptr) {
printf("State 2: %s\n", juice_state_to_string(state));
if (state == JUICE_STATE_CONNECTED) {
// Agent 2: on connected, send a message
const char *message = "Hello from 2";
juice_send(agent, message, strlen(message));
}
}
// Agent 1: on local candidate gathered
static void on_candidate1(juice_agent_t *agent, const char *sdp, void *user_ptr) {
printf("Candidate 1: %s\n", sdp);
// Agent 2: Receive it from agent 1
juice_add_remote_candidate(agent2, sdp);
}
// Agent 2: on local candidate gathered
static void on_candidate2(juice_agent_t *agent, const char *sdp, void *user_ptr) {
printf("Candidate 2: %s\n", sdp);
// Agent 1: Receive it from agent 2
juice_add_remote_candidate(agent1, sdp);
}
// Agent 1: on local candidates gathering done
static void on_gathering_done1(juice_agent_t *agent, void *user_ptr) {
printf("Gathering done 1\n");
juice_set_remote_gathering_done(agent2); // optional
}
// Agent 2: on local candidates gathering done
static void on_gathering_done2(juice_agent_t *agent, void *user_ptr) {
printf("Gathering done 2\n");
juice_set_remote_gathering_done(agent1); // optional
}
// Agent 1: on message received
static void on_recv1(juice_agent_t *agent, const char *data, size_t size, void *user_ptr) {
char buffer[BUFFER_SIZE];
if (size > BUFFER_SIZE - 1)
size = BUFFER_SIZE - 1;
memcpy(buffer, data, size);
buffer[size] = '\0';
printf("Received 1: %s\n", buffer);
}
// Agent 2: on message received
static void on_recv2(juice_agent_t *agent, const char *data, size_t size, void *user_ptr) {
char buffer[BUFFER_SIZE];
if (size > BUFFER_SIZE - 1)
size = BUFFER_SIZE - 1;
memcpy(buffer, data, size);
buffer[size] = '\0';
printf("Received 2: %s\n", buffer);
}

228
thirdparty/libjuice/test/connectivity.c vendored Normal file
View File

@@ -0,0 +1,228 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "juice/juice.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
static void sleep(unsigned int secs) { Sleep(secs * 1000); }
#else
#include <unistd.h> // for sleep
#endif
#define BUFFER_SIZE 4096
static juice_agent_t *agent1;
static juice_agent_t *agent2;
static void on_state_changed1(juice_agent_t *agent, juice_state_t state, void *user_ptr);
static void on_state_changed2(juice_agent_t *agent, juice_state_t state, void *user_ptr);
static void on_candidate1(juice_agent_t *agent, const char *sdp, void *user_ptr);
static void on_candidate2(juice_agent_t *agent, const char *sdp, void *user_ptr);
static void on_gathering_done1(juice_agent_t *agent, void *user_ptr);
static void on_gathering_done2(juice_agent_t *agent, void *user_ptr);
static void on_recv1(juice_agent_t *agent, const char *data, size_t size, void *user_ptr);
static void on_recv2(juice_agent_t *agent, const char *data, size_t size, void *user_ptr);
int test_connectivity() {
juice_set_log_level(JUICE_LOG_LEVEL_DEBUG);
// Agent 1: Create agent
juice_config_t config1;
memset(&config1, 0, sizeof(config1));
// STUN server example
config1.stun_server_host = "stun.l.google.com";
config1.stun_server_port = 19302;
config1.cb_state_changed = on_state_changed1;
config1.cb_candidate = on_candidate1;
config1.cb_gathering_done = on_gathering_done1;
config1.cb_recv = on_recv1;
config1.user_ptr = NULL;
agent1 = juice_create(&config1);
// Agent 2: Create agent
juice_config_t config2;
memset(&config2, 0, sizeof(config2));
// STUN server example
config2.stun_server_host = "stun.l.google.com";
config2.stun_server_port = 19302;
// Port range example
config2.local_port_range_begin = 60000;
config2.local_port_range_end = 61000;
config2.cb_state_changed = on_state_changed2;
config2.cb_candidate = on_candidate2;
config2.cb_gathering_done = on_gathering_done2;
config2.cb_recv = on_recv2;
config2.user_ptr = NULL;
agent2 = juice_create(&config2);
// Agent 1: Generate local description
char sdp1[JUICE_MAX_SDP_STRING_LEN];
juice_get_local_description(agent1, sdp1, JUICE_MAX_SDP_STRING_LEN);
printf("Local description 1:\n%s\n", sdp1);
// Agent 2: Receive description from agent 1
juice_set_remote_description(agent2, sdp1);
// Agent 2: Generate local description
char sdp2[JUICE_MAX_SDP_STRING_LEN];
juice_get_local_description(agent2, sdp2, JUICE_MAX_SDP_STRING_LEN);
printf("Local description 2:\n%s\n", sdp2);
// Agent 1: Receive description from agent 2
juice_set_remote_description(agent1, sdp2);
// Agent 1: Gather candidates (and send them to agent 2)
juice_gather_candidates(agent1);
sleep(2);
// Agent 2: Gather candidates (and send them to agent 1)
juice_gather_candidates(agent2);
sleep(2);
// -- Connection should be finished --
// Check states
juice_state_t state1 = juice_get_state(agent1);
juice_state_t state2 = juice_get_state(agent2);
bool success = (state1 == JUICE_STATE_COMPLETED && state2 == JUICE_STATE_COMPLETED);
// Retrieve candidates
char local[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
char remote[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
if (success &=
(juice_get_selected_candidates(agent1, local, JUICE_MAX_CANDIDATE_SDP_STRING_LEN, remote,
JUICE_MAX_CANDIDATE_SDP_STRING_LEN) == 0)) {
printf("Local candidate 1: %s\n", local);
printf("Remote candidate 1: %s\n", remote);
if ((!strstr(local, "typ host") && !strstr(local, "typ prflx")) ||
(!strstr(remote, "typ host") && !strstr(remote, "typ prflx")))
success = false; // local connection should be possible
}
if (success &=
(juice_get_selected_candidates(agent2, local, JUICE_MAX_CANDIDATE_SDP_STRING_LEN, remote,
JUICE_MAX_CANDIDATE_SDP_STRING_LEN) == 0)) {
printf("Local candidate 2: %s\n", local);
printf("Remote candidate 2: %s\n", remote);
if ((!strstr(local, "typ host") && !strstr(local, "typ prflx")) ||
(!strstr(remote, "typ host") && !strstr(remote, "typ prflx")))
success = false; // local connection should be possible
}
// Retrieve addresses
char localAddr[JUICE_MAX_ADDRESS_STRING_LEN];
char remoteAddr[JUICE_MAX_ADDRESS_STRING_LEN];
if (success &= (juice_get_selected_addresses(agent1, localAddr, JUICE_MAX_ADDRESS_STRING_LEN,
remoteAddr, JUICE_MAX_ADDRESS_STRING_LEN) == 0)) {
printf("Local address 1: %s\n", localAddr);
printf("Remote address 1: %s\n", remoteAddr);
}
if (success &= (juice_get_selected_addresses(agent2, localAddr, JUICE_MAX_ADDRESS_STRING_LEN,
remoteAddr, JUICE_MAX_ADDRESS_STRING_LEN) == 0)) {
printf("Local address 2: %s\n", localAddr);
printf("Remote address 2: %s\n", remoteAddr);
}
// Agent 1: destroy
juice_destroy(agent1);
// Agent 2: destroy
juice_destroy(agent2);
if (success) {
printf("Success\n");
return 0;
} else {
printf("Failure\n");
return -1;
}
}
// Agent 1: on state changed
static void on_state_changed1(juice_agent_t *agent, juice_state_t state, void *user_ptr) {
printf("State 1: %s\n", juice_state_to_string(state));
if (state == JUICE_STATE_CONNECTED) {
// Agent 1: on connected, send a message
const char *message = "Hello from 1";
juice_send(agent, message, strlen(message));
}
}
// Agent 2: on state changed
static void on_state_changed2(juice_agent_t *agent, juice_state_t state, void *user_ptr) {
printf("State 2: %s\n", juice_state_to_string(state));
if (state == JUICE_STATE_CONNECTED) {
// Agent 2: on connected, send a message
const char *message = "Hello from 2";
juice_send(agent, message, strlen(message));
}
}
// Agent 1: on local candidate gathered
static void on_candidate1(juice_agent_t *agent, const char *sdp, void *user_ptr) {
printf("Candidate 1: %s\n", sdp);
// Agent 2: Receive it from agent 1
juice_add_remote_candidate(agent2, sdp);
}
// Agent 2: on local candidate gathered
static void on_candidate2(juice_agent_t *agent, const char *sdp, void *user_ptr) {
printf("Candidate 2: %s\n", sdp);
// Agent 1: Receive it from agent 2
juice_add_remote_candidate(agent1, sdp);
}
// Agent 1: on local candidates gathering done
static void on_gathering_done1(juice_agent_t *agent, void *user_ptr) {
printf("Gathering done 1\n");
juice_set_remote_gathering_done(agent2); // optional
}
// Agent 2: on local candidates gathering done
static void on_gathering_done2(juice_agent_t *agent, void *user_ptr) {
printf("Gathering done 2\n");
juice_set_remote_gathering_done(agent1); // optional
}
// Agent 1: on message received
static void on_recv1(juice_agent_t *agent, const char *data, size_t size, void *user_ptr) {
char buffer[BUFFER_SIZE];
if (size > BUFFER_SIZE - 1)
size = BUFFER_SIZE - 1;
memcpy(buffer, data, size);
buffer[size] = '\0';
printf("Received 1: %s\n", buffer);
}
// Agent 2: on message received
static void on_recv2(juice_agent_t *agent, const char *data, size_t size, void *user_ptr) {
char buffer[BUFFER_SIZE];
if (size > BUFFER_SIZE - 1)
size = BUFFER_SIZE - 1;
memcpy(buffer, data, size);
buffer[size] = '\0';
printf("Received 2: %s\n", buffer);
}

22
thirdparty/libjuice/test/crc32.c vendored Normal file
View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "crc32.h"
#include <stdint.h>
#include <string.h>
int test_crc32(void) {
const char *str = "The quick brown fox jumps over the lazy dog";
uint32_t expected = 0x414fa339;
if (CRC32(str, strlen(str)) != expected)
return -1;
return 0;
}

95
thirdparty/libjuice/test/gathering.c vendored Normal file
View File

@@ -0,0 +1,95 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "juice/juice.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
static void sleep(unsigned int secs) { Sleep(secs * 1000); }
#else
#include <unistd.h> // for sleep
#endif
#define BUFFER_SIZE 4096
static juice_agent_t *agent;
static bool success = false;
static bool done = false;
static void on_state_changed(juice_agent_t *agent, juice_state_t state, void *user_ptr);
static void on_candidate(juice_agent_t *agent, const char *sdp, void *user_ptr);
static void on_gathering_done(juice_agent_t *agent, void *user_ptr);
int test_gathering() {
juice_set_log_level(JUICE_LOG_LEVEL_DEBUG);
// Create agent
juice_config_t config;
memset(&config, 0, sizeof(config));
// STUN server example
config.stun_server_host = "stun.l.google.com";
config.stun_server_port = 19302;
config.cb_state_changed = on_state_changed;
config.cb_candidate = on_candidate;
config.cb_gathering_done = on_gathering_done;
config.user_ptr = NULL;
agent = juice_create(&config);
// Generate local description
char sdp[JUICE_MAX_SDP_STRING_LEN];
juice_get_local_description(agent, sdp, JUICE_MAX_SDP_STRING_LEN);
printf("Local description:\n%s\n", sdp);
// Gather candidates
juice_gather_candidates(agent);
// Wait until gathering done
int secs = 10;
while (secs-- && !done && !success)
sleep(1);
// Destroy
juice_destroy(agent);
if (success) {
printf("Success\n");
return 0;
} else {
printf("Failure\n");
return -1;
}
}
// On state changed
static void on_state_changed(juice_agent_t *agent, juice_state_t state, void *user_ptr) {
printf("State: %s\n", juice_state_to_string(state));
}
// On local candidate gathered
static void on_candidate(juice_agent_t *agent, const char *sdp, void *user_ptr) {
printf("Candidate: %s\n", sdp);
// Success if a valid srflx candidate is emitted
if (strstr(sdp, " typ srflx raddr 0.0.0.0 rport 0"))
success = true;
}
// On local candidates gathering done
static void on_gathering_done(juice_agent_t *agent, void *user_ptr) {
printf("Gathering done\n");
done = true;
}

110
thirdparty/libjuice/test/main.c vendored Normal file
View File

@@ -0,0 +1,110 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "juice/juice.h"
#include <stdio.h>
int test_crc32(void);
int test_base64(void);
int test_stun(void);
int test_connectivity(void);
int test_thread(void);
int test_mux(void);
int test_notrickle(void);
int test_gathering(void);
int test_turn(void);
int test_conflict(void);
int test_bind(void);
#ifndef NO_SERVER
int test_server(void);
#endif
int main(int argc, char **argv) {
juice_set_log_level(JUICE_LOG_LEVEL_WARN);
printf("\nRunning CRC32 implementation test...\n");
if (test_crc32()) {
fprintf(stderr, "CRC32 implementation test failed\n");
return -2;
}
printf("\nRunning base64 implementation test...\n");
if (test_base64()) {
fprintf(stderr, "base64 implementation test failed\n");
return -2;
}
printf("\nRunning STUN parsing implementation test...\n");
if (test_stun()) {
fprintf(stderr, "STUN parsing implementation test failed\n");
return -3;
}
printf("\nRunning candidates gathering test...\n");
if (test_gathering()) {
fprintf(stderr, "Candidates gathering test failed\n");
return -1;
}
printf("\nRunning connectivity test...\n");
if (test_connectivity()) {
fprintf(stderr, "Connectivity test failed\n");
return -1;
}
// Disabled as the Open Relay TURN server is unreliable
/*
printf("\nRunning TURN connectivity test...\n");
if (test_turn()) {
fprintf(stderr, "TURN connectivity test failed\n");
return -1;
}
*/
printf("\nRunning thread-mode connectivity test...\n");
if (test_thread()) {
fprintf(stderr, "Thread-mode connectivity test failed\n");
return -1;
}
printf("\nRunning mux-mode connectivity test...\n");
if (test_mux()) {
fprintf(stderr, "Mux-mode connectivity test failed\n");
return -1;
}
printf("\nRunning non-trickled connectivity test...\n");
if (test_notrickle()) {
fprintf(stderr, "Non-trickled connectivity test failed\n");
return -1;
}
printf("\nRunning connectivity test with role conflict...\n");
if (test_conflict()) {
fprintf(stderr, "Connectivity test with role conflict failed\n");
return -1;
}
printf("\nRunning connectivity test with bind address...\n");
if (test_bind()) {
fprintf(stderr, "Connectivity test with bind address failed\n");
return -1;
}
#ifndef NO_SERVER
printf("\nRunning server test...\n");
if (test_server()) {
fprintf(stderr, "Server test failed\n");
return -1;
}
#endif
return 0;
}

226
thirdparty/libjuice/test/mux.c vendored Normal file
View File

@@ -0,0 +1,226 @@
/**
* Copyright (c) 2022 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "juice/juice.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
static void sleep(unsigned int secs) { Sleep(secs * 1000); }
#else
#include <unistd.h> // for sleep
#endif
#define BUFFER_SIZE 4096
static juice_agent_t *agent1;
static juice_agent_t *agent2;
static void on_state_changed1(juice_agent_t *agent, juice_state_t state, void *user_ptr);
static void on_state_changed2(juice_agent_t *agent, juice_state_t state, void *user_ptr);
static void on_candidate1(juice_agent_t *agent, const char *sdp, void *user_ptr);
static void on_candidate2(juice_agent_t *agent, const char *sdp, void *user_ptr);
static void on_gathering_done1(juice_agent_t *agent, void *user_ptr);
static void on_gathering_done2(juice_agent_t *agent, void *user_ptr);
static void on_recv1(juice_agent_t *agent, const char *data, size_t size, void *user_ptr);
static void on_recv2(juice_agent_t *agent, const char *data, size_t size, void *user_ptr);
bool endswith(const char *str, const char *suffix) {
size_t str_len = strlen(str);
size_t suffix_len = strlen(suffix);
return str_len >= suffix_len && memcmp(str + str_len - suffix_len, suffix, suffix_len) == 0;
}
int test_mux() {
juice_set_log_level(JUICE_LOG_LEVEL_DEBUG);
// Agent 1: Create agent
juice_config_t config1;
memset(&config1, 0, sizeof(config1));
config1.stun_server_host = "stun.l.google.com";
config1.stun_server_port = 19302;
config1.cb_state_changed = on_state_changed1;
config1.cb_candidate = on_candidate1;
config1.cb_gathering_done = on_gathering_done1;
config1.cb_recv = on_recv1;
config1.user_ptr = NULL;
agent1 = juice_create(&config1);
// Agent 2: Create agent in mux mode on port 60000
juice_config_t config2;
memset(&config2, 0, sizeof(config2));
config2.concurrency_mode = JUICE_CONCURRENCY_MODE_MUX;
config2.local_port_range_begin = 60000;
config2.local_port_range_end = 60000;
config2.cb_state_changed = on_state_changed1;
config2.cb_candidate = on_candidate1;
config2.cb_gathering_done = on_gathering_done1;
config2.cb_recv = on_recv1;
config2.user_ptr = NULL;
agent2 = juice_create(&config2);
// Agent 1: Generate local description
char sdp1[JUICE_MAX_SDP_STRING_LEN];
juice_get_local_description(agent1, sdp1, JUICE_MAX_SDP_STRING_LEN);
printf("Local description 1:\n%s\n", sdp1);
// Agent 2: Receive description from agent 1
juice_set_remote_description(agent2, sdp1);
// Agent 2: Generate local description
char sdp2[JUICE_MAX_SDP_STRING_LEN];
juice_get_local_description(agent2, sdp2, JUICE_MAX_SDP_STRING_LEN);
printf("Local description 2:\n%s\n", sdp2);
// Agent 1: Receive description from agent 2
juice_set_remote_description(agent1, sdp2);
// Agent 1: Gather candidates (and send them to agent 2)
juice_gather_candidates(agent1);
sleep(2);
// Agent 2: Gather candidates (and send them to agent 1)
juice_gather_candidates(agent2);
sleep(2);
// -- Connection should be finished --
// Check states
juice_state_t state1 = juice_get_state(agent1);
juice_state_t state2 = juice_get_state(agent2);
bool success = (state1 == JUICE_STATE_COMPLETED && state2 == JUICE_STATE_COMPLETED);
// Retrieve candidates
char local[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
char remote[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
if (success &=
(juice_get_selected_candidates(agent1, local, JUICE_MAX_CANDIDATE_SDP_STRING_LEN, remote,
JUICE_MAX_CANDIDATE_SDP_STRING_LEN) == 0)) {
printf("Local candidate 1: %s\n", local);
printf("Remote candidate 1: %s\n", remote);
if ((!strstr(local, "typ host") && !strstr(local, "typ prflx")) ||
(!strstr(remote, "typ host") && !strstr(remote, "typ prflx")))
success = false; // local connection should be possible
}
if (success &=
(juice_get_selected_candidates(agent2, local, JUICE_MAX_CANDIDATE_SDP_STRING_LEN, remote,
JUICE_MAX_CANDIDATE_SDP_STRING_LEN) == 0)) {
printf("Local candidate 2: %s\n", local);
printf("Remote candidate 2: %s\n", remote);
if ((!strstr(local, "typ host") && !strstr(local, "typ prflx")) ||
(!strstr(remote, "typ host") && !strstr(remote, "typ prflx")))
success = false; // local connection should be possible
}
// Retrieve addresses
char localAddr[JUICE_MAX_ADDRESS_STRING_LEN];
char remoteAddr[JUICE_MAX_ADDRESS_STRING_LEN];
if (success &= (juice_get_selected_addresses(agent1, localAddr, JUICE_MAX_ADDRESS_STRING_LEN,
remoteAddr, JUICE_MAX_ADDRESS_STRING_LEN) == 0)) {
printf("Local address 1: %s\n", localAddr);
printf("Remote address 1: %s\n", remoteAddr);
success &= endswith(remoteAddr, ":60000");
}
if (success &= (juice_get_selected_addresses(agent2, localAddr, JUICE_MAX_ADDRESS_STRING_LEN,
remoteAddr, JUICE_MAX_ADDRESS_STRING_LEN) == 0)) {
printf("Local address 2: %s\n", localAddr);
printf("Remote address 2: %s\n", remoteAddr);
success &= endswith(localAddr, ":60000");
}
// Agent 1: destroy
juice_destroy(agent1);
// Agent 2: destroy
juice_destroy(agent2);
if (success) {
printf("Success\n");
return 0;
} else {
printf("Failure\n");
return -1;
}
}
// Agent 1: on state changed
static void on_state_changed1(juice_agent_t *agent, juice_state_t state, void *user_ptr) {
printf("State 1: %s\n", juice_state_to_string(state));
if (state == JUICE_STATE_CONNECTED) {
// Agent 1: on connected, send a message
const char *message = "Hello from 1";
juice_send(agent, message, strlen(message));
}
}
// Agent 2: on state changed
static void on_state_changed2(juice_agent_t *agent, juice_state_t state, void *user_ptr) {
printf("State 2: %s\n", juice_state_to_string(state));
if (state == JUICE_STATE_CONNECTED) {
// Agent 2: on connected, send a message
const char *message = "Hello from 2";
juice_send(agent, message, strlen(message));
}
}
// Agent 1: on local candidate gathered
static void on_candidate1(juice_agent_t *agent, const char *sdp, void *user_ptr) {
printf("Candidate 1: %s\n", sdp);
// Agent 2: Receive it from agent 1
juice_add_remote_candidate(agent2, sdp);
}
// Agent 2: on local candidate gathered
static void on_candidate2(juice_agent_t *agent, const char *sdp, void *user_ptr) {
printf("Candidate 2: %s\n", sdp);
// Agent 1: Receive it from agent 2
juice_add_remote_candidate(agent1, sdp);
}
// Agent 1: on local candidates gathering done
static void on_gathering_done1(juice_agent_t *agent, void *user_ptr) {
printf("Gathering done 1\n");
juice_set_remote_gathering_done(agent2); // optional
}
// Agent 2: on local candidates gathering done
static void on_gathering_done2(juice_agent_t *agent, void *user_ptr) {
printf("Gathering done 2\n");
juice_set_remote_gathering_done(agent1); // optional
}
// Agent 1: on message received
static void on_recv1(juice_agent_t *agent, const char *data, size_t size, void *user_ptr) {
char buffer[BUFFER_SIZE];
if (size > BUFFER_SIZE - 1)
size = BUFFER_SIZE - 1;
memcpy(buffer, data, size);
buffer[size] = '\0';
printf("Received 1: %s\n", buffer);
}
// Agent 2: on message received
static void on_recv2(juice_agent_t *agent, const char *data, size_t size, void *user_ptr) {
char buffer[BUFFER_SIZE];
if (size > BUFFER_SIZE - 1)
size = BUFFER_SIZE - 1;
memcpy(buffer, data, size);
buffer[size] = '\0';
printf("Received 2: %s\n", buffer);
}

201
thirdparty/libjuice/test/notrickle.c vendored Normal file
View File

@@ -0,0 +1,201 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "juice/juice.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
static void sleep(unsigned int secs) { Sleep(secs * 1000); }
#else
#include <unistd.h> // for sleep
#endif
#define BUFFER_SIZE 4096
static juice_agent_t *agent1;
static juice_agent_t *agent2;
static void on_state_changed1(juice_agent_t *agent, juice_state_t state, void *user_ptr);
static void on_state_changed2(juice_agent_t *agent, juice_state_t state, void *user_ptr);
static void on_gathering_done1(juice_agent_t *agent, void *user_ptr);
static void on_gathering_done2(juice_agent_t *agent, void *user_ptr);
static void on_recv1(juice_agent_t *agent, const char *data, size_t size, void *user_ptr);
static void on_recv2(juice_agent_t *agent, const char *data, size_t size, void *user_ptr);
int test_notrickle() {
juice_set_log_level(JUICE_LOG_LEVEL_DEBUG);
// Agent 1: Create agent
juice_config_t config1;
memset(&config1, 0, sizeof(config1));
// STUN server example
config1.stun_server_host = "stun.l.google.com";
config1.stun_server_port = 19302;
config1.cb_state_changed = on_state_changed1;
config1.cb_gathering_done = on_gathering_done1;
config1.cb_recv = on_recv1;
config1.user_ptr = NULL;
agent1 = juice_create(&config1);
// Agent 2: Create agent
juice_config_t config2;
memset(&config2, 0, sizeof(config2));
// STUN server example
config2.stun_server_host = "stun.l.google.com";
config2.stun_server_port = 19302;
config2.cb_state_changed = on_state_changed2;
config2.cb_gathering_done = on_gathering_done2;
config2.cb_recv = on_recv2;
config2.user_ptr = NULL;
agent2 = juice_create(&config2);
// Agent 1: Gather candidates
juice_gather_candidates(agent1);
sleep(4);
// -- Connection should be finished --
// Check states
juice_state_t state1 = juice_get_state(agent1);
juice_state_t state2 = juice_get_state(agent2);
bool success = (state1 == JUICE_STATE_COMPLETED && state2 == JUICE_STATE_COMPLETED);
// Retrieve candidates
char local[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
char remote[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
if (success &=
(juice_get_selected_candidates(agent1, local, JUICE_MAX_CANDIDATE_SDP_STRING_LEN, remote,
JUICE_MAX_CANDIDATE_SDP_STRING_LEN) == 0)) {
printf("Local candidate 1: %s\n", local);
printf("Remote candidate 1: %s\n", remote);
}
if (success &=
(juice_get_selected_candidates(agent2, local, JUICE_MAX_CANDIDATE_SDP_STRING_LEN, remote,
JUICE_MAX_CANDIDATE_SDP_STRING_LEN) == 0)) {
printf("Local candidate 2: %s\n", local);
printf("Remote candidate 2: %s\n", remote);
}
// Retrieve addresses
char localAddr[JUICE_MAX_ADDRESS_STRING_LEN];
char remoteAddr[JUICE_MAX_ADDRESS_STRING_LEN];
if (success &= (juice_get_selected_addresses(agent1, localAddr, JUICE_MAX_ADDRESS_STRING_LEN,
remoteAddr, JUICE_MAX_ADDRESS_STRING_LEN) == 0)) {
printf("Local address 1: %s\n", localAddr);
printf("Remote address 1: %s\n", remoteAddr);
if ((!strstr(local, "typ host") && !strstr(local, "typ prflx")) ||
(!strstr(remote, "typ host") && !strstr(remote, "typ prflx")))
success = false; // local connection should be possible
}
if (success &= (juice_get_selected_addresses(agent2, localAddr, JUICE_MAX_ADDRESS_STRING_LEN,
remoteAddr, JUICE_MAX_ADDRESS_STRING_LEN) == 0)) {
printf("Local address 2: %s\n", localAddr);
printf("Remote address 2: %s\n", remoteAddr);
if ((!strstr(local, "typ host") && !strstr(local, "typ prflx")) ||
(!strstr(remote, "typ host") && !strstr(remote, "typ prflx")))
success = false; // local connection should be possible
}
// Agent 1: destroy
juice_destroy(agent1);
// Agent 2: destroy
juice_destroy(agent2);
if (success) {
printf("Success\n");
return 0;
} else {
printf("Failure\n");
return -1;
}
}
// Agent 1: on state changed
static void on_state_changed1(juice_agent_t *agent, juice_state_t state, void *user_ptr) {
printf("State 1: %s\n", juice_state_to_string(state));
if (state == JUICE_STATE_CONNECTED) {
// Agent 1: on connected, send a message
const char *message = "Hello from 1";
juice_send(agent, message, strlen(message));
}
}
// Agent 2: on state changed
static void on_state_changed2(juice_agent_t *agent, juice_state_t state, void *user_ptr) {
printf("State 2: %s\n", juice_state_to_string(state));
if (state == JUICE_STATE_CONNECTED) {
// Agent 2: on connected, send a message
const char *message = "Hello from 2";
juice_send(agent, message, strlen(message));
}
}
// Agent 1: on local candidates gathering done
static void on_gathering_done1(juice_agent_t *agent, void *user_ptr) {
printf("Gathering done 1\n");
// Agent 1: Generate local description
char sdp1[JUICE_MAX_SDP_STRING_LEN];
juice_get_local_description(agent1, sdp1, JUICE_MAX_SDP_STRING_LEN);
printf("Local description 1:\n%s\n", sdp1);
// Agent 2: Receive description from agent 1
juice_set_remote_description(agent2, sdp1);
// Agent 2: Gather candidates
juice_gather_candidates(agent2);
}
// Agent 2: on local candidates gathering done
static void on_gathering_done2(juice_agent_t *agent, void *user_ptr) {
printf("Gathering done 2\n");
// Agent 2: Generate local description
char sdp2[JUICE_MAX_SDP_STRING_LEN];
juice_get_local_description(agent2, sdp2, JUICE_MAX_SDP_STRING_LEN);
printf("Local description 2:\n%s\n", sdp2);
// Agent 1: Receive description from agent 2
juice_set_remote_description(agent1, sdp2);
}
// Agent 1: on message received
static void on_recv1(juice_agent_t *agent, const char *data, size_t size, void *user_ptr) {
char buffer[BUFFER_SIZE];
if (size > BUFFER_SIZE - 1)
size = BUFFER_SIZE - 1;
memcpy(buffer, data, size);
buffer[size] = '\0';
printf("Received 1: %s\n", buffer);
}
// Agent 2: on message received
static void on_recv2(juice_agent_t *agent, const char *data, size_t size, void *user_ptr) {
char buffer[BUFFER_SIZE];
if (size > BUFFER_SIZE - 1)
size = BUFFER_SIZE - 1;
memcpy(buffer, data, size);
buffer[size] = '\0';
printf("Received 2: %s\n", buffer);
}

274
thirdparty/libjuice/test/server.c vendored Normal file
View File

@@ -0,0 +1,274 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef NO_SERVER
#include "juice/juice.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
static void sleep(unsigned int secs) { Sleep(secs * 1000); }
#else
#include <unistd.h> // for sleep
#endif
#define BUFFER_SIZE 4096
#define TURN_USERNAME1 "server_test1"
#define TURN_PASSWORD1 "79874638521694"
#define TURN_USERNAME2 "server_test2"
#define TURN_PASSWORD2 "36512189907731"
static juice_server_t *server;
static juice_agent_t *agent1;
static juice_agent_t *agent2;
static bool srflx_success = false;
static bool relay_success = false;
static bool success = false;
static void on_state_changed1(juice_agent_t *agent, juice_state_t state, void *user_ptr);
static void on_state_changed2(juice_agent_t *agent, juice_state_t state, void *user_ptr);
static void on_candidate1(juice_agent_t *agent, const char *sdp, void *user_ptr);
static void on_candidate2(juice_agent_t *agent, const char *sdp, void *user_ptr);
static void on_gathering_done1(juice_agent_t *agent, void *user_ptr);
static void on_gathering_done2(juice_agent_t *agent, void *user_ptr);
static void on_recv1(juice_agent_t *agent, const char *data, size_t size, void *user_ptr);
static void on_recv2(juice_agent_t *agent, const char *data, size_t size, void *user_ptr);
int test_server() {
juice_set_log_level(JUICE_LOG_LEVEL_DEBUG);
// Create server
juice_server_credentials_t credentials[1];
memset(&credentials, 0, sizeof(credentials));
credentials[0].username = TURN_USERNAME1;
credentials[0].password = TURN_PASSWORD1;
juice_server_config_t server_config;
memset(&server_config, 0, sizeof(server_config));
server_config.port = 3478;
server_config.credentials = credentials;
server_config.credentials_count = 1;
server_config.max_allocations = 100;
server_config.realm = "Juice test server";
server = juice_server_create(&server_config);
if(juice_server_get_port(server) != 3478) {
printf("juice_server_get_port failed\n");
juice_server_destroy(server);
return -1;
}
// Added credentials example
juice_server_credentials_t added_credentials[1];
memset(&added_credentials, 0, sizeof(added_credentials));
added_credentials[0].username = TURN_USERNAME2;
added_credentials[0].password = TURN_PASSWORD2;
juice_server_add_credentials(server, added_credentials, 60000); // 60s
// Agent 1: Create agent
juice_config_t config1;
memset(&config1, 0, sizeof(config1));
// Set STUN server
config1.stun_server_host = "localhost";
config1.stun_server_port = 3478;
// Set TURN server
juice_turn_server_t turn_server1;
memset(&turn_server1, 0, sizeof(turn_server1));
turn_server1.host = "localhost";
turn_server1.port = 3478;
turn_server1.username = TURN_USERNAME1;
turn_server1.password = TURN_PASSWORD1;
config1.turn_servers = &turn_server1;
config1.turn_servers_count = 1;
config1.cb_state_changed = on_state_changed1;
config1.cb_candidate = on_candidate1;
config1.cb_gathering_done = on_gathering_done1;
config1.cb_recv = on_recv1;
config1.user_ptr = NULL;
agent1 = juice_create(&config1);
// Agent 2: Create agent
juice_config_t config2;
memset(&config2, 0, sizeof(config2));
// Set STUN server
config2.stun_server_host = "localhost";
config2.stun_server_port = 3478;
// Set TURN server
juice_turn_server_t turn_server2;
memset(&turn_server2, 0, sizeof(turn_server2));
turn_server2.host = "localhost";
turn_server2.port = 3478;
turn_server2.username = TURN_USERNAME2;
turn_server2.password = TURN_PASSWORD2;
config2.turn_servers = &turn_server2;
config2.turn_servers_count = 1;
config2.cb_state_changed = on_state_changed2;
config2.cb_candidate = on_candidate2;
config2.cb_gathering_done = on_gathering_done2;
config2.cb_recv = on_recv2;
config2.user_ptr = NULL;
agent2 = juice_create(&config2);
// Agent 1: Generate local description
char sdp1[JUICE_MAX_SDP_STRING_LEN];
juice_get_local_description(agent1, sdp1, JUICE_MAX_SDP_STRING_LEN);
printf("Local description 1:\n%s\n", sdp1);
// Agent 2: Receive description from agent 1
juice_set_remote_description(agent2, sdp1);
// Agent 2: Generate local description
char sdp2[JUICE_MAX_SDP_STRING_LEN];
juice_get_local_description(agent2, sdp2, JUICE_MAX_SDP_STRING_LEN);
printf("Local description 2:\n%s\n", sdp2);
// Agent 1: Receive description from agent 2
juice_set_remote_description(agent1, sdp2);
// Agent 1: Gather candidates (and send them to agent 2)
juice_gather_candidates(agent1);
sleep(2);
// Agent 2: Gather candidates (and send them to agent 1)
juice_gather_candidates(agent2);
sleep(2);
// -- Connection should be finished --
// Agent 1: destroy
juice_destroy(agent1);
// Agent 2: destroy
juice_destroy(agent2);
// Destroy server
juice_server_destroy(server);
if (srflx_success && relay_success && success) {
printf("Success\n");
return 0;
} else {
printf("Failure\n");
return -1;
}
}
// Agent 1: on state changed
static void on_state_changed1(juice_agent_t *agent, juice_state_t state, void *user_ptr) {
printf("State 1: %s\n", juice_state_to_string(state));
if (state == JUICE_STATE_CONNECTED) {
// Agent 1: on connected, send a message
const char *message = "Hello from 1";
juice_send(agent, message, strlen(message));
}
}
// Agent 2: on state changed
static void on_state_changed2(juice_agent_t *agent, juice_state_t state, void *user_ptr) {
printf("State 2: %s\n", juice_state_to_string(state));
if (state == JUICE_STATE_CONNECTED) {
// Agent 2: on connected, send a message
const char *message = "Hello from 2";
juice_send(agent, message, strlen(message));
}
}
// Agent 1: on local candidate gathered
static void on_candidate1(juice_agent_t *agent, const char *sdp, void *user_ptr) {
printf("Candidate 1: %s\n", sdp);
// Success if a valid srflx candidate is emitted
if (strstr(sdp, " typ srflx raddr 0.0.0.0 rport 0"))
srflx_success = true;
// Success if a valid relay candidate is emitted
if (strstr(sdp, " typ relay raddr 0.0.0.0 rport 0"))
relay_success = true;
// Filter relayed candidates
if (!strstr(sdp, "relay"))
return;
// Agent 2: Receive it from agent 1
juice_add_remote_candidate(agent2, sdp);
}
// Agent 2: on local candidate gathered
static void on_candidate2(juice_agent_t *agent, const char *sdp, void *user_ptr) {
printf("Candidate 2: %s\n", sdp);
// Success if a valid srflx candidate is emitted
if (strstr(sdp, " typ srflx raddr 0.0.0.0 rport 0"))
srflx_success = true;
// Success if a valid relay candidate is emitted
if (strstr(sdp, " typ relay raddr 0.0.0.0 rport 0"))
relay_success = true;
// Filter relayed candidates
if (!strstr(sdp, "relay"))
return;
// Agent 1: Receive it from agent 2
juice_add_remote_candidate(agent1, sdp);
}
// Agent 1: on local candidates gathering done
static void on_gathering_done1(juice_agent_t *agent, void *user_ptr) {
printf("Gathering done 1\n");
juice_set_remote_gathering_done(agent2); // optional
}
// Agent 2: on local candidates gathering done
static void on_gathering_done2(juice_agent_t *agent, void *user_ptr) {
printf("Gathering done 2\n");
juice_set_remote_gathering_done(agent1); // optional
}
// Agent 1: on message received
static void on_recv1(juice_agent_t *agent, const char *data, size_t size, void *user_ptr) {
char buffer[BUFFER_SIZE];
if (size > BUFFER_SIZE - 1)
size = BUFFER_SIZE - 1;
memcpy(buffer, data, size);
buffer[size] = '\0';
printf("Received 1: %s\n", buffer);
success = true;
}
// Agent 2: on message received
static void on_recv2(juice_agent_t *agent, const char *data, size_t size, void *user_ptr) {
char buffer[BUFFER_SIZE];
if (size > BUFFER_SIZE - 1)
size = BUFFER_SIZE - 1;
memcpy(buffer, data, size);
buffer[size] = '\0';
printf("Received 2: %s\n", buffer);
success = true;
}
#endif // ifndef NO_SERVER

155
thirdparty/libjuice/test/stun.c vendored Normal file
View File

@@ -0,0 +1,155 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "stun.h"
#include <stdint.h>
#include <string.h>
int test_stun(void) {
stun_message_t msg;
uint8_t message1[] = {
0x00, 0x01, 0x00, 0x58, // Request type and message length
0x21, 0x12, 0xa4, 0x42, // Magic cookie
0xb7, 0xe7, 0xa7, 0x01, // Transaction ID
0xbc, 0x34, 0xd6, 0x86, //
0xfa, 0x87, 0xdf, 0xae, //
0x80, 0x22, 0x00, 0x10, // SOFTWARE attribute header
0x53, 0x54, 0x55, 0x4e, //
0x20, 0x74, 0x65, 0x73, //
0x74, 0x20, 0x63, 0x6c, //
0x69, 0x65, 0x6e, 0x74, //
0x00, 0x24, 0x00, 0x04, // PRIORITY attribute header
0x6e, 0x00, 0x01, 0xff, //
0x80, 0x29, 0x00, 0x08, // ICE-CONTROLLED attribute header
0x93, 0x2f, 0xf9, 0xb1, //
0x51, 0x26, 0x3b, 0x36, //
0x00, 0x06, 0x00, 0x09, // USERNAME attribute header
0x65, 0x76, 0x74, 0x6a, //
0x3a, 0x68, 0x36, 0x76, //
0x59, 0x20, 0x20, 0x20, //
0x00, 0x08, 0x00, 0x14, // MESSAGE-INTEGRITY attribute header
0x9a, 0xea, 0xa7, 0x0c, //
0xbf, 0xd8, 0xcb, 0x56, //
0x78, 0x1e, 0xf2, 0xb5, //
0xb2, 0xd3, 0xf2, 0x49, //
0xc1, 0xb5, 0x71, 0xa2, //
0x80, 0x28, 0x00, 0x04, // FINGERPRINT attribute header
0xe5, 0x7a, 0x3b, 0xcf, //
};
memset(&msg, 0, sizeof(msg));
if (_juice_stun_read(message1, sizeof(message1), &msg) <= 0)
return -1;
if(msg.msg_class != STUN_CLASS_REQUEST || msg.msg_method != STUN_METHOD_BINDING)
return -1;
if (memcmp(msg.transaction_id, message1 + 8, 12) != 0)
return -1;
if (msg.priority != 0x6e0001ff)
return -1;
if (msg.ice_controlled != 0x932ff9b151263b36LL)
return -1;
if (!msg.has_integrity)
return -1;
if (!_juice_stun_check_integrity(message1, sizeof(message1), &msg, "VOkJxbRl1RmTxUk/WvJxBt"))
return -1;
if(msg.error_code != 0)
return -1;
// The test vector in RFC 8489 is completely wrong
// See https://www.rfc-editor.org/errata_search.php?rfc=8489
uint8_t message2[] = {
0x00, 0x01, 0x00, 0x90, // Request type and message length
0x21, 0x12, 0xa4, 0x42, // Magic cookie
0x78, 0xad, 0x34, 0x33, // Transaction ID
0xc6, 0xad, 0x72, 0xc0, //
0x29, 0xda, 0x41, 0x2e, //
0x00, 0x1e, 0x00, 0x20, // USERHASH attribute header
0x4a, 0x3c, 0xf3, 0x8f, // Userhash value (32 bytes)
0xef, 0x69, 0x92, 0xbd, //
0xa9, 0x52, 0xc6, 0x78, //
0x04, 0x17, 0xda, 0x0f, //
0x24, 0x81, 0x94, 0x15, //
0x56, 0x9e, 0x60, 0xb2, //
0x05, 0xc4, 0x6e, 0x41, //
0x40, 0x7f, 0x17, 0x04, //
0x00, 0x15, 0x00, 0x29, // NONCE attribute header
0x6f, 0x62, 0x4d, 0x61, // Nonce value and padding (3 bytes)
0x74, 0x4a, 0x6f, 0x73, //
0x32, 0x41, 0x41, 0x41, //
0x43, 0x66, 0x2f, 0x2f, //
0x34, 0x39, 0x39, 0x6b, //
0x39, 0x35, 0x34, 0x64, //
0x36, 0x4f, 0x4c, 0x33, //
0x34, 0x6f, 0x4c, 0x39, //
0x46, 0x53, 0x54, 0x76, //
0x79, 0x36, 0x34, 0x73, //
0x41, 0x00, 0x00, 0x00, //
0x00, 0x14, 0x00, 0x0b, // REALM attribute header
0x65, 0x78, 0x61, 0x6d, // Realm value (11 bytes) and padding (1 byte)
0x70, 0x6c, 0x65, 0x2e, //
0x6f, 0x72, 0x67, 0x00, //
0x00, 0x1d, 0x00, 0x04, // PASSWORD-ALGORITHM attribute header
0x00, 0x02, 0x00, 0x00, // PASSWORD-ALGORITHM value (4 bytes)
0x00, 0x1c, 0x00, 0x20, // MESSAGE-INTEGRITY-SHA256 attribute header
0xb5, 0xc7, 0xbf, 0x00, // HMAC-SHA256 value
0x5b, 0x6c, 0x52, 0xa2, //
0x1c, 0x51, 0xc5, 0xe8, //
0x92, 0xf8, 0x19, 0x24, //
0x13, 0x62, 0x96, 0xcb, //
0x92, 0x7c, 0x43, 0x14, //
0x93, 0x09, 0x27, 0x8c, //
0xc6, 0x51, 0x8e, 0x65, //
};
memset(&msg, 0, sizeof(msg));
if (_juice_stun_read(message2, sizeof(message2), &msg) <= 0)
return -1;
if(msg.msg_class != STUN_CLASS_REQUEST || msg.msg_method != STUN_METHOD_BINDING)
return -1;
if (memcmp(msg.transaction_id, message2 + 8, 12) != 0)
return -1;
if (!msg.credentials.enable_userhash)
return -1;
if (memcmp(msg.credentials.userhash, message2 + 24, 32) != 0)
return -1;
if (strcmp(msg.credentials.realm, "example.org") != 0)
return -1;
if (strcmp(msg.credentials.nonce, "obMatJos2AAACf//499k954d6OL34oL9FSTvy64sA") != 0)
return -1;
if (!msg.has_integrity)
return -1;
// Username is "<U+30DE><U+30C8><U+30EA><U+30C3><U+30AF><U+30B9>" or "マトリックス"
// aka "The Matrix" in Japanese
strcpy(msg.credentials.username, "マトリックス");
if (!_juice_stun_check_integrity(message2, sizeof(message2), &msg, "TheMatrIX"))
return -1;
if(msg.error_code != STUN_ERROR_INTERNAL_VALIDATION_FAILED)
return -1;
return 0;
}

220
thirdparty/libjuice/test/thread.c vendored Normal file
View File

@@ -0,0 +1,220 @@
/**
* Copyright (c) 2022 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "juice/juice.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
static void sleep(unsigned int secs) { Sleep(secs * 1000); }
#else
#include <unistd.h> // for sleep
#endif
#define BUFFER_SIZE 4096
static juice_agent_t *agent1;
static juice_agent_t *agent2;
static void on_state_changed1(juice_agent_t *agent, juice_state_t state, void *user_ptr);
static void on_state_changed2(juice_agent_t *agent, juice_state_t state, void *user_ptr);
static void on_candidate1(juice_agent_t *agent, const char *sdp, void *user_ptr);
static void on_candidate2(juice_agent_t *agent, const char *sdp, void *user_ptr);
static void on_gathering_done1(juice_agent_t *agent, void *user_ptr);
static void on_gathering_done2(juice_agent_t *agent, void *user_ptr);
static void on_recv1(juice_agent_t *agent, const char *data, size_t size, void *user_ptr);
static void on_recv2(juice_agent_t *agent, const char *data, size_t size, void *user_ptr);
int test_thread() {
juice_set_log_level(JUICE_LOG_LEVEL_DEBUG);
// Agent 1: Create agent in thread concurrency mode
juice_config_t config1;
memset(&config1, 0, sizeof(config1));
config1.concurrency_mode = JUICE_CONCURRENCY_MODE_THREAD;
config1.stun_server_host = "stun.l.google.com";
config1.stun_server_port = 19302;
config1.cb_state_changed = on_state_changed1;
config1.cb_candidate = on_candidate1;
config1.cb_gathering_done = on_gathering_done1;
config1.cb_recv = on_recv1;
config1.user_ptr = NULL;
agent1 = juice_create(&config1);
// Agent 2: Create agent in thread concurrency mode
juice_config_t config2;
memset(&config2, 0, sizeof(config2));
config2.concurrency_mode = JUICE_CONCURRENCY_MODE_THREAD;
config2.stun_server_host = "stun.l.google.com";
config2.stun_server_port = 19302;
config2.cb_state_changed = on_state_changed2;
config2.cb_candidate = on_candidate2;
config2.cb_gathering_done = on_gathering_done2;
config2.cb_recv = on_recv2;
config2.user_ptr = NULL;
agent2 = juice_create(&config2);
// Agent 1: Generate local description
char sdp1[JUICE_MAX_SDP_STRING_LEN];
juice_get_local_description(agent1, sdp1, JUICE_MAX_SDP_STRING_LEN);
printf("Local description 1:\n%s\n", sdp1);
// Agent 2: Receive description from agent 1
juice_set_remote_description(agent2, sdp1);
// Agent 2: Generate local description
char sdp2[JUICE_MAX_SDP_STRING_LEN];
juice_get_local_description(agent2, sdp2, JUICE_MAX_SDP_STRING_LEN);
printf("Local description 2:\n%s\n", sdp2);
// Agent 1: Receive description from agent 2
juice_set_remote_description(agent1, sdp2);
// Agent 1: Gather candidates (and send them to agent 2)
juice_gather_candidates(agent1);
sleep(2);
// Agent 2: Gather candidates (and send them to agent 1)
juice_gather_candidates(agent2);
sleep(2);
// -- Connection should be finished --
// Check states
juice_state_t state1 = juice_get_state(agent1);
juice_state_t state2 = juice_get_state(agent2);
bool success = (state1 == JUICE_STATE_COMPLETED && state2 == JUICE_STATE_COMPLETED);
// Retrieve candidates
char local[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
char remote[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
if (success &=
(juice_get_selected_candidates(agent1, local, JUICE_MAX_CANDIDATE_SDP_STRING_LEN, remote,
JUICE_MAX_CANDIDATE_SDP_STRING_LEN) == 0)) {
printf("Local candidate 1: %s\n", local);
printf("Remote candidate 1: %s\n", remote);
if ((!strstr(local, "typ host") && !strstr(local, "typ prflx")) ||
(!strstr(remote, "typ host") && !strstr(remote, "typ prflx")))
success = false; // local connection should be possible
}
if (success &=
(juice_get_selected_candidates(agent2, local, JUICE_MAX_CANDIDATE_SDP_STRING_LEN, remote,
JUICE_MAX_CANDIDATE_SDP_STRING_LEN) == 0)) {
printf("Local candidate 2: %s\n", local);
printf("Remote candidate 2: %s\n", remote);
if ((!strstr(local, "typ host") && !strstr(local, "typ prflx")) ||
(!strstr(remote, "typ host") && !strstr(remote, "typ prflx")))
success = false; // local connection should be possible
}
// Retrieve addresses
char localAddr[JUICE_MAX_ADDRESS_STRING_LEN];
char remoteAddr[JUICE_MAX_ADDRESS_STRING_LEN];
if (success &= (juice_get_selected_addresses(agent1, localAddr, JUICE_MAX_ADDRESS_STRING_LEN,
remoteAddr, JUICE_MAX_ADDRESS_STRING_LEN) == 0)) {
printf("Local address 1: %s\n", localAddr);
printf("Remote address 1: %s\n", remoteAddr);
}
if (success &= (juice_get_selected_addresses(agent2, localAddr, JUICE_MAX_ADDRESS_STRING_LEN,
remoteAddr, JUICE_MAX_ADDRESS_STRING_LEN) == 0)) {
printf("Local address 2: %s\n", localAddr);
printf("Remote address 2: %s\n", remoteAddr);
}
// Agent 1: destroy
juice_destroy(agent1);
// Agent 2: destroy
juice_destroy(agent2);
if (success) {
printf("Success\n");
return 0;
} else {
printf("Failure\n");
return -1;
}
}
// Agent 1: on state changed
static void on_state_changed1(juice_agent_t *agent, juice_state_t state, void *user_ptr) {
printf("State 1: %s\n", juice_state_to_string(state));
if (state == JUICE_STATE_CONNECTED) {
// Agent 1: on connected, send a message
const char *message = "Hello from 1";
juice_send(agent, message, strlen(message));
}
}
// Agent 2: on state changed
static void on_state_changed2(juice_agent_t *agent, juice_state_t state, void *user_ptr) {
printf("State 2: %s\n", juice_state_to_string(state));
if (state == JUICE_STATE_CONNECTED) {
// Agent 2: on connected, send a message
const char *message = "Hello from 2";
juice_send(agent, message, strlen(message));
}
}
// Agent 1: on local candidate gathered
static void on_candidate1(juice_agent_t *agent, const char *sdp, void *user_ptr) {
printf("Candidate 1: %s\n", sdp);
// Agent 2: Receive it from agent 1
juice_add_remote_candidate(agent2, sdp);
}
// Agent 2: on local candidate gathered
static void on_candidate2(juice_agent_t *agent, const char *sdp, void *user_ptr) {
printf("Candidate 2: %s\n", sdp);
// Agent 1: Receive it from agent 2
juice_add_remote_candidate(agent1, sdp);
}
// Agent 1: on local candidates gathering done
static void on_gathering_done1(juice_agent_t *agent, void *user_ptr) {
printf("Gathering done 1\n");
juice_set_remote_gathering_done(agent2); // optional
}
// Agent 2: on local candidates gathering done
static void on_gathering_done2(juice_agent_t *agent, void *user_ptr) {
printf("Gathering done 2\n");
juice_set_remote_gathering_done(agent1); // optional
}
// Agent 1: on message received
static void on_recv1(juice_agent_t *agent, const char *data, size_t size, void *user_ptr) {
char buffer[BUFFER_SIZE];
if (size > BUFFER_SIZE - 1)
size = BUFFER_SIZE - 1;
memcpy(buffer, data, size);
buffer[size] = '\0';
printf("Received 1: %s\n", buffer);
}
// Agent 2: on message received
static void on_recv2(juice_agent_t *agent, const char *data, size_t size, void *user_ptr) {
char buffer[BUFFER_SIZE];
if (size > BUFFER_SIZE - 1)
size = BUFFER_SIZE - 1;
memcpy(buffer, data, size);
buffer[size] = '\0';
printf("Received 2: %s\n", buffer);
}

241
thirdparty/libjuice/test/turn.c vendored Normal file
View File

@@ -0,0 +1,241 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "juice/juice.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
static void sleep(unsigned int secs) { Sleep(secs * 1000); }
#else
#include <unistd.h> // for sleep
#endif
#define BUFFER_SIZE 4096
static juice_agent_t *agent1;
static juice_agent_t *agent2;
static void on_state_changed1(juice_agent_t *agent, juice_state_t state, void *user_ptr);
static void on_state_changed2(juice_agent_t *agent, juice_state_t state, void *user_ptr);
static void on_candidate1(juice_agent_t *agent, const char *sdp, void *user_ptr);
static void on_candidate2(juice_agent_t *agent, const char *sdp, void *user_ptr);
static void on_gathering_done1(juice_agent_t *agent, void *user_ptr);
static void on_gathering_done2(juice_agent_t *agent, void *user_ptr);
static void on_recv1(juice_agent_t *agent, const char *data, size_t size, void *user_ptr);
static void on_recv2(juice_agent_t *agent, const char *data, size_t size, void *user_ptr);
int test_turn() {
juice_set_log_level(JUICE_LOG_LEVEL_DEBUG);
// Agent 1: Create agent
juice_config_t config1;
memset(&config1, 0, sizeof(config1));
// STUN server example (use your own server in production)
config1.stun_server_host = "openrelay.metered.ca";
config1.stun_server_port = 80;
// TURN server example (use your own server in production)
juice_turn_server_t turn_server;
memset(&turn_server, 0, sizeof(turn_server));
turn_server.host = "openrelay.metered.ca";
turn_server.port = 80;
turn_server.username = "openrelayproject";
turn_server.password = "openrelayproject";
config1.turn_servers = &turn_server;
config1.turn_servers_count = 1;
config1.cb_state_changed = on_state_changed1;
config1.cb_candidate = on_candidate1;
config1.cb_gathering_done = on_gathering_done1;
config1.cb_recv = on_recv1;
config1.user_ptr = NULL;
agent1 = juice_create(&config1);
// Agent 2: Create agent
juice_config_t config2;
memset(&config2, 0, sizeof(config2));
// STUN server example (use your own server in production)
config2.stun_server_host = "openrelay.metered.ca";
config2.stun_server_port = 80;
config2.cb_state_changed = on_state_changed2;
config2.cb_candidate = on_candidate2;
config2.cb_gathering_done = on_gathering_done2;
config2.cb_recv = on_recv2;
config2.user_ptr = NULL;
agent2 = juice_create(&config2);
// Agent 1: Generate local description
char sdp1[JUICE_MAX_SDP_STRING_LEN];
juice_get_local_description(agent1, sdp1, JUICE_MAX_SDP_STRING_LEN);
printf("Local description 1:\n%s\n", sdp1);
// Agent 2: Receive description from agent 1
juice_set_remote_description(agent2, sdp1);
// Agent 2: Generate local description
char sdp2[JUICE_MAX_SDP_STRING_LEN];
juice_get_local_description(agent2, sdp2, JUICE_MAX_SDP_STRING_LEN);
printf("Local description 2:\n%s\n", sdp2);
// Agent 1: Receive description from agent 2
juice_set_remote_description(agent1, sdp2);
// Agent 1: Gather candidates (and send them to agent 2)
juice_gather_candidates(agent1);
sleep(2);
// Agent 2: Gather candidates (and send them to agent 1)
juice_gather_candidates(agent2);
sleep(2);
// -- Connection should be finished --
// Check states
juice_state_t state1 = juice_get_state(agent1);
juice_state_t state2 = juice_get_state(agent2);
bool success = ((state1 == JUICE_STATE_COMPLETED || state1 == JUICE_STATE_CONNECTED) &&
(state2 == JUICE_STATE_CONNECTED || state2 == JUICE_STATE_COMPLETED));
// Retrieve candidates
char local[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
char remote[JUICE_MAX_CANDIDATE_SDP_STRING_LEN];
if (success &=
(juice_get_selected_candidates(agent1, local, JUICE_MAX_CANDIDATE_SDP_STRING_LEN, remote,
JUICE_MAX_CANDIDATE_SDP_STRING_LEN) == 0)) {
printf("Local candidate 1: %s\n", local);
printf("Remote candidate 1: %s\n", remote);
success &= (strstr(local, "relay") != NULL);
}
if (success &=
(juice_get_selected_candidates(agent2, local, JUICE_MAX_CANDIDATE_SDP_STRING_LEN, remote,
JUICE_MAX_CANDIDATE_SDP_STRING_LEN) == 0)) {
printf("Local candidate 2: %s\n", local);
printf("Remote candidate 2: %s\n", remote);
success &= (strstr(remote, "relay") != NULL);
}
// Retrieve addresses
char localAddr[JUICE_MAX_ADDRESS_STRING_LEN];
char remoteAddr[JUICE_MAX_ADDRESS_STRING_LEN];
if (success &= (juice_get_selected_addresses(agent1, localAddr, JUICE_MAX_ADDRESS_STRING_LEN,
remoteAddr, JUICE_MAX_ADDRESS_STRING_LEN) == 0)) {
printf("Local address 1: %s\n", localAddr);
printf("Remote address 1: %s\n", remoteAddr);
}
if (success &= (juice_get_selected_addresses(agent2, localAddr, JUICE_MAX_ADDRESS_STRING_LEN,
remoteAddr, JUICE_MAX_ADDRESS_STRING_LEN) == 0)) {
printf("Local address 2: %s\n", localAddr);
printf("Remote address 2: %s\n", remoteAddr);
}
// Agent 1: destroy
juice_destroy(agent1);
// Agent 2: destroy
juice_destroy(agent2);
if (success) {
printf("Success\n");
return 0;
} else {
printf("Failure\n");
return -1;
}
}
// Agent 1: on state changed
static void on_state_changed1(juice_agent_t *agent, juice_state_t state, void *user_ptr) {
printf("State 1: %s\n", juice_state_to_string(state));
if (state == JUICE_STATE_CONNECTED) {
// Agent 1: on connected, send a message
const char *message = "Hello from 1";
juice_send(agent, message, strlen(message));
}
}
// Agent 2: on state changed
static void on_state_changed2(juice_agent_t *agent, juice_state_t state, void *user_ptr) {
printf("State 2: %s\n", juice_state_to_string(state));
if (state == JUICE_STATE_CONNECTED) {
// Agent 2: on connected, send a message
const char *message = "Hello from 2";
juice_send(agent, message, strlen(message));
}
}
// Agent 1: on local candidate gathered
static void on_candidate1(juice_agent_t *agent, const char *sdp, void *user_ptr) {
// Filter relayed candidates
if (!strstr(sdp, "relay"))
return;
printf("Candidate 1: %s\n", sdp);
// Agent 2: Receive it from agent 1
juice_add_remote_candidate(agent2, sdp);
}
// Agent 2: on local candidate gathered
static void on_candidate2(juice_agent_t *agent, const char *sdp, void *user_ptr) {
// Filter server reflexive candidates
if (!strstr(sdp, "srflx"))
return;
printf("Candidate 2: %s\n", sdp);
// Agent 1: Receive it from agent 2
juice_add_remote_candidate(agent1, sdp);
}
// Agent 1: on local candidates gathering done
static void on_gathering_done1(juice_agent_t *agent, void *user_ptr) {
printf("Gathering done 1\n");
juice_set_remote_gathering_done(agent2); // optional
}
// Agent 2: on local candidates gathering done
static void on_gathering_done2(juice_agent_t *agent, void *user_ptr) {
printf("Gathering done 2\n");
juice_set_remote_gathering_done(agent1); // optional
}
// Agent 1: on message received
static void on_recv1(juice_agent_t *agent, const char *data, size_t size, void *user_ptr) {
char buffer[BUFFER_SIZE];
if (size > BUFFER_SIZE - 1)
size = BUFFER_SIZE - 1;
memcpy(buffer, data, size);
buffer[size] = '\0';
printf("Received 1: %s\n", buffer);
}
// Agent 2: on message received
static void on_recv2(juice_agent_t *agent, const char *data, size_t size, void *user_ptr) {
char buffer[BUFFER_SIZE];
if (size > BUFFER_SIZE - 1)
size = BUFFER_SIZE - 1;
memcpy(buffer, data, size);
buffer[size] = '\0';
printf("Received 2: %s\n", buffer);
}

12
thirdparty/libjuice/xmake.lua vendored Normal file
View File

@@ -0,0 +1,12 @@
package("libjuice")
add_deps("cmake")
set_sourcedir(path.join(os.scriptdir(), ""))
on_install(function (package)
local configs = {}
table.insert(configs, "-DNO_EXPORT_HEADER=ON -DNO_TESTS=ON")
import("package.tools.cmake").install(package, configs)
end)
-- on_test(function (package)
-- assert(package:has_cfuncs("juice_create", {includes = "juice/juice.h"}))
-- end)
package_end()

1
thirdparty/xmake.lua vendored Normal file
View File

@@ -0,0 +1 @@
includes("libjuice")

View File

@@ -7,6 +7,7 @@ set_languages("c++17")
add_rules("mode.release", "mode.debug") add_rules("mode.release", "mode.debug")
add_requires("asio 1.24.0", "nlohmann_json", "spdlog 1.11.0") add_requires("asio 1.24.0", "nlohmann_json", "spdlog 1.11.0")
add_requires("libjuice", {system = false})
add_defines("JUICE_STATIC") add_defines("JUICE_STATIC")
add_defines("ASIO_STANDALONE","_WEBSOCKETPP_CPP11_INTERNAL_", "ASIO_HAS_STD_TYPE_TRAITS", "ASIO_HAS_STD_SHARED_PTR", add_defines("ASIO_STANDALONE","_WEBSOCKETPP_CPP11_INTERNAL_", "ASIO_HAS_STD_TYPE_TRAITS", "ASIO_HAS_STD_SHARED_PTR",
@@ -17,15 +18,15 @@ add_links("ws2_32", "Bcrypt")
add_cxflags("-MD") add_cxflags("-MD")
add_packages("spdlog") add_packages("spdlog")
includes("thirdparty")
target("ice") target("ice")
set_kind("static") set_kind("static")
add_deps("log", "ws") add_deps("log", "ws")
add_packages("asio", "nlohmann_json") add_packages("asio", "nlohmann_json", "libjuice")
add_files("src/ice/*.cpp") add_files("src/ice/*.cpp")
add_links("juice")
add_includedirs("src/ws") add_includedirs("src/ws")
add_includedirs("thirdparty/libjuice/include", {public = true}) add_includedirs("thirdparty/libjuice/include", {public = true})
add_linkdirs("thirdparty/libjuice/lib")
target("ws") target("ws")
set_kind("static") set_kind("static")