├── rust-toolchain ├── src ├── node │ ├── .nvmrc │ ├── .npmrc │ ├── .prettierignore │ ├── .gitignore │ ├── .eslintignore │ ├── scripts │ │ ├── build-help.sh │ │ ├── prepublish.js │ │ └── fetch-prebuild.js │ ├── .prettierrc.js │ ├── tsconfig.json │ ├── ringrtc │ │ ├── Native.ts │ │ ├── CallLinks.ts │ │ └── CallSummary.ts │ ├── index.ts │ ├── test │ │ └── Utils.ts │ └── package.json ├── android │ ├── .gitignore │ ├── libs │ │ └── .gitignore │ ├── jniLibs │ │ └── .gitignore │ ├── src │ │ ├── main │ │ │ └── AndroidManifest.xml │ │ └── androidTest │ │ │ └── java │ │ │ ├── CallTestBase.java │ │ │ └── CallIdTest.java │ ├── api │ │ └── org │ │ │ └── signal │ │ │ └── ringrtc │ │ │ ├── Remote.java │ │ │ ├── BuildInfo.java │ │ │ ├── CalledByNative.java │ │ │ ├── HttpHeader.java │ │ │ ├── CallException.java │ │ │ ├── package.html │ │ │ ├── NetworkRoute.java │ │ │ ├── WebRtcLogger.java │ │ │ ├── CameraControl.java │ │ │ ├── AudioConfig.java │ │ │ ├── CallLinkRootKey.java │ │ │ ├── Util.java │ │ │ ├── CallLinkState.java │ │ │ ├── CallId.java │ │ │ ├── Log.java │ │ │ ├── Connection.java │ │ │ └── CallLinkEpoch.java │ └── proguard-rules.pro ├── ios │ ├── README │ ├── SignalRingRTC │ │ ├── Gemfile │ │ ├── CARGO_BUILD_TARGET.xcconfig │ │ ├── Podfile │ │ ├── SignalRingRTC.xcodeproj │ │ │ └── project.xcworkspace │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── SignalRingRTC │ │ │ ├── SignalRingRTC.modulemap │ │ │ ├── SignalRingRTC.h │ │ │ ├── Info.plist │ │ │ ├── Assertions.swift │ │ │ ├── CallManagerUtil.swift │ │ │ ├── RingValidation.swift │ │ │ ├── ConnectionMediaStream.swift │ │ │ ├── Connection.swift │ │ │ ├── CallContext.swift │ │ │ └── CallManagerGlobal.swift │ │ ├── SignalRingRTC.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── SignalRingRTCTests │ │ │ ├── Info.plist │ │ │ ├── AudioDeviceModuleForTests.swift │ │ │ └── TestGroupCallDelegate.swift │ │ ├── Podfile.lock │ │ └── Gemfile.lock │ └── .gitignore └── rust │ ├── src │ ├── common │ │ ├── time.rs │ │ └── units.rs │ ├── webrtc │ │ ├── sim │ │ │ ├── ice_gatherer.rs │ │ │ ├── ref_count.rs │ │ │ ├── rtp_observer.rs │ │ │ ├── peer_connection_observer.rs │ │ │ └── stats_observer.rs │ │ ├── ffi │ │ │ ├── ice_gatherer.rs │ │ │ ├── logging.rs │ │ │ ├── rtp_observer.rs │ │ │ ├── stats_observer.rs │ │ │ ├── peer_connection_observer.rs │ │ │ └── ref_count.rs │ │ ├── ice_gatherer.rs │ │ ├── rtp.rs │ │ ├── logging.rs │ │ └── arc.rs │ ├── ios │ │ └── error.rs │ ├── android │ │ ├── webrtc_peer_connection_factory.rs │ │ ├── webrtc_java_media_stream.rs │ │ └── error.rs │ ├── sim │ │ └── error.rs │ ├── core │ │ └── call_mutex.rs │ ├── bin │ │ └── call_sim-cli │ │ │ └── util.rs │ └── lite │ │ └── logging.rs │ ├── scripts │ └── run-tests │ ├── regex-aot │ ├── Cargo.toml │ └── src │ │ └── lib.rs │ └── cbindgen.toml ├── call_sim ├── .gitignore ├── docker │ ├── signaling_server │ │ ├── .dockerignore │ │ ├── Cargo.toml │ │ └── Dockerfile │ ├── pesq_mos │ │ ├── Dockerfile │ │ └── pesq_mos.py │ ├── plc_mos │ │ └── Dockerfile │ ├── ringrtc │ │ └── Dockerfile │ └── visqol_mos │ │ └── Dockerfile ├── config │ └── local │ │ └── client.json ├── Cargo.toml └── src │ └── config.rs ├── bin ├── logs-notebook │ ├── .gitignore │ ├── emos.py │ ├── debuglogs.ipynb │ └── README.md ├── env-ios.sh ├── env-mac.sh ├── env-unix.sh ├── env-android.sh ├── build-javadoc ├── fetch-artifact ├── env-windows.sh ├── build-aar ├── build-rustdoc ├── regenerate_acknowledgments.sh ├── rust-lint-check ├── gsync-webrtc ├── prepare-workspace ├── measure-cpu.py ├── set-up-for-cocoapods ├── env.sh └── build-gctc ├── .flake8 ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── config ├── version.properties ├── webrtc.gclient.ios ├── webrtc.gclient.mac ├── webrtc.gclient.windows ├── webrtc.gclient.unix ├── webrtc.gclient.android ├── webrtc.gclient.common └── version.sh ├── rustfmt.toml ├── .github ├── FUNDING.yml ├── stale.yml └── workflows │ ├── ios_artifacts.yml │ ├── android_artifacts.yml │ └── ringrtc_release.yml ├── .gitattributes ├── mrp ├── src │ ├── lib.rs │ └── merge_buffer.rs └── Cargo.toml ├── acknowledgments ├── acknowledgments.md.hbs ├── README.md ├── acknowledgments.plist.hbs └── acknowledgments.html.hbs ├── .dockerignore ├── settings.gradle ├── .gitignore ├── SECURITY.md ├── protobuf ├── Cargo.toml ├── build.rs ├── protobuf │ ├── rtp_data.proto │ ├── call_summary.proto │ ├── call_sim.proto │ └── signaling.proto └── src │ └── lib.rs ├── Cargo.toml ├── gradle.properties ├── README.md └── gradlew.bat /rust-toolchain: -------------------------------------------------------------------------------- 1 | 1.91.1 2 | -------------------------------------------------------------------------------- /src/node/.nvmrc: -------------------------------------------------------------------------------- 1 | 22.20.0 2 | -------------------------------------------------------------------------------- /src/android/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /src/node/.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | -------------------------------------------------------------------------------- /src/node/.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | build/ -------------------------------------------------------------------------------- /call_sim/.gitignore: -------------------------------------------------------------------------------- 1 | test_results 2 | media 3 | -------------------------------------------------------------------------------- /src/android/libs/.gitignore: -------------------------------------------------------------------------------- 1 | libwebrtc.jar 2 | -------------------------------------------------------------------------------- /src/ios/README: -------------------------------------------------------------------------------- 1 | # iOS Interface to RingRTC 2 | -------------------------------------------------------------------------------- /bin/logs-notebook/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .ipynb_checkpoints 3 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = 3 | # E501 line too long 4 | E501, 5 | -------------------------------------------------------------------------------- /src/android/jniLibs/.gitignore: -------------------------------------------------------------------------------- 1 | arm64-v8a/ 2 | armeabi-v7a/ 3 | x86/ 4 | x86_64/ 5 | -------------------------------------------------------------------------------- /call_sim/docker/signaling_server/.dockerignore: -------------------------------------------------------------------------------- 1 | target 2 | Dockerfile 3 | .dockerignore -------------------------------------------------------------------------------- /src/node/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | build 4 | .DS_Store 5 | *.tgz 6 | .eslintcache 7 | -------------------------------------------------------------------------------- /src/node/.eslintignore: -------------------------------------------------------------------------------- 1 | build/** 2 | dist/** 3 | 4 | # TypeScript generated files 5 | ts/**/*.js 6 | -------------------------------------------------------------------------------- /src/node/scripts/build-help.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | mkdir -p dist/bin && cp ../../bin/virtual_audio.sh dist/bin/ 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalapp/ringrtc/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /config/version.properties: -------------------------------------------------------------------------------- 1 | webrtc.version=7444a 2 | 3 | ringrtc.version.major=2 4 | ringrtc.version.minor=61 5 | ringrtc.version.revision=0 6 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | unstable_features = true 2 | 3 | reorder_imports = true 4 | group_imports = "StdExternalCrate" 5 | imports_granularity = "Crate" -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Signal Messenger, LLC 2 | # SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | custom: https://signal.org/donate/ 5 | -------------------------------------------------------------------------------- /config/webrtc.gclient.ios: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019-2021 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | # 5 | 6 | target_os = ["ios", "mac"] 7 | -------------------------------------------------------------------------------- /config/webrtc.gclient.mac: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019-2021 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | # 5 | 6 | target_os = ["ios", "mac"] 7 | -------------------------------------------------------------------------------- /config/webrtc.gclient.windows: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019-2021 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | # 5 | 6 | target_os = ["windows"] 7 | -------------------------------------------------------------------------------- /config/webrtc.gclient.unix: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019-2021 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | # 5 | 6 | target_os = ["android", "unix"] 7 | -------------------------------------------------------------------------------- /config/webrtc.gclient.android: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019-2021 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | # 5 | 6 | target_os = ["android", "unix"] 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Prevent auto-merging of generated acknowledgment files 2 | acknowledgments/acknowledgments.* -merge -text 3 | acknowledgments/acknowledgments.*.hbs merge text=auto 4 | -------------------------------------------------------------------------------- /src/ios/SignalRingRTC/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "cocoapods" 4 | 5 | # Fixes missing `kconv` library which is now part of `nkf` in Ruby 3.4+. 6 | gem "nkf" 7 | -------------------------------------------------------------------------------- /src/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/node/.prettierrc.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2020 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | module.exports = { 5 | singleQuote: true, 6 | arrowParens: 'avoid', 7 | trailingComma: 'es5', 8 | }; 9 | -------------------------------------------------------------------------------- /src/ios/SignalRingRTC/CARGO_BUILD_TARGET.xcconfig: -------------------------------------------------------------------------------- 1 | CARGO_BUILD_TARGET[sdk=iphonesimulator*][arch=*] = x86_64-apple-ios 2 | CARGO_BUILD_TARGET[sdk=iphonesimulator*][arch=arm64] = aarch64-apple-ios-sim 3 | CARGO_BUILD_TARGET[sdk=iphoneos*] = aarch64-apple-ios 4 | -------------------------------------------------------------------------------- /src/ios/SignalRingRTC/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '15.0' 2 | 3 | use_frameworks! 4 | 5 | target 'SignalRingRTC' do 6 | pod 'WebRTCForTesting', path: '../../../out/' 7 | end 8 | 9 | target 'SignalRingRTCTests' do 10 | pod 'Nimble', '~> 13.7.1' 11 | end 12 | -------------------------------------------------------------------------------- /src/ios/SignalRingRTC/SignalRingRTC.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/node/scripts/prepublish.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | if (!process.env.npm_package_config_prebuildChecksum) { 7 | throw new Error('must set prebuildChecksum before publishing'); 8 | } 9 | -------------------------------------------------------------------------------- /mrp/src/lib.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2024 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | mod merge_buffer; 7 | mod stream; 8 | mod window; 9 | 10 | pub use stream::{MrpHeader, MrpReceiveError, MrpSendError, MrpStream, PacketWrapper}; 11 | -------------------------------------------------------------------------------- /call_sim/docker/pesq_mos/Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2024 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | # 5 | 6 | FROM python:3.11 7 | 8 | RUN python -m pip install scipy pesq 9 | 10 | COPY pesq_mos.py . 11 | 12 | ENTRYPOINT ["python", "./pesq_mos.py"] 13 | -------------------------------------------------------------------------------- /acknowledgments/acknowledgments.md.hbs: -------------------------------------------------------------------------------- 1 | # Acknowledgments 2 | 3 | RingRTC makes use of the following open source projects. 4 | 5 | {{#each licenses}} 6 | ## {{#each used_by}}{{#unless @first}}, {{/unless}}{{crate.name}} {{crate.version}}{{/each}} 7 | 8 | ``` 9 | {{{text}}} 10 | ``` 11 | 12 | {{/each}} -------------------------------------------------------------------------------- /config/webrtc.gclient.common: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019-2021 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | # 5 | 6 | solutions = [ 7 | { "name" : "src", 8 | "url" : "https://github.com/signalapp/webrtc.git", 9 | "deps_file" : "DEPS", 10 | }, 11 | ] 12 | -------------------------------------------------------------------------------- /src/node/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "outDir": "./dist", 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "sourceMap": true, 10 | "stripInternal": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | out/ 2 | out-arm/ 3 | cscope.* 4 | .DS_Store 5 | .dir-locals.el 6 | .cargo 7 | .gradle/ 8 | *.code-workspace 9 | **/.vscode 10 | .idea 11 | src/webrtc* 12 | src/jar.list 13 | .project 14 | .classpath 15 | org.eclipse.buildship.core.prefs 16 | local.properties 17 | *.tar.gz 18 | .cargo 19 | .spr.yml -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | 9 | rootProject.name = "RingRtcGradle" 10 | include ':ringrtc' 11 | project(':ringrtc').projectDir = file('src') 12 | 13 | include ':ringrtc:android' 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | out-arm/ 3 | cscope.* 4 | .DS_Store 5 | .dir-locals.el 6 | .cargo 7 | .gradle/ 8 | *.code-workspace 9 | **/.vscode 10 | .idea 11 | src/webrtc* 12 | src/jar.list 13 | .project 14 | .classpath 15 | org.eclipse.buildship.core.prefs 16 | local.properties 17 | *.tar.gz 18 | .cargo 19 | target 20 | .spr.yml 21 | -------------------------------------------------------------------------------- /src/rust/src/common/time.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | use std::time::{Duration, SystemTime}; 7 | 8 | pub fn saturating_epoch_time(ts: SystemTime) -> Duration { 9 | ts.duration_since(std::time::UNIX_EPOCH).unwrap_or_default() 10 | } 11 | -------------------------------------------------------------------------------- /src/rust/src/webrtc/sim/ice_gatherer.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | //! WebRTC Simulation IceGatherer 7 | 8 | /// Simulation type for IceGatherer. 9 | pub type RffiIceGatherer = u32; 10 | 11 | pub static FAKE_ICE_GATHERER: RffiIceGatherer = 20; 12 | -------------------------------------------------------------------------------- /src/ios/SignalRingRTC/SignalRingRTC/SignalRingRTC.modulemap: -------------------------------------------------------------------------------- 1 | framework module SignalRingRTC { 2 | umbrella header "SignalRingRTC.h" 3 | 4 | export * 5 | module * { export * } 6 | 7 | explicit module RingRTC { 8 | header "ringrtc.h" 9 | link "ringrtc" 10 | export * 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /call_sim/docker/pesq_mos/pesq_mos.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2024 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | # 5 | 6 | import sys 7 | from scipy.io import wavfile 8 | from pesq import pesq 9 | 10 | _, ref = wavfile.read(sys.argv[1]) 11 | rate, deg = wavfile.read(sys.argv[2]) 12 | 13 | print(pesq(rate, ref, deg, 'wb')) 14 | -------------------------------------------------------------------------------- /src/ios/SignalRingRTC/SignalRingRTC.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/ios/SignalRingRTC/SignalRingRTC.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /bin/env-ios.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright 2019-2021 Signal Messenger, LLC 5 | # SPDX-License-Identifier: AGPL-3.0-only 6 | # 7 | 8 | # iOS specific environment variables 9 | # @note Nothing here yet. 10 | 11 | prepare_workspace_platform() { 12 | echo "Preparing workspace for iOS..." 13 | 14 | # @note Nothing here yet. 15 | } 16 | -------------------------------------------------------------------------------- /bin/env-mac.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright 2019-2021 Signal Messenger, LLC 5 | # SPDX-License-Identifier: AGPL-3.0-only 6 | # 7 | 8 | # MacOS specific environment variables 9 | # @note Nothing here yet. 10 | 11 | prepare_workspace_platform() { 12 | echo "Preparing workspace for MacOS..." 13 | 14 | # @note Nothing here yet. 15 | } 16 | -------------------------------------------------------------------------------- /bin/env-unix.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright 2019-2021 Signal Messenger, LLC 5 | # SPDX-License-Identifier: AGPL-3.0-only 6 | # 7 | 8 | # Unix specific environment variables 9 | # @note Nothing here yet. 10 | 11 | prepare_workspace_platform() { 12 | echo "Preparing workspace for Unix..." 13 | 14 | # @note Nothing here yet. 15 | } 16 | -------------------------------------------------------------------------------- /src/ios/SignalRingRTC/SignalRingRTC.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/rust/scripts/run-tests: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright 2019-2021 Signal Messenger, LLC 5 | # SPDX-License-Identifier: AGPL-3.0-only 6 | # 7 | 8 | OUTPUT_DIR="${OUTPUT_DIR:-/tmp/ringrtc-tests}" 9 | export RANDOM_SEED="${RANDOM_SEED:-$(date +%s)}" 10 | cargo test -p mrp -p ringrtc --features=sim --target-dir="$OUTPUT_DIR" "$@" -- --nocapture --test-threads=1 11 | -------------------------------------------------------------------------------- /bin/env-android.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright 2019-2021 Signal Messenger, LLC 5 | # SPDX-License-Identifier: AGPL-3.0-only 6 | # 7 | 8 | # Allow non-exported environment variables 9 | # shellcheck disable=SC2034 10 | 11 | # Android specific environment variables 12 | 13 | prepare_workspace_platform() { 14 | echo "Preparing workspace for Android..." 15 | } 16 | -------------------------------------------------------------------------------- /bin/build-javadoc: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright 2019-2021 Signal Messenger, LLC 5 | # SPDX-License-Identifier: AGPL-3.0-only 6 | # 7 | 8 | set -e 9 | 10 | # shellcheck source=bin/env.sh 11 | . "$(dirname "$0")"/env.sh 12 | 13 | cd "${PROJECT_ROOT}" 14 | ./gradlew javadoc \ 15 | -PwebrtcJar="${OUTPUT_DIR}/release/libs/libwebrtc.jar" \ 16 | -PdocsDir="${OUTPUT_DIR}" 17 | -------------------------------------------------------------------------------- /bin/fetch-artifact: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright 2023 Signal Messenger, LLC 5 | # SPDX-License-Identifier: AGPL-3.0-only 6 | # 7 | 8 | set -e 9 | 10 | # shellcheck source=bin/env.sh 11 | . "$(dirname "$0")"/env.sh 12 | 13 | "${0}.py" \ 14 | --output-dir="${OUTPUT_DIR}" \ 15 | --webrtc-version="${WEBRTC_VERSION}" \ 16 | "$@" 17 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip 5 | networkTimeout=10000 6 | validateDistributionUrl=true 7 | zipStoreBase=GRADLE_USER_HOME 8 | zipStorePath=wrapper/dists 9 | -------------------------------------------------------------------------------- /src/node/ringrtc/Native.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2023 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | import * as os from 'os'; 7 | import * as process from 'process'; 8 | 9 | // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require 10 | export default require( 11 | `../../build/${os.platform()}/libringrtc-${process.arch}.node` 12 | ); 13 | -------------------------------------------------------------------------------- /src/android/api/org/signal/ringrtc/Remote.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2021 Signal Messenger, LLC 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | package org.signal.ringrtc; 7 | 8 | /** 9 | * 10 | * An interface that represents the remote side of a peer-to-peer call 11 | * connection. 12 | * 13 | */ 14 | public interface Remote { 15 | public boolean recipientEquals(Remote remote); 16 | } 17 | -------------------------------------------------------------------------------- /src/android/api/org/signal/ringrtc/BuildInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2021 Signal Messenger, LLC 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | package org.signal.ringrtc; 7 | 8 | public final class BuildInfo { 9 | 10 | private static final String TAG = Log.class.getSimpleName(); 11 | public boolean debug; 12 | 13 | BuildInfo(boolean debug) { 14 | this.debug = debug; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /bin/env-windows.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright 2019-2022 Signal Messenger, LLC 5 | # SPDX-License-Identifier: AGPL-3.0-only 6 | # 7 | 8 | # Windows specific environment variables 9 | 10 | # Use the locally-installed Visual Studio rather than depot_tools' hermetic toolchain. 11 | export DEPOT_TOOLS_WIN_TOOLCHAIN=0 12 | 13 | prepare_workspace_platform() { 14 | echo "Preparing workspace for Windows..." 15 | 16 | # @note Nothing here yet. 17 | } 18 | -------------------------------------------------------------------------------- /src/android/src/androidTest/java/CallTestBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Signal Messenger, LLC 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import org.signal.ringrtc.CallManager; 7 | 8 | import androidx.test.core.app.ApplicationProvider; 9 | import java.util.HashMap; 10 | 11 | import static org.mockito.Mockito.mock; 12 | 13 | public class CallTestBase { 14 | static { 15 | CallManager.initialize(ApplicationProvider.getApplicationContext(), mock(), new HashMap<>()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | If you've found a security vulnerability in Signal, please report it via email to 2 | . 3 | 4 | Please only use this address to report security flaws in the Signal application (including this 5 | repository). For questions, support, or feature requests concerning the app, please submit a 6 | [support request][] or join the [unofficial community forum][]. 7 | 8 | [support request]: https://support.signal.org/hc/requests/new 9 | [unofficial community forum]: https://community.signalusers.org/ 10 | -------------------------------------------------------------------------------- /bin/build-aar: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright 2019-2021 Signal Messenger, LLC 5 | # SPDX-License-Identifier: AGPL-3.0-only 6 | # 7 | 8 | set -e 9 | 10 | # shellcheck source=bin/env.sh 11 | . "$(dirname "$0")"/env.sh 12 | 13 | "${0}.py" \ 14 | --build-dir="${OUTPUT_DIR}" \ 15 | --webrtc-src-dir="${WEBRTC_SRC_DIR}" \ 16 | --gradle-dir="${PROJECT_DIR}" \ 17 | --publish-version="${PROJECT_VERSION}" \ 18 | --webrtc-version="${WEBRTC_VERSION}" \ 19 | "$@" 20 | -------------------------------------------------------------------------------- /protobuf/Cargo.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2024 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | # 5 | 6 | [package] 7 | name = "protobuf" 8 | edition = "2024" 9 | version.workspace = true 10 | authors.workspace = true 11 | license = "AGPL-3.0-only" 12 | 13 | [lib] 14 | name = "protobuf" 15 | path = "src/lib.rs" 16 | proc-macro = true 17 | 18 | [features] 19 | default = ["signaling"] 20 | signaling = [] 21 | call_sim = ["signaling"] 22 | 23 | [build-dependencies] 24 | prost-build = "0.14.1" 25 | tonic-prost-build = "0.14.2" 26 | -------------------------------------------------------------------------------- /src/rust/regex-aot/Cargo.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2022 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | # 5 | 6 | [package] 7 | name = "regex-aot" 8 | version = "0.1.0" 9 | authors = ["Calling Team "] 10 | edition = "2024" 11 | license = "AGPL-3.0-only" 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | [dependencies] 17 | proc-macro2 = "1.0.103" 18 | quote = "1.0.42" 19 | regex-automata = { version = "0.4.13", default-features = false, features = ["dfa", "perf", "std", "syntax"] } 20 | syn = "1.0.109" 21 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019-2022 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | # 5 | 6 | [workspace] 7 | resolver = "2" 8 | members = [ 9 | "call_sim", 10 | "mrp", 11 | "protobuf", 12 | "src/rust", 13 | ] 14 | 15 | [workspace.package] 16 | version = "2.61.0" 17 | authors = ["Calling Team "] 18 | 19 | [patch.crates-io] 20 | # Use our fork of curve25519-dalek for zkgroup compatibility. 21 | curve25519-dalek = { git = 'https://github.com/signalapp/curve25519-dalek', tag = 'signal-curve25519-4.1.3' } 22 | -------------------------------------------------------------------------------- /call_sim/docker/plc_mos/Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2024 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | # 5 | 6 | FROM python:3.11 7 | 8 | RUN mkdir repo \ 9 | && cd repo \ 10 | && git init . \ 11 | && git remote add origin https://github.com/microsoft/PLC-Challenge.git \ 12 | && git fetch --depth 1 origin c7a6ee438c1c42a7c668f3a19bf3ca0c6aad195d \ 13 | && git checkout FETCH_HEAD 14 | 15 | WORKDIR repo/PLCMOS 16 | 17 | RUN python -m pip install -r requirements_standalone.txt 18 | 19 | ENTRYPOINT ["python", "./plc_mos.py"] 20 | -------------------------------------------------------------------------------- /src/rust/src/webrtc/ffi/ice_gatherer.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | //! WebRTC FFI IceGatherer 7 | 8 | use crate::webrtc; 9 | 10 | /// Incomplete type for C++ IceGathererInterface. 11 | #[repr(C)] 12 | pub struct RffiIceGatherer { 13 | _private: [u8; 0], 14 | } 15 | 16 | // See "class IceGathererInterface : public rtc::RefCountInterface" 17 | // in webrtc/api/ice_gatherer_interface.h 18 | // (in RingRTC's forked version of WebRTC). 19 | impl webrtc::RefCounted for RffiIceGatherer {} 20 | -------------------------------------------------------------------------------- /mrp/Cargo.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2024 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | # 5 | 6 | [package] 7 | name = "mrp" 8 | version.workspace = true 9 | authors.workspace = true 10 | edition = "2024" 11 | description = "A MRP library, a simple way to reliably send packets" 12 | license = "AGPL-3.0-only" 13 | 14 | [dependencies] 15 | anyhow = "1.0.100" 16 | log = { version = "0.4.29", features = ["std", "max_level_trace", "release_max_level_info"] } 17 | thiserror = "1.0.69" 18 | 19 | [dev-dependencies] 20 | rand = { version = "0.8.5", features = [] } 21 | -------------------------------------------------------------------------------- /src/ios/SignalRingRTC/SignalRingRTC/SignalRingRTC.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | #import 7 | 8 | //! Project version number for SignalRingRTC. 9 | FOUNDATION_EXPORT double SignalRingRTCVersionNumber; 10 | 11 | //! Project version string for SignalRingRTC. 12 | FOUNDATION_EXPORT const unsigned char SignalRingRTCVersionString[]; 13 | 14 | // In this header, you should import all the public headers of your framework using statements like #import 15 | -------------------------------------------------------------------------------- /call_sim/config/local/client.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "group_sim_client", 3 | "userId": "", 4 | "deviceId": "1", 5 | "sfuUrl": "https://", 6 | "groups": [ 7 | { 8 | "name": "", 9 | "id": "", 11 | "members": [ 12 | { 13 | "userId": "", 14 | "memberId": "" 15 | } 16 | ] 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /src/rust/cbindgen.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019-2021 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | # 5 | 6 | header = """ 7 | /* 8 | * 9 | * Copyright 2019-2021 Signal Messenger, LLC 10 | * SPDX-License-Identifier: AGPL-3.0-only 11 | * 12 | */""" 13 | autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" 14 | language = "C" 15 | namespace = "ffi" 16 | include_guard = "CBINDGEN_BINDINGS_H" 17 | tab_width = 4 18 | 19 | [defines] 20 | "target_os=ios" = "TARGET_OS_IOS" 21 | "target_os=android" = "TARGET_OS_ANDROID" 22 | -------------------------------------------------------------------------------- /src/rust/src/webrtc/sim/ref_count.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | //! Simulation of Wrapper around rtc::RefCountInterface 7 | 8 | use crate::webrtc; 9 | 10 | /// # Safety 11 | pub fn dec(_rc: webrtc::ptr::OwnedRc) { 12 | info!("ref_count::dec()"); 13 | } 14 | 15 | /// # Safety 16 | pub unsafe fn inc( 17 | rc: webrtc::ptr::BorrowedRc, 18 | ) -> webrtc::ptr::OwnedRc { 19 | info!("ref_count::inc()"); 20 | unsafe { webrtc::ptr::OwnedRc::from_ptr(rc.as_ptr()) } 21 | } 22 | -------------------------------------------------------------------------------- /call_sim/docker/signaling_server/Cargo.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2023 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | # 5 | 6 | [workspace] 7 | 8 | [package] 9 | name = "signaling_server" 10 | version = "0.1.0" 11 | authors = ["Calling Team "] 12 | edition = "2024" 13 | license = "AGPL-3.0-only" 14 | 15 | [dependencies] 16 | protobuf = { path = "../../../protobuf", features = ["call_sim"] } 17 | env_logger = "0.11.8" 18 | futures-core = "0.3.31" 19 | log = "0.4.29" 20 | prost = "0.14.1" 21 | tokio = { version = "1.48.0", features = ["rt-multi-thread", "macros", "signal", "sync", "time"] } 22 | tokio-stream = "0.1.17" 23 | tonic = "0.14.2" 24 | tonic-prost = "0.14.2" 25 | -------------------------------------------------------------------------------- /src/android/api/org/signal/ringrtc/CalledByNative.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2021 Signal Messenger, LLC 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | package org.signal.ringrtc; 7 | 8 | import java.lang.annotation.ElementType; 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.RetentionPolicy; 11 | import java.lang.annotation.Target; 12 | 13 | /** 14 | * An annotation class, borrowed from org.webrtc, for indicating that a 15 | * class method is intended to be called from native code. 16 | * 17 | */ 18 | 19 | @Target({ElementType.CONSTRUCTOR, ElementType.METHOD}) 20 | @Retention(RetentionPolicy.CLASS) 21 | public @interface CalledByNative { 22 | String value() default ""; 23 | } 24 | -------------------------------------------------------------------------------- /src/android/api/org/signal/ringrtc/HttpHeader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2021 Signal Messenger, LLC 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | package org.signal.ringrtc; 7 | 8 | import androidx.annotation.NonNull; 9 | 10 | /** 11 | * 12 | * Represents a Http header name/value pair. 13 | */ 14 | public final class HttpHeader { 15 | @NonNull private final String name; 16 | @NonNull private final String value; 17 | 18 | public HttpHeader(String name, String value) { 19 | this.name = name; 20 | this.value = value; 21 | } 22 | 23 | @NonNull 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | @NonNull 29 | public String getValue() { 30 | return value; 31 | } 32 | } -------------------------------------------------------------------------------- /src/rust/src/ios/error.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | //! iOS Error Codes 7 | 8 | use thiserror::Error; 9 | 10 | /// iOS specific error codes. 11 | #[allow(non_camel_case_types)] 12 | #[derive(Error, Debug)] 13 | pub enum IosError { 14 | // iOS error codes 15 | #[error("Creating RTCPeerConnection in App failed")] 16 | CreateAppPeerConnection, 17 | #[error("Creating MediaStream in App failed")] 18 | CreateAppMediaStream, 19 | #[error("Creating IosMediaStream failed")] 20 | CreateIosMediaStream, 21 | 22 | // iOS Misc error codes 23 | #[error("Extracting native PeerConnection failed")] 24 | ExtractNativePeerConnection, 25 | } 26 | -------------------------------------------------------------------------------- /src/android/api/org/signal/ringrtc/CallException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2021 Signal Messenger, LLC 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | package org.signal.ringrtc; 7 | 8 | /** 9 | * A simple exception class that can be thrown by any of the {@link 10 | * org.signal.ringrtc.CallManager} class methods. 11 | */ 12 | public class CallException extends Exception { 13 | public CallException() { 14 | } 15 | 16 | public CallException(String detailMessage) { 17 | super(detailMessage); 18 | } 19 | 20 | public CallException(String detailMessage, Throwable throwable) { 21 | super(detailMessage, throwable); 22 | } 23 | 24 | public CallException(Throwable throwable) { 25 | super(throwable); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/rust/src/webrtc/ffi/logging.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | use crate::webrtc; 7 | 8 | #[repr(C)] 9 | #[derive(Clone, Copy, Debug)] 10 | #[allow(dead_code)] 11 | pub enum LogSeverity { 12 | Verbose, 13 | Info, 14 | Warn, 15 | Error, 16 | None, 17 | } 18 | 19 | #[repr(C)] 20 | #[allow(non_snake_case)] 21 | pub struct LoggerCallbacks { 22 | pub onLogMessage: extern "C" fn(LogSeverity, webrtc::ptr::Borrowed), 23 | } 24 | 25 | unsafe extern "C" { 26 | #[allow(dead_code)] 27 | pub fn Rust_setLogger( 28 | callbacks: webrtc::ptr::Borrowed, 29 | min_severity: LogSeverity, 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/android/api/org/signal/ringrtc/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Provides the classes and interfaces necessary to create a call connection object and integrate it with a client Android application. 4 |

5 | The package provides two high level objects of interest. 6 | 7 |

    8 |
  • org.signal.ringrtc.CallConnection -- represents the session of a call, 9 | very similar to org.webrtc.PeerConnection. 10 |
  • org.signal.ringrtc.CallConnectionFactory -- creates CallConnection 11 | objects, very similar to org.webrtc.PeerConnectionFactory. 12 |
13 | @see org.signal.ringrtc.CallConnectionFactory 14 | @see org.signal.ringrtc.CallConnection 15 | @see org.webrtc.PeerConnectionFactory 16 | @see org.webrtc.PeerConnection 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # AndroidX package structure to make it clearer which packages are bundled with the 11 | # Android operating system, and which are packaged with your app"s APK 12 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 13 | android.useAndroidX=true 14 | -------------------------------------------------------------------------------- /src/android/api/org/signal/ringrtc/NetworkRoute.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2021 Signal Messenger, LLC 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | package org.signal.ringrtc; 7 | 8 | import org.webrtc.PeerConnection; 9 | 10 | /** 11 | * 12 | * Information about the network route being used for sending audio/video/data 13 | * 14 | */ 15 | public class NetworkRoute { 16 | PeerConnection.AdapterType localAdapterType; 17 | 18 | public NetworkRoute() { 19 | this.localAdapterType = PeerConnection.AdapterType.UNKNOWN; 20 | } 21 | 22 | public NetworkRoute(PeerConnection.AdapterType localAdapterType) { 23 | this.localAdapterType = localAdapterType; 24 | } 25 | 26 | public PeerConnection.AdapterType getLocalAdapterType() { 27 | return this.localAdapterType; 28 | } 29 | } -------------------------------------------------------------------------------- /src/android/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/android-sdk-linux/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | -dontwarn org.webrtc.NetworkMonitorAutoDetect 11 | -dontwarn android.net.Network 12 | -dontwarn android.support.v4.media.AudioAttributesCompat 13 | -dontwarn android.support.v4.media.AudioAttributesImplApi21 14 | -dontwarn android.support.v4.media.AudioAttributesImplBase 15 | -keep class org.webrtc.** { *; } 16 | -keep class org.signal.ringrtc.** { *; } 17 | -keep class org.jni_zero.** { *; } 18 | -------------------------------------------------------------------------------- /src/ios/SignalRingRTC/SignalRingRTCTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /bin/build-rustdoc: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright 2019-2021 Signal Messenger, LLC 5 | # SPDX-License-Identifier: AGPL-3.0-only 6 | # 7 | 8 | set -e 9 | 10 | # shellcheck source=bin/env.sh 11 | . "$(dirname "$0")"/env.sh 12 | 13 | MANIFEST="${RINGRTC_SRC_DIR}/rust/Cargo.toml" 14 | 15 | OUTPUT_DIR="${OUTPUT_DIR}/rustdoc" 16 | 17 | case $WEBRTC_PLATFORM in 18 | android) 19 | TARGET=aarch64-linux-android 20 | ;; 21 | ios) 22 | TARGET=aarch64-apple-ios 23 | ;; 24 | *) 25 | echo "ERROR: Unknown platform: $WEBRTC_PLATFORM" 26 | exit 1 27 | esac 28 | 29 | cargo doc \ 30 | --no-deps \ 31 | --document-private-items \ 32 | --target $TARGET \ 33 | --target-dir "$OUTPUT_DIR" \ 34 | --manifest-path "$MANIFEST" 35 | 36 | echo "Docs ready in: ${OUTPUT_DIR}/${TARGET}/doc/ringrtc" 37 | -------------------------------------------------------------------------------- /src/ios/SignalRingRTC/SignalRingRTC/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/android/src/androidTest/java/CallIdTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Signal Messenger, LLC 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import org.signal.ringrtc.CallId; 7 | 8 | import org.junit.Test; 9 | 10 | import static org.junit.Assert.assertEquals; 11 | import static org.junit.Assert.assertNotEquals; 12 | 13 | public class CallIdTest extends CallTestBase { 14 | @Test 15 | public void testFromEra() throws Exception { 16 | CallId fromEra = CallId.fromEra("1122334455667788"); 17 | CallId fromHex = new CallId(0x1122334455667788L); 18 | assertEquals(fromEra, fromHex); 19 | 20 | // Just don't crash. 21 | CallId fromUnusualEra = CallId.fromEra("mesozoic"); 22 | assertNotEquals(fromEra, fromUnusualEra); 23 | assertNotEquals(0, fromUnusualEra.longValue()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/rust/src/webrtc/ice_gatherer.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | //! WebRTC IceGatherer Interface. 7 | 8 | use crate::webrtc; 9 | #[cfg(not(feature = "sim"))] 10 | use crate::webrtc::ffi::ice_gatherer::RffiIceGatherer; 11 | #[cfg(feature = "sim")] 12 | use crate::webrtc::sim::ice_gatherer::RffiIceGatherer; 13 | 14 | /// Rust wrapper around WebRTC C++ IceGatherer object. 15 | #[derive(Debug)] 16 | pub struct IceGatherer { 17 | rffi: webrtc::Arc, 18 | } 19 | 20 | impl IceGatherer { 21 | /// Create a new Rust IceGatherer object from a WebRTC C++ IceGatherer object. 22 | pub fn new(rffi: webrtc::Arc) -> Self { 23 | Self { rffi } 24 | } 25 | 26 | pub fn rffi(&self) -> &webrtc::Arc { 27 | &self.rffi 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /bin/regenerate_acknowledgments.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright 2023 Signal Messenger, LLC. 5 | # SPDX-License-Identifier: AGPL-3.0-only 6 | # 7 | 8 | set -euo pipefail 9 | 10 | SCRIPT_DIR=$(dirname "$0") 11 | cd "${SCRIPT_DIR}"/.. 12 | 13 | echo "Checking cargo-about version" 14 | VERSION=$(cargo about --version) 15 | echo "Found $VERSION" 16 | 17 | EXPECTED_VERSION="cargo-about 0.6.2" 18 | if [ "$VERSION" != "$EXPECTED_VERSION" ]; then 19 | echo "This tool works with $EXPECTED_VERSION but $VERSION is installed" 20 | false 21 | fi 22 | 23 | for template in acknowledgments/*.hbs; do 24 | template_basename=$(basename "${template%.hbs}") 25 | echo "Generating ${template_basename}" ... >&2 26 | cargo about generate --config acknowledgments/about.toml --features electron --manifest-path src/rust/Cargo.toml --fail "$template" --output-file "${template%.hbs}" 27 | done 28 | -------------------------------------------------------------------------------- /src/ios/SignalRingRTC/SignalRingRTC/Assertions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2024 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | import SignalRingRTC.RingRTC 7 | 8 | internal func fail( 9 | _ message: String, 10 | file: StaticString = #fileID, 11 | function: StaticString = #function, 12 | line: Int = #line 13 | ) -> Never { 14 | failDebug(message, file: file, function: function, line: line) 15 | 16 | Logger.error(Thread.callStackSymbols.joined(separator: "\n")) 17 | Logger.flush() 18 | 19 | fatalError(message) 20 | } 21 | 22 | internal func failDebug( 23 | _ message: String, 24 | file: StaticString = #fileID, 25 | function: StaticString = #function, 26 | line: Int = #line 27 | ) { 28 | Logger.error(message, file: file, function: function, line: UInt32(line)) 29 | assertionFailure(message, file: file, line: UInt(line)) 30 | } 31 | -------------------------------------------------------------------------------- /src/rust/src/webrtc/sim/rtp_observer.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2025 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | //! WebRTC Simulation RtpObserver 7 | 8 | use crate::webrtc; 9 | 10 | /// Simulation type for RtpObserver. 11 | pub type RffiRtpObserver = u32; 12 | 13 | static FAKE_RTP_OBSERVER: u32 = 17; 14 | 15 | #[allow(non_snake_case, clippy::missing_safety_doc)] 16 | pub unsafe fn Rust_createRtpObserver( 17 | _observer: webrtc::ptr::Borrowed, 18 | _callbacks: webrtc::ptr::Borrowed, 19 | ) -> webrtc::ptr::Owned { 20 | info!("Rust_createRtpObserver():"); 21 | unsafe { webrtc::ptr::Owned::from_ptr(&FAKE_RTP_OBSERVER) } 22 | } 23 | 24 | #[allow(non_snake_case, clippy::missing_safety_doc)] 25 | pub unsafe fn Rust_deleteRtpObserver(_observer: webrtc::ptr::Owned) { 26 | info!("Rust_deleteRtpObserver():"); 27 | } 28 | -------------------------------------------------------------------------------- /acknowledgments/README.md: -------------------------------------------------------------------------------- 1 | This directory contains pre-generated acknowledgments for the Rust dependencies of RingRTC. CI enforces that they are kept up to date. 2 | 3 | ## Updating 4 | 5 | If you update RingRTC's dependencies, you'll need to update this listing. Install [cargo-about][] if you haven't already: 6 | 7 | ```shell 8 | cargo +stable install --locked cargo-about --version 0.6.2 9 | ``` 10 | 11 | Then: 12 | 13 | 1. Run `bin/regenerate_acknowledgments.sh`. 14 | 2. Check the HTML output for new "synthesized" entries. This can indicate that the license for a particular dependency was not properly detected. 15 | 3. If there are any "synthesized" entries (besides those in this repository, "ringrtc" and "regex-aot"), add new "[clarify][]" entries to about.toml. 16 | 17 | [cargo-about]: https://embarkstudios.github.io/cargo-about/ 18 | [clarify]: https://embarkstudios.github.io/cargo-about/cli/generate/config.html#the-clarify-field-optional 19 | -------------------------------------------------------------------------------- /src/android/api/org/signal/ringrtc/WebRtcLogger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2021 Signal Messenger, LLC 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | package org.signal.ringrtc; 7 | 8 | import org.webrtc.Logging.Severity; 9 | 10 | public final class WebRtcLogger implements org.webrtc.Loggable { 11 | 12 | private static final String TAG = Log.class.getSimpleName(); 13 | 14 | @Override 15 | public void onLogMessage(String message, Severity severity, String tag) { 16 | 17 | switch(severity) { 18 | case LS_NONE: 19 | // eat it 20 | break; 21 | case LS_ERROR: 22 | Log.e(tag, message); break; 23 | case LS_WARNING: 24 | Log.w(tag, message); break; 25 | case LS_INFO: 26 | Log.i(tag, message); break; 27 | case LS_VERBOSE: 28 | Log.d(tag, message); break; 29 | default: 30 | Log.w(TAG, "Unknown log level: " + tag + ", " + message); 31 | } 32 | 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /bin/rust-lint-check: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright 2019-2021 Signal Messenger, LLC 5 | # SPDX-License-Identifier: AGPL-3.0-only 6 | # 7 | 8 | set -e 9 | 10 | # shellcheck source=bin/env.sh 11 | . "$(dirname "$0")"/env.sh 12 | 13 | MANIFEST="${RINGRTC_SRC_DIR}/rust/Cargo.toml" 14 | 15 | OUTPUT_DIR="${OUTPUT_DIR}/rust-lint" 16 | 17 | # Remove some of the build products, so that the lint is generated 18 | # every time. 19 | rm -rf "${OUTPUT_DIR}"/*/*/*/*ringrtc* "${OUTPUT_DIR}"/*/*/.fingerprint/*ringrtc* 20 | 21 | case $WEBRTC_PLATFORM in 22 | android) 23 | TARGET=aarch64-linux-android 24 | ;; 25 | ios) 26 | TARGET=aarch64-apple-ios 27 | ;; 28 | *) 29 | echo "ERROR: Unknown platform: $WEBRTC_PLATFORM" 30 | exit 1 31 | esac 32 | 33 | echo "Entering directory \`$(dirname "$MANIFEST")'" 34 | 35 | cargo clippy \ 36 | --target $TARGET \ 37 | --target-dir "$OUTPUT_DIR" \ 38 | --manifest-path "$MANIFEST" \ 39 | -- -D warnings 40 | -------------------------------------------------------------------------------- /src/rust/src/webrtc/ffi/rtp_observer.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2025 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | //! WebRTC FFI RTP Observer Interface. 7 | 8 | use crate::webrtc; 9 | 10 | /// Incomplete type for C++ RtpObserverRffi. 11 | #[repr(C)] 12 | #[derive(Clone, Copy)] 13 | pub struct RffiRtpObserver { 14 | _private: [u8; 0], 15 | } 16 | 17 | impl webrtc::ptr::Delete for RffiRtpObserver { 18 | fn delete(owned: webrtc::ptr::Owned) { 19 | unsafe { Rust_deleteRtpObserver(owned) }; 20 | } 21 | } 22 | 23 | unsafe extern "C" { 24 | // The passed-in observer must live as long as the returned value, 25 | // which in turn must live as long as the PeerConnection it is passed to. 26 | pub fn Rust_createRtpObserver( 27 | observer: webrtc::ptr::Borrowed, 28 | callbacks: webrtc::ptr::Borrowed, 29 | ) -> webrtc::ptr::Owned; 30 | 31 | pub fn Rust_deleteRtpObserver(observer: webrtc::ptr::Owned); 32 | } 33 | -------------------------------------------------------------------------------- /bin/gsync-webrtc: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright 2019-2021 Signal Messenger, LLC 5 | # SPDX-License-Identifier: AGPL-3.0-only 6 | # 7 | 8 | set -e 9 | 10 | WEBRTC_REVISION="$1" 11 | # shellcheck source=bin/env.sh 12 | . "$(dirname "$0")"/env.sh 13 | 14 | # Create gclient config file, based on platform 15 | mkdir -p "$WEBRTC_DIR" 16 | TARGET_GCLIENT="${WEBRTC_DIR}/.gclient" 17 | cp "${CONFIG_DIR}/webrtc.gclient.common" "$TARGET_GCLIENT" 18 | case "$WEBRTC_PLATFORM" in 19 | android|ios|mac|unix|windows) 20 | cat "${CONFIG_DIR}/webrtc.gclient.${WEBRTC_PLATFORM}" >> "$TARGET_GCLIENT" 21 | esac 22 | 23 | [ -d "$RINGRTC_SRC_DIR" ] || { 24 | echo "ERROR: Unable to find RINGRTC_SRC directory: $RINGRTC_SRC_DIR" 25 | exit 1 26 | } 27 | 28 | echo "Downloading WebRTC dependencies to ${WEBRTC_DIR} from version ${WEBRTC_VERSION}" 29 | echo "CONFIGURED_WEBRTC_VERSION=${WEBRTC_VERSION}" > "${OUTPUT_DIR}/webrtc-version.env" 30 | 31 | ( 32 | cd "$WEBRTC_DIR" 33 | gclient sync --no-history --jobs 32 --with_tags "--revision=src@${WEBRTC_VERSION}" 34 | ) 35 | -------------------------------------------------------------------------------- /src/rust/src/webrtc/rtp.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | pub type PayloadType = u8; 7 | pub type Ssrc = u32; 8 | pub type SequenceNumber = u16; 9 | pub type Timestamp = u32; 10 | 11 | #[derive(Clone, Debug)] 12 | pub struct Header { 13 | pub pt: PayloadType, 14 | pub seqnum: SequenceNumber, 15 | pub timestamp: Timestamp, 16 | pub ssrc: Ssrc, 17 | } 18 | 19 | impl Extend
for Header { 20 | fn extend>(&mut self, iter: T) { 21 | for header in iter { 22 | if header.pt != self.pt { 23 | warn!("Tried to extend header with mismatched payload type"); 24 | continue; 25 | } 26 | if self.ssrc != header.ssrc { 27 | warn!("Tried to extend header with mismatched ssrc"); 28 | continue; 29 | } 30 | 31 | self.timestamp = header.timestamp; 32 | self.seqnum = header.seqnum; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /bin/logs-notebook/emos.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2023 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | # 5 | 6 | # Based on https://github.com/twilio/twilio-video.js/blob/master/lib/preflight/mos.ts 7 | def compute_emos(rtt: int, jitter: int, fractionLost: float) -> float: 8 | """ 9 | Computes the MOS value between [1, 4.5) based on rtt, jitter, and packet loss 10 | """ 11 | 12 | r0: float = 94.768 13 | 14 | effectiveLatency: int = rtt + (jitter * 2) + 10 15 | 16 | rFactor: float = 0 17 | if effectiveLatency < 160: 18 | rFactor = r0 - (effectiveLatency / 40) 19 | elif effectiveLatency < 1000: 20 | rFactor = r0 - ((effectiveLatency - 120) / 10) 21 | 22 | # Adjust "rFactor" with the fraction of packets lost. 23 | if fractionLost <= (rFactor / 2.5): 24 | rFactor = max(rFactor - fractionLost * 2.5, 6.52) 25 | else: 26 | rFactor = 0 27 | 28 | # Compute MOS from "rFactor". 29 | mos: float = 1 + (0.035 * rFactor) + (0.000007 * rFactor) * (rFactor - 60) * (100 - rFactor) 30 | 31 | return mos 32 | -------------------------------------------------------------------------------- /src/rust/src/android/webrtc_peer_connection_factory.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | //! Re-exports WebRTC JNI interfaces 7 | 8 | use jni::{ 9 | JNIEnv, 10 | objects::{JClass, JObject}, 11 | sys::jlong, 12 | }; 13 | 14 | use crate::{webrtc, webrtc::peer_connection::RffiPeerConnection}; 15 | 16 | unsafe extern "C" { 17 | /// Export the nativeCreatePeerConnection() call from the 18 | /// org.webrtc.PeerConnectionFactory class. 19 | pub fn Java_org_webrtc_PeerConnectionFactory_nativeCreatePeerConnection( 20 | env: JNIEnv, 21 | class: JClass, 22 | factory: jlong, 23 | rtcConfig: JObject, 24 | constraints: JObject, 25 | nativeObserver: jlong, 26 | sslCertificateVerifier: JObject, 27 | ) -> jlong; 28 | } 29 | 30 | // Get the native PeerConnection inside of the Java wrapper. 31 | unsafe extern "C" { 32 | pub fn Rust_borrowPeerConnectionFromJniOwnedPeerConnection( 33 | jni_owned_pc: i64, 34 | ) -> webrtc::ptr::BorrowedRc; 35 | } 36 | -------------------------------------------------------------------------------- /src/rust/src/webrtc/ffi/stats_observer.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | //! WebRTC FFI Stats Observer 7 | 8 | use crate::webrtc; 9 | 10 | /// Incomplete type for C++ webrtc::rffi::StatsObserverRffi 11 | #[repr(C)] 12 | pub struct RffiStatsObserver { 13 | _private: [u8; 0], 14 | } 15 | 16 | // See "class StatsObserver : public rtc::RefCountInterface" 17 | // in webrtc/api/peer_connection_interface.h. 18 | impl webrtc::RefCounted for RffiStatsObserver {} 19 | 20 | unsafe extern "C" { 21 | // The passed-in values observer must live as long as the returned value, 22 | // which in turn must live as long as the call to PeerConnection::getStats. 23 | pub fn Rust_createStatsObserver( 24 | stats_observer: webrtc::ptr::Borrowed, 25 | stats_observer_cbs: webrtc::ptr::Borrowed, 26 | ) -> webrtc::ptr::OwnedRc; 27 | 28 | pub fn Rust_setCollectRawStatsReport( 29 | stats_observer: webrtc::ptr::BorrowedRc, 30 | collect_raw_stats_report: bool, 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /bin/logs-notebook/debuglogs.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "a5e8fbfe-6f28-4a96-91a1-f7ae53f35773", 7 | "metadata": { 8 | "tags": [] 9 | }, 10 | "outputs": [], 11 | "source": [ 12 | "import call_log_parser\n", 13 | "\n", 14 | "calls = call_log_parser.load_calls(\"\")" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "id": "5417c7d2-ec1e-4b51-b4ec-467fba2b52a4", 21 | "metadata": { 22 | "tags": [] 23 | }, 24 | "outputs": [], 25 | "source": [ 26 | "call_log_parser.describe(calls)" 27 | ] 28 | } 29 | ], 30 | "metadata": { 31 | "kernelspec": { 32 | "display_name": "Python 3 (ipykernel)", 33 | "language": "python", 34 | "name": "python3" 35 | }, 36 | "language_info": { 37 | "codemirror_mode": { 38 | "name": "ipython", 39 | "version": 3 40 | }, 41 | "file_extension": ".py", 42 | "mimetype": "text/x-python", 43 | "name": "python", 44 | "nbconvert_exporter": "python", 45 | "pygments_lexer": "ipython3", 46 | "version": "3.9.6" 47 | } 48 | }, 49 | "nbformat": 4, 50 | "nbformat_minor": 5 51 | } 52 | -------------------------------------------------------------------------------- /bin/prepare-workspace: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright 2019-2021 Signal Messenger, LLC 5 | # SPDX-License-Identifier: AGPL-3.0-only 6 | # 7 | 8 | set -e 9 | 10 | # shellcheck source=bin/env.sh 11 | . "$(dirname "$0")"/env.sh 12 | 13 | case "$1" in 14 | android) 15 | WEBRTC_PLATFORM="android" 16 | ;; 17 | ios) 18 | WEBRTC_PLATFORM="ios" 19 | ;; 20 | mac) 21 | WEBRTC_PLATFORM="mac" 22 | ;; 23 | unix) 24 | WEBRTC_PLATFORM="unix" 25 | ;; 26 | windows) 27 | WEBRTC_PLATFORM="windows" 28 | ;; 29 | *) 30 | echo "ERROR: Unknown platform type: $1" 31 | echo "Supported platforms: 'android', 'ios', 'mac', 'unix', 'windows'" 32 | exit 1 33 | esac 34 | 35 | # shellcheck source=bin/env.sh 36 | . "$(dirname "$0")"/env.sh 37 | 38 | mkdir -p "$OUTPUT_DIR" 39 | echo "WEBRTC_PLATFORM=$WEBRTC_PLATFORM" > "${OUTPUT_DIR}/platform.env" 40 | 41 | # WebRTC checkout 42 | "$BIN_DIR"/gsync-webrtc "$WEBRTC_REVISION" 43 | 44 | # Platform specific setup 45 | prepare_workspace_platform 46 | 47 | # shellcheck disable=SC2317 48 | echo "WEBRTC_PLATFORM=$WEBRTC_PLATFORM" > "${OUTPUT_DIR}/${WEBRTC_PLATFORM}.env" 49 | -------------------------------------------------------------------------------- /call_sim/Cargo.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2023 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | # 5 | 6 | [package] 7 | name = "call_sim" 8 | version.workspace = true 9 | authors.workspace = true 10 | edition = "2024" 11 | license = "AGPL-3.0-only" 12 | 13 | [dependencies] 14 | anyhow = "1.0.100" 15 | base64 = "0.22.1" 16 | bollard = "0.17.1" 17 | protobuf = { path = "../protobuf", features = ["call_sim"] } 18 | chrono = "0.4.42" 19 | clap = { version = "4.5.53", features = ["derive"] } 20 | futures-util = "0.3.31" 21 | hex = { version = "0.4.3", features = ["serde"] } 22 | hmac = "0.12.1" 23 | hound = "3.5.1" 24 | itertools = "0.13.0" 25 | plotters = { version = "0.3.7", default-features = false, features = ["fontconfig-dlopen", "full_palette", "histogram", "line_series", "svg_backend", "ttf"] } 26 | prost = "0.14.1" 27 | regex = "1.12.2" 28 | relative-path = "1.9.3" 29 | serde = { version = "1.0.228", features = ["derive"] } 30 | serde_json = "1.0.145" 31 | sha2 = "0.10.9" 32 | tokio = { version = "1.48.0", features = ["rt-multi-thread", "macros", "time", "fs", "process"] } 33 | tonic = "0.14.2" 34 | tonic-prost = "0.14.2" 35 | tower = { version = "0.4.13", features = ["timeout"] } 36 | uuid = { version = "1.19.0", features = ["v4", "fast-rng"] } 37 | -------------------------------------------------------------------------------- /call_sim/docker/ringrtc/Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2023 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | # 5 | 6 | FROM ubuntu:24.04 7 | 8 | RUN apt-get update \ 9 | && apt-get install -y curl \ 10 | iproute2 \ 11 | iperf \ 12 | iptables \ 13 | iputils-ping \ 14 | protobuf-compiler \ 15 | libpulse-dev \ 16 | linux-tools-generic \ 17 | linux-tools-common \ 18 | build-essential \ 19 | pulseaudio-utils \ 20 | libpulse0 \ 21 | pipewire \ 22 | dbus \ 23 | pipewire-pulse \ 24 | wireplumber \ 25 | psmisc \ 26 | dbus-user-session \ 27 | && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \ 28 | && . ~/.cargo/env \ 29 | && cargo install --features=bin addr2line && cargo install inferno 30 | 31 | RUN ln -s "$(find /usr/lib/linux-tools/*/perf | head -1)" /usr/local/bin/perf 32 | 33 | RUN curl https://raw.githubusercontent.com/torvalds/linux/refs/heads/master/tools/perf/perf-archive.sh > /usr/local/bin/perf-archive && chmod +x /usr/local/bin/perf-archive 34 | 35 | ENV XDG_RUNTIME_DIR=/tmp 36 | ENV DBUS_SESSION_BUS_ADDRESS=unix:path=/tmp/bus 37 | 38 | COPY target/release/call_sim-cli /usr/local/bin/ 39 | -------------------------------------------------------------------------------- /config/version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright 2019-2021 Signal Messenger, LLC 5 | # SPDX-License-Identifier: AGPL-3.0-only 6 | # 7 | 8 | # Allow non-exported environment variables 9 | # shellcheck disable=SC2034 10 | 11 | if [ -n "${PROJECT_DIR}" ]; then 12 | SCRIPT_DIR="${PROJECT_DIR}/config" 13 | else 14 | SCRIPT_DIR=$(dirname "$0") 15 | fi 16 | 17 | property() { 18 | grep "${1}" "${SCRIPT_DIR}/version.properties" | cut -d'=' -f2 19 | } 20 | 21 | # Specify WebRTC version. This corresponds to the 22 | # branch or tag of the signalapp/webrtc repository. 23 | WEBRTC_VERSION=$(property 'webrtc.version') 24 | 25 | RINGRTC_MAJOR_VERSION=$(property 'ringrtc.version.major') 26 | RINGRTC_MINOR_VERSION=$(property 'ringrtc.version.minor') 27 | RINGRTC_REVISION=$(property 'ringrtc.version.revision') 28 | 29 | # Specify RingRTC version to publish. 30 | RINGRTC_VERSION="${RINGRTC_MAJOR_VERSION}.${RINGRTC_MINOR_VERSION}.${RINGRTC_REVISION}" 31 | 32 | # Release candidate -- for pre-release versions. Uncomment to use. 33 | # RC_VERSION="alpha" 34 | 35 | # Project version is the combination of the two 36 | PROJECT_VERSION="${OVERRIDE_VERSION:-${RINGRTC_VERSION}}${RC_VERSION:+-$RC_VERSION}" 37 | 38 | echo "WebRTC : ${WEBRTC_VERSION}" 39 | echo "RingRTC: ${RINGRTC_VERSION}" 40 | -------------------------------------------------------------------------------- /src/android/api/org/signal/ringrtc/CameraControl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2021 Signal Messenger, LLC 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | package org.signal.ringrtc; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.annotation.Nullable; 10 | 11 | import org.webrtc.CapturerObserver; 12 | 13 | /** 14 | * 15 | * An interface for controlling the camera devices 16 | * 17 | */ 18 | public interface CameraControl { 19 | 20 | public boolean hasCapturer(); 21 | 22 | public void initCapturer(@NonNull CapturerObserver observer); 23 | 24 | public void setEnabled(boolean enable); 25 | 26 | public void flip(); 27 | 28 | /** 29 | * 30 | * Sets the orientation of the camera to a rotation defined by the 31 | * application. The orientation (how the device is rotated) can be 32 | * any of the integer degree values (i.e. 0, 90, 180, 270) defined 33 | * by: CameraCharacteristics::SENSOR_ORIENTATION 34 | * 35 | * If not called or if null is set as the orientation, the default 36 | * behavior will be used (CameraSession.getDeviceOrientation()). 37 | * 38 | * @param orientation the current rotation value of the device 39 | * 40 | */ 41 | public void setOrientation(@Nullable Integer orientation); 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/rust/src/webrtc/sim/peer_connection_observer.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | //! WebRTC Simulation PeerConnectionObserver 7 | 8 | use crate::webrtc; 9 | 10 | /// Simulation type for PeerConnectionObserver. 11 | pub type RffiPeerConnectionObserver = u32; 12 | 13 | static FAKE_OBSERVER: u32 = 7; 14 | 15 | impl webrtc::ptr::Delete for u32 { 16 | fn delete(_owned: webrtc::ptr::Owned) {} 17 | } 18 | 19 | #[allow(non_snake_case, clippy::missing_safety_doc)] 20 | pub unsafe fn Rust_createPeerConnectionObserver( 21 | _cc_ptr: webrtc::ptr::Borrowed, 22 | _pc_observer_cb: webrtc::ptr::Borrowed, 23 | _enable_frame_encryption: bool, 24 | _enable_video_frame_event: bool, 25 | _enable_video_frame_content: bool, 26 | ) -> webrtc::ptr::Owned { 27 | info!("Rust_createPeerConnectionObserver():"); 28 | unsafe { webrtc::ptr::Owned::from_ptr(&FAKE_OBSERVER) } 29 | } 30 | 31 | #[allow(non_snake_case, clippy::missing_safety_doc)] 32 | pub unsafe fn Rust_deletePeerConnectionObserver( 33 | _observer: webrtc::ptr::Owned, 34 | ) { 35 | info!("Rust_deletePeerConnectionObserver():"); 36 | } 37 | -------------------------------------------------------------------------------- /src/rust/src/sim/error.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | //! Simulation Error Codes and Utilities. 7 | 8 | use thiserror::Error; 9 | 10 | /// Simulation specific error codes. 11 | #[derive(Error, Debug)] 12 | pub enum SimError { 13 | #[error("Simulation: testing error code: {0}")] 14 | TestError(String), 15 | #[error("Simulation: Intentional: Send offer failed")] 16 | SendOfferError, 17 | #[error("Simulation: Intentional: Send answer failed")] 18 | SendAnswerError, 19 | #[error("Simulation: Intentional: Send ICE candidate failed")] 20 | SendIceCandidateError, 21 | #[error("Simulation: Intentional: Send hangup failed")] 22 | SendHangupError, 23 | #[error("Simulation: Intentional: Send busy failed")] 24 | SendBusyError, 25 | #[error("Simulation: Intentional: Send accepted failed")] 26 | SendAcceptedError, 27 | #[error("Simulation: Intentional: Add Media Stream failed")] 28 | MediaStreamError, 29 | #[error("Simulation: Intentional: Close Media failed")] 30 | CloseMediaError, 31 | #[error("Simulation: Intentional: Start Call failed")] 32 | StartCallError, 33 | #[error("Simulation: Intentional: Call Concluded failed")] 34 | CallConcludedError, 35 | } 36 | -------------------------------------------------------------------------------- /acknowledgments/acknowledgments.plist.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | RingRTC makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | {{#each licenses}} 16 | 17 | FooterText 18 | {{text}} 19 | License 20 | {{name}} 21 | Title 22 | {{#each used_by}}{{#unless @first}}, {{/unless}}{{crate.name}} {{crate.version}}{{/each}} 23 | Type 24 | PSGroupSpecifier 25 | 26 | {{/each}} 27 | 28 | FooterText 29 | Generated by cargo-about 30 | Title 31 | 32 | Type 33 | PSGroupSpecifier 34 | 35 | 36 | StringsTable 37 | Acknowledgements 38 | Title 39 | Acknowledgements 40 | 41 | -------------------------------------------------------------------------------- /src/rust/src/webrtc/ffi/peer_connection_observer.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | //! WebRTC FFI Peer Connection Observer Interface. 7 | 8 | use crate::webrtc; 9 | 10 | /// Incomplete type for C++ PeerConnectionObserver. 11 | #[repr(C)] 12 | #[derive(Clone, Copy)] 13 | pub struct RffiPeerConnectionObserver { 14 | _private: [u8; 0], 15 | } 16 | 17 | impl webrtc::ptr::Delete for RffiPeerConnectionObserver { 18 | fn delete(owned: webrtc::ptr::Owned) { 19 | unsafe { Rust_deletePeerConnectionObserver(owned) }; 20 | } 21 | } 22 | 23 | unsafe extern "C" { 24 | // The passed-in observer must live as long as the returned value, 25 | // which in turn must live as long as the PeerConnections it is passed to. 26 | pub fn Rust_createPeerConnectionObserver( 27 | pc_observer: webrtc::ptr::Borrowed, 28 | pc_observer_cb: webrtc::ptr::Borrowed, 29 | enable_frame_encryption: bool, 30 | enable_video_frame_event: bool, 31 | enable_video_frame_content: bool, 32 | ) -> webrtc::ptr::Owned; 33 | 34 | pub fn Rust_deletePeerConnectionObserver( 35 | observer: webrtc::ptr::Owned, 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/rust/src/webrtc/ffi/ref_count.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | //! Wrapper around rtc::RefCountInterface 7 | 8 | use crate::webrtc; 9 | 10 | #[repr(C)] 11 | pub struct RffiRefCounted { 12 | _private: [u8; 0], 13 | } 14 | 15 | impl webrtc::RefCounted for RffiRefCounted {} 16 | 17 | /// Decrements the ref count. 18 | /// If the ref count goes to zero, the object is deleted. 19 | pub fn dec(rc: webrtc::ptr::OwnedRc) { 20 | unsafe { 21 | Rust_decRc(webrtc::ptr::OwnedRc::from_ptr( 22 | rc.as_ptr() as *const RffiRefCounted 23 | )); 24 | } 25 | } 26 | 27 | /// Increments the ref count. 28 | /// The borrowed RC becomes an owned RC. 29 | /// # Safety 30 | /// The pointee must still be alive 31 | pub unsafe fn inc( 32 | rc: webrtc::ptr::BorrowedRc, 33 | ) -> webrtc::ptr::OwnedRc { 34 | unsafe { 35 | Rust_incRc(webrtc::ptr::BorrowedRc::from_ptr( 36 | rc.as_ptr() as *const RffiRefCounted 37 | )); 38 | webrtc::ptr::OwnedRc::from_ptr(rc.as_ptr()) 39 | } 40 | } 41 | 42 | unsafe extern "C" { 43 | fn Rust_decRc(rc: webrtc::ptr::OwnedRc); 44 | fn Rust_incRc(rc: webrtc::ptr::BorrowedRc); 45 | } 46 | -------------------------------------------------------------------------------- /src/ios/SignalRingRTC/SignalRingRTC/CallManagerUtil.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | import SignalRingRTC.RingRTC 7 | 8 | extension AppByteSlice { 9 | func asUnsafeBufferPointer() -> UnsafeBufferPointer { 10 | return UnsafeBufferPointer(start: bytes, count: len) 11 | } 12 | 13 | func asString(encoding: String.Encoding = String.Encoding.utf8) -> String? { 14 | if self.bytes == nil { 15 | return nil 16 | } 17 | return String(bytes: asUnsafeBufferPointer(), encoding: encoding) 18 | } 19 | 20 | func asBytes() -> [UInt8]? { 21 | if self.bytes == nil { 22 | return nil 23 | } 24 | return Array(asUnsafeBufferPointer()) 25 | } 26 | 27 | func asData() -> Data? { 28 | if self.bytes == nil { 29 | return nil 30 | } 31 | return Data(asUnsafeBufferPointer()) 32 | } 33 | 34 | func toUUID() -> UUID? { 35 | guard let ptr = UnsafeRawPointer(self.bytes), 36 | self.len >= MemoryLayout.size else { 37 | return nil 38 | } 39 | return UUID(uuid: ptr.loadUnaligned(as: uuid_t.self)) 40 | } 41 | } 42 | 43 | extension UUID { 44 | var data: Data { 45 | return withUnsafeBytes(of: self.uuid, { Data($0) }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /protobuf/build.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2024 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | fn main() { 7 | // Explicitly state that by depending on build.rs itself, as recommended. 8 | println!("cargo:rerun-if-changed=build.rs"); 9 | 10 | if cfg!(feature = "signaling") { 11 | let protos = [ 12 | "protobuf/group_call.proto", 13 | "protobuf/rtp_data.proto", 14 | "protobuf/signaling.proto", 15 | "protobuf/call_summary.proto", 16 | ]; 17 | 18 | prost_build::Config::new() 19 | .type_attribute(".call_summary", "#[serde_with::skip_serializing_none]") 20 | .type_attribute(".call_summary", "#[derive(serde::Serialize)]") 21 | .compile_protos(&protos, &["protobuf"]) 22 | .expect("Protobufs are valid"); 23 | 24 | for proto in &protos { 25 | println!("cargo:rerun-if-changed={}", proto); 26 | } 27 | } 28 | 29 | if cfg!(feature = "call_sim") { 30 | tonic_prost_build::configure() 31 | .build_client(true) 32 | .build_server(true) 33 | .build_transport(true) 34 | .protoc_arg("--experimental_allow_proto3_optional") 35 | .compile_protos(&["protobuf/call_sim.proto"], &["protobuf"]) 36 | .expect("call_sim service protobufs are valid") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/node/index.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | import { RingRTCType } from './ringrtc/Service'; 7 | 8 | export { 9 | AnswerMessage, 10 | AudioDevice, 11 | DataMode, 12 | BusyMessage, 13 | Call, 14 | CallEndReason, 15 | CallId, 16 | CallLogLevel, 17 | CallMessageUrgency, 18 | CallRejectReason, 19 | CallSettings, 20 | CallState, 21 | CallingMessage, 22 | ConnectionState, 23 | DeviceId, 24 | GroupCall, 25 | GroupCallKind, 26 | GroupCallObserver, 27 | GroupMemberInfo, 28 | HangupMessage, 29 | HangupType, 30 | HttpMethod, 31 | HttpResult, 32 | IceCandidateMessage, 33 | JoinState, 34 | LocalDeviceState, 35 | OfferMessage, 36 | OfferType, 37 | OpaqueMessage, 38 | PeekDeviceInfo, 39 | PeekInfo, 40 | PeekStatusCodes, 41 | Reaction, 42 | RemoteDeviceState, 43 | RingCancelReason, 44 | RingRTCType, 45 | RingUpdate, 46 | SpeechEvent, 47 | UserId, 48 | VideoFrameSender, 49 | VideoFrameSource, 50 | VideoPixelFormatEnum, 51 | videoPixelFormatToEnum, 52 | VideoRequest, 53 | callIdFromEra, 54 | callIdFromRingId, 55 | } from './ringrtc/Service'; 56 | 57 | export { 58 | CallLinkRootKey, 59 | CallLinkRestrictions, 60 | CallLinkState, 61 | CallLinkEpoch, 62 | } from './ringrtc/CallLinks'; 63 | 64 | export { CallSummary, QualityStats } from './ringrtc/CallSummary'; 65 | 66 | export const RingRTC = new RingRTCType(); 67 | -------------------------------------------------------------------------------- /src/ios/SignalRingRTC/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - CwlCatchException (2.2.1): 3 | - CwlCatchExceptionSupport (~> 2.2.1) 4 | - CwlCatchExceptionSupport (2.2.1) 5 | - CwlMachBadInstructionHandler (2.2.2) 6 | - CwlPosixPreconditionTesting (2.2.2) 7 | - CwlPreconditionTesting (2.2.2): 8 | - CwlCatchException (~> 2.2.1) 9 | - CwlMachBadInstructionHandler (~> 2.2.2) 10 | - CwlPosixPreconditionTesting (~> 2.2.2) 11 | - Nimble (13.7.1): 12 | - CwlPreconditionTesting (~> 2.2.0) 13 | - WebRTCForTesting (0.0.1) 14 | 15 | DEPENDENCIES: 16 | - Nimble (~> 13.7.1) 17 | - WebRTCForTesting (from `../../../out/`) 18 | 19 | SPEC REPOS: 20 | trunk: 21 | - CwlCatchException 22 | - CwlCatchExceptionSupport 23 | - CwlMachBadInstructionHandler 24 | - CwlPosixPreconditionTesting 25 | - CwlPreconditionTesting 26 | - Nimble 27 | 28 | EXTERNAL SOURCES: 29 | WebRTCForTesting: 30 | :path: "../../../out/" 31 | 32 | SPEC CHECKSUMS: 33 | CwlCatchException: 7acc161b299a6de7f0a46a6ed741eae2c8b4d75a 34 | CwlCatchExceptionSupport: 54ccab8d8c78907b57f99717fb19d4cc3bce02dc 35 | CwlMachBadInstructionHandler: dae4fdd124d45c9910ac240287cc7b898f4502a1 36 | CwlPosixPreconditionTesting: ecd095aa2129e740b44301c34571e8d85906fb88 37 | CwlPreconditionTesting: 67a0047dd4de4382b93442c0e3f25207f984f35a 38 | Nimble: 317d713c30c3336dd8571da1889f7ec3afc626e8 39 | WebRTCForTesting: 576c1235b642e5d767e2d13e25ababad3ca20718 40 | 41 | PODFILE CHECKSUM: 2d92f7584bb8f8f2a3f2f917bed1726530529d4a 42 | 43 | COCOAPODS: 1.16.2 44 | -------------------------------------------------------------------------------- /src/ios/.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | *.hmap 25 | *.ipa 26 | 27 | ## Xcode Patch 28 | *.xcodeproj/* 29 | !*.xcodeproj/project.pbxproj 30 | !*.xcodeproj/xcshareddata/ 31 | !*.xcworkspace/contents.xcworkspacedata 32 | /*.gcno 33 | 34 | # Bundler 35 | .bundle 36 | 37 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 38 | # Carthage/Checkouts 39 | 40 | Carthage/Build 41 | 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 45 | # 46 | # Note: if you ignore the Pods directory, make sure to uncomment 47 | # `pod install` in .travis.yml 48 | # 49 | Pods/ 50 | 51 | ##### 52 | # OS X temporary files that should never be committed 53 | # 54 | # c.f. http://www.westwind.com/reference/os-x/invisibles.html 55 | 56 | .DS_Store 57 | -------------------------------------------------------------------------------- /src/rust/src/core/call_mutex.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | //! Call Mutex 7 | /// 8 | /// Wrapper around std::mpsc::Mutex::lock() that on error consumes 9 | /// the poisoned mutex and returns a simple error code. 10 | /// 11 | use std::sync::{Mutex, MutexGuard}; 12 | 13 | use crate::{common::Result, error::RingRtcError}; 14 | 15 | #[derive(Debug)] 16 | pub struct CallMutex { 17 | /// Human readable label for the mutex 18 | label: String, 19 | /// The actual mutex 20 | mutex: Mutex, 21 | } 22 | 23 | unsafe impl Send for CallMutex {} 24 | unsafe impl Sync for CallMutex {} 25 | 26 | impl CallMutex { 27 | /// Creates a new CallMutex 28 | pub fn new(t: T, label: &str) -> CallMutex { 29 | CallMutex { 30 | mutex: Mutex::new(t), 31 | label: label.to_string(), 32 | } 33 | } 34 | 35 | pub fn lock(&self) -> Result> { 36 | match self.mutex.lock() { 37 | Ok(v) => Ok(v), 38 | Err(_) => Err(RingRtcError::MutexPoisoned(self.label.clone()).into()), 39 | } 40 | } 41 | 42 | pub fn lock_or_reset(&self, reset: impl FnOnce(&str) -> T) -> MutexGuard<'_, T> { 43 | self.mutex.lock().unwrap_or_else(|mut poison_error| { 44 | let new_value = reset(&self.label); 45 | **poison_error.get_mut() = new_value; 46 | poison_error.into_inner() 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/node/test/Utils.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2023 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | /* eslint-disable no-console */ 7 | 8 | import { chunk } from 'lodash'; 9 | import { assert } from 'chai'; 10 | 11 | export function countDownLatch(count: number): { 12 | countDown: () => void; 13 | finished: Promise; 14 | } { 15 | assert(count > 0, 'count must be a positive number'); 16 | let resolve: () => void; 17 | const finished = new Promise(resolveInternal => { 18 | resolve = resolveInternal; 19 | }); 20 | 21 | const countDown = () => { 22 | count--; 23 | if (count == 0) { 24 | resolve(); 25 | } 26 | }; 27 | 28 | return { 29 | countDown: countDown, 30 | finished, 31 | }; 32 | } 33 | 34 | export function log(line: string): void { 35 | // Standard logging used for checkpoints. 36 | // Use --renderer to see the log output. (edit: Maybe always shown now?) 37 | // BgYellow 38 | console.log(`\x1b[43m${line}\x1b[0m`); 39 | } 40 | 41 | export function sleep(timeout: number): Promise { 42 | return new Promise(resolve => { 43 | setTimeout(() => { 44 | // BgBlue 45 | console.log(`\x1b[44msleeping ${timeout} ms\x1b[0m`); 46 | resolve(); 47 | }, timeout); 48 | }); 49 | } 50 | 51 | export function uuidToBytes(uuid: string): Uint8Array { 52 | if (uuid.length !== 36) { 53 | return new Uint8Array(0); 54 | } 55 | 56 | return Uint8Array.from( 57 | chunk(uuid.replace(/-/g, ''), 2).map(pair => parseInt(pair.join(''), 16)) 58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /protobuf/protobuf/rtp_data.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2021 Signal Messenger, LLC 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | syntax = "proto2"; 7 | 8 | // Messages sent over RTP data 9 | 10 | package rtp_data; 11 | 12 | message Accepted { 13 | optional uint64 id = 1; 14 | } 15 | 16 | message Hangup { 17 | enum Type { 18 | HANGUP_NORMAL = 0; 19 | HANGUP_ACCEPTED = 1; 20 | HANGUP_DECLINED = 2; 21 | HANGUP_BUSY = 3; 22 | HANGUP_NEED_PERMISSION = 4; 23 | } 24 | 25 | optional uint64 id = 1; 26 | optional Type type = 2; 27 | optional uint32 deviceId = 3; 28 | } 29 | 30 | message SenderStatus { 31 | optional uint64 id = 1; 32 | optional bool video_enabled = 2; 33 | optional bool sharing_screen = 3; 34 | optional bool audio_enabled = 4; 35 | } 36 | 37 | message ReceiverStatus { 38 | optional uint64 id = 1; 39 | // Used during the call to convey the bitrate that should be used for sending. 40 | optional uint64 max_bitrate_bps = 2; 41 | } 42 | 43 | message Message { 44 | optional Accepted accepted = 1; 45 | optional Hangup hangup = 2; 46 | optional SenderStatus senderStatus = 3; 47 | // If set, a larger value means a later message than a smaller value. 48 | // Can be used to detect that messages are out of order. 49 | // Useful when sending over transports that don't have ordering 50 | // (or when sending over more than one transport) 51 | optional uint64 seqnum = 4; 52 | optional ReceiverStatus receiverStatus = 5; 53 | } 54 | -------------------------------------------------------------------------------- /src/rust/src/webrtc/logging.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | use crate::webrtc::{ 7 | self, 8 | ffi::logging::{LogSeverity, LoggerCallbacks, Rust_setLogger}, 9 | }; 10 | 11 | pub fn set_logger(filter: log::LevelFilter) { 12 | let cbs = LoggerCallbacks { 13 | onLogMessage: log_sink_OnLogMessage, 14 | }; 15 | let cbs_ptr: *const LoggerCallbacks = &cbs; 16 | let min_severity = match filter { 17 | log::LevelFilter::Off => LogSeverity::None, 18 | log::LevelFilter::Error => LogSeverity::Error, 19 | log::LevelFilter::Warn => LogSeverity::Warn, 20 | log::LevelFilter::Info => LogSeverity::Info, 21 | log::LevelFilter::Debug => LogSeverity::Verbose, 22 | log::LevelFilter::Trace => LogSeverity::Verbose, 23 | }; 24 | unsafe { 25 | Rust_setLogger(webrtc::ptr::Borrowed::from_ptr(cbs_ptr), min_severity); 26 | } 27 | } 28 | 29 | #[allow(non_snake_case)] 30 | extern "C" fn log_sink_OnLogMessage( 31 | severity: LogSeverity, 32 | c_message: webrtc::ptr::Borrowed, 33 | ) { 34 | let message = unsafe { 35 | std::ffi::CStr::from_ptr(c_message.as_ptr()) 36 | .to_string_lossy() 37 | .into_owned() 38 | }; 39 | match severity { 40 | LogSeverity::Error => error!("{}", message), 41 | LogSeverity::Warn => warn!("{}", message), 42 | LogSeverity::Info => info!("{}", message), 43 | LogSeverity::Verbose => debug!("{}", message), 44 | _ => {} 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/android/api/org/signal/ringrtc/AudioConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Signal Messenger, LLC 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | package org.signal.ringrtc; 7 | 8 | import java.util.Objects; 9 | 10 | public class AudioConfig { 11 | public boolean useOboe; 12 | public boolean useSoftwareAec; 13 | public boolean useSoftwareNs; 14 | public boolean useInputLowLatency; 15 | public boolean useInputVoiceComm; 16 | 17 | public AudioConfig() { 18 | this.useOboe = false; 19 | this.useSoftwareAec = false; 20 | this.useSoftwareNs = false; 21 | this.useInputLowLatency = true; 22 | this.useInputVoiceComm = true; 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return "AudioConfig{" + 28 | "useOboe=" + useOboe + 29 | ", useSoftwareAec=" + useSoftwareAec + 30 | ", useSoftwareNs=" + useSoftwareNs + 31 | ", useInputLowLatency=" + useInputLowLatency + 32 | ", useInputVoiceComm=" + useInputVoiceComm + 33 | "}"; 34 | } 35 | 36 | @Override 37 | public boolean equals(Object o) { 38 | if (this == o) return true; 39 | if (o == null || getClass() != o.getClass()) return false; 40 | AudioConfig that = (AudioConfig) o; 41 | return useOboe == that.useOboe && 42 | useSoftwareAec == that.useSoftwareAec && 43 | useSoftwareNs == that.useSoftwareNs && 44 | useInputLowLatency == that.useInputLowLatency && 45 | useInputVoiceComm == that.useInputVoiceComm; 46 | } 47 | 48 | @Override 49 | public int hashCode() { 50 | return Objects.hash(useOboe, useSoftwareAec, useSoftwareNs, useInputLowLatency, useInputVoiceComm); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/rust/src/bin/call_sim-cli/util.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2024 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | use log::*; 7 | use ringrtc::webrtc::peer_connection_factory::IceServer; 8 | 9 | pub fn string_to_uuid(id: &str) -> Result, anyhow::Error> { 10 | if id.len() != 32 && id.len() != 36 { 11 | return Err(anyhow::anyhow!( 12 | "Expected string to be 32 or 36 characters long." 13 | )); 14 | } 15 | 16 | Ok(hex::decode(id.replace('-', ""))?) 17 | } 18 | 19 | pub fn convert_relay_config_to_ice_servers( 20 | username: String, 21 | password: String, 22 | urls: Vec, 23 | urls_with_ips: Vec, 24 | hostname: Option, 25 | ) -> Vec { 26 | let mut ice_servers = vec![]; 27 | let mut index = 1; 28 | 29 | info!("Relay Servers:"); 30 | info!(" username: {}", username); 31 | info!(" password: {}", password); 32 | if let Some(hostname) = hostname { 33 | info!(" hostname: {}", hostname); 34 | 35 | for url in &urls_with_ips { 36 | info!(" server {}. {}", index, url); 37 | index += 1; 38 | } 39 | 40 | ice_servers.push(IceServer::new( 41 | username.clone(), 42 | password.clone(), 43 | hostname, 44 | urls_with_ips, 45 | )); 46 | } else { 47 | error!(" No hostname provided for urls_with_ips!"); 48 | } 49 | 50 | if !urls.is_empty() { 51 | for url in &urls { 52 | info!(" server {}. {}", index, url); 53 | index += 1; 54 | } 55 | 56 | ice_servers.push(IceServer::new(username, password, "".to_string(), urls)); 57 | } 58 | 59 | ice_servers 60 | } 61 | -------------------------------------------------------------------------------- /src/android/api/org/signal/ringrtc/CallLinkRootKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Signal Messenger, LLC 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | package org.signal.ringrtc; 7 | 8 | import androidx.annotation.NonNull; 9 | 10 | public class CallLinkRootKey { 11 | @NonNull 12 | private final byte[] rawKey; 13 | 14 | public CallLinkRootKey(@NonNull String keyString) throws CallException { 15 | this.rawKey = nativeParseKeyString(keyString); 16 | } 17 | 18 | public CallLinkRootKey(@NonNull byte[] keyBytes) throws CallException { 19 | nativeValidateKeyBytes(keyBytes); 20 | this.rawKey = keyBytes; 21 | } 22 | 23 | @NonNull 24 | public static native CallLinkRootKey generate(); 25 | 26 | @NonNull 27 | public static native byte[] generateAdminPasskey(); 28 | 29 | @NonNull 30 | public byte[] deriveRoomId() { 31 | try { 32 | return nativeDeriveRoomId(rawKey); 33 | } catch (CallException e) { 34 | throw new AssertionError(e); 35 | } 36 | } 37 | 38 | /** Returns the internal storage, so don't modify it! */ 39 | @NonNull 40 | public byte[] getKeyBytes() { 41 | return rawKey; 42 | } 43 | 44 | @NonNull @Override 45 | public String toString() { 46 | try { 47 | return nativeToFormattedString(rawKey); 48 | } catch (CallException e) { 49 | throw new AssertionError(e); 50 | } 51 | } 52 | 53 | // Native-only methods. 54 | private static native byte[] nativeParseKeyString(String keyString) throws CallException; 55 | private static native void nativeValidateKeyBytes(byte[] keyBytes) throws CallException; 56 | private static native byte[] nativeDeriveRoomId(byte[] keyBytes) throws CallException; 57 | private static native String nativeToFormattedString(byte[] keyBytes) throws CallException; 58 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RingRTC 2 | 3 | RingRTC is a middleware library providing [Signal Messenger](https://www.signal.org/) applications with video and voice calling services built on top of [WebRTC](https://webrtc.org/). 4 | 5 | ## Building 6 | 7 | For building RingRTC see [BUILDING.md](BUILDING.md). 8 | 9 | ## Contributing 10 | Contributions to this project are welcome. Pull requests should be tested across all supported platforms. 11 | 12 | For larger changes and feature ideas, we ask that you propose it on the [unofficial Community Forum](https://community.signalusers.org) for a high-level discussion with the wider community before implementation. 13 | 14 | Signing a [CLA (Contributor License Agreement)](https://signal.org/cla/) is required for all contributions. 15 | 16 | # Legal things 17 | ## Cryptography Notice 18 | 19 | This distribution includes cryptographic software. The country in which you currently reside may have restrictions on the import, possession, use, and/or re-export to another country, of encryption software. BEFORE using any encryption software, please check your country's laws, regulations and policies concerning the import, possession, or use, and re-export of encryption software, to see if this is permitted. See for more information. 20 | 21 | The U.S. Government Department of Commerce, Bureau of Industry and Security (BIS), has classified this software as Export Commodity Control Number (ECCN) 5D002.C.1, which includes information security software using or performing cryptographic functions with asymmetric algorithms. The form and manner of this distribution makes it eligible for export under the License Exception ENC Technology Software Unrestricted (TSU) exception (see the BIS Export Administration Regulations, Section 740.13) for both object code and source code. 22 | 23 | ## License 24 | 25 | Copyright 2019-2021 Signal Messenger, LLC
26 | 27 | Licensed under [AGPLv3](https://www.gnu.org/licenses/agpl-3.0.html) only. 28 | -------------------------------------------------------------------------------- /src/android/api/org/signal/ringrtc/Util.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2021 Signal Messenger, LLC 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | package org.signal.ringrtc; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | import java.nio.ByteBuffer; 10 | import java.util.Collection; 11 | import java.util.UUID; 12 | 13 | public final class Util { 14 | // Based on https://gist.github.com/jeffjohnson9046/c663dd22bbe6bb0b3f5e. 15 | public static byte[] getBytesFromUuid(UUID uuid) { 16 | ByteBuffer bytes = ByteBuffer.wrap(new byte[16]); 17 | bytes.putLong(uuid.getMostSignificantBits()); 18 | bytes.putLong(uuid.getLeastSignificantBits()); 19 | 20 | return bytes.array(); 21 | } 22 | 23 | public static UUID getUuidFromBytes(byte[] bytes) { 24 | ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); 25 | Long high = byteBuffer.getLong(); 26 | Long low = byteBuffer.getLong(); 27 | 28 | return new UUID(high, low); 29 | } 30 | 31 | // Convert an array of GroupMemberInfo classes to a byte[] using 32-byte chunks. 32 | public static byte[] serializeFromGroupMemberInfo(@NonNull Collection groupMembers) { 33 | if (groupMembers != null && groupMembers.size() > 0) { 34 | // Serialize 16-byte UUID and 65-byte cipher to a byte[] as uuid|cipher|uuid|... 35 | byte[] serializedGroupMembers = new byte[groupMembers.size() * 81]; 36 | int position = 0; 37 | 38 | for (GroupCall.GroupMemberInfo member : groupMembers) { 39 | // Copy in the userId UUID as a byte[]. 40 | System.arraycopy(getBytesFromUuid(member.userId), 0, serializedGroupMembers, position, 16); 41 | position += 16; 42 | 43 | // Copy in the ciphertext. 44 | System.arraycopy(member.userIdCipherText, 0, serializedGroupMembers, position, 65); 45 | position += 65; 46 | } 47 | 48 | return serializedGroupMembers; 49 | } else { 50 | // Return an empty array. 51 | return new byte[0]; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /bin/measure-cpu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 4 | # Copyright 2019-2021 Signal Messenger, LLC 5 | # SPDX-License-Identifier: AGPL-3.0-only 6 | # 7 | 8 | # Before running: 9 | # 10 | # pip install --no-binary :all psutil 11 | # 12 | # 13 | # To run: 14 | # 15 | # python3 measure-cpu.py 5 electron 16 | # 17 | # or: 18 | # 19 | # ./measure-cpu.py $duration_secs $process_name $iterations 20 | # 21 | # For more info: https://pypi.org/project/psutil/ 22 | 23 | import psutil 24 | import sys 25 | import time 26 | 27 | def get_arg(index, parse, default): 28 | try: 29 | return parse(sys.argv[index]) 30 | except: 31 | return default 32 | 33 | def proc_matches_search(proc, search): 34 | if search is None: 35 | return True 36 | else: 37 | return search.lower() in proc.name().lower() 38 | 39 | def get_name(proc): 40 | try: 41 | return proc.name() 42 | except: 43 | return "Unknown" 44 | 45 | def get_cpu_percent(proc): 46 | try: 47 | return proc.cpu_percent() 48 | except: 49 | return 0 50 | 51 | duration = get_arg(1, int, 1) 52 | search = get_arg(2, str, None) 53 | iterations = get_arg(3, int, 1) 54 | 55 | print(f"Searching for processes that contain the name '{search}'", flush=True) 56 | 57 | procs = [proc for proc in psutil.process_iter() if proc_matches_search(proc, search)] 58 | names = set((get_name(proc) for proc in procs)) 59 | 60 | print(f"Getting CPU for processes with the names {names}", flush=True) 61 | 62 | _ = [get_cpu_percent(proc) for proc in procs] 63 | 64 | if iterations > 1: 65 | print(f"Waiting for {duration} seconds in {iterations} iterations", flush=True) 66 | else: 67 | print(f"Waiting for {duration} seconds", flush=True) 68 | 69 | samples = [] 70 | for i in range(iterations): 71 | time.sleep(duration) 72 | 73 | total_cpu_percent = sum((get_cpu_percent(proc) for proc in procs)) 74 | samples.append(total_cpu_percent) 75 | 76 | print (f"Total CPU percent for iteration {i}: {total_cpu_percent:.2f}", flush=True) 77 | 78 | if iterations > 1: 79 | average = sum(samples)/len(samples) 80 | print (f"Average across iterations: {average:.2f}", flush=True) 81 | -------------------------------------------------------------------------------- /bin/set-up-for-cocoapods: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright 2023 Signal Messenger, LLC 5 | # SPDX-License-Identifier: AGPL-3.0-only 6 | # 7 | 8 | # Do NOT include env.sh here; this script needs to work without grealpath installed. 9 | 10 | set -eu 11 | 12 | PROJECT_DIR=$(dirname "$0")/.. 13 | cd "$PROJECT_DIR" 14 | # Without realpath, PROJECT_DIR may be a relative path that's no longer correct. 15 | # Reset it to the current directory, enough for config/version.sh to work. 16 | PROJECT_DIR=. 17 | 18 | . config/version.sh 19 | 20 | CACHE_DIR=~/Library/Caches/org.signal.ringrtc 21 | PREBUILD_DIR=${CACHE_DIR}/prebuild-${RINGRTC_VERSION} 22 | 23 | if [ -f out/release/libringrtc/ringrtc.h ] && [ ! -L out/release/libringrtc/ringrtc.h ]; then 24 | echo 'Existing RingRTC build detected; assuming local development' >&2 25 | exit 26 | fi 27 | 28 | if [ -n "${RINGRTC_PREBUILD_CHECKSUM:-}" ]; then 29 | echo "${RINGRTC_PREBUILD_CHECKSUM}" > prebuild-checksum 30 | elif [ -f prebuild-checksum ]; then 31 | RINGRTC_PREBUILD_CHECKSUM=$(cat prebuild-checksum) 32 | else 33 | echo 'RINGRTC_PREBUILD_CHECKSUM not set; assuming local development' >&2 34 | exit 35 | fi 36 | 37 | echo 'using' "${PREBUILD_DIR}" 'as cache directory' >&2 38 | 39 | mkdir -p "${PREBUILD_DIR}" 40 | rm -rf "${PREBUILD_DIR}"/release 41 | 42 | rm -rf out 43 | mkdir -p out/release/libringrtc 44 | echo '/*' > out/.gitignore 45 | 46 | # Unfortunately we can't symlink the whole build directory at once. 47 | # CocoaPods wants to see every referenced file exist within the source directory. 48 | # But we can symlink the whole xcframework and everything in the libringrtc/ directory. 49 | bin/fetch-artifact.py -p ios --webrtc-version "${WEBRTC_VERSION}" -o "${PREBUILD_DIR}" --archive-dir "${CACHE_DIR}" 50 | ln -fns "${PREBUILD_DIR}"/release/WebRTC.xcframework "${PREBUILD_DIR}"/release/acknowledgments-webrtc-ios.plist out/release 51 | 52 | bin/fetch-artifact.py -u "https://build-artifacts.signal.org/libraries/ringrtc-ios-build-v${RINGRTC_VERSION}.tar.bz2" -c "${RINGRTC_PREBUILD_CHECKSUM}" -o "${PREBUILD_DIR}" 53 | ln -fns "${PREBUILD_DIR}"/release/libringrtc/* out/release/libringrtc 54 | 55 | cp prebuild-checksum "${PREBUILD_DIR}" 56 | -------------------------------------------------------------------------------- /src/rust/regex-aot/src/lib.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | //! Uses regex-automata's serialization support to build regexes at compile time. 7 | //! 8 | //! ``` 9 | //! let re: regex_automata::dfa::regex::Regex<_> = regex_aot::regex!(".+@.+"); 10 | //! ``` 11 | 12 | use quote::quote; 13 | 14 | fn regex_impl( 15 | input: syn::LitStr, 16 | ) -> Result> { 17 | // Possible future work: 18 | // - figure out how big the state ID size needs to be in advance, like ucd-generate does. 19 | // - emit both little- and big-endian forms, guarded by cfg(...). 20 | // - accept several adjacent literals so the regex can be broken up into multiple lines. 21 | // - let the caller choose between a SparseDFA (smaller) and a DenseDFA (faster) 22 | let regex = regex_automata::dfa::regex::Regex::new(&input.value())?; 23 | let fwd_bytes = regex.forward().to_sparse()?.to_bytes_little_endian(); 24 | let fwd_bytes = proc_macro2::Literal::byte_string(&fwd_bytes); 25 | let rev_bytes = regex.reverse().to_sparse()?.to_bytes_little_endian(); 26 | let rev_bytes = proc_macro2::Literal::byte_string(&rev_bytes); 27 | Ok(quote! { { 28 | #[cfg(not(target_endian = "little"))] 29 | compile_error!("only little-endian platforms are supported"); 30 | let fwd: ::regex_automata::dfa::sparse::DFA<&[u8]> = 31 | ::regex_automata::dfa::sparse::DFA::from_bytes(#fwd_bytes).unwrap().0; 32 | let rev: ::regex_automata::dfa::sparse::DFA<&[u8]> = 33 | ::regex_automata::dfa::sparse::DFA::from_bytes(#rev_bytes).unwrap().0; 34 | ::regex_automata::dfa::regex::Regex::builder().build_from_dfas(fwd, rev) 35 | }}) 36 | } 37 | 38 | #[proc_macro] 39 | pub fn regex(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 40 | let input = syn::parse_macro_input!(input as syn::LitStr); 41 | regex_impl(input) 42 | .unwrap_or_else(|e| { 43 | let msg = e.to_string(); 44 | quote! { 45 | compile_error!(#msg) 46 | } 47 | }) 48 | .into() 49 | } 50 | -------------------------------------------------------------------------------- /call_sim/docker/signaling_server/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1.7-labs 2 | 3 | # 4 | # Copyright 2023 Signal Messenger, LLC 5 | # SPDX-License-Identifier: AGPL-3.0-only 6 | # 7 | 8 | FROM ubuntu:22.04 AS build-stage 9 | 10 | # Update system packages. 11 | RUN apt-get update \ 12 | && apt upgrade -y \ 13 | && apt-get install -y --no-install-recommends --no-install-suggests curl build-essential ca-certificates protobuf-compiler \ 14 | && update-ca-certificates 15 | 16 | SHELL [ "/bin/bash", "-c"] 17 | 18 | # Install Rust. 19 | RUN curl https://sh.rustup.rs -sSf | sh -s -- -y 20 | ENV PATH="/root/.cargo/bin:${PATH}" 21 | 22 | # Since protobuf is a workspace package, we have to stub the entire workspace 23 | WORKDIR /usr/src/ringrtc 24 | COPY --parents ./**/Cargo.toml ./**/Cargo.lock ./**/main.rs ./**/lib.rs ./**/bin/*.rs ./ 25 | RUN shopt -s globstar; shopt -s nullglob; \ 26 | for filename in ./**/main.rs; do \ 27 | truncate -s 0 "$filename"; \ 28 | done; \ 29 | for filename in ./**/lib.rs; do \ 30 | truncate -s 0 "$filename"; \ 31 | done; \ 32 | for filename in ./**/bin/*.rs; do \ 33 | truncate -s 0 "$filename"; \ 34 | done 35 | # stub the signaling server, but remove the actual code first 36 | RUN cd call_sim/docker && \ 37 | rm -rf signaling_server && \ 38 | cargo new signaling_server 39 | COPY call_sim/docker/signaling_server/Cargo.* ./call_sim/docker/signaling_server/ 40 | RUN cd call_sim/docker/signaling_server && cargo build --release 41 | 42 | # Copy the source and build the project normally. 43 | COPY protobuf protobuf 44 | RUN cd protobuf && cargo build --release 45 | 46 | COPY call_sim/docker/signaling_server call_sim/docker/signaling_server 47 | RUN cd call_sim/docker/signaling_server && cargo build --release 48 | 49 | FROM ubuntu:22.04 AS run-stage 50 | 51 | RUN apt-get update \ 52 | && apt upgrade -y \ 53 | && rm -rf /var/lib/apt/lists/* 54 | 55 | COPY --from=build-stage /usr/src/ringrtc/call_sim/docker/signaling_server/target/release/signaling_server /usr/local/bin/ 56 | 57 | USER nobody:nogroup 58 | 59 | # Expose http server access ports to this container. 60 | EXPOSE 8080 61 | 62 | ENTRYPOINT ["signaling_server"] 63 | -------------------------------------------------------------------------------- /src/android/api/org/signal/ringrtc/CallLinkState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Signal Messenger, LLC 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | package org.signal.ringrtc; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.annotation.Nullable; 10 | import java.time.Instant; 11 | 12 | public class CallLinkState { 13 | public enum Restrictions { 14 | NONE, 15 | ADMIN_APPROVAL, 16 | UNKNOWN, 17 | } 18 | 19 | /** Is never null, but may be empty. */ 20 | @NonNull 21 | private final String name; 22 | @NonNull 23 | private final Restrictions restrictions; 24 | private final boolean revoked; 25 | @NonNull 26 | private final Instant expiration; 27 | @Nullable 28 | private final CallLinkEpoch epoch; 29 | 30 | /** Should only be used for testing. */ 31 | public CallLinkState(@NonNull String name, @NonNull Restrictions restrictions, boolean revoked, @NonNull Instant expiration, @Nullable CallLinkEpoch epoch) { 32 | this.name = name; 33 | this.restrictions = restrictions; 34 | this.revoked = revoked; 35 | this.expiration = expiration; 36 | this.epoch = epoch; 37 | } 38 | 39 | @CalledByNative 40 | private CallLinkState(@NonNull String name, int rawRestrictions, boolean revoked, long expirationEpochSecond, @Nullable CallLinkEpoch epoch) { 41 | this.name = name; 42 | switch (rawRestrictions) { 43 | case 0: 44 | this.restrictions = Restrictions.NONE; 45 | break; 46 | case 1: 47 | this.restrictions = Restrictions.ADMIN_APPROVAL; 48 | break; 49 | default: 50 | this.restrictions = Restrictions.UNKNOWN; 51 | } 52 | this.revoked = revoked; 53 | this.expiration = Instant.ofEpochSecond(expirationEpochSecond); 54 | this.epoch = epoch; 55 | } 56 | 57 | /** Is never null, but may be empty. */ 58 | @NonNull 59 | public String getName() { 60 | return name; 61 | } 62 | 63 | @NonNull 64 | public Restrictions getRestrictions() { 65 | return restrictions; 66 | } 67 | 68 | public boolean hasBeenRevoked() { 69 | return revoked; 70 | } 71 | 72 | @NonNull 73 | public Instant getExpiration() { 74 | return expiration; 75 | } 76 | 77 | @Nullable 78 | public CallLinkEpoch getEpoch() { 79 | return epoch; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Signal Messenger, LLC 2 | # SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | # Configuration for probot-stale - https://github.com/probot/stale 5 | 6 | # Number of days of inactivity before an Issue or Pull Request becomes stale 7 | daysUntilStale: 90 8 | 9 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 10 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 11 | daysUntilClose: 7 12 | 13 | # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) 14 | onlyLabels: [] 15 | 16 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 17 | exemptLabels: 18 | - acknowledged 19 | 20 | # Set to true to ignore issues in a project (defaults to false) 21 | exemptProjects: false 22 | 23 | # Set to true to ignore issues in a milestone (defaults to false) 24 | exemptMilestones: false 25 | 26 | # Set to true to ignore issues with an assignee (defaults to false) 27 | exemptAssignees: true 28 | 29 | # Label to use when marking as stale 30 | staleLabel: stale 31 | 32 | # Comment to post when marking as stale. Set to `false` to disable 33 | markComment: > 34 | This issue has been automatically marked as stale because it has not had 35 | recent activity. It will be closed if no further activity occurs. Thank you 36 | for your contributions. 37 | 38 | # Comment to post when removing the stale label. 39 | # unmarkComment: > 40 | # Your comment here. 41 | 42 | # Comment to post when closing a stale Issue or Pull Request. 43 | closeComment: > 44 | This issue has been closed due to inactivity. 45 | 46 | # Limit the number of actions per hour, from 1-30. Default is 30 47 | limitPerRun: 5 48 | 49 | # Limit to only `issues` or `pulls` 50 | # only: issues 51 | 52 | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': 53 | # pulls: 54 | # daysUntilStale: 30 55 | # markComment: > 56 | # This pull request has been automatically marked as stale because it has not had 57 | # recent activity. It will be closed if no further activity occurs. Thank you 58 | # for your contributions. 59 | 60 | # issues: 61 | # exemptLabels: 62 | # - confirmed 63 | 64 | -------------------------------------------------------------------------------- /src/rust/src/android/webrtc_java_media_stream.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | //! webrtc::jni::JavaMediaStream Interface. 7 | 8 | use jni::{ 9 | JNIEnv, 10 | objects::{GlobalRef, JObject}, 11 | sys::jobject, 12 | }; 13 | 14 | use crate::{ 15 | android::error::AndroidError, 16 | common::Result, 17 | webrtc::{ 18 | self, 19 | media::{MediaStream, RffiMediaStream}, 20 | }, 21 | }; 22 | 23 | /// Incomplete type for C++ JavaMediaStream. 24 | #[repr(C)] 25 | pub struct RffiJavaMediaStream { 26 | _private: [u8; 0], 27 | } 28 | 29 | /// Rust wrapper around webrtc::jni::JavaMediaStream object. 30 | pub struct JavaMediaStream { 31 | rffi: webrtc::ptr::Unique, 32 | } 33 | 34 | unsafe impl Sync for JavaMediaStream {} 35 | unsafe impl Send for JavaMediaStream {} 36 | 37 | impl webrtc::ptr::Delete for RffiJavaMediaStream { 38 | fn delete(owned: webrtc::ptr::Owned) { 39 | unsafe { Rust_deleteJavaMediaStream(owned) }; 40 | } 41 | } 42 | 43 | impl JavaMediaStream { 44 | /// Create a JavaMediaStream from a MediaStream object. 45 | pub fn new(stream: MediaStream) -> Result { 46 | let rffi = 47 | webrtc::ptr::Unique::from(unsafe { Rust_createJavaMediaStream(stream.into_owned()) }); 48 | if rffi.is_null() { 49 | return Err(AndroidError::CreateJavaMediaStream.into()); 50 | } 51 | Ok(Self { rffi }) 52 | } 53 | 54 | /// Return a JNI GlobalRef to the JavaMediaStream object 55 | pub fn global_ref(&self, env: &JNIEnv) -> Result { 56 | unsafe { 57 | let object = Rust_getJavaMediaStreamObject(self.rffi.borrow()); 58 | Ok(env.new_global_ref(JObject::from_raw(object))?) 59 | } 60 | } 61 | } 62 | 63 | unsafe extern "C" { 64 | fn Rust_createJavaMediaStream( 65 | rffi_media_stream: webrtc::ptr::OwnedRc, 66 | ) -> webrtc::ptr::Owned; 67 | 68 | fn Rust_deleteJavaMediaStream(rffi_java_media_stream: webrtc::ptr::Owned); 69 | 70 | fn Rust_getJavaMediaStreamObject( 71 | rffi_java_media_stream: webrtc::ptr::Borrowed, 72 | ) -> jobject; 73 | } 74 | -------------------------------------------------------------------------------- /acknowledgments/acknowledgments.html.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RingRTC Acknowledgments 5 | 37 | 38 | 39 | 40 |
41 |
42 |

Third Party Licenses

43 |

This page lists the licenses of the projects used in RingRTC. Listings marked "synthesized" did not have an appropriate standalone license file in the crate.

44 |
45 | 46 |

Overview of licenses:

47 |
    48 | {{#each overview}} 49 |
  • {{name}} ({{count}})
  • 50 | {{/each}} 51 |
52 | 53 |

All license text:

54 |
    55 | {{#each licenses}} 56 |
  • 57 |

    {{name}}{{#unless source_path}} (synthesized){{/unless}}

    58 |

    Used by:

    59 | 64 |
    {{text}}
    65 |
  • 66 | {{/each}} 67 |
68 |
69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /.github/workflows/ios_artifacts.yml: -------------------------------------------------------------------------------- 1 | name: Build RingRTC iOS Artifacts 2 | run-name: Build RingRTC iOS Artifacts (${{ github.ref_name }}) 3 | 4 | on: 5 | workflow_dispatch: 6 | inputs: 7 | dry_run: 8 | description: "When true, don't upload to GCS" 9 | default: false 10 | required: false 11 | type: boolean 12 | runner: 13 | description: "Mac runner:" 14 | default: 'macos-15' 15 | required: true 16 | type: string 17 | 18 | workflow_call: 19 | inputs: 20 | dry_run: 21 | description: "When true, don't upload to GCS" 22 | default: true 23 | required: true 24 | type: boolean 25 | runner: 26 | description: "Mac runner:" 27 | default: 'macos-15' 28 | required: true 29 | type: string 30 | 31 | env: 32 | CARGO_TERM_COLOR: always 33 | 34 | jobs: 35 | build_ios: 36 | name: Build iOS 37 | 38 | permissions: 39 | # Needed to clone the repo 40 | contents: 'read' 41 | # Needed for google-github-actions/auth. 42 | id-token: 'write' 43 | 44 | runs-on: ${{ inputs.runner }} 45 | 46 | steps: 47 | - uses: actions/checkout@v4 48 | - run: brew install protobuf coreutils # for grealpath 49 | - run: rustup toolchain install $(cat rust-toolchain) --profile minimal --target x86_64-apple-ios,aarch64-apple-ios,aarch64-apple-ios-sim --component rust-src 50 | - run: cargo install cbindgen 51 | - run: sudo xcodes select 26.1.1 52 | - run: ./bin/fetch-artifact --platform ios --release 53 | - run: ./bin/build-ios --ringrtc-only --archive-ringrtc --release 54 | - name: Output Artifact Checksum 55 | run: 'sha256sum out/ringrtc-ios-build-v*.tar.bz2 | tee -a $GITHUB_STEP_SUMMARY' 56 | 57 | - uses: google-github-actions/auth@v2 58 | with: 59 | workload_identity_provider: 'projects/741367068918/locations/global/workloadIdentityPools/github/providers/github-actions' 60 | service_account: 'github-actions@signal-build-artifacts.iam.gserviceaccount.com' 61 | 62 | - uses: google-github-actions/upload-cloud-storage@v2 63 | if: ${{ !inputs.dry_run }} 64 | with: 65 | path: 'out' 66 | destination: build-artifacts.signal.org/libraries 67 | glob: 'ringrtc-ios-build-v*.tar.bz2' 68 | parent: false 69 | process_gcloudignore: false 70 | -------------------------------------------------------------------------------- /src/ios/SignalRingRTC/SignalRingRTCTests/AudioDeviceModuleForTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2024 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | @testable import SignalRingRTC 7 | import WebRTC 8 | 9 | public class AudioDeviceModuleForTests: NSObject, RTCAudioDevice { 10 | public var deviceInputSampleRate: Double = 48000.0 11 | public var inputIOBufferDuration: TimeInterval = 0.020 12 | public var inputNumberOfChannels: Int = 1 13 | public var inputLatency: TimeInterval = 0.0 14 | public var deviceOutputSampleRate: Double = 48000.0 15 | public var outputIOBufferDuration: TimeInterval = 0.020 16 | public var outputNumberOfChannels: Int = 1 17 | public var outputLatency: TimeInterval = 0.0 18 | public var isInitialized: Bool 19 | 20 | override init() { 21 | Logger.debug("Dummy ADM for testing.") 22 | isInitialized = false 23 | isPlayoutInitialized = false 24 | isPlaying = false 25 | isRecordingInitialized = false 26 | isRecording = false 27 | } 28 | 29 | public func initialize(with delegate: any RTCAudioDeviceDelegate) -> Bool { 30 | isInitialized = true 31 | return true 32 | } 33 | 34 | public func terminateDevice() -> Bool { 35 | isInitialized = false 36 | isPlayoutInitialized = false 37 | isPlaying = false 38 | isRecordingInitialized = false 39 | isRecording = false 40 | return true 41 | } 42 | 43 | public var isPlayoutInitialized: Bool 44 | 45 | public func initializePlayout() -> Bool { 46 | isPlayoutInitialized = true 47 | return true 48 | } 49 | 50 | public var isPlaying: Bool 51 | 52 | public func startPlayout() -> Bool { 53 | isPlaying = true 54 | return true 55 | } 56 | 57 | public func stopPlayout() -> Bool { 58 | isPlaying = false 59 | return true 60 | } 61 | 62 | public var isRecordingInitialized: Bool 63 | 64 | public func initializeRecording() -> Bool { 65 | isRecordingInitialized = true 66 | return true 67 | } 68 | 69 | public var isRecording: Bool 70 | 71 | public func startRecording() -> Bool { 72 | isRecording = true 73 | return true 74 | } 75 | 76 | public func stopRecording() -> Bool { 77 | isRecording = false 78 | return true 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@signalapp/ringrtc", 3 | "version": "2.61.0", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/signalapp/ringrtc.git", 7 | "directory": "src/node" 8 | }, 9 | "description": "Signal Messenger voice and video calling library.", 10 | "main": "dist/index.js", 11 | "types": "dist/index.d.ts", 12 | "files": [ 13 | "dist/*.js", 14 | "dist/*.d.ts", 15 | "dist/ringrtc/*.js", 16 | "dist/ringrtc/*.d.ts", 17 | "dist/acknowledgments.md", 18 | "scripts/fetch-prebuild.js" 19 | ], 20 | "bin": { 21 | "virtual_audio": "dist/bin/virtual_audio.sh" 22 | }, 23 | "scripts": { 24 | "build": "tsc && bash scripts/build-help.sh", 25 | "clean": "rimraf dist", 26 | "test": "electron-mocha --renderer --recursive dist/test --timeout 10000 --require source-map-support/register", 27 | "eslint": "eslint .", 28 | "lint": "npm run format --list-different && npm run eslint", 29 | "format": "prettier --write .", 30 | "check-format": "prettier . --check", 31 | "install": "node scripts/fetch-prebuild.js", 32 | "prepublishOnly": "node scripts/prepublish.js" 33 | }, 34 | "config": { 35 | "prebuildUrl": "https://build-artifacts.signal.org/libraries/ringrtc-desktop-build-v${npm_package_version}.tar.gz", 36 | "prebuildChecksum": "" 37 | }, 38 | "author": "", 39 | "license": "AGPL-3.0-only", 40 | "dependencies": { 41 | "https-proxy-agent": "7.0.6", 42 | "tar": "^6.2.1" 43 | }, 44 | "devDependencies": { 45 | "@types/chai": "4.3.16", 46 | "@types/chai-as-promised": "^7.1.4", 47 | "@types/lodash": "^4.14.106", 48 | "@types/mocha": "10.0.9", 49 | "@types/node": "20.17.6", 50 | "@types/sinon-chai": "^3.2.12", 51 | "chai": "4.4.1", 52 | "chai-as-promised": "^7.1.1", 53 | "electron": "39.2.4", 54 | "electron-mocha": "13.0.1", 55 | "eslint": "8.56.0", 56 | "eslint-config-airbnb-typescript-prettier": "5.0.0", 57 | "eslint-config-prettier": "9.1.0", 58 | "eslint-plugin-import": "2.29.0", 59 | "eslint-plugin-mocha": "10.2.0", 60 | "eslint-plugin-more": "1.0.5", 61 | "lodash": "4.17.21", 62 | "long": "5.2.3", 63 | "mocha": "10.8.2", 64 | "prettier": "3.3.3", 65 | "rimraf": "5.0.10", 66 | "sinon": "^19.0.2", 67 | "sinon-chai": "^3.7.0", 68 | "source-map-support": "^0.5.21", 69 | "typescript": "5.6.3" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/android/api/org/signal/ringrtc/CallId.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2021 Signal Messenger, LLC 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | package org.signal.ringrtc; 7 | 8 | import androidx.annotation.NonNull; 9 | 10 | import java.lang.Comparable; 11 | 12 | /** 13 | * 14 | * Represents a unique call identifier. 15 | * 16 | * Internally the call identifier is stored as a 64-bit integer. 17 | * 18 | */ 19 | public final class CallId implements Comparable { 20 | private final String TAG = CallId.class.getSimpleName(); 21 | 22 | private final long callId; 23 | 24 | /** 25 | * 26 | * Create a new CallId from a raw integer value. 27 | * 28 | * @param callId 64-bit call identifier. 29 | */ 30 | public CallId(long callId) { 31 | this.callId = callId; 32 | } 33 | 34 | /** 35 | * Derive a call ID from a group call era. 36 | */ 37 | public static CallId fromEra(@NonNull String eraId) { 38 | try { 39 | return new CallId(ringrtcFromEraId(eraId)); 40 | } catch (CallException e) { 41 | throw new AssertionError(e); 42 | } 43 | } 44 | 45 | /** 46 | * 47 | * Returns an integer representation of the CallId 48 | * 49 | * @return The internal representation. 50 | */ 51 | public long longValue() { 52 | return callId; 53 | } 54 | 55 | /** 56 | * 57 | * Formats the CallId with an additional integer appended 58 | * 59 | * @param id Integer representing a remote device Id 60 | * @return A String representation of CallId plus the deviceId. 61 | */ 62 | public String format(Integer id) { 63 | return this + "-" + id; 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | return "0x" + Long.toHexString(callId); 69 | } 70 | 71 | @Override 72 | public boolean equals(Object obj) { 73 | if (obj == this) { 74 | return true; 75 | } 76 | if (obj != null) { 77 | if (this.getClass() == obj.getClass()) { 78 | CallId that = (CallId)obj; 79 | return this.compareTo(that) == 0; 80 | } 81 | } 82 | return false; 83 | } 84 | 85 | @Override 86 | public int hashCode() { 87 | return Long.valueOf(callId).hashCode(); 88 | } 89 | 90 | @Override 91 | public int compareTo(CallId obj) { 92 | return Long.compare(callId, obj.callId); 93 | } 94 | 95 | private static native 96 | long ringrtcFromEraId(String eraId) 97 | throws CallException; 98 | } 99 | -------------------------------------------------------------------------------- /protobuf/src/lib.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2024 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | //! This crate is a wrapper around prost and tonic, removing the need for copies of protobuf files 7 | //! and protobuf builds in build.rs. Note that dependent crates still need to add prost and tonic 8 | //! to their dependencies. 9 | //! 10 | //! Just like prost/tonic, we expose a include_xyz_proto macros so protobuf types are local to a 11 | //! crate. This allows crates to define traits on the types. 12 | //! 13 | //! We "curry" the protobuf code here. Since macros don't have eager evaluation, nested macros would 14 | //! be evaluated at the wrong point in compilation, 15 | //! i.e. `include!(concat!(env!("OUT_DIR"), "/group_call.rs"))` would look in the wrong OUT_DIR. So 16 | //! we save the proto code here during this package's compilation and emit it using a proc macro. 17 | 18 | use proc_macro::TokenStream; 19 | 20 | #[cfg(feature = "signaling")] 21 | const GROUP_PROTO: &str = include_str!(concat!(env!("OUT_DIR"), "/group_call.rs")); 22 | 23 | #[cfg(feature = "signaling")] 24 | const RTP_DATA_PROTO: &str = include_str!(concat!(env!("OUT_DIR"), "/rtp_data.rs")); 25 | 26 | #[cfg(feature = "signaling")] 27 | const SIGNALING_PROTO: &str = include_str!(concat!(env!("OUT_DIR"), "/signaling.rs")); 28 | 29 | #[cfg(feature = "signaling")] 30 | const CALL_SUMMARY_PROTO: &str = include_str!(concat!(env!("OUT_DIR"), "/call_summary.rs")); 31 | 32 | #[cfg(feature = "call_sim")] 33 | const CALL_SIM_PROTO: &str = include_str!(concat!(env!("OUT_DIR"), "/calling.rs")); 34 | 35 | #[cfg(feature = "signaling")] 36 | #[proc_macro] 37 | pub fn include_groupcall_proto(_input: TokenStream) -> TokenStream { 38 | GROUP_PROTO.parse().unwrap() 39 | } 40 | 41 | #[cfg(feature = "signaling")] 42 | #[proc_macro] 43 | pub fn include_rtp_proto(_input: TokenStream) -> TokenStream { 44 | RTP_DATA_PROTO.parse().unwrap() 45 | } 46 | 47 | #[cfg(feature = "signaling")] 48 | #[proc_macro] 49 | pub fn include_signaling_proto(_input: TokenStream) -> TokenStream { 50 | SIGNALING_PROTO.parse().unwrap() 51 | } 52 | 53 | #[cfg(feature = "signaling")] 54 | #[proc_macro] 55 | pub fn include_call_summary_proto(_input: TokenStream) -> TokenStream { 56 | CALL_SUMMARY_PROTO.parse().unwrap() 57 | } 58 | 59 | #[cfg(feature = "call_sim")] 60 | #[proc_macro] 61 | pub fn include_call_sim_proto(_input: TokenStream) -> TokenStream { 62 | CALL_SIM_PROTO.parse().unwrap() 63 | } 64 | -------------------------------------------------------------------------------- /src/rust/src/webrtc/sim/stats_observer.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | //! WebRTC Simulation Create/Set SessionDescription 7 | 8 | use std::ptr; 9 | 10 | use crate::webrtc::{ 11 | self, 12 | stats_observer::{ 13 | ConnectionStatistics, MediaStatistics, StatsObserver, StatsObserverCallbacks, 14 | }, 15 | }; 16 | 17 | /// Simulation type for webrtc::rffi::StatsObserverRffi 18 | pub type RffiStatsObserver = u32; 19 | 20 | static FAKE_STATS_OBSERVER: u32 = 21; 21 | 22 | #[allow(non_snake_case, clippy::missing_safety_doc)] 23 | pub unsafe fn Rust_createStatsObserver( 24 | stats_observer: webrtc::ptr::Borrowed, 25 | callbacks: webrtc::ptr::Borrowed, 26 | ) -> webrtc::ptr::OwnedRc { 27 | info!("Rust_createStatsObserver():"); 28 | 29 | let dummy = MediaStatistics { 30 | timestamp_us: 0, 31 | audio_sender_statistics_size: 0, 32 | audio_sender_statistics: ptr::null(), 33 | video_sender_statistics_size: 0, 34 | video_sender_statistics: ptr::null(), 35 | audio_receiver_statistics_size: 0, 36 | audio_receiver_statistics: ptr::null(), 37 | video_receiver_statistics_size: 0, 38 | video_receiver_statistics: ptr::null(), 39 | nominated_connection_statistics: ConnectionStatistics::default(), 40 | connection_statistics: ptr::null(), 41 | connection_statistics_size: 0, 42 | }; 43 | 44 | // Hit on the onComplete() callback 45 | let callbacks = callbacks.as_ptr() as *const StatsObserverCallbacks; 46 | let report_json = std::ffi::CString::new("{}").expect("CString::new failed"); 47 | unsafe { 48 | ((*callbacks).onStatsComplete)( 49 | webrtc::ptr::Borrowed::from_ptr(stats_observer.as_ptr() as *mut StatsObserver), 50 | webrtc::ptr::Borrowed::from_ptr(&dummy), 51 | webrtc::ptr::Borrowed::from_ptr(report_json.as_ptr()), 52 | ); 53 | 54 | webrtc::ptr::OwnedRc::from_ptr(&FAKE_STATS_OBSERVER) 55 | } 56 | } 57 | 58 | #[allow(non_snake_case, clippy::missing_safety_doc)] 59 | pub unsafe fn Rust_setCollectRawStatsReport( 60 | _stats_observer: webrtc::ptr::BorrowedRc, 61 | collect_raw_stats_report: bool, 62 | ) { 63 | info!( 64 | "Rust_setCollectRawStatsReport: {}", 65 | collect_raw_stats_report 66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /src/ios/SignalRingRTC/SignalRingRTC/RingValidation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | import SignalRingRTC.RingRTC 7 | 8 | /// Type of media for call at time of origination. 9 | public enum CallMediaType: Int32 { 10 | /// Call should start as audio only. 11 | case audioCall = 0 12 | /// Call should start as audio/video. 13 | case videoCall = 1 14 | } 15 | 16 | public func isValidOfferMessage(opaque: Data, messageAgeSec: UInt64, callMediaType: CallMediaType) -> Bool { 17 | Logger.debug("") 18 | 19 | return opaque.withUnsafeBytes { buffer in 20 | ringrtcIsValidOffer(AppByteSlice(bytes: buffer.baseAddress?.assumingMemoryBound(to: UInt8.self), 21 | len: buffer.count), 22 | messageAgeSec, 23 | callMediaType.rawValue) 24 | } 25 | } 26 | 27 | public func isValidOpaqueRing(opaqueCallMessage: Data, 28 | messageAgeSec: UInt64, 29 | validateGroupRing: (_ groupId: Data, _ ringId: Int64) -> Bool) -> Bool { 30 | // Use a pointer to the argument to pass a closure down through a C-based interface; 31 | // withoutActuallyEscaping promises the compiler we won't persist it. 32 | // This is different from most RingRTC APIs, which are asynchronous; this one is synchronous and stateless. 33 | withoutActuallyEscaping(validateGroupRing) { validateGroupRing in 34 | withUnsafePointer(to: validateGroupRing) { validateGroupRingPtr in 35 | typealias CallbackType = (Data, Int64) -> Bool 36 | Logger.debug("") 37 | 38 | return opaqueCallMessage.withUnsafeBytes { buffer in 39 | let opaqueSlice = AppByteSlice(bytes: buffer.baseAddress?.assumingMemoryBound(to: UInt8.self), 40 | len: buffer.count) 41 | return ringrtcIsCallMessageValidOpaqueRing(opaqueSlice, 42 | messageAgeSec, 43 | UnsafeMutableRawPointer(mutating: validateGroupRingPtr)) { 44 | (groupId, ringId, context) in 45 | let innerValidate = context!.assumingMemoryBound(to: CallbackType.self) 46 | return innerValidate.pointee(groupId.asData() ?? Data(), ringId) 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/android_artifacts.yml: -------------------------------------------------------------------------------- 1 | name: Build RingRTC Android Artifacts 2 | run-name: Build RingRTC Android Artifacts (${{ github.ref_name }}) 3 | 4 | on: 5 | workflow_dispatch: 6 | inputs: 7 | dry_run: 8 | description: "When true, don't upload to Sonatype" 9 | default: false 10 | required: false 11 | type: boolean 12 | runner: 13 | description: "Linux runner:" 14 | default: 'ubuntu-24.04-4-cores' 15 | required: true 16 | type: string 17 | workflow_call: 18 | inputs: 19 | dry_run: 20 | description: "When true, don't upload to Sonatype" 21 | default: true 22 | required: true 23 | type: boolean 24 | runner: 25 | description: "Linux runner:" 26 | default: 'ubuntu-24.04-4-cores' 27 | required: true 28 | type: string 29 | 30 | env: 31 | CARGO_TERM_COLOR: always 32 | NDK_VERSION: '28.0.13004108' 33 | 34 | jobs: 35 | build_android: 36 | name: Build Android 37 | 38 | runs-on: ${{ inputs.runner }} 39 | 40 | steps: 41 | - uses: actions/checkout@v4 42 | 43 | - name: Install NDK 44 | run: echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "ndk;${{ env.NDK_VERSION }}" 45 | 46 | - run: rustup toolchain install $(cat rust-toolchain) --profile minimal --target aarch64-linux-android,armv7-linux-androideabi,x86_64-linux-android,i686-linux-android 47 | 48 | - name: Install protoc 49 | run: sudo apt-get update && sudo apt-get install -y protobuf-compiler 50 | 51 | - name: set up JDK 17 52 | uses: actions/setup-java@v4 53 | with: 54 | distribution: temurin 55 | java-version: 17 56 | 57 | - run: ./bin/fetch-artifact --platform android --release 58 | 59 | - run: ANDROID_NDK_HOME="$ANDROID_HOME/ndk/$NDK_VERSION" ./bin/build-aar --ringrtc-only --release 60 | if: ${{ inputs.dry_run }} 61 | 62 | - run: ANDROID_NDK_HOME="$ANDROID_HOME/ndk/$NDK_VERSION" ./bin/build-aar --ringrtc-only --release 63 | if: ${{ !inputs.dry_run }} 64 | env: 65 | ORG_GRADLE_PROJECT_signalSonatypeUsername: ${{ secrets.SONATYPE_USER }} 66 | ORG_GRADLE_PROJECT_signalSonatypePassword: ${{ secrets.SONATYPE_PASSWORD }} 67 | # The last 8 characters of the key ID 68 | ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.SIGNING_KEYID }} 69 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }} 70 | # ASCII-armored PGP secret key 71 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }} 72 | -------------------------------------------------------------------------------- /src/ios/SignalRingRTC/SignalRingRTC/ConnectionMediaStream.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | import SignalRingRTC.RingRTC 7 | import WebRTC 8 | 9 | // See comment of IosMediaStream to understand 10 | // where this fits in the many layers of wrappers. 11 | @available(iOSApplicationExtension, unavailable) 12 | public class ConnectionMediaStream { 13 | 14 | // Associate this application MediaStream object with a Connection. 15 | let connection: Connection 16 | 17 | // Hold on to the stream object when it is created. 18 | var mediaStream: RTCMediaStream? 19 | 20 | init(connection: Connection) { 21 | self.connection = connection 22 | 23 | Logger.debug("object! ConnectionMediaStream created... \(ObjectIdentifier(self))") 24 | } 25 | 26 | deinit { 27 | Logger.debug("object! ConnectionMediaStream destroyed... \(ObjectIdentifier(self))") 28 | } 29 | 30 | func getWrapper() -> AppMediaStreamInterface { 31 | return AppMediaStreamInterface( 32 | object: UnsafeMutableRawPointer(Unmanaged.passRetained(self).toOpaque()), 33 | destroy: connectionMediaStreamDestroy, 34 | createMediaStream: connectionMediaStreamCreateMediaStream) 35 | } 36 | } 37 | 38 | @available(iOSApplicationExtension, unavailable) 39 | func connectionMediaStreamDestroy(object: UnsafeMutableRawPointer?) { 40 | guard let object = object else { 41 | failDebug("object was unexpectedly nil") 42 | return 43 | } 44 | 45 | Logger.debug("") 46 | 47 | _ = Unmanaged.fromOpaque(object).takeRetainedValue() 48 | // @note There should not be any retainers left for the object 49 | // so deinit should be called implicitly. 50 | } 51 | 52 | @available(iOSApplicationExtension, unavailable) 53 | func connectionMediaStreamCreateMediaStream(object: UnsafeMutableRawPointer?, nativeStreamBorrowedRc: UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer? { 54 | guard let object = object else { 55 | failDebug("object was unexpectedly nil") 56 | return nil 57 | } 58 | 59 | let obj: ConnectionMediaStream = Unmanaged.fromOpaque(object).takeUnretainedValue() 60 | 61 | Logger.debug("") 62 | 63 | guard let nativeStreamBorrowedRc = nativeStreamBorrowedRc else { 64 | failDebug("nativeStreamBorrowedRc was unexpectedly nil") 65 | return nil 66 | } 67 | 68 | let mediaStream = obj.connection.createStream(nativeStreamBorrowedRc: nativeStreamBorrowedRc) 69 | obj.mediaStream = mediaStream 70 | 71 | return UnsafeMutableRawPointer(Unmanaged.passUnretained(mediaStream).toOpaque()) 72 | } 73 | -------------------------------------------------------------------------------- /src/node/ringrtc/CallLinks.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2023 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | import Native from './Native'; 7 | 8 | export class CallLinkRootKey { 9 | readonly bytes: Uint8Array; 10 | 11 | private constructor(bytes: Uint8Array) { 12 | this.bytes = bytes; 13 | } 14 | 15 | static parse(str: string): CallLinkRootKey { 16 | return new CallLinkRootKey(Native.CallLinkRootKey_parse(str)); 17 | } 18 | 19 | static fromBytes(bytes: Uint8Array): CallLinkRootKey { 20 | Native.CallLinkRootKey_validate(bytes); 21 | return new CallLinkRootKey(bytes); 22 | } 23 | 24 | static generate(): CallLinkRootKey { 25 | return new CallLinkRootKey(Native.CallLinkRootKey_generate()); 26 | } 27 | 28 | static generateAdminPassKey(): Uint8Array { 29 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return 30 | return Native.CallLinkRootKey_generateAdminPasskey(); 31 | } 32 | 33 | deriveRoomId(): Uint8Array { 34 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return 35 | return Native.CallLinkRootKey_deriveRoomId(this.bytes); 36 | } 37 | 38 | toString(): string { 39 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return 40 | return Native.CallLinkRootKey_toFormattedString(this.bytes); 41 | } 42 | } 43 | 44 | export class CallLinkState { 45 | constructor( 46 | public name: string, 47 | public restrictions: CallLinkRestrictions, 48 | public revoked: boolean, 49 | public expiration: Date, 50 | public epoch?: CallLinkEpoch 51 | ) {} 52 | } 53 | 54 | export enum CallLinkRestrictions { 55 | None, 56 | AdminApproval, 57 | Unknown, 58 | } 59 | 60 | export class CallLinkEpoch { 61 | /** @internal */ 62 | value: number; 63 | 64 | /** @internal */ 65 | constructor(value: number) { 66 | this.value = value; 67 | } 68 | 69 | /** @internal */ 70 | asNumber(): number { 71 | return this.value; 72 | } 73 | 74 | static parse(str: string): CallLinkEpoch { 75 | return new CallLinkEpoch(Native.CallLinkEpoch_parse(str)); 76 | } 77 | 78 | toString(): string { 79 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return 80 | return Native.CallLinkEpoch_toFormattedString(this.value); 81 | } 82 | 83 | get bytes(): Uint8Array { 84 | const value = this.value & 0xffffffff; 85 | const bytes = new Uint8Array(4); 86 | bytes[0] = value & 0x000000ff; 87 | bytes[1] = (value & 0x0000ff00) >> 8; 88 | bytes[2] = (value & 0x00ff0000) >> 16; 89 | bytes[3] = (value & 0xff000000) >> 24; 90 | return bytes; 91 | } 92 | 93 | static fromBytes(bytes: Uint8Array): CallLinkEpoch { 94 | const value = 95 | bytes[0] + bytes[1] * 0x100 + 0x10000 * (bytes[2] + bytes[3] * 0x100); 96 | return new CallLinkEpoch(value); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/ios/SignalRingRTC/SignalRingRTCTests/TestGroupCallDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2023 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | import Foundation 7 | import SignalRingRTC 8 | 9 | class TestGroupCallDelegate: GroupCallDelegate { 10 | var requestMembershipProofCount = 0 11 | var requestGroupMembersCount = 0 12 | var onLocalDeviceStateChangedCount = 0 13 | var onRemoteDeviceStatesChangedCount = 0 14 | var onAudioLevelsCount = 0 15 | var onLowBandwidthForVideoCount = 0 16 | var onReactionsCount = 0 17 | var onRaisedHandsCount = 0 18 | var onPeekChangedCount = 0 19 | var onEndedCount = 0 20 | var onSpeakingCount = 0 21 | var lastOnEndedReason: CallEndReason? = nil 22 | var lastOnSpeakingEvent: SpeechEvent? = nil 23 | var remoteMuteCount = 0 24 | var lastRemoteMuteSource: UInt32 = 0 25 | var lastObservedRemoteMute: (UInt32, UInt32) = (0, 0) 26 | 27 | func groupCall(requestMembershipProof groupCall: GroupCall) { 28 | requestMembershipProofCount += 1 29 | } 30 | 31 | func groupCall(requestGroupMembers groupCall: GroupCall) { 32 | requestGroupMembersCount += 1 33 | } 34 | 35 | func groupCall(onLocalDeviceStateChanged groupCall: GroupCall) { 36 | onLocalDeviceStateChangedCount += 1 37 | } 38 | 39 | func groupCall(onRemoteDeviceStatesChanged groupCall: GroupCall) { 40 | onRemoteDeviceStatesChangedCount += 1 41 | } 42 | 43 | func groupCall(onAudioLevels groupCall: GroupCall) { 44 | onAudioLevelsCount += 1 45 | } 46 | 47 | func groupCall(onLowBandwidthForVideo groupCall: GroupCall, recovered: Bool) { 48 | onLowBandwidthForVideoCount += 1 49 | } 50 | 51 | func groupCall(onReactions groupCall: GroupCall, reactions: [Reaction]) { 52 | onReactionsCount += 1 53 | } 54 | 55 | func groupCall(onRaisedHands groupCall: GroupCall, raisedHands: [UInt32]) { 56 | onRaisedHandsCount += 1 57 | } 58 | 59 | func groupCall(onPeekChanged groupCall: GroupCall) { 60 | onPeekChangedCount += 1 61 | } 62 | 63 | func groupCall(onEnded groupCall: GroupCall, reason: CallEndReason, summary: CallSummary) { 64 | onEndedCount += 1 65 | lastOnEndedReason = reason 66 | } 67 | 68 | func groupCall(onSpeakingNotification groupCall: GroupCall, event: SpeechEvent) { 69 | onSpeakingCount += 1 70 | lastOnSpeakingEvent = event 71 | } 72 | 73 | func groupCall(onRemoteMuteRequest groupCall: GroupCall, muteSource: UInt32) { 74 | remoteMuteCount += 1 75 | lastRemoteMuteSource = muteSource 76 | } 77 | 78 | func groupCall(onObservedRemoteMute groupCall: GroupCall, muteSource: UInt32, muteTarget: UInt32) { 79 | lastObservedRemoteMute = (muteSource, muteTarget) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /call_sim/docker/visqol_mos/Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2023 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | # 5 | 6 | FROM ubuntu:22.04 AS build-stage 7 | 8 | RUN apt update \ 9 | && apt upgrade -y 10 | 11 | # Install all build dependencies and get bazelisk via npm. 12 | RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt install -y --no-install-recommends --no-install-suggests build-essential git curl unzip libboost-all-dev python3-pip \ 13 | && pip3 install numpy absl-py 14 | RUN apt install -y --no-install-recommends --no-install-suggests npm \ 15 | && npm install -g @bazel/bazelisk 16 | 17 | ENV USE_BAZEL_VERSION=5.3.2 18 | 19 | WORKDIR /usr/src 20 | 21 | RUN git clone --depth 1 --branch v3.3.3 https://github.com/google/visqol 22 | 23 | WORKDIR /usr/src/visqol 24 | 25 | # Build fix: Override the tensorflow runtime package since it was moved. 26 | RUN mkdir -p /usr/src/visqol/overrides/tf_runtime \ 27 | && curl -L -o /usr/src/visqol/overrides/tf_runtime.tar.gz https://github.com/tensorflow/runtime/archive/4ce3e4da2e21ae4dfcee9366415e55f408c884ec.tar.gz \ 28 | && tar -xzf /usr/src/visqol/overrides/tf_runtime.tar.gz -C /usr/src/visqol/overrides/tf_runtime --strip-components=1 29 | 30 | # Build fix: Override the XNNPACK package since it was moved. 31 | RUN mkdir -p /usr/src/visqol/overrides/XNNPACK \ 32 | && curl -L -o /usr/src/visqol/overrides/XNNPACK.zip https://github.com/google/XNNPACK/archive/e8f74a9763aa36559980a0c2f37f587794995622.zip \ 33 | && unzip -q /usr/src/visqol/overrides/XNNPACK.zip -d /usr/src/visqol/overrides/XNNPACK \ 34 | && mv /usr/src/visqol/overrides/XNNPACK/XNNPACK-*/* /usr/src/visqol/overrides/XNNPACK/ \ 35 | && rm -rf /usr/src/visqol/overrides/XNNPACK/XNNPACK-* 36 | 37 | # Build fix: Use a current armadillo package since the old ones were archived. 38 | RUN sed -i 's|^ sha256 = "d856ea58c18998997bcae6689784d2d3eeb5daf1379d569fddc277fe046a996b"| sha256 = "248e2535fc092add6cb7dea94fc86ae1c463bda39e46fd82d2a7165c1c197dff"|' WORKSPACE && \ 39 | sed -i 's|^ strip_prefix = "armadillo-9.860.2"| strip_prefix = "armadillo-14.0.2"|' WORKSPACE && \ 40 | sed -i 's|^ urls = \["http://sourceforge.net/projects/arma/files/armadillo-9.860.2.tar.xz"\]| urls = \["http://sourceforge.net/projects/arma/files/armadillo-14.0.2.tar.xz"\]|' WORKSPACE 41 | 42 | RUN bazelisk build :visqol -c opt \ 43 | --override_repository=tf_runtime=/usr/src/visqol/overrides/tf_runtime \ 44 | --override_repository=XNNPACK=/usr/src/visqol/overrides/XNNPACK 45 | 46 | FROM ubuntu:22.04 AS run-stage 47 | 48 | RUN apt-get update \ 49 | && apt upgrade -y \ 50 | && rm -rf /var/lib/apt/lists/* 51 | 52 | WORKDIR /usr/src/visqol 53 | 54 | COPY --from=build-stage /usr/src/visqol/bazel-bin/visqol /usr/local/bin/ 55 | COPY --from=build-stage /usr/src/visqol/model /usr/src/visqol/model/ 56 | 57 | ENTRYPOINT ["visqol"] 58 | -------------------------------------------------------------------------------- /call_sim/src/config.rs: -------------------------------------------------------------------------------- 1 | use std::time::SystemTime; 2 | 3 | use base64::{Engine, prelude::BASE64_STANDARD}; 4 | use hex::ToHex; 5 | use hmac::{Hmac, Mac}; 6 | use sha2::{Digest, Sha256}; 7 | 8 | use crate::common::{ClientProfile, Group, GroupMember}; 9 | 10 | type HmacSha256 = Hmac; 11 | const GV2_AUTH_MATCH_LIMIT: usize = 10; 12 | 13 | pub fn generate_client_profiles( 14 | num_profiles: usize, 15 | auth_key: &[u8; 32], 16 | now: SystemTime, 17 | ) -> Vec { 18 | let user_id_hex = gen_uuid(); 19 | let user_id_base64 = BASE64_STANDARD.encode(hex::decode(&user_id_hex).unwrap()); 20 | 21 | let member_id_hex = format!("{}{}", gen_uuid(), gen_uuid()); 22 | let member_id_base64 = BASE64_STANDARD.encode(hex::decode(&member_id_hex).unwrap()); 23 | 24 | let group_name = "generated_group".to_owned(); 25 | let group_id_hex = gen_uuid(); 26 | let group_id_base64 = BASE64_STANDARD.encode(hex::decode(&group_id_hex).unwrap()); 27 | 28 | let timestamp = now.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(); 29 | let membership_proof = BASE64_STANDARD.encode(generate_signed_v2_password( 30 | &member_id_hex, 31 | &group_id_hex, 32 | timestamp, 33 | ALL_PERMISSIONS, 34 | auth_key, 35 | )); 36 | let members = vec![GroupMember { 37 | user_id: user_id_base64.clone(), 38 | member_id: member_id_base64, 39 | }]; 40 | let groups = vec![Group { 41 | name: group_name, 42 | id: group_id_base64, 43 | membership_proof, 44 | members, 45 | }]; 46 | 47 | (1..=num_profiles) 48 | .map(|idx| ClientProfile { 49 | user_id: user_id_hex.clone(), 50 | device_id: idx.to_string(), 51 | groups: groups.clone(), 52 | }) 53 | .collect() 54 | } 55 | 56 | fn gen_uuid() -> String { 57 | uuid::Uuid::new_v4().to_string().replace('-', "") 58 | } 59 | 60 | const ALL_PERMISSIONS: &str = "1"; 61 | 62 | fn generate_signed_v2_password( 63 | user_id_hex: &str, 64 | group_id_hex: &str, 65 | timestamp: u64, 66 | permission: &str, 67 | key: &[u8; 32], 68 | ) -> String { 69 | let opaque_user_id = sha256_as_hexstring(&hex::decode(user_id_hex).unwrap()); 70 | // Format the credentials string. 71 | let credentials = format!( 72 | "2:{}:{}:{}:{}", 73 | opaque_user_id, group_id_hex, timestamp, permission 74 | ); 75 | 76 | // Get the MAC for the credentials. 77 | let mut hmac = HmacSha256::new_from_slice(key).unwrap(); 78 | hmac.update(credentials.as_bytes()); 79 | let mac = hmac.finalize().into_bytes(); 80 | let mac = &mac[..GV2_AUTH_MATCH_LIMIT]; 81 | 82 | // Append the MAC to the credentials. 83 | format!("{}:{}", credentials, mac.encode_hex::()) 84 | } 85 | 86 | fn sha256_as_hexstring(data: &[u8]) -> String { 87 | Sha256::digest(data).encode_hex() 88 | } 89 | -------------------------------------------------------------------------------- /mrp/src/merge_buffer.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2025 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | #[derive(Debug, PartialEq, Clone)] 7 | pub(crate) enum AssemblyError { 8 | ContentLengthExceeded, 9 | } 10 | 11 | /// Data structure that buffers data that can be combined with Extend. 12 | /// Buffers in vec so that it makes one allocation on new() and one on merge() 13 | #[derive(Debug, PartialEq, Clone)] 14 | pub(crate) struct MergeBuffer { 15 | data: Vec, 16 | content_length: usize, 17 | } 18 | 19 | impl MergeBuffer 20 | where 21 | T: Extend, 22 | { 23 | /// creates buffer with content_length capacity 24 | /// returns None if content_length == 0 25 | pub fn new(content_length: u32) -> Option { 26 | if content_length == 0 { 27 | return None; 28 | } 29 | 30 | let content_length = content_length as usize; 31 | Some(Self { 32 | data: Vec::with_capacity(content_length), 33 | content_length, 34 | }) 35 | } 36 | 37 | /// consumes buffer to create combined value 38 | /// panics if called prematurely 39 | pub fn merge(self) -> T { 40 | assert_eq!(self.data.len(), self.content_length); 41 | let mut iter = self.data.into_iter(); 42 | iter.next() 43 | .map(|mut first| { 44 | first.extend(iter); 45 | first 46 | }) 47 | .unwrap() 48 | } 49 | 50 | /// returns AssemblyError::ContentLengthExceeded if content length already reached 51 | /// returns true if buffer is ready to merge 52 | pub fn push(&mut self, data: T) -> Result { 53 | if self.data.len() == self.content_length { 54 | Err(AssemblyError::ContentLengthExceeded) 55 | } else { 56 | self.data.push(data); 57 | Ok(self.data.len() == self.content_length) 58 | } 59 | } 60 | } 61 | 62 | #[cfg(test)] 63 | mod tests { 64 | use crate::merge_buffer::{AssemblyError, MergeBuffer}; 65 | 66 | #[derive(Debug, PartialEq, Eq)] 67 | struct Extendable(Vec); 68 | 69 | impl Extend for Extendable { 70 | fn extend>(&mut self, iter: T) { 71 | for extendable in iter.into_iter() { 72 | self.0.extend(extendable.0); 73 | } 74 | } 75 | } 76 | 77 | #[test] 78 | fn test_merge() { 79 | let mut buffer = MergeBuffer::new(10).unwrap(); 80 | for i in 1..=10 { 81 | assert_eq!(Ok(i == 10), buffer.push(Extendable(vec![i]))); 82 | } 83 | assert_eq!( 84 | Err(AssemblyError::ContentLengthExceeded), 85 | buffer.push(Extendable(vec![11])) 86 | ); 87 | 88 | let merged = buffer.merge(); 89 | assert_eq!(Extendable((1..=10).collect::>()), merged); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /protobuf/protobuf/call_summary.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Signal Messenger, LLC 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | syntax = "proto2"; 7 | 8 | package call_summary; 9 | 10 | // The mean, standard deviation, minimum, and maximum values will always be 11 | // be present. If there were no captured samples then the entire 12 | // DistributionSummary structure will be absent. 13 | message DistributionSummary { 14 | optional float mean = 1; 15 | optional float std_dev = 2; 16 | optional float min_val = 3; 17 | optional float max_val = 4; 18 | optional uint32 sample_count = 5; 19 | } 20 | 21 | message StreamSummary { 22 | optional DistributionSummary bitrate = 1; 23 | optional DistributionSummary packet_loss_pct = 2; 24 | optional DistributionSummary jitter = 3; 25 | } 26 | 27 | message StreamSummaries { 28 | map audio_send_stream_summaries = 1; 29 | map audio_recv_stream_summaries = 2; 30 | map video_send_stream_summaries = 3; 31 | map video_recv_stream_summaries = 4; 32 | } 33 | 34 | message StreamStats { 35 | optional uint32 ssrc = 1; 36 | optional float bitrate = 2; 37 | optional float packet_loss = 3; 38 | optional float jitter = 4; 39 | optional float rtt = 5; 40 | optional float jitter_buffer_delay = 6; 41 | optional float framerate = 7; 42 | } 43 | 44 | message StatsSet { 45 | optional uint64 timestamp = 1; 46 | repeated StreamStats audio_recv_stats = 2; 47 | repeated StreamStats audio_send_stats = 3; 48 | repeated StreamStats video_recv_stats = 4; 49 | repeated StreamStats video_send_stats = 5; 50 | repeated Event events = 6; 51 | repeated float rtt_stun = 7; 52 | } 53 | 54 | enum Event { 55 | ICE_CONNECTED = 1; 56 | ICE_DISCONNECTED = 2; 57 | ICE_FAILED = 3; 58 | ICE_NETWORK_ROUTE_CHANGED = 4; 59 | GROUP_CALL_DISCONNECTED = 5; 60 | GROUP_CALL_CONNECTING = 6; 61 | GROUP_CALL_CONNECTED = 7; 62 | GROUP_CALL_RECONNECTING = 8; 63 | } 64 | 65 | message DirectCallSummary { 66 | optional StreamSummaries stream_summaries = 1; 67 | optional uint32 ice_candidate_switch_count = 2; 68 | optional uint32 ice_reconnect_count = 3; 69 | optional bool relayed = 4; 70 | } 71 | 72 | message GroupCallSummary { 73 | optional StreamSummaries stream_summaries = 1; 74 | } 75 | 76 | message CallTelemetry { 77 | required uint32 version = 1; 78 | required uint64 start_time = 2; 79 | optional uint64 connect_time = 3; 80 | required uint64 end_time = 4; 81 | optional bool cellular = 5; 82 | optional GroupCallSummary group_call_summary = 6; 83 | optional DirectCallSummary direct_call_summary = 7; 84 | repeated StatsSet stats_sets = 8; 85 | } 86 | 87 | -------------------------------------------------------------------------------- /src/node/ringrtc/CallSummary.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2025 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | /** 7 | * Media quality statistics for audio or video streams. 8 | * 9 | * Contains network quality metrics including RTT, jitter, and packet loss. 10 | */ 11 | 12 | export class MediaQualityStats { 13 | /** 14 | * Median RTT in milliseconds calculated via RTP/RTCP, or undefined if unavailable. 15 | */ 16 | readonly rttMedianMillis: number | undefined; 17 | /** 18 | * Median jitter for sent packets as reported by remote peer in milliseconds, 19 | * or undefined if unavailable. 20 | */ 21 | readonly jitterMedianSendMillis: number | undefined; 22 | /** 23 | * Median jitter for received packets in milliseconds, or undefined if unavailable. 24 | */ 25 | readonly jitterMedianRecvMillis: number | undefined; 26 | /** 27 | * Packet loss fraction for sent packets as reported by remote peer, 28 | * or undefined if unavailable. 29 | */ 30 | readonly packetLossFractionSend: number | undefined; 31 | /** 32 | * Packet loss fraction for received packets, or undefined if unavailable. 33 | */ 34 | readonly packetLossFractionRecv: number | undefined; 35 | } 36 | 37 | /** 38 | * Overall call quality statistics. 39 | * 40 | * Contains connection-level metrics and separate audio/video quality stats. 41 | */ 42 | export class QualityStats { 43 | /** 44 | * Median connection RTT in milliseconds calculated via STUN/ICE, 45 | * or undefined if unavailable. 46 | */ 47 | readonly rttMedianConnection: number | undefined; 48 | /** Audio quality statistics. */ 49 | readonly audioStats!: MediaQualityStats; 50 | /** Video quality statistics. */ 51 | readonly videoStats!: MediaQualityStats; 52 | } 53 | 54 | /** 55 | * Summary of call telemetry data providing a synopsis of call quality. 56 | * 57 | * Statistics are captured when the call ends and are available for reporting. 58 | */ 59 | export class CallSummary { 60 | /** 61 | * Call start timestamp in milliseconds since January 1, 1970 00:00:00 UTC. 62 | */ 63 | readonly startTime!: number; 64 | /** 65 | * Call end timestamp in milliseconds since January 1, 1970 00:00:00 UTC. 66 | */ 67 | readonly endTime!: number; 68 | /** 69 | * High-level call quality statistics with cumulative metrics for the entire 70 | * call session, including connection-level stats and separate audio/video 71 | * quality stats. 72 | */ 73 | readonly qualityStats!: QualityStats; 74 | /** 75 | * Raw call telemetry data containing periodic internal/opaque values for the 76 | * last few seconds of the call, or undefined if unavailable. 77 | */ 78 | readonly rawStats: Uint8Array | undefined; 79 | /** 80 | * Textual description of raw telemetry data, or undefined if unavailable. 81 | */ 82 | readonly rawStatsText: string | undefined; 83 | /** 84 | * Textual representation of the call end reason. 85 | */ 86 | readonly callEndReasonText!: string; 87 | /** 88 | * Whether the call is eligible for user survey (i.e., the call actually connected). 89 | */ 90 | readonly isSurveyCandidate!: boolean; 91 | } 92 | -------------------------------------------------------------------------------- /src/rust/src/webrtc/arc.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | //! Rust-friendly wrapper around rtc::RefCountInterface, similar to 7 | //! WebRTC's scoped_refptr. 8 | 9 | use std::{ 10 | fmt, 11 | marker::{Send, Sync}, 12 | }; 13 | 14 | use crate::webrtc; 15 | #[cfg(not(feature = "sim"))] 16 | use crate::webrtc::ffi::ref_count; 17 | #[cfg(feature = "sim")] 18 | use crate::webrtc::sim::ref_count; 19 | 20 | pub struct Arc { 21 | owned: webrtc::ptr::OwnedRc, 22 | } 23 | 24 | impl fmt::Debug for Arc { 25 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 26 | f.write_fmt(format_args!("webrtc::Arc({:p})", self.owned.as_ptr())) 27 | } 28 | } 29 | 30 | impl Arc { 31 | // Takes ownership of one reference. 32 | // Does not increment the ref count. 33 | // Should be called with a pointer returned from scoped_refptr::release(). 34 | // or from "auto t = new rtc::RefCountedObject(...); t->AddRef()" 35 | pub fn from_owned(owned: webrtc::ptr::OwnedRc) -> Self { 36 | Self { owned } 37 | } 38 | 39 | /// Convenience function which is the same as Arc::from_owned(webrtc::ptr::OwnedRc::from_ptr(stc::ptr::null())) 40 | pub fn null() -> Self { 41 | // Safe because a dropped null will do nothing. 42 | Self::from_owned(webrtc::ptr::OwnedRc::null()) 43 | } 44 | 45 | /// Clones ownership (increments the ref count). 46 | /// # Safety 47 | /// The pointee must be alive. 48 | pub unsafe fn from_borrowed(borrowed: webrtc::ptr::BorrowedRc) -> Self { 49 | unsafe { Self::from_owned(ref_count::inc(borrowed)) } 50 | } 51 | 52 | pub fn as_borrowed(&self) -> webrtc::ptr::BorrowedRc { 53 | self.owned.borrow() 54 | } 55 | 56 | pub fn take_owned(&mut self) -> webrtc::ptr::OwnedRc { 57 | std::mem::replace(&mut self.owned, webrtc::ptr::OwnedRc::null()) 58 | } 59 | 60 | pub fn into_owned(mut self) -> webrtc::ptr::OwnedRc { 61 | self.take_owned() 62 | } 63 | 64 | /// Convenience function which is the same as self.as_borrowed().is_null() 65 | pub fn is_null(&self) -> bool { 66 | self.owned.is_null() 67 | } 68 | 69 | /// Convenience function which is the same as self.as_borrowed().as_ref() 70 | /// # Safety 71 | /// Just as safe as any pointer deref. 72 | pub unsafe fn as_ref(&self) -> Option<&T> { 73 | self.owned.as_ref() 74 | } 75 | } 76 | 77 | impl Clone for Arc { 78 | fn clone(&self) -> Self { 79 | // Safe because from_borrowed is only unsafe because the passed-in BorrowedRc 80 | // might not longer be alive, but in this case we know it's still alive. 81 | unsafe { Self::from_borrowed(self.as_borrowed()) } 82 | } 83 | } 84 | 85 | impl Drop for Arc { 86 | fn drop(&mut self) { 87 | ref_count::dec(self.owned.take()); 88 | } 89 | } 90 | 91 | unsafe impl Send for Arc {} 92 | unsafe impl Sync for Arc {} 93 | -------------------------------------------------------------------------------- /bin/logs-notebook/README.md: -------------------------------------------------------------------------------- 1 | # logs-notebook 2 | 3 | A [Jupyter](https://jupyter.org/) notebook for analyzing RingRTC stats in 4 | debuglogs. 5 | 6 | ## How to use 7 | 8 | After setting up the dependencies, run the following command in this directory 9 | to start Jupyter in your browser: 10 | 11 | ```shell 12 | jupyter lab 13 | ``` 14 | 15 | Then there are two main functions that the notebook provides: 16 | 17 | ```python 18 | import call_log_parser 19 | 20 | calls = call_log_parser.load_calls("https://debuglogs.org/platform/version/hash") 21 | 22 | call_log_parser.describe(calls) 23 | ``` 24 | 25 | `load_calls` takes a URL and returns a list of `Call`s (one for each call in 26 | the log). 27 | 28 | `describe` takes a list of `Call`s and summarizes information about them into 29 | a [pandas `DataFrame`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html). 30 | 31 | `load_calls` can also load multiple logs at once, and the results will include 32 | only the calls that appear in all given logs: 33 | 34 | ```python 35 | (caller, callee) = call_log_parser.load_calls( 36 | "https://debuglogs.org/platform/version/hash", 37 | "https://debuglogs.org/platform/version/hash2", 38 | ) 39 | ``` 40 | 41 | A single `Call` has the following attributes: 42 | 43 | - `connection` 44 | - `audio_send` 45 | - `audio_recv` 46 | - `video_send` 47 | - `video_recv` 48 | - `sfu_recv` 49 | - `ice_network_route_change` 50 | 51 | These correspond to the `ringrtc_stats!` and `ringrtc!` lines in the logs. 52 | 53 | A `Call` also has this attribute: 54 | 55 | - `media_key_recv` 56 | 57 | The associated values of all of these attributes are a `DataFrame` of parsed 58 | values. 59 | 60 | The following methods are also available on `Call`: 61 | 62 | |Method |Description| 63 | |------------------------------------|-----------| 64 | |`ssrc` |Prints the SSRCs of the audio and the lowest layer video stream of the participant who submitted the logs.| 65 | |`describe_connection` |Plots the `connection` stats.| 66 | |`describe_audio_send` |Plots the `audio,send` stats.| 67 | |`describe_audio_recv` |Plots the `audio,recv` stats. For group calls, the SSRC of the desired stream needs to be passed.| 68 | |`describe_video_send` |Plots the `video,send` stats. All video layers are plotted by default for group calls. Pass the index of the layer to show only one.| 69 | |`describe_video_recv` |Plots the `video,recv` stats. For group calls, the SSRC of the desired stream needs to be passed.| 70 | |`describe_sfu_recv` |Plots the `sfu,recv` stats. Only for group calls.| 71 | |`describe_system` |Plots the `system` stats.| 72 | |`describe_ice_network_route_change` |Plots the `ice_network_route_change` local and remote relay values.| 73 | |`logs` |Prints the logs for the call that contain the passed query.| 74 | 75 | ## Dependencies 76 | 77 | These Python packages need to be accessible from the Jupyter environment for 78 | the logs to be fetched and analyzed. 79 | 80 | - [pandas](https://pypi.org/project/pandas/) 81 | - [matplotlib](https://pypi.org/project/matplotlib/) 82 | - [requests](https://pypi.org/project/requests/) 83 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 1>&2 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 48 | echo. 1>&2 49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 50 | echo location of your Java installation. 1>&2 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 1>&2 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 62 | echo. 1>&2 63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 64 | echo location of your Java installation. 1>&2 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /src/ios/SignalRingRTC/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.8) 5 | activesupport (7.2.3) 6 | base64 7 | benchmark (>= 0.3) 8 | bigdecimal 9 | concurrent-ruby (~> 1.0, >= 1.3.1) 10 | connection_pool (>= 2.2.5) 11 | drb 12 | i18n (>= 1.6, < 2) 13 | logger (>= 1.4.2) 14 | minitest (>= 5.1) 15 | securerandom (>= 0.3) 16 | tzinfo (~> 2.0, >= 2.0.5) 17 | addressable (2.8.8) 18 | public_suffix (>= 2.0.2, < 8.0) 19 | algoliasearch (1.27.5) 20 | httpclient (~> 2.8, >= 2.8.3) 21 | json (>= 1.5.1) 22 | atomos (0.1.3) 23 | base64 (0.3.0) 24 | benchmark (0.5.0) 25 | bigdecimal (3.3.1) 26 | claide (1.1.0) 27 | cocoapods (1.16.2) 28 | addressable (~> 2.8) 29 | claide (>= 1.0.2, < 2.0) 30 | cocoapods-core (= 1.16.2) 31 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 32 | cocoapods-downloader (>= 2.1, < 3.0) 33 | cocoapods-plugins (>= 1.0.0, < 2.0) 34 | cocoapods-search (>= 1.0.0, < 2.0) 35 | cocoapods-trunk (>= 1.6.0, < 2.0) 36 | cocoapods-try (>= 1.1.0, < 2.0) 37 | colored2 (~> 3.1) 38 | escape (~> 0.0.4) 39 | fourflusher (>= 2.3.0, < 3.0) 40 | gh_inspector (~> 1.0) 41 | molinillo (~> 0.8.0) 42 | nap (~> 1.0) 43 | ruby-macho (>= 2.3.0, < 3.0) 44 | xcodeproj (>= 1.27.0, < 2.0) 45 | cocoapods-core (1.16.2) 46 | activesupport (>= 5.0, < 8) 47 | addressable (~> 2.8) 48 | algoliasearch (~> 1.0) 49 | concurrent-ruby (~> 1.1) 50 | fuzzy_match (~> 2.0.4) 51 | nap (~> 1.0) 52 | netrc (~> 0.11) 53 | public_suffix (~> 4.0) 54 | typhoeus (~> 1.0) 55 | cocoapods-deintegrate (1.0.5) 56 | cocoapods-downloader (2.1) 57 | cocoapods-plugins (1.0.0) 58 | nap 59 | cocoapods-search (1.0.1) 60 | cocoapods-trunk (1.6.0) 61 | nap (>= 0.8, < 2.0) 62 | netrc (~> 0.11) 63 | cocoapods-try (1.2.0) 64 | colored2 (3.1.2) 65 | concurrent-ruby (1.3.5) 66 | connection_pool (3.0.2) 67 | drb (2.2.3) 68 | escape (0.0.4) 69 | ethon (0.18.0) 70 | ffi (>= 1.15.0) 71 | logger 72 | ffi (1.17.2) 73 | fourflusher (2.3.1) 74 | fuzzy_match (2.0.4) 75 | gh_inspector (1.1.3) 76 | httpclient (2.9.0) 77 | mutex_m 78 | i18n (1.14.7) 79 | concurrent-ruby (~> 1.0) 80 | json (2.17.1) 81 | logger (1.7.0) 82 | minitest (5.26.2) 83 | molinillo (0.8.0) 84 | mutex_m (0.3.0) 85 | nanaimo (0.4.0) 86 | nap (1.1.0) 87 | netrc (0.11.0) 88 | nkf (0.2.0) 89 | public_suffix (4.0.7) 90 | rexml (3.4.4) 91 | ruby-macho (2.5.1) 92 | securerandom (0.4.1) 93 | typhoeus (1.4.1) 94 | ethon (>= 0.9.0) 95 | tzinfo (2.0.6) 96 | concurrent-ruby (~> 1.0) 97 | xcodeproj (1.27.0) 98 | CFPropertyList (>= 2.3.3, < 4.0) 99 | atomos (~> 0.1.3) 100 | claide (>= 1.0.2, < 2.0) 101 | colored2 (~> 3.1) 102 | nanaimo (~> 0.4.0) 103 | rexml (>= 3.3.6, < 4.0) 104 | 105 | PLATFORMS 106 | ruby 107 | 108 | DEPENDENCIES 109 | cocoapods 110 | nkf 111 | 112 | BUNDLED WITH 113 | 2.7.1 114 | -------------------------------------------------------------------------------- /src/rust/src/lite/logging.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | //! Make calls to the platform to do logging 7 | 8 | #[cfg(any(target_os = "ios", feature = "check-all"))] 9 | pub mod ios { 10 | use std::ffi::c_void; 11 | 12 | use crate::lite::ffi::ios::{FromOrDefault, rtc_OptionalU32, rtc_String}; 13 | 14 | #[repr(C)] 15 | pub struct rtc_log_Record<'a> { 16 | message: rtc_String<'a>, 17 | file: rtc_String<'a>, 18 | line: rtc_OptionalU32, 19 | level: u8, 20 | } 21 | 22 | // It's up to the other side of the bridge to provide a Sync-friendly context. 23 | unsafe impl Send for rtc_log_Delegate {} 24 | unsafe impl Sync for rtc_log_Delegate {} 25 | 26 | #[repr(C)] 27 | pub struct rtc_log_Delegate { 28 | pub ctx: *mut c_void, 29 | pub log: extern "C" fn(ctx: *mut c_void, record: rtc_log_Record), 30 | pub flush: extern "C" fn(ctx: *mut c_void), 31 | } 32 | 33 | #[unsafe(no_mangle)] 34 | pub extern "C" fn rtc_log_init(delegate: rtc_log_Delegate, max_level: u8) -> bool { 35 | if log::set_boxed_logger(Box::new(delegate)).is_err() { 36 | warn!("Logging already initialized"); 37 | return false; 38 | } 39 | 40 | let max_level_filter = match max_level { 41 | level if level == (log::LevelFilter::Off as u8) => Some(log::LevelFilter::Off), 42 | level if level == (log::LevelFilter::Error as u8) => Some(log::LevelFilter::Error), 43 | level if level == (log::LevelFilter::Warn as u8) => Some(log::LevelFilter::Warn), 44 | level if level == (log::LevelFilter::Info as u8) => Some(log::LevelFilter::Info), 45 | level if level == (log::LevelFilter::Debug as u8) => Some(log::LevelFilter::Debug), 46 | level if level == (log::LevelFilter::Trace as u8) => Some(log::LevelFilter::Trace), 47 | _ => None, 48 | }; 49 | 50 | if let Some(max_level_filter) = max_level_filter { 51 | log::set_max_level(max_level_filter); 52 | } else { 53 | log::set_max_level(log::LevelFilter::Debug); 54 | warn!("Invalid max log level = {:?}. Using Debug", max_level); 55 | } 56 | 57 | std::panic::set_hook(Box::new(|panic_info| { 58 | error!("Critical error: {}", panic_info); 59 | })); 60 | 61 | debug!("RingRTC logging system initialized!"); 62 | 63 | true 64 | } 65 | 66 | impl log::Log for rtc_log_Delegate { 67 | fn enabled(&self, _metadata: &log::Metadata) -> bool { 68 | true 69 | } 70 | 71 | fn log(&self, record: &log::Record) { 72 | if !self.enabled(record.metadata()) { 73 | return; 74 | } 75 | 76 | let message = format!("{}", record.args()); 77 | 78 | (self.log)( 79 | self.ctx, 80 | rtc_log_Record { 81 | message: rtc_String::from(&message), 82 | file: rtc_String::from_or_default(record.file()), 83 | line: rtc_OptionalU32::from_or_default(record.line()), 84 | level: record.level() as u8, 85 | }, 86 | ); 87 | } 88 | 89 | fn flush(&self) {} 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /bin/env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright 2019-2021 Signal Messenger, LLC 5 | # SPDX-License-Identifier: AGPL-3.0-only 6 | # 7 | 8 | # Allow non-exported environment variables 9 | # shellcheck disable=SC2034 10 | 11 | REALPATH=realpath 12 | if ! $REALPATH -e . >/dev/null 2>&1 ; then 13 | # Can be true on macOS Ventura+ and coreutils from HomeBrew 14 | REALPATH=grealpath 15 | fi 16 | 17 | BIN_DIR=$(dirname "$0") 18 | BIN_DIR=$($REALPATH "$BIN_DIR") 19 | 20 | [ -d "$BIN_DIR" ] || { 21 | echo "ERROR: project bin directory does not exist: $BIN_DIR" 22 | exit 1 23 | } 24 | 25 | # project root directory 26 | PROJECT_DIR=$(dirname "$BIN_DIR") 27 | 28 | # project configuration directory 29 | CONFIG_DIR="${PROJECT_DIR}/config" 30 | 31 | # project patches directory 32 | PATCH_DIR="${PROJECT_DIR}/patches/webrtc" 33 | 34 | RINGRTC_SRC_DIR="${PROJECT_DIR}/src" 35 | 36 | # build products 37 | 38 | OUTPUT_DIR="${OUTPUT_DIR:-${PROJECT_DIR}/out}" 39 | OUTPUT_DIR="$($REALPATH "$OUTPUT_DIR")" 40 | 41 | 42 | # patch hash file 43 | PATCH_HASH="${OUTPUT_DIR}/patch-hash" 44 | 45 | WEBRTC_DIR="${PROJECT_DIR}/src/webrtc" 46 | WEBRTC_SRC_DIR="${WEBRTC_DIR}/src" 47 | 48 | RINGRTC_WEBRTC_SRC_DIR="${WEBRTC_DIR}/src/ringrtc" 49 | 50 | case "$(uname -s | tr '[:upper:]' '[:lower:]')" in 51 | linux) 52 | HOST_PLATFORM="linux" 53 | ;; 54 | msys*|mingw*) 55 | HOST_PLATFORM="windows" 56 | ;; 57 | darwin) 58 | HOST_PLATFORM="mac" 59 | ;; 60 | *) 61 | HOST_PLATFORM="unknown" 62 | ;; 63 | esac 64 | 65 | VERSION_INFO="${CONFIG_DIR}/version.sh" 66 | [ -f "$VERSION_INFO" ] || { 67 | echo "ERROR: unable to load version configuration: $VERSION_INFO" 68 | exit 1 69 | } 70 | # shellcheck source=config/version.sh 71 | . "$VERSION_INFO" 72 | 73 | if [ -f "${OUTPUT_DIR}/webrtc-version.env" ] ; then 74 | # shellcheck disable=SC1090,SC1091 # can't check generated file 75 | . "${OUTPUT_DIR}/webrtc-version.env" 76 | fi 77 | 78 | # This is the release branch of webrtc to check out 79 | WEBRTC_REVISION="branch-heads/${WEBRTC_VERSION}" 80 | 81 | # This function should be overridden by a platform specific 82 | # implementation. 83 | prepare_workspace_platform() { 84 | echo "ERROR: prepare_workspace_platform() is undefined for this platform: $WEBRTC_PLATFORM" 85 | exit 1 86 | } 87 | 88 | INTENDED_WEBRTC_PLATFORM=$WEBRTC_PLATFORM 89 | 90 | # current platform if it exists 91 | if [ -f "${OUTPUT_DIR}/platform.env" ] ; then 92 | # shellcheck disable=SC1090,SC1091 # can't check generated file 93 | . "${OUTPUT_DIR}/platform.env" 94 | fi 95 | if [ -n "$WEBRTC_PLATFORM" ] ; then 96 | 97 | # don't mix platforms 98 | if [ -n "$INTENDED_WEBRTC_PLATFORM" ] && [ "$WEBRTC_PLATFORM" != "$INTENDED_WEBRTC_PLATFORM" ] ; then 99 | echo "ERROR: $WEBRTC_PLATFORM platform already exists, try 'make distclean' first." 100 | exit 1 101 | fi 102 | 103 | # platform specific env if it exists 104 | PLATFORM_ENV="${BIN_DIR}/env-${WEBRTC_PLATFORM}.sh" 105 | if [ -f "$PLATFORM_ENV" ] ; then 106 | # shellcheck disable=SC1090 # can't check platform-specific file 107 | . "$PLATFORM_ENV" 108 | else 109 | echo "ERROR: Unable to find platform specific environment settings: $PLATFORM_ENV" 110 | exit 1 111 | fi 112 | fi 113 | -------------------------------------------------------------------------------- /src/rust/src/android/error.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | //! Android Error Codes and Utilities. 7 | 8 | use anyhow::Error; 9 | use jni::{JNIEnv, errors, objects::JThrowable}; 10 | use thiserror::Error; 11 | 12 | use crate::{android::jni_util::*, core::util::try_scoped}; 13 | 14 | const CALL_EXCEPTION_CLASS: &str = jni_class_name!(org.signal.ringrtc.CallException); 15 | 16 | /// Convert a `Error` into a Java `org.signal.ringrtc.CallException` 17 | /// and throw it. 18 | /// 19 | /// This is used to communicate synchronous errors to the client 20 | /// application. 21 | pub fn throw_error(env: &mut JNIEnv, error: Error) { 22 | if let Ok(exception) = env.exception_occurred() { 23 | if env.exception_clear().is_ok() { 24 | let _ = try_scoped(|| { 25 | let message = env.new_string(error.to_string())?; 26 | let call_exception: JThrowable = jni_new_object( 27 | env, 28 | CALL_EXCEPTION_CLASS, 29 | jni_args!(( 30 | message => java.lang.String, 31 | exception => java.lang.Throwable, 32 | ) -> void), 33 | )? 34 | .into(); 35 | Ok(env.throw(call_exception)?) 36 | }); 37 | } else { 38 | // Don't try to throw our own exception on top of another exception. 39 | } 40 | } else { 41 | let _ = env.throw_new(CALL_EXCEPTION_CLASS, format!("{}", error)); 42 | } 43 | } 44 | 45 | /// Android specific error codes. 46 | #[derive(Error, Debug)] 47 | pub enum AndroidError { 48 | // Android JNI error codes 49 | #[error("JNI: static method lookup failed. Class: {0}, Method: {1}, Sig: {2}")] 50 | JniStaticMethodLookup(String, String, String), 51 | #[error("JNI: calling method failed. Method: {0}, Sig: {1}, Error: {2}")] 52 | JniCallMethod(String, String, errors::Error), 53 | #[error("JNI: calling static method failed. Class: {0}, Method: {1}, Sig: {2}")] 54 | JniCallStaticMethod(String, String, String), 55 | #[error("JNI: calling constructor failed. Constructor: {0}, Sig: {1}")] 56 | JniCallConstructor(String, String), 57 | #[error("JNI: getting field failed. Field: {0}, Type: {1}")] 58 | JniGetField(String, String), 59 | #[error("JNI: class not found. Type: {0} Add to the cache?")] 60 | JniGetLangClassNotFound(String), 61 | #[error("JNI: new object failed. Type: {0}")] 62 | JniNewLangObjectFailed(String), 63 | #[error("JNI: invalid serialized buffer.")] 64 | JniInvalidSerializedBuffer, 65 | 66 | // Android Class Cache error codes 67 | #[error("ClassCache: Class is already in cache: {0}")] 68 | ClassCacheDuplicate(String), 69 | #[error("ClassCache: class not found in jvm: {0}")] 70 | ClassCacheNotFound(String), 71 | #[error("ClassCache: class not found in cache: {0}")] 72 | ClassCacheLookup(String), 73 | 74 | // Android Misc error codes 75 | #[error("Creating JNI PeerConnection failed")] 76 | CreateJniPeerConnection, 77 | #[error("Extracting native PeerConnection failed")] 78 | ExtractNativePeerConnection, 79 | #[error("Creating JNI Connection failed")] 80 | CreateJniConnection, 81 | 82 | // WebRTC / JNI C++ error codes 83 | #[error("Unable to create C++ JavaMediaStream")] 84 | CreateJavaMediaStream, 85 | } 86 | -------------------------------------------------------------------------------- /src/ios/SignalRingRTC/SignalRingRTC/Connection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | import SignalRingRTC.RingRTC 7 | import WebRTC 8 | 9 | @available(iOSApplicationExtension, unavailable) 10 | public class Connection { 11 | private var audioSender: RTCRtpSender? 12 | private var videoSender: RTCRtpSender? 13 | 14 | private var peerConnection: RTCPeerConnection 15 | private var nativePeerConnection: UnsafeMutableRawPointer 16 | 17 | init(pcObserverOwned: UnsafeMutableRawPointer, factory: RTCPeerConnectionFactory, configuration: RTCConfiguration, constraints: RTCMediaConstraints) { 18 | // Takes an owned pointer to the observer. 19 | // See "std::unique_ptr _customObserver" 20 | // in webrtc/src/sdk/objc/api/peerconnection/RTCPeerConnection.mm 21 | // which is modified in RingRTC's fork of WebRTC. 22 | self.peerConnection = factory.peerConnection(with: configuration, constraints: constraints, observer: pcObserverOwned) 23 | self.nativePeerConnection = self.peerConnection.getNativePeerConnectionPointer() 24 | 25 | Logger.debug("object! Connection created... \(ObjectIdentifier(self))") 26 | } 27 | 28 | deinit { 29 | Logger.debug("Closing PeerConnection") 30 | self.peerConnection.close() 31 | 32 | Logger.debug("object! Connection destroyed... \(ObjectIdentifier(self))") 33 | } 34 | 35 | func getRawPeerConnection() -> UnsafeMutableRawPointer? { 36 | return self.nativePeerConnection 37 | } 38 | 39 | func getWrapper(pc: UnsafeMutableRawPointer?) -> AppConnectionInterface { 40 | return AppConnectionInterface( 41 | object: UnsafeMutableRawPointer(Unmanaged.passRetained(self).toOpaque()), 42 | pc: pc, 43 | destroy: connectionDestroy) 44 | } 45 | 46 | func createStream(nativeStreamBorrowedRc: UnsafeMutableRawPointer) -> RTCMediaStream { 47 | // This gets converted into a rtc::scoped_refptr. 48 | // In other words, the ref count gets incremented, 49 | // so what's passed in is a borrowed RC. 50 | return self.peerConnection.createStream(fromNative: nativeStreamBorrowedRc) 51 | } 52 | 53 | func createAudioSender(audioTrack: RTCAudioTrack) { 54 | // We use addTrack instead of createSender or addTransceiver because it uses 55 | // the track's ID instead of a random ID in the SDP, which is important 56 | // for call forking. 57 | self.audioSender = self.peerConnection.add(audioTrack, streamIds: ["ARDAMS"]) 58 | } 59 | 60 | func createVideoSender(videoTrack: RTCVideoTrack) { 61 | // We use addTrack instead of createSender or addTransceiver because it uses 62 | // the track's ID instead of a random ID in the SDP, which is important 63 | // for call forking. 64 | self.videoSender = self.peerConnection.add(videoTrack, streamIds: ["ARDAMS"]) 65 | } 66 | } 67 | 68 | @available(iOSApplicationExtension, unavailable) 69 | func connectionDestroy(object: UnsafeMutableRawPointer?) { 70 | guard let object = object else { 71 | failDebug("object was unexpectedly nil") 72 | return 73 | } 74 | 75 | let _ = Unmanaged.fromOpaque(object).takeRetainedValue() 76 | // @note There should not be any retainers left for the object 77 | // so deinit should be called implicitly. 78 | } 79 | -------------------------------------------------------------------------------- /protobuf/protobuf/call_sim.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Signal Messenger, LLC 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | syntax = "proto3"; 7 | 8 | package calling; 9 | 10 | message Empty {} 11 | 12 | message Registration { 13 | // From, the client that is registering. 14 | string client = 1; 15 | } 16 | 17 | message CallMessage { 18 | message Offer { 19 | enum Type { 20 | OFFER_AUDIO_CALL = 0; 21 | OFFER_VIDEO_CALL = 1; 22 | } 23 | 24 | uint64 id = 1; 25 | Type type = 2; 26 | bytes opaque = 3; 27 | } 28 | 29 | message Answer { 30 | uint64 id = 1; 31 | bytes opaque = 2; 32 | } 33 | 34 | message IceUpdate { 35 | uint64 id = 1; 36 | bytes opaque = 2; 37 | } 38 | 39 | message Busy { 40 | uint64 id = 1; 41 | } 42 | 43 | message Hangup { 44 | enum Type { 45 | HANGUP_NORMAL = 0; 46 | HANGUP_ACCEPTED = 1; 47 | HANGUP_DECLINED = 2; 48 | HANGUP_BUSY = 3; 49 | HANGUP_NEED_PERMISSION = 4; 50 | } 51 | 52 | uint64 id = 1; 53 | Type type = 2; 54 | uint32 deviceId = 3; 55 | } 56 | 57 | Offer offer = 1; 58 | Answer answer = 2; 59 | repeated IceUpdate iceUpdate = 3; 60 | Busy busy = 4; 61 | Hangup hangup = 5; 62 | } 63 | 64 | // The RelayMessage is essentially the message envelope, containing the actual 65 | // call message along with other meta data. 66 | message RelayMessage { 67 | // From, the client that is sending the message. 68 | string client = 1; 69 | // The deviceId of the client that is sending the message. 70 | uint32 deviceId = 2; 71 | // The actual message we are passing through. 72 | CallMessage callMessage = 3; 73 | // Intended for Group CallMessage, which uses the Opaque field in the Signal Protocol 74 | optional bytes opaqueMessage = 4; 75 | } 76 | 77 | service SignalingRelay { 78 | // The client will register with the server, and this is a "server-side streaming RPC". 79 | rpc Register (Registration) returns (stream RelayMessage); 80 | // After registering, the client can send messages, and this is a "Simple RPC". 81 | rpc Send (RelayMessage) returns (Empty); 82 | } 83 | 84 | message CommandMessage { 85 | enum Command { 86 | START_AS_CALLER = 0; 87 | START_AS_CALLEE = 1; 88 | STOP = 2; 89 | } 90 | 91 | // To, the client that should receive the message. 92 | string client = 1; 93 | // The command to send. 94 | Command command = 2; 95 | } 96 | 97 | message Event { 98 | // The number of clients that are ready for the test. Increments with ready, decrements with done. 99 | int32 readyCount = 1; 100 | } 101 | 102 | service TestManagement { 103 | // Each test client will let the server know when it is ready (generally after it registers with the 104 | // relay server, and thus be able to receive commands). 105 | rpc Ready (Registration) returns (stream CommandMessage); 106 | // The client can let the server (and test manager) know it is done (after stopping). 107 | rpc Done (Registration) returns (Empty); 108 | // A simplistic notification scheme to send updates to the test manager. 109 | rpc Notification (Empty) returns (stream Event); 110 | // The controller can send messages to specific clients in order to instruct them to do things. 111 | rpc SendCommand (CommandMessage) returns (Empty); 112 | } 113 | -------------------------------------------------------------------------------- /src/ios/SignalRingRTC/SignalRingRTC/CallContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | import SignalRingRTC.RingRTC 7 | import WebRTC 8 | 9 | // This class's member functions are all called from the CallManager class 10 | // on the main thread. 11 | @available(iOSApplicationExtension, unavailable) 12 | public class CallContext { 13 | 14 | // A camera queue on which to perform camera operations. 15 | private static let cameraQueue = DispatchQueue(label: "CallContextCameraQueue") 16 | 17 | let iceServers: [RTCIceServer] 18 | let hideIp: Bool 19 | 20 | let audioSource: RTCAudioSource 21 | let audioTrack: RTCAudioTrack 22 | weak var videoCaptureController: VideoCaptureController! 23 | let videoSource: RTCVideoSource 24 | let videoTrack: RTCVideoTrack 25 | 26 | // Cache the latest settings so we don't repeat them. 27 | var currentVideoEnableSetting: Bool 28 | 29 | init (iceServers: [RTCIceServer], hideIp: Bool, audioSource: RTCAudioSource, audioTrack: RTCAudioTrack, videoSource: RTCVideoSource, videoTrack: RTCVideoTrack, videoCaptureController: VideoCaptureController) { 30 | self.iceServers = iceServers 31 | self.hideIp = hideIp 32 | self.audioSource = audioSource 33 | self.audioTrack = audioTrack 34 | self.videoSource = videoSource 35 | self.videoTrack = videoTrack 36 | self.videoCaptureController = videoCaptureController 37 | 38 | // For now, assume video starts out as disabled. 39 | currentVideoEnableSetting = false 40 | 41 | Logger.debug("object! CallContext created... \(ObjectIdentifier(self))") 42 | } 43 | 44 | deinit { 45 | Logger.debug("object! CallContext destroyed... \(ObjectIdentifier(self))") 46 | } 47 | 48 | func getWrapper() -> AppCallContext { 49 | return AppCallContext( 50 | object: UnsafeMutableRawPointer(Unmanaged.passRetained(self).toOpaque()), 51 | destroy: callContextDestroy) 52 | } 53 | 54 | func getCaptureSession() -> AVCaptureSession { 55 | return videoCaptureController.captureSession 56 | } 57 | 58 | func setAudioEnabled(enabled: Bool) { 59 | audioTrack.isEnabled = enabled 60 | } 61 | 62 | func setVideoEnabled(enabled: Bool) -> Bool { 63 | if (enabled == currentVideoEnableSetting) { 64 | // Video state is not changed. 65 | return false 66 | } else { 67 | videoTrack.isEnabled = enabled 68 | currentVideoEnableSetting = enabled 69 | return true 70 | } 71 | } 72 | 73 | func setCameraEnabled(enabled: Bool) { 74 | if enabled { 75 | videoCaptureController.startCapture() 76 | } else { 77 | videoCaptureController.stopCapture() 78 | } 79 | } 80 | 81 | func setCameraSource(isUsingFrontCamera: Bool) { 82 | CallContext.cameraQueue.async { 83 | self.videoCaptureController.switchCamera(isUsingFrontCamera: isUsingFrontCamera) 84 | } 85 | } 86 | } 87 | 88 | @available(iOSApplicationExtension, unavailable) 89 | func callContextDestroy(object: UnsafeMutableRawPointer?) { 90 | guard let object = object else { 91 | failDebug("object was unexpectedly nil") 92 | return 93 | } 94 | 95 | _ = Unmanaged.fromOpaque(object).takeRetainedValue() 96 | // @note There should not be any retainers left for the object 97 | // so deinit should be called implicitly. 98 | } 99 | -------------------------------------------------------------------------------- /src/node/scripts/fetch-prebuild.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | /* eslint-disable no-console */ 7 | 8 | const https = require('https'); 9 | const { HttpsProxyAgent } = require('https-proxy-agent'); 10 | const fs = require('fs'); 11 | const path = require('path'); 12 | const crypto = require('crypto'); 13 | const tar = require('tar'); 14 | const { Transform } = require('stream'); 15 | const { pipeline } = require('stream/promises'); 16 | 17 | const VERSION = process.env.npm_package_version; 18 | 19 | let config; 20 | 21 | // When installing from the registry, `npm` doesn't set `npm_package_config_*` 22 | // environment variables. However, unlike `yarn`, `npm` always provides a path 23 | // to the `package.json` so we can read `config` from it. 24 | if (process.env.npm_package_json) { 25 | const json = fs.readFileSync(process.env.npm_package_json, { 26 | encoding: 'utf8', 27 | }); 28 | 29 | ({ config } = JSON.parse(json)); 30 | } else { 31 | config = { 32 | prebuildUrl: process.env.npm_package_config_prebuildUrl, 33 | prebuildChecksum: process.env.npm_package_config_prebuildChecksum, 34 | }; 35 | } 36 | 37 | const URL = config.prebuildUrl.replace( 38 | '${npm_package_version}', // eslint-disable-line no-template-curly-in-string 39 | VERSION 40 | ); 41 | const HASH = config.prebuildChecksum; 42 | 43 | const tmpFile = path.join(__dirname, 'unverified-prebuild.tmp'); 44 | const finalFile = path.join(__dirname, 'prebuild.tar.gz'); 45 | 46 | async function main() { 47 | if (!HASH) { 48 | console.log('(no checksum provided; assuming local build)'); 49 | process.exit(0); 50 | } 51 | 52 | await downloadIfNeeded(); 53 | console.log('extracting...'); 54 | await tar.extract({ file: finalFile, onwarn: process.emitWarning }); 55 | } 56 | 57 | async function downloadIfNeeded() { 58 | if (fs.statSync(finalFile, { throwIfNoEntry: false })) { 59 | const hash = crypto.createHash('sha256'); 60 | await pipeline(fs.createReadStream(finalFile), hash); 61 | if (hash.digest('hex') === HASH) { 62 | console.log('local build artifact is up-to-date'); 63 | return; 64 | } 65 | 66 | console.log('local build artifact is outdated'); 67 | } 68 | await download(); 69 | } 70 | 71 | function download() { 72 | console.log(`downloading ${URL}`); 73 | return new Promise((resolve, reject) => { 74 | let options = {}; 75 | if (process.env.HTTPS_PROXY != undefined) { 76 | options.agent = new HttpsProxyAgent(process.env.HTTPS_PROXY); 77 | } 78 | https.get(URL, options, async res => { 79 | try { 80 | const out = fs.createWriteStream(tmpFile); 81 | 82 | const hash = crypto.createHash('sha256'); 83 | 84 | const t = new Transform({ 85 | transform(chunk, encoding, callback) { 86 | hash.write(chunk, encoding); 87 | callback(null, chunk); 88 | }, 89 | }); 90 | 91 | await pipeline(res, t, out); 92 | 93 | const actualDigest = hash.digest('hex'); 94 | if (actualDigest !== HASH) { 95 | fs.unlinkSync(tmpFile); 96 | throw new Error( 97 | `Digest mismatch. Expected ${HASH} got ${actualDigest}` 98 | ); 99 | } 100 | 101 | fs.renameSync(tmpFile, finalFile); 102 | resolve(); 103 | } catch (error) { 104 | reject(error); 105 | } 106 | }); 107 | }); 108 | } 109 | 110 | main(); 111 | -------------------------------------------------------------------------------- /.github/workflows/ringrtc_release.yml: -------------------------------------------------------------------------------- 1 | name: RingRTC Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | dry_run: 7 | description: "When true, don't publish artifacts" 8 | default: true 9 | required: false 10 | type: boolean 11 | build_desktop: 12 | description: "When true, build desktop" 13 | default: true 14 | required: false 15 | type: boolean 16 | build_ios: 17 | description: "When true, build iOS" 18 | default: true 19 | required: false 20 | type: boolean 21 | build_android: 22 | description: "When true, build Android" 23 | default: true 24 | required: false 25 | type: boolean 26 | runner_linux: 27 | description: "Linux runner:" 28 | default: 'ubuntu-latest-4-cores' 29 | required: true 30 | type: string 31 | runner_linux_arm64: 32 | description: "ARM64 Linux runner:" 33 | default: 'ubuntu-22.04-arm64-4-cores' 34 | required: true 35 | type: string 36 | runner_windows: 37 | description: "Windows runner:" 38 | default: 'windows-2025-4-cores' 39 | required: true 40 | type: string 41 | runner_mac: 42 | description: "Mac runner:" 43 | default: 'macos-15' 44 | required: true 45 | type: string 46 | runner_ios: 47 | description: "Mac iOS runner:" 48 | default: 'macos-15' 49 | required: true 50 | type: string 51 | runner_android: 52 | description: "Linux android runner:" 53 | default: 'ubuntu-24.04-4-cores' 54 | required: true 55 | type: string 56 | 57 | jobs: 58 | slow_tests: 59 | name: Run Slow Tests 60 | uses: ./.github/workflows/slow_tests.yml 61 | permissions: 62 | # createCommitComment is supposed to only need the default 'read' permissions... 63 | # ...but maybe it's different for private repositories. 64 | contents: write 65 | issues: write 66 | # Needed for google-github-actions/auth. 67 | id-token: 'write' 68 | 69 | build_desktop: 70 | name: Build Desktop 71 | needs: [slow_tests] 72 | if: ${{ inputs.build_desktop && needs.slow_tests.result == 'success' }} 73 | uses: ./.github/workflows/desktop_artifacts.yml 74 | with: 75 | dry_run: ${{ inputs.dry_run }} 76 | runner_linux: ${{ inputs.runner_linux }} 77 | runner_linux_arm64: ${{ inputs.runner_linux_arm64 }} 78 | runner_windows: ${{ inputs.runner_windows }} 79 | runner_mac: ${{ inputs.runner_mac }} 80 | secrets: inherit 81 | permissions: 82 | contents: 'read' 83 | # Needed for google-github-actions/auth. 84 | id-token: 'write' 85 | 86 | build_ios: 87 | name: Build iOS 88 | needs: [slow_tests] 89 | if: ${{ inputs.build_ios && needs.slow_tests.result == 'success' }} 90 | uses: ./.github/workflows/ios_artifacts.yml 91 | with: 92 | dry_run: ${{ inputs.dry_run }} 93 | runner: ${{ inputs.runner_ios }} 94 | secrets: inherit 95 | permissions: 96 | contents: 'read' 97 | # Needed for google-github-actions/auth. 98 | id-token: 'write' 99 | 100 | build_android: 101 | name: Build Android 102 | needs: [slow_tests] 103 | if: ${{ inputs.build_android && needs.slow_tests.result == 'success' }} 104 | uses: ./.github/workflows/android_artifacts.yml 105 | with: 106 | dry_run: ${{ inputs.dry_run }} 107 | runner: ${{ inputs.runner_android }} 108 | secrets: inherit 109 | -------------------------------------------------------------------------------- /protobuf/protobuf/signaling.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2021 Signal Messenger, LLC 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | syntax = "proto2"; 7 | 8 | // Messages sent over the signaling channel 9 | 10 | package signaling; 11 | 12 | import "group_call.proto"; 13 | 14 | // A serialized one these goes in the "opaque" field of the CallingMessage::Offer in SignalService.proto 15 | // For future compatibility, we can add new slots (v5, v6, ...) 16 | message Offer { 17 | optional ConnectionParametersV4 v4 = 4; 18 | } 19 | 20 | // A serialized one these goes in the "opaque" field of the CallingMessage::Offer in SignalService.proto 21 | message Answer { 22 | optional ConnectionParametersV4 v4 = 4; 23 | } 24 | 25 | // A serialized one these goes in the "opaque" field of the CallingMessage::Ice in SignalService.proto 26 | // Unlike other message types, the ICE message contains many of these, not just one. 27 | // We should perhaps rename this to "IceUpdate" since it can either be a candidate 28 | // or a removal of a candidate. But it would require a lot of FFI code to be renamed 29 | // which doesn't seem worth it at the moment. 30 | message IceCandidate { 31 | // Use a field value of 2 for compatibility since both V2 and V3 have the same format. 32 | optional IceCandidateV3 added_V3 = 2; 33 | // ICE candidate removal identifies the removed candidate 34 | // by (transport_name, component, ip, port, udp/tcp). 35 | // But we assume transport_name = "audio", component = 1, and udp 36 | // So we just need (ip, port) 37 | optional SocketAddr removed = 3; 38 | } 39 | 40 | message IceCandidateV3 { 41 | optional string sdp = 1; 42 | } 43 | 44 | message SocketAddr { 45 | optional bytes ip = 1; // IPv4: 4 bytes; IPv6: 16 bytes 46 | optional uint32 port = 2; 47 | } 48 | 49 | enum VideoCodecType { 50 | VP8 = 8; 51 | VP9 = 9; 52 | // Keep these H264 definitions for better logging. 53 | H264_CONSTRAINED_BASELINE = 40; 54 | H264_CONSTRAINED_HIGH = 46; 55 | } 56 | 57 | message VideoCodec { 58 | optional VideoCodecType type = 1; 59 | reserved 2; // Was used for H264 level 60 | } 61 | 62 | message ConnectionParametersV4 { 63 | optional bytes public_key = 1; 64 | optional string ice_ufrag = 2; 65 | optional string ice_pwd = 3; 66 | // In other words, the video codecs the sender can receive. 67 | repeated VideoCodec receive_video_codecs = 4; 68 | // Used at call establishment to convey the bitrate that the signaling sender (media receiver) 69 | // wants the signaling receiver (media sender) to send. 70 | optional uint64 max_bitrate_bps = 5; 71 | } 72 | 73 | // A generic calling message that is opaque to the application but interpreted by RingRTC. 74 | // A serialized one of these goes into the "Opaque" field in the CallingMessage variant 75 | // in Signal protocol messages. 76 | message CallMessage { 77 | message RingIntention { 78 | enum Type { 79 | RING = 0; 80 | CANCELLED = 1; 81 | } 82 | 83 | optional bytes group_id = 1; 84 | optional Type type = 2; 85 | // This is signed so it fits in a SQLite integer column. 86 | optional sfixed64 ring_id = 3; 87 | } 88 | 89 | message RingResponse { 90 | enum Type { 91 | RINGING = 0; 92 | ACCEPTED = 1; 93 | DECLINED = 2; 94 | BUSY = 3; 95 | } 96 | 97 | optional bytes group_id = 1; 98 | optional Type type = 2; 99 | // This is signed so it fits in a SQLite integer column. 100 | optional sfixed64 ring_id = 3; 101 | } 102 | 103 | optional group_call.DeviceToDevice group_call_message = 1; 104 | optional RingIntention ring_intention = 2; 105 | optional RingResponse ring_response = 3; 106 | } 107 | -------------------------------------------------------------------------------- /src/android/api/org/signal/ringrtc/Log.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2021 Signal Messenger, LLC 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | package org.signal.ringrtc; 7 | 8 | public class Log { 9 | 10 | private static final String TAG = "ringrtc"; 11 | 12 | // Log levels corresponding to rust's Log::Level values 13 | private static final int LL_ERROR = 1; 14 | private static final int LL_WARN = 2; 15 | private static final int LL_INFO = 3; 16 | private static final int LL_DEBUG = 4; 17 | private static final int LL_TRACE = 5; 18 | 19 | private static Log.Logger logger; 20 | 21 | public static void initialize(Log.Logger logger) { 22 | Log.logger = logger; 23 | } 24 | 25 | public static void log(int level, String message) { 26 | 27 | switch(level) { 28 | case LL_ERROR: 29 | e(TAG, message); break; 30 | case LL_WARN: 31 | w(TAG, message); break; 32 | case LL_INFO: 33 | i(TAG, message); break; 34 | case LL_DEBUG: 35 | d(TAG, message); break; 36 | case LL_TRACE: 37 | v(TAG, message); break; 38 | default: 39 | w(TAG, "Unknown log level: " + message); 40 | } 41 | 42 | } 43 | 44 | public static void v(String tag, String message) { 45 | v(tag, message, null); 46 | } 47 | 48 | public static void d(String tag, String message) { 49 | d(tag, message, null); 50 | } 51 | 52 | public static void i(String tag, String message) { 53 | i(tag, message, null); 54 | } 55 | 56 | public static void w(String tag, String message) { 57 | w(tag, message, null); 58 | } 59 | 60 | public static void e(String tag, String message) { 61 | e(tag, message, null); 62 | } 63 | 64 | public static void v(String tag, Throwable t) { 65 | v(tag, null, t); 66 | } 67 | 68 | public static void d(String tag, Throwable t) { 69 | d(tag, null, t); 70 | } 71 | 72 | public static void i(String tag, Throwable t) { 73 | i(tag, null, t); 74 | } 75 | 76 | public static void w(String tag, Throwable t) { 77 | w(tag, null, t); 78 | } 79 | 80 | public static void e(String tag, Throwable t) { 81 | e(tag, null, t); 82 | } 83 | 84 | public static void v(String tag, String message, Throwable t) { 85 | if (logger != null) { 86 | logger.v(tag, message, t); 87 | } else { 88 | android.util.Log.v(tag, message, t); 89 | } 90 | } 91 | 92 | public static void d(String tag, String message, Throwable t) { 93 | if (logger != null) { 94 | logger.d(tag, message, t); 95 | } else { 96 | android.util.Log.d(tag, message, t); 97 | } 98 | } 99 | 100 | public static void i(String tag, String message, Throwable t) { 101 | if (logger != null) { 102 | logger.i(tag, message, t); 103 | } else { 104 | android.util.Log.i(tag, message, t); 105 | } 106 | } 107 | 108 | public static void w(String tag, String message, Throwable t) { 109 | if (logger != null) { 110 | logger.w(tag, message, t); 111 | } else { 112 | android.util.Log.w(tag, message, t); 113 | } 114 | } 115 | 116 | public static void e(String tag, String message, Throwable t) { 117 | if (logger != null) { 118 | logger.e(tag, message, t); 119 | } else { 120 | android.util.Log.e(tag, message, t); 121 | } 122 | } 123 | 124 | public interface Logger { 125 | void v(String tag, String message, Throwable t); 126 | 127 | void d(String tag, String message, Throwable t); 128 | 129 | void i(String tag, String message, Throwable t); 130 | 131 | void w(String tag, String message, Throwable t); 132 | 133 | void e(String tag, String message, Throwable t); 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /src/ios/SignalRingRTC/SignalRingRTC/CallManagerGlobal.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | import SignalRingRTC.RingRTC 7 | import WebRTC 8 | 9 | // Global singleton to guarantee certain things are only invoked 10 | // once... 11 | @available(iOSApplicationExtension, unavailable) 12 | public class CallManagerGlobal { 13 | 14 | // CallManagerGlobal is a singleton. 15 | static let shared = CallManagerGlobal() 16 | 17 | let webRtcLogger: RTCCallbackLogger 18 | private let lock = NSLock() 19 | private var hasInitializedFieldTrials = false 20 | 21 | // MARK: Object Lifetime 22 | 23 | private init() { 24 | // This initialization will be done only once per application lifetime. 25 | 26 | let maxLogLevel: RingRTCLogLevel 27 | #if DEBUG 28 | if let overrideLogLevelString = ProcessInfo().environment["RINGRTC_MAX_LOG_LEVEL"], 29 | let overrideLogLevelRaw = UInt8(overrideLogLevelString), 30 | let overrideLogLevel = RingRTCLogLevel(rawValue: overrideLogLevelRaw) { 31 | maxLogLevel = overrideLogLevel 32 | } else { 33 | maxLogLevel = .trace 34 | } 35 | #else 36 | maxLogLevel = .trace 37 | #endif 38 | 39 | // Don't write WebRTC logs to stdout. 40 | RTCSetMinDebugLogLevel(.none) 41 | 42 | // Show WebRTC logs via application Logger. 43 | webRtcLogger = RTCCallbackLogger() 44 | 45 | let webRtcLogLevel: RingRTCLogLevel 46 | #if DEBUG 47 | webRtcLogLevel = min(maxLogLevel, .info) 48 | #else 49 | webRtcLogLevel = min(maxLogLevel, .warn) 50 | #endif 51 | 52 | webRtcLogger.severity = webRtcLogLevel.toWebRTC 53 | 54 | webRtcLogger.start { (message, severity) in 55 | let message = message.replacingOccurrences(of: "::", with: ":") 56 | switch severity { 57 | case .verbose: 58 | Logger.verbose(message, file: "", function: "", line: 0) 59 | case .info: 60 | Logger.info(message, file: "", function: "", line: 0) 61 | case .warning: 62 | Logger.warn(message, file: "", function: "", line: 0) 63 | case .error: 64 | Logger.error(message, file: "", function: "", line: 0) 65 | case .none: 66 | // should not happen 67 | break 68 | @unknown default: 69 | break 70 | } 71 | } 72 | 73 | Logger.debug("object! CallManagerGlobal created... \(ObjectIdentifier(self))") 74 | } 75 | 76 | static func initialize(fieldTrials: [String: String]) { 77 | // Implicitly initialize the shared instance, then use it to track whether we've set up the field trials. 78 | Self.shared.initFieldTrials(fieldTrials) 79 | } 80 | 81 | private func initFieldTrials(_ fieldTrials: [String: String]) { 82 | lock.lock() 83 | defer { lock.unlock() } 84 | 85 | guard !hasInitializedFieldTrials else { 86 | return 87 | } 88 | hasInitializedFieldTrials = true 89 | 90 | let fieldTrialsWithDefaults = fieldTrials.merging([ 91 | "RingRTC-AnyAddressPortsKillSwitch": "Enabled", 92 | "RingRTC-PruneTurnPorts": "Enabled", 93 | "WebRTC-Bwe-ProbingConfiguration": "skip_if_est_larger_than_fraction_of_max:0.99", 94 | "WebRTC-IncreaseIceCandidatePriorityHostSrflx": "Enabled", 95 | ]) { (provided, _) in provided } 96 | RTCInitFieldTrialDictionary(fieldTrialsWithDefaults) 97 | Logger.info("Initialized field trials with \(fieldTrialsWithDefaults)") 98 | } 99 | 100 | deinit { 101 | webRtcLogger.stop() 102 | 103 | Logger.debug("object! CallManagerGlobal destroyed. \(ObjectIdentifier(self))") 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /bin/build-gctc: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright 2022 Signal Messenger, LLC 5 | # SPDX-License-Identifier: AGPL-3.0-only 6 | # 7 | 8 | set -e 9 | 10 | # shellcheck source=bin/env.sh 11 | . "$(dirname "$0")"/env.sh 12 | 13 | usage() 14 | { 15 | echo 'usage: build-gctc [-d|-r|-c] 16 | where: 17 | -d to create a debug build (default) 18 | -r to create a release build 19 | -c to clean the build artifacts' 20 | } 21 | 22 | clean() 23 | { 24 | # Remove all possible artifact directories. 25 | rm -rf ./src/node/build 26 | rm -rf ./src/node/dist 27 | rm -rf ./src/node/node_modules 28 | cargo clean 29 | } 30 | 31 | BUILD_TYPE=debug 32 | 33 | while [ "$1" != "" ]; do 34 | case $1 in 35 | -d | --debug ) 36 | BUILD_TYPE=debug 37 | ;; 38 | -r | --release ) 39 | BUILD_TYPE=release 40 | ;; 41 | -c | --clean ) 42 | clean 43 | exit 44 | ;; 45 | -h | --help ) 46 | usage 47 | exit 48 | ;; 49 | * ) 50 | usage 51 | exit 1 52 | esac 53 | shift 54 | done 55 | 56 | get_default_platform() 57 | { 58 | hash rustup 2>/dev/null || { echo >&2 "Make sure you have rustup installed and properly configured! Aborting."; exit 1; } 59 | 60 | case "$(rustup show active-toolchain)" in 61 | *"x86_64-apple-darwin"* | *"aarch64-apple-darwin"* ) 62 | echo "darwin" 63 | ;; 64 | *"x86_64-pc-windows"* ) 65 | echo "win32" 66 | ;; 67 | *"x86_64-unknown-linux"* ) 68 | echo "linux" 69 | ;; 70 | * ) 71 | echo "unknown" 72 | esac 73 | } 74 | 75 | DEFAULT_PLATFORM=$(get_default_platform) 76 | if [ "${DEFAULT_PLATFORM}" = "unknown" ] 77 | then 78 | printf "Unknown platform detected!\nPlease make sure you have installed a valid Rust toolchain via rustup! Aborting.\n" 79 | exit 1 80 | fi 81 | 82 | export MACOSX_DEPLOYMENT_TARGET="10.15" 83 | 84 | # Ensure that experimental compact relocation is disabled until upstream projects properly set it. 85 | # https://issues.webrtc.org/issues/407797634 86 | # https://chromium-review.googlesource.com/c/chromium/src/+/5938657 87 | if [ "$(uname)" = "Linux" ] 88 | then 89 | # Comment out the line that enables experimental crel. 90 | sed -i '/^[^#].*--allow-experimental-crel/ s/^/#/' src/webrtc/src/build/config/compiler/BUILD.gn 91 | fi 92 | 93 | # Build WebRTC. 94 | ( 95 | ( 96 | cd src/webrtc/src 97 | WEBRTC_ARGS="rtc_build_examples=false rtc_build_tools=false rtc_include_tests=false rtc_enable_protobuf=false rtc_use_x11=false rtc_enable_sctp=false rtc_libvpx_build_vp9=false rtc_disable_metrics=true rtc_disable_trace_events=true" 98 | 99 | if [ "${BUILD_TYPE}" = "release" ] 100 | then 101 | WEBRTC_ARGS="${WEBRTC_ARGS} is_debug=false" 102 | fi 103 | gn gen -C out/"${BUILD_TYPE}" "--args=${WEBRTC_ARGS}" 104 | third_party/siso/cipd/siso ninja -C out/"${BUILD_TYPE}" 105 | ) 106 | WEBRTC_OUT_PATH="src/webrtc/src/out" 107 | 108 | mkdir -p "${OUTPUT_DIR}/${BUILD_TYPE}/obj" 109 | STATIC_LIB_PATH="${BUILD_TYPE}"/obj/webrtc.lib 110 | if [ ! -e "${WEBRTC_OUT_PATH}/${STATIC_LIB_PATH}" ]; then 111 | STATIC_LIB_PATH="${BUILD_TYPE}"/obj/libwebrtc.a 112 | fi 113 | cp -f "${WEBRTC_OUT_PATH}/${STATIC_LIB_PATH}" "${OUTPUT_DIR}/${BUILD_TYPE}/obj" 114 | ) 115 | 116 | # Build and link the final RingRTC library. 117 | ( 118 | if [ "${BUILD_TYPE}" = "debug" ] 119 | then 120 | OUTPUT_DIR="${OUTPUT_DIR}" cargo build --package ringrtc --bin group_call --features=native,sim_http 121 | echo "Can run with target/debug/group_call" 122 | else 123 | OUTPUT_DIR="${OUTPUT_DIR}" cargo build --package ringrtc --bin group_call --features=native,sim_http --release 124 | echo "Can run with target/release/group_call" 125 | fi 126 | ) 127 | -------------------------------------------------------------------------------- /src/rust/src/common/units.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 Signal Messenger, LLC 3 | // SPDX-License-Identifier: AGPL-3.0-only 4 | // 5 | 6 | use std::{ 7 | cmp::{Ord, PartialEq, PartialOrd}, 8 | ops::{Add, Div, Mul}, 9 | time::Duration, 10 | }; 11 | 12 | #[derive(Debug, Copy, Clone, Eq, Ord, PartialEq, PartialOrd, Default)] 13 | pub struct DataRate { 14 | size_per_second: DataSize, 15 | } 16 | 17 | #[allow(dead_code)] 18 | impl DataRate { 19 | pub const fn per_second(size_per_second: DataSize) -> Self { 20 | Self { size_per_second } 21 | } 22 | 23 | pub const fn from_bps(bps: u64) -> Self { 24 | Self::per_second(DataSize::from_bits(bps)) 25 | } 26 | 27 | pub fn as_bps(self) -> u64 { 28 | self.size_per_second.as_bits() 29 | } 30 | 31 | pub const fn from_kbps(kbps: u64) -> Self { 32 | Self::per_second(DataSize::from_kilobits(kbps)) 33 | } 34 | 35 | pub fn as_kbps(self) -> u64 { 36 | self.size_per_second.as_kilobits() 37 | } 38 | 39 | pub const fn from_mbps(mbps: u64) -> Self { 40 | Self::per_second(DataSize::from_megabits(mbps)) 41 | } 42 | 43 | pub fn as_mbps(self) -> u64 { 44 | self.size_per_second.as_megabits() 45 | } 46 | 47 | // Only apply min if the other value is Some 48 | #[must_use] 49 | pub fn min_opt(self, other: Option) -> Self { 50 | if let Some(other) = other { 51 | self.min(other) 52 | } else { 53 | self 54 | } 55 | } 56 | } 57 | 58 | impl Mul for DataRate { 59 | type Output = DataSize; 60 | 61 | fn mul(self, duration: Duration) -> DataSize { 62 | DataSize::from_bits(((self.as_bps() as f64) * duration.as_secs_f64()) as u64) 63 | } 64 | } 65 | 66 | #[derive(Debug, Copy, Clone, Eq, Ord, PartialEq, PartialOrd, Default)] 67 | pub struct DataSize { 68 | bits: u64, 69 | } 70 | 71 | #[allow(dead_code)] 72 | impl DataSize { 73 | pub const fn per_second(self) -> DataRate { 74 | DataRate::per_second(self) 75 | } 76 | 77 | pub const fn from_bits(bits: u64) -> Self { 78 | Self { bits } 79 | } 80 | 81 | pub fn as_bits(self) -> u64 { 82 | self.bits 83 | } 84 | 85 | pub const fn from_bytes(bytes: u64) -> Self { 86 | Self::from_bits(bytes * 8) 87 | } 88 | 89 | pub fn as_bytes(self) -> u64 { 90 | self.as_bits() / 8 91 | } 92 | 93 | pub const fn from_kilobits(kbits: u64) -> Self { 94 | Self::from_bits(kbits * 1000) 95 | } 96 | 97 | pub fn as_kilobits(self) -> u64 { 98 | self.as_bits() / 1000 99 | } 100 | 101 | pub const fn from_kilobytes(kbytes: u64) -> Self { 102 | Self::from_bytes(kbytes * 1000) 103 | } 104 | 105 | pub fn as_kilobytes(self) -> u64 { 106 | self.as_bytes() / 1000 107 | } 108 | 109 | pub const fn from_megabits(mbits: u64) -> Self { 110 | Self::from_kilobits(mbits * 1000) 111 | } 112 | 113 | pub fn as_megabits(self) -> u64 { 114 | self.as_kilobits() / 1000 115 | } 116 | 117 | pub const fn from_megabytes(mbytes: u64) -> Self { 118 | Self::from_kilobytes(mbytes * 1000) 119 | } 120 | 121 | pub fn as_megabytes(self) -> u64 { 122 | self.as_kilobytes() / 1000 123 | } 124 | } 125 | 126 | impl Div for DataSize { 127 | type Output = DataRate; 128 | 129 | fn div(self, duration: Duration) -> DataRate { 130 | DataRate::from_bps((self.as_bits() as f64 / duration.as_secs_f64()) as u64) 131 | } 132 | } 133 | 134 | impl Div for DataSize { 135 | type Output = Duration; 136 | 137 | fn div(self, rate: DataRate) -> Duration { 138 | Duration::from_secs_f64((self.as_bits() as f64) / (rate.as_bps() as f64)) 139 | } 140 | } 141 | 142 | impl Add for DataSize { 143 | type Output = DataSize; 144 | 145 | fn add(self, other: DataSize) -> DataSize { 146 | DataSize::from_bits(self.bits + other.bits) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/android/api/org/signal/ringrtc/Connection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2021 Signal Messenger, LLC 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | package org.signal.ringrtc; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.annotation.Nullable; 10 | 11 | import org.webrtc.AudioSource; 12 | import org.webrtc.AudioTrack; 13 | import org.webrtc.NativePeerConnectionFactory; 14 | import org.webrtc.PeerConnection; 15 | 16 | /** 17 | * 18 | * Represents the connection to a remote peer 19 | * 20 | *

This class inherits from org.webrtc.PeerConnection and 21 | * encapsulates the lifecycle of establishing, creating, and tearing 22 | * down a connection. 23 | * 24 | */ 25 | public class Connection extends PeerConnection { 26 | 27 | @NonNull 28 | private static final String TAG = Connection.class.getSimpleName(); 29 | @NonNull 30 | private final CallId callId; 31 | private long nativePeerConnection; 32 | private int remoteDevice; 33 | @Nullable 34 | private AudioSource audioSource; 35 | @Nullable 36 | private AudioTrack audioTrack; 37 | 38 | Connection(NativeFactory factory) { 39 | super(factory); 40 | this.nativePeerConnection = factory.nativePeerConnection; 41 | this.callId = factory.callId; 42 | this.remoteDevice = factory.remoteDevice; 43 | Log.i(TAG, "ctor(): connectionId: " + callId.format(remoteDevice)); 44 | } 45 | 46 | @Override 47 | public String toString() { 48 | return callId.format(remoteDevice); 49 | } 50 | 51 | private void checkConnectionExists() { 52 | if (nativePeerConnection == 0) { 53 | throw new IllegalStateException("Connection has been closed."); 54 | } 55 | } 56 | 57 | void setAudioSource(@NonNull AudioSource audioSource, 58 | @NonNull AudioTrack audioTrack) { 59 | checkConnectionExists(); 60 | 61 | this.audioSource = audioSource; 62 | this.audioTrack = audioTrack; 63 | 64 | } 65 | 66 | void setAudioEnabled(boolean enabled) { 67 | Log.i(TAG, "audioTrack.setEnabled(" + enabled + ")"); 68 | audioTrack.setEnabled(enabled); 69 | } 70 | 71 | /** 72 | * 73 | * Close the Connection object and clean up. 74 | * 75 | */ 76 | void shutdown() { 77 | checkConnectionExists(); 78 | 79 | audioSource.dispose(); 80 | 81 | try { 82 | Log.i(TAG, "Connection.shutdown(): calling super.close()"); 83 | close(); 84 | Log.i(TAG, "Connection.shutdown(): calling super.dispose()"); 85 | dispose(); 86 | Log.i(TAG, "Connection.shutdown(): after calling super.dispose()"); 87 | } catch (Exception e) { 88 | Log.e(TAG, "Problem closing PeerConnection: ", e); 89 | } 90 | 91 | nativePeerConnection = 0; 92 | 93 | } 94 | 95 | /** 96 | * 97 | * Represents the native call connection factory 98 | * 99 | * This class is an implementation detail, used to encapsulate the 100 | * native call connection pointer created by the call connection 101 | * factory. 102 | * 103 | * One way of constructing a PeerConnection (Connection's super 104 | * class) is by passing an object that implements 105 | * NativePeerConnectionFactory, which is done here. 106 | * 107 | */ 108 | static class NativeFactory implements NativePeerConnectionFactory { 109 | private long nativePeerConnection; 110 | private CallId callId; 111 | private int remoteDevice; 112 | 113 | protected NativeFactory( long nativePeerConnection, 114 | @NonNull CallId callId, 115 | int remoteDevice) { 116 | this.nativePeerConnection = nativePeerConnection; 117 | this.callId = callId; 118 | this.remoteDevice = remoteDevice; 119 | } 120 | 121 | @Override 122 | public long createNativePeerConnection() { 123 | return nativePeerConnection; 124 | } 125 | 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/android/api/org/signal/ringrtc/CallLinkEpoch.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2025 Signal Messenger, LLC 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | package org.signal.ringrtc; 7 | 8 | import androidx.annotation.NonNull; 9 | 10 | /** 11 | * Represents a call link epoch. When a call link is created, the calling server will issue 12 | * a randomly-generated "epoch". This gets included in the URL and in requests to use the link. 13 | * If it is mismatched, the request will fail. 14 | * 15 | * Internally, the epoch is stored as a 32-bit integer. 16 | */ 17 | public final class CallLinkEpoch { 18 | private final String TAG = CallLinkEpoch.class.getSimpleName(); 19 | 20 | private final int epoch; 21 | 22 | /** 23 | * 24 | * Create a new CallLinkEpoch from a raw integer value. 25 | * 26 | * @param epoch 32-bit epoch identifier. 27 | * 28 | */ 29 | @CalledByNative 30 | private CallLinkEpoch(int epoch) { 31 | this.epoch = epoch; 32 | } 33 | 34 | /** 35 | * 36 | * Create a new CallLinkEpoch from a consonant base-16 encoded string. 37 | * 38 | * @param epoch Encoded string. 39 | * 40 | */ 41 | public CallLinkEpoch(@NonNull String epoch) throws CallException { 42 | this.epoch = nativeParse(epoch); 43 | } 44 | 45 | public static final int SERIALIZED_SIZE = 4; 46 | 47 | /** 48 | * 49 | * Serializes this {@code CallLinkEpoch} into a byte array. 50 | * 51 | * @return Byte array. 52 | * 53 | */ 54 | @NonNull 55 | public byte[] getBytes() { 56 | return new byte[]{ 57 | (byte) ((epoch & 0x000000ff)), 58 | (byte) ((epoch & 0x0000ff00) >> 8), 59 | (byte) ((epoch & 0x00ff0000) >> 16), 60 | (byte) ((epoch & 0xff000000) >> 24) 61 | }; 62 | } 63 | 64 | /** 65 | * 66 | * Deserializes a {@link CallLinkEpoch} from the given byte array. 67 | * 68 | * @param bytes Byte array 69 | * @throws IllegalArgumentException if {@code bytes} does not contain enough data. 70 | * 71 | * @return Instance of {@link CallLinkEpoch}. 72 | * 73 | */ 74 | @NonNull 75 | public static CallLinkEpoch fromBytes(@NonNull byte[] bytes) { 76 | return fromBytes(bytes, 0); 77 | } 78 | 79 | /** 80 | * 81 | * Deserializes a {@link CallLinkEpoch} from the given byte array. 82 | * 83 | * @param bytes Byte array 84 | * @param from Starting offset 85 | * @throws IllegalArgumentException if {@code bytes} does not contain enough data. 86 | * 87 | * @return Instance of {@link CallLinkEpoch}. 88 | * 89 | */ 90 | @NonNull 91 | public static CallLinkEpoch fromBytes(@NonNull byte[] bytes, int from) { 92 | if (bytes.length - from < SERIALIZED_SIZE) { 93 | throw new IllegalArgumentException("length"); 94 | } 95 | int epoch = (bytes[from] & 0xff) | 96 | (bytes[from + 1] & 0xff) << 8 | 97 | (bytes[from + 2] & 0xff) << 16 | 98 | (bytes[from + 3] & 0xff) << 24; 99 | return new CallLinkEpoch(epoch); 100 | } 101 | 102 | /** 103 | * 104 | * Creates a consonant base-16 encoded string representation of the epoch. 105 | * 106 | * @return String value. 107 | * 108 | */ 109 | @NonNull 110 | @Override 111 | public String toString() { 112 | try { 113 | return nativeToFormattedString(epoch); 114 | } catch (CallException e) { 115 | throw new AssertionError(e); 116 | } 117 | } 118 | 119 | @Override 120 | public boolean equals(Object obj) { 121 | if (obj == this) { 122 | return true; 123 | } 124 | if (obj != null) { 125 | if (this.getClass() == obj.getClass()) { 126 | CallLinkEpoch that = (CallLinkEpoch)obj; 127 | return this.epoch == that.epoch; 128 | } 129 | } 130 | return false; 131 | } 132 | 133 | @Override 134 | public int hashCode() { 135 | return epoch; 136 | } 137 | 138 | private static native int nativeParse(String s) throws CallException; 139 | private static native String nativeToFormattedString(int v) throws CallException; 140 | } 141 | --------------------------------------------------------------------------------