├── debian ├── docs ├── compat ├── source │ └── format ├── po │ ├── POTFILES.in │ └── templates.pot ├── changelog ├── README.Debian ├── install ├── templates ├── copyright ├── preinst ├── control ├── rules ├── postrm ├── manpage.1 └── init.d ├── .gitattributes ├── doc ├── diagram.key ├── diagram.png ├── reservation.md ├── shibboleth.md ├── health-checks.md └── conference-request.md ├── resources ├── config │ └── jicofo-logrotate.d ├── mvn-exec.sh ├── jicofo.bat ├── jicofo.sh ├── collect-dump-logs.sh └── build_deb_package.sh ├── .gitignore ├── .editorconfig ├── SECURITY.md ├── .github ├── ISSUE_TEMPLATE │ └── bug-report.md └── workflows │ └── maven.yml ├── jicofo-common ├── src │ ├── main │ │ ├── kotlin │ │ │ └── org │ │ │ │ └── jitsi │ │ │ │ └── jicofo │ │ │ │ ├── xmpp │ │ │ │ ├── XmppConnectionEnum.kt │ │ │ │ ├── muc │ │ │ │ │ ├── ChatRoomListener.kt │ │ │ │ │ ├── MemberRole.kt │ │ │ │ │ ├── RoomMetadata.kt │ │ │ │ │ ├── ChatRoomMember.kt │ │ │ │ │ └── SourceInfo.kt │ │ │ │ ├── JingleUtils.kt │ │ │ │ ├── jingle │ │ │ │ │ ├── JingleStats.kt │ │ │ │ │ └── JingleIqRequestHandler.kt │ │ │ │ ├── XmppCapsStats.kt │ │ │ │ ├── Features.kt │ │ │ │ └── Util.kt │ │ │ │ ├── conference │ │ │ │ └── source │ │ │ │ │ ├── VideoType.kt │ │ │ │ │ └── SsrcGroupSemantics.kt │ │ │ │ ├── codec │ │ │ │ └── OfferOptions.kt │ │ │ │ ├── util │ │ │ │ ├── SynchronizedDelegate.kt │ │ │ │ ├── WeakValueMap.kt │ │ │ │ └── PendingCount.kt │ │ │ │ ├── OctoConfig.kt │ │ │ │ ├── metrics │ │ │ │ ├── JicofoMetricsContainer.kt │ │ │ │ └── GlobalMetrics.kt │ │ │ │ ├── JicofoConfig.kt │ │ │ │ └── TaskPools.kt │ │ └── java │ │ │ └── org │ │ │ └── jitsi │ │ │ └── impl │ │ │ └── protocol │ │ │ └── xmpp │ │ │ └── log │ │ │ ├── ExcludeXmppPackets.java │ │ │ ├── IncludeXmppPackets.java │ │ │ ├── XmppPacketsFileHandler.java │ │ │ └── PacketDebugger.java │ └── test │ │ └── kotlin │ │ └── org │ │ └── jitsi │ │ └── jicofo │ │ └── xmpp │ │ ├── FeaturesTest.kt │ │ └── muc │ │ └── RoomMetadataTest.kt └── spotbugs-exclude.xml ├── jicofo ├── src │ ├── main │ │ ├── kotlin │ │ │ └── org │ │ │ │ └── jitsi │ │ │ │ └── jicofo │ │ │ │ ├── util │ │ │ │ ├── Cancelable.kt │ │ │ │ ├── OfferOptionsUtil.kt │ │ │ │ ├── RateLimit.kt │ │ │ │ └── RateLimitedStat.kt │ │ │ │ ├── ReinviteMethod.kt │ │ │ │ ├── Offer.kt │ │ │ │ ├── conference │ │ │ │ ├── SourcesToAddOrRemove.kt │ │ │ │ └── JitsiMeetConfig.kt │ │ │ │ ├── ConferenceStore.kt │ │ │ │ ├── health │ │ │ │ └── HealthConfig.kt │ │ │ │ ├── visitors │ │ │ │ └── VisitorsConfig.kt │ │ │ │ ├── jigasi │ │ │ │ └── JigasiConfig.kt │ │ │ │ ├── ktor │ │ │ │ ├── RestConfig.kt │ │ │ │ └── exception │ │ │ │ │ └── Exceptions.kt │ │ │ │ ├── jibri │ │ │ │ ├── JibriStats.kt │ │ │ │ ├── JibriConfig.kt │ │ │ │ └── JibriDetectorMetrics.kt │ │ │ │ ├── xmpp │ │ │ │ ├── ConfigurationChangeHandler.kt │ │ │ │ └── JibriIqHandler.kt │ │ │ │ └── ConferenceRequest.kt │ │ └── java │ │ │ └── org │ │ │ └── jitsi │ │ │ └── jicofo │ │ │ ├── conference │ │ │ └── MuteResult.java │ │ │ ├── util │ │ │ └── ErrorResponse.java │ │ │ └── auth │ │ │ ├── AuthenticationListener.java │ │ │ ├── ExternalJWTAuthority.java │ │ │ └── ErrorFactory.java │ ├── test │ │ └── kotlin │ │ │ └── org │ │ │ └── jitsi │ │ │ └── jicofo │ │ │ ├── KotestProjectConfig.kt │ │ │ ├── util │ │ │ ├── ListConferenceStore.kt │ │ │ └── RateLimitTest.kt │ │ │ ├── mock │ │ │ ├── MockXmppProvider.kt │ │ │ ├── MockXmppConnection.kt │ │ │ └── MockChatRoom.kt │ │ │ ├── OctoConfigTest.kt │ │ │ ├── ConferenceIqHandlerTest.kt │ │ │ ├── JicofoConfigTest.kt │ │ │ ├── ConferenceConfigTest.kt │ │ │ ├── codec │ │ │ └── JingleOfferFactoryTest.kt │ │ │ ├── auth │ │ │ └── AuthConfigTest.kt │ │ │ ├── conference │ │ │ └── source │ │ │ │ ├── SsrcGroupTest.kt │ │ │ │ └── SourceTest.kt │ │ │ └── xmpp │ │ │ └── muc │ │ │ └── MemberRoleTest.kt │ └── assembly │ │ └── archive.xml └── spotbugs-exclude.xml ├── jicofo-selector ├── src │ ├── main │ │ └── kotlin │ │ │ └── org │ │ │ └── jitsi │ │ │ └── jicofo │ │ │ └── bridge │ │ │ ├── Util.kt │ │ │ ├── HealthCheckListener.kt │ │ │ ├── colibri │ │ │ ├── Exceptions.kt │ │ │ ├── ColibriAllocation.kt │ │ │ └── ParticipantInfo.kt │ │ │ ├── BridgeMetrics.kt │ │ │ ├── TopologySelectionStrategy.kt │ │ │ ├── SingleMeshTopologyStrategy.kt │ │ │ ├── BridgeMucDetector.kt │ │ │ ├── IntraRegionBridgeSelectionStrategy.kt │ │ │ ├── SingleBridgeSelectionStrategy.kt │ │ │ ├── VisitorSelectionStrategy.kt │ │ │ └── SplitBridgeSelectionStrategy.kt │ └── test │ │ └── kotlin │ │ └── org │ │ └── jitsi │ │ └── jicofo │ │ ├── util │ │ └── ConfigHelpers.kt │ │ ├── JicofoSelectorKotestProjectConfig.kt │ │ └── bridge │ │ └── UtilTest.kt └── spotbugs-exclude.xml ├── lib └── logging.properties └── checkstyle.xml /debian/docs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 12 2 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.java text eol=lf diff=java 2 | -------------------------------------------------------------------------------- /debian/po/POTFILES.in: -------------------------------------------------------------------------------- 1 | [type: gettext/rfc822deb] templates -------------------------------------------------------------------------------- /doc/diagram.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/jicofo/master/doc/diagram.key -------------------------------------------------------------------------------- /doc/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/jicofo/master/doc/diagram.png -------------------------------------------------------------------------------- /doc/reservation.md: -------------------------------------------------------------------------------- 1 | This document has been moved [here](https://jitsi.github.io/handbook/docs/devops-guide/reservation). 2 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | jicofo (1.0-0-g9fc93ed-1) unstable; urgency=low 2 | 3 | * Initial release. 4 | 5 | -- Damian Minkov Fri, 10 Oct 2017 16:18:00 +0000 6 | 7 | -------------------------------------------------------------------------------- /doc/shibboleth.md: -------------------------------------------------------------------------------- 1 | # Shibboleth authentication 2 | 3 | Shibboleth authentication is no longer supported. 4 | 5 | If you'd still like to use it you can use this project: https://github.com/Renater/Jitsi-SAML2JWT 6 | -------------------------------------------------------------------------------- /resources/config/jicofo-logrotate.d: -------------------------------------------------------------------------------- 1 | /var/log/jitsi/jicofo.log { 2 | daily 3 | missingok 4 | rotate 7 5 | compress 6 | delaycompress 7 | notifempty 8 | copytruncate 9 | su jicofo jitsi 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | /classes/ 3 | /dist/ 4 | *.jar 5 | /junit-reports/ 6 | *.swp 7 | /target/ 8 | jicofo-common/target/ 9 | jicofo-selector/target/ 10 | jicofo/target/ 11 | .classpath 12 | .project 13 | .settings/ 14 | **/.idea/ 15 | *.iml 16 | -------------------------------------------------------------------------------- /debian/README.Debian: -------------------------------------------------------------------------------- 1 | jicofo for Debian 2 | --------------------- 3 | 4 | If you want a WebRTC videoconferencing solution you have to install jitsi-meet 5 | and jitsi-videobridge packages which uses Jitsi Videobridge as a SFU (Selective Forwarding Unit) 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{kt,kts}] 2 | max_line_length=120 3 | ktlint_code_style = intellij_idea 4 | 5 | # I find trailing commas annoying 6 | ktlint_standard_trailing-comma-on-call-site = disabled 7 | ktlint_standard_trailing-comma-on-declaration-site = disabled 8 | -------------------------------------------------------------------------------- /debian/install: -------------------------------------------------------------------------------- 1 | jicofo/target/dependency/* usr/share/jicofo/lib 2 | lib/logging.properties etc/jitsi/jicofo 3 | jicofo.jar usr/share/jicofo 4 | resources/jicofo.sh usr/share/jicofo 5 | resources/collect-dump-logs.sh usr/share/jicofo 6 | jicofo/jicofo etc/logrotate.d/ 7 | -------------------------------------------------------------------------------- /debian/templates: -------------------------------------------------------------------------------- 1 | Template: jitsi-videobridge/jvb-hostname 2 | Type: string 3 | _Description: Hostname: 4 | The jicofo package needs the DNS hostname of your instance. 5 | 6 | Template: jicofo/jicofo-authpassword 7 | Type: password 8 | _Description: Jicofo user password: 9 | The secret used to connect to xmpp server as jicofo user. 10 | -------------------------------------------------------------------------------- /resources/mvn-exec.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | if [[ ! "$JAVA_SYS_PROPS" == *"-Dconfig.file="* ]]; then 4 | echo 5 | echo "To run jicofo you need a configuration file. Use environment variable JAVA_SYS_PROPS." 6 | echo "e.g. export JAVA_SYS_PROPS=\"-Dconfig.file=/etc/jitsi/jicofo/jicofo.conf\"" 7 | echo 8 | exit 2 9 | fi 10 | 11 | exec mvn ${JAVA_SYS_PROPS} compile exec:java -pl jicofo -Dexec.mainClass=org.jitsi.jicofo.Main 12 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: Jitsi Conference Focus 3 | Upstream-Contact: Emil Ivov 4 | Source: https://github.com/jitsi/jicofo 5 | 6 | Files: * 7 | Copyright: 2013-2018 Jitsi 8 | License: Apache 2.0 9 | 10 | License: Apache 2.0 11 | On Debian systems, the full text of the Apache licence 12 | version 2.0 can be found in the file 13 | '/usr/share/common-licenses/Apache-2.0'. 14 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | ## Reporting security issues 4 | 5 | We take security very seriously and develop all Jitsi projects to be secure and safe. 6 | 7 | If you find (or simply suspect) a security issue in any of the Jitsi projects, please report it to us via [HackerOne](https://hackerone.com/8x8-bounty) or send us an email to security@jitsi.org. 8 | 9 | **We encourage responsible disclosure for the sake of our users, so please reach out before posting in a public space.** 10 | -------------------------------------------------------------------------------- /debian/preinst: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # preinst script for jicofo 3 | 4 | set -e 5 | echo "Running preinst $@" 6 | 7 | if [ "$1" == "upgrade" ] ;then 8 | if [ -d /etc/logrotate.d/jicofo ] ;then 9 | # An earlier version of the package incorrectly installed a directory in logrotate.d. If that's the case 10 | # remove it, so the file can be properly installed. 11 | echo "/etc/logrotate.d/jicofo is a directory, removing." 12 | rm -rf /etc/logrotate.d/jicofo 13 | fi 14 | fi 15 | -------------------------------------------------------------------------------- /resources/jicofo.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | if "%1"=="/h" goto usage 4 | if "%1"=="/?" goto usage 5 | goto :begin 6 | 7 | :usage 8 | echo Usage: %0 9 | exit /B 1 10 | 11 | :begin 12 | 13 | :: needed to overcome weird loop behavior in conjunction with variable expansion 14 | SETLOCAL enabledelayedexpansion 15 | 16 | set mainClass=org.jitsi.jicofo.Main 17 | set cp=jicofo.jar 18 | FOR %%F IN (lib/*.jar) DO ( 19 | SET cp=!cp!;lib/%%F% 20 | ) 21 | 22 | java -Djava.util.logging.config.file=lib/logging.properties -Djdk.tls.ephemeralDHKeySize=2048 -cp %cp% %mainClass% %* 23 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: jicofo 2 | Section: net 3 | Priority: extra 4 | Maintainer: Jitsi Team 5 | Uploaders: Emil Ivov , Damian Minkov , Pawel Domas 6 | Build-Depends: debhelper, openjdk-11-jdk | openjdk-17-jdk, maven 7 | Standards-Version: 3.9.3 8 | Homepage: https://jitsi.org/meet 9 | 10 | Package: jicofo 11 | Architecture: all 12 | Depends: ${misc:Depends}, openjdk-11-jre-headless | openjdk-11-jre | openjdk-17-jre-headless | openjdk-17-jre, ruby-hocon, jq 13 | Description: JItsi Meet COnference FOcus 14 | Jicofo is a conference focus for Jitsi Meet application. 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Before posting, please make sure you check https://community.jitsi.org 4 | 5 | --- 6 | 7 | *This Issue tracker is only for reporting bugs and tracking code related issues.* 8 | 9 | Before posting, please make sure you check community.jitsi.org to see if the same or similar bugs have already been discussed. General questions, installation help, and feature requests can also be posted to community.jitsi.org. 10 | 11 | ## Description 12 | --- 13 | 14 | ## Current behavior 15 | --- 16 | 17 | ## Expected Behavior 18 | --- 19 | 20 | ## Possible Solution 21 | --- 22 | 23 | ## Steps to reproduce 24 | --- 25 | 26 | # Environment details 27 | --- 28 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/xmpp/XmppConnectionEnum.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2018 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.jicofo.xmpp 17 | 18 | enum class XmppConnectionEnum { Client, Service } 19 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | # Uncomment this to turn on verbose mode. 4 | #export DH_VERBOSE=1 5 | #export DEB_VERBOSE_ALL=true 6 | 7 | %: 8 | dh $@ 9 | 10 | override_dh_auto_clean: 11 | mvn -Dmaven.repo.local=${HOME}/.m2/repository clean 12 | 13 | override_dh_auto_configure: 14 | # do nothing 15 | 16 | override_dh_auto_test: 17 | # do nothing 18 | 19 | override_dh_auto_build: 20 | mvn -Dmaven.repo.local=${HOME}/.m2/repository -DskipTests -Dassembly.skipAssembly=true package 21 | mvn -Dmaven.repo.local=${HOME}/.m2/repository dependency:copy-dependencies -DincludeScope=runtime 22 | 23 | override_dh_install: 24 | mkdir -p debian/tmp/jicofo 25 | cp resources/config/jicofo-logrotate.d debian/tmp/jicofo/jicofo 26 | cp jicofo/target/jicofo*.jar debian/tmp/jicofo.jar 27 | dh_install 28 | 29 | override_dh_auto_install: 30 | # do nothing 31 | -------------------------------------------------------------------------------- /jicofo/src/main/kotlin/org/jitsi/jicofo/util/Cancelable.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2021-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.util 19 | 20 | interface Cancelable { 21 | fun cancel() 22 | } 23 | -------------------------------------------------------------------------------- /jicofo/src/main/kotlin/org/jitsi/jicofo/ReinviteMethod.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2022 - present 8x8, Inc 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo 19 | 20 | enum class ReinviteMethod { RestartJingle, ReplaceTransport } 21 | -------------------------------------------------------------------------------- /jicofo/src/main/java/org/jitsi/jicofo/conference/MuteResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2015-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.conference; 19 | 20 | /** The result of a mute operation. */ 21 | public enum MuteResult 22 | { 23 | SUCCESS, 24 | NOT_ALLOWED, 25 | ERROR 26 | } 27 | -------------------------------------------------------------------------------- /jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/Util.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2018 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.jicofo.bridge 17 | 18 | import org.jitsi.xmpp.extensions.colibri.ColibriStatsExtension 19 | 20 | fun ColibriStatsExtension.getDouble(name: String): Double? = try { 21 | getValueAsString(name)?.toDouble() 22 | } catch (e: Exception) { 23 | null 24 | } 25 | -------------------------------------------------------------------------------- /jicofo-selector/src/test/kotlin/org/jitsi/jicofo/util/ConfigHelpers.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2020 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.jicofo.util 17 | 18 | import io.kotest.core.spec.style.ShouldSpec 19 | import org.jitsi.config.withNewConfig 20 | 21 | fun ShouldSpec.context(config: String, name: String, block: () -> Unit) = context(name) { 22 | withNewConfig(config) { 23 | block() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/HealthCheckListener.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2021-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.bridge 19 | 20 | import org.jxmpp.jid.Jid 21 | 22 | interface HealthCheckListener { 23 | fun healthCheckPassed(bridgeJid: Jid) 24 | fun healthCheckFailed(bridgeJid: Jid) 25 | fun healthCheckTimedOut(bridgeJid: Jid) 26 | } 27 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/conference/source/VideoType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2022 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.jicofo.conference.source 17 | 18 | enum class VideoType { 19 | Camera, 20 | Desktop; 21 | 22 | override fun toString() = super.toString().lowercase() 23 | companion object { 24 | fun parseString(s: String?): VideoType = VideoType.values().find { it.name.equals(s, ignoreCase = true) } 25 | ?: throw IllegalArgumentException("Invalid VideoType: $s") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /jicofo-common/src/test/kotlin/org/jitsi/jicofo/xmpp/FeaturesTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2015-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.xmpp 19 | 20 | import io.kotest.core.spec.style.ShouldSpec 21 | import io.kotest.matchers.shouldBe 22 | 23 | class FeaturesTest : ShouldSpec() { 24 | init { 25 | context("Parsing") { 26 | Features.parseString(Features.AUDIO.value) shouldBe Features.AUDIO 27 | Features.parseString("something else") shouldBe null 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | env: 13 | # Java version to use for the release 14 | RELEASE_JAVA_VERSION: 11 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | 20 | strategy: 21 | matrix: 22 | java: [ 11, 17, 21 ] 23 | 24 | name: Java ${{ matrix.java }} 25 | 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@v4 29 | 30 | - name: Set up JDK ${{ matrix.java }} 31 | uses: actions/setup-java@v4 32 | with: 33 | distribution: temurin 34 | java-version: ${{ matrix.java }} 35 | cache: maven 36 | 37 | - name: Build and test with Maven 38 | run: mvn verify -B -Pcoverage 39 | 40 | - name: Upload coverage report 41 | uses: codecov/codecov-action@v4 42 | with: 43 | token: ${{ secrets.CODECOV_TOKEN }} 44 | -------------------------------------------------------------------------------- /jicofo-common/src/main/java/org/jitsi/impl/protocol/xmpp/log/ExcludeXmppPackets.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2018 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.impl.protocol.xmpp.log; 17 | 18 | import java.util.logging.*; 19 | 20 | /** 21 | * Excludes logs coming from {@link PacketDebugger}. 22 | */ 23 | public class ExcludeXmppPackets implements Filter 24 | { 25 | /** 26 | * {@inheritDoc} 27 | */ 28 | @Override 29 | public boolean isLoggable(LogRecord record) 30 | { 31 | return !record.getLoggerName().equals(PacketDebugger.class.getName()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /jicofo-common/src/main/java/org/jitsi/impl/protocol/xmpp/log/IncludeXmppPackets.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2018 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.impl.protocol.xmpp.log; 17 | 18 | import java.util.logging.*; 19 | 20 | /** 21 | * Allows only logs coming from {@link PacketDebugger}. 22 | */ 23 | public class IncludeXmppPackets implements Filter 24 | { 25 | /** 26 | * {@inheritDoc} 27 | */ 28 | @Override 29 | public boolean isLoggable(LogRecord record) 30 | { 31 | return record.getLoggerName().equals(PacketDebugger.class.getName()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/codec/OfferOptions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2015-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.codec 19 | 20 | /** 21 | * Options for an offer that jicofo generates for a specific participant (or for an Octo link). 22 | */ 23 | data class OfferOptions( 24 | var audio: Boolean = true, 25 | var video: Boolean = true, 26 | var sctp: Boolean = true, 27 | var tcc: Boolean = true, 28 | var remb: Boolean = false, 29 | var rtx: Boolean = true, 30 | var opusRed: Boolean = true 31 | ) 32 | -------------------------------------------------------------------------------- /jicofo/src/main/kotlin/org/jitsi/jicofo/Offer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2021-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo 19 | 20 | import org.jitsi.jicofo.conference.source.ConferenceSourceMap 21 | import org.jitsi.xmpp.extensions.jingle.ContentPacketExtension 22 | 23 | /** 24 | * Represent a Jingle offer consisting of a set of "content" extensions (which internally contain RTP payload 25 | * information, transport information, etc) and a set of "sources". 26 | */ 27 | data class Offer(val sources: ConferenceSourceMap, val contents: List) 28 | -------------------------------------------------------------------------------- /jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/colibri/Exceptions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2018 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.jicofo.bridge.colibri 17 | 18 | /** Bridge selection failed, i.e. there were no bridges available. */ 19 | class BridgeSelectionFailedException : Exception("Bridge selection failed") 20 | 21 | open class ColibriAllocationFailedException( 22 | message: String = "", 23 | val removeBridge: Boolean = false 24 | ) : Exception(message) 25 | 26 | class ConferenceAlreadyExistsException( 27 | message: String, 28 | removeBridge: Boolean 29 | ) : ColibriAllocationFailedException(message, removeBridge) 30 | -------------------------------------------------------------------------------- /jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/BridgeMetrics.kt: -------------------------------------------------------------------------------- 1 | package org.jitsi.jicofo.bridge 2 | 3 | import org.jitsi.jicofo.metrics.JicofoMetricsContainer.Companion.instance as metricsContainer 4 | class BridgeMetrics { 5 | companion object { 6 | /** Total number of participants that requested a restart for a specific bridge. */ 7 | val restartRequestsMetric = metricsContainer.registerCounter( 8 | "bridge_restart_requests_total", 9 | "Total number of requests to restart a bridge", 10 | labelNames = listOf("jvb") 11 | ) 12 | val endpoints = metricsContainer.registerLongGauge( 13 | "bridge_endpoints", 14 | "The number of endpoints on a bridge.", 15 | labelNames = listOf("jvb") 16 | ) 17 | val failingIce = metricsContainer.registerBooleanMetric( 18 | "bridge_failing_ice", 19 | "Whether a bridge is currently in the failing ICE state.", 20 | labelNames = listOf("jvb") 21 | ) 22 | val endpointsMoved = metricsContainer.registerCounter( 23 | "bridge_endpoints_moved", 24 | "Total number of endpoints moved away from a bridge for automatic load redistribution.", 25 | labelNames = listOf("jvb") 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /jicofo/src/main/kotlin/org/jitsi/jicofo/util/OfferOptionsUtil.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2015-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.util 19 | 20 | import org.jitsi.jicofo.codec.OfferOptions 21 | import org.jitsi.jicofo.conference.Participant 22 | 23 | fun OfferOptions.applyConstraints(participant: Participant) { 24 | audio = audio && participant.hasAudioSupport() 25 | video = video && participant.hasVideoSupport() 26 | sctp = sctp && participant.hasSctpSupport() 27 | rtx = rtx && participant.hasRtxSupport() 28 | remb = remb && participant.hasRembSupport() 29 | tcc = tcc && participant.hasTccSupport() 30 | opusRed = opusRed && participant.hasOpusRedSupport() 31 | } 32 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/util/SynchronizedDelegate.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2020 - present 8x8, Inc 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.util 19 | 20 | import kotlin.properties.ReadWriteProperty 21 | import kotlin.reflect.KProperty 22 | 23 | class SynchronizedDelegate(defaultValue: T, private val syncRoot: Any) : ReadWriteProperty { 24 | private var backingField = defaultValue 25 | 26 | override fun getValue(thisRef: Any, property: KProperty<*>): T = synchronized(syncRoot) { 27 | backingField 28 | } 29 | 30 | override fun setValue(thisRef: Any, property: KProperty<*>, value: T) = synchronized(syncRoot) { 31 | backingField = value 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/conference/source/SsrcGroupSemantics.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2021 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.jicofo.conference.source 17 | 18 | import kotlin.jvm.Throws 19 | 20 | /** The supported semantics for [SsrcGroup] */ 21 | enum class SsrcGroupSemantics { 22 | /** Simulcast. */ 23 | Sim, 24 | 25 | /** Flow ID, used for RTX. */ 26 | Fid; 27 | 28 | override fun toString() = super.toString().uppercase() 29 | 30 | companion object { 31 | /** Parse a string case-insensitively. */ 32 | @Throws(NoSuchElementException::class) 33 | fun fromString(str: String): SsrcGroupSemantics = 34 | values().first { it.toString().lowercase() == str.lowercase() } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /jicofo/src/test/kotlin/org/jitsi/jicofo/KotestProjectConfig.kt: -------------------------------------------------------------------------------- 1 | import io.kotest.core.config.AbstractProjectConfig 2 | import org.jitsi.jicofo.metrics.JicofoMetricsContainer 3 | import org.jitsi.metaconfig.MetaconfigSettings 4 | 5 | /* 6 | * Copyright @ 2020 - present 8x8, Inc. 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | 21 | class KotestProjectConfig : AbstractProjectConfig() { 22 | override suspend fun beforeProject() = super.beforeProject().also { 23 | // The only purpose of config caching is performance. We always want caching disabled in tests (so we can 24 | // freely modify the config without affecting other tests executing afterwards). 25 | MetaconfigSettings.cacheEnabled = false 26 | 27 | JicofoMetricsContainer.instance.checkForNameConflicts = false 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/OctoConfig.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2020 - present 8x8, Inc 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo 19 | 20 | import org.jitsi.config.JitsiConfig.Companion.newConfig 21 | import org.jitsi.metaconfig.config 22 | 23 | class OctoConfig { 24 | val enabled: Boolean by config { 25 | "jicofo.octo.enabled".from(newConfig) 26 | } 27 | 28 | val allowMixedVersions: Boolean by config { 29 | "jicofo.octo.allow-mixed-versions".from(newConfig) 30 | } 31 | 32 | val sctpDatachannels: Boolean by config { 33 | "jicofo.octo.sctp-datachannels".from(newConfig) 34 | } 35 | 36 | companion object { 37 | @JvmField 38 | val config = OctoConfig() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /debian/postrm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # postrm script for jicofo 3 | 4 | set -e 5 | 6 | # Load debconf 7 | . /usr/share/debconf/confmodule 8 | 9 | 10 | case "$1" in 11 | purge) 12 | 13 | # Clear the debconf variable 14 | db_purge 15 | 16 | # clear user and group 17 | # ensure nothing is running as jicofo user 18 | if ps -u jicofo h; then 19 | `ps -u jicofo -o pid h | xargs kill` 20 | fi 21 | # and then remove the user 22 | if getent passwd jicofo > /dev/null ; then 23 | deluser jicofo 24 | fi 25 | 26 | # clear logs 27 | if [ -f "/var/log/jitsi/jicofo.log" ]; then 28 | rm -f "/var/log/jitsi/jicofo.log" 29 | fi 30 | 31 | rm -rf /usr/share/jicofo/ 32 | rm -f /etc/jitsi/jicofo/config 33 | rm -f /etc/jitsi/jicofo/sip-communicator.properties 34 | rm -f /etc/jitsi/jicofo/logging.properties 35 | rm -f /etc/jitsi/jicofo/jicofo.conf 36 | ;; 37 | 38 | remove) 39 | ;; 40 | 41 | upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) 42 | ;; 43 | 44 | *) 45 | echo "postrm called with unknown argument \`$1'" >&2 46 | exit 1 47 | ;; 48 | esac 49 | 50 | # dh_installdeb will replace this with shell code automatically 51 | # generated by other debhelper scripts. 52 | 53 | #DEBHELPER# 54 | 55 | db_stop 56 | 57 | exit 0 58 | -------------------------------------------------------------------------------- /resources/jicofo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ "$1" == "--help" ]]; then 4 | echo -e "Usage: $0" 5 | echo 6 | echo -e "Supported environment variables: JICOFO_MAX_MEMORY, JAVA_SYS_PROPS." 7 | echo 8 | exit 1 9 | fi 10 | 11 | if [[ ! "$JAVA_SYS_PROPS" == *"-Dconfig.file="* ]]; then 12 | if [[ -f /etc/jitsi/jicofo/jicofo.conf ]]; then 13 | JAVA_SYS_PROPS="$JAVA_SYS_PROPS -Dconfig.file=/etc/jitsi/jicofo/jicofo.conf" 14 | else 15 | echo 16 | echo "To run jicofo you need a configuration file. Use environment variable JAVA_SYS_PROPS." 17 | echo "e.g. export JAVA_SYS_PROPS=\"-Dconfig.file=/etc/jitsi/jicofo/jicofo.conf\"" 18 | echo 19 | exit 2 20 | fi 21 | fi 22 | 23 | SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" 24 | 25 | mainClass="org.jitsi.jicofo.Main" 26 | cp=$(JARS=($SCRIPT_DIR/jicofo*.jar $SCRIPT_DIR/lib/*.jar); IFS=:; echo "${JARS[*]}") 27 | logging_config="$SCRIPT_DIR/lib/logging.properties" 28 | 29 | # if there is a logging config file in lib folder use it (running from source) 30 | if [ -f $logging_config ]; then 31 | LOGGING_CONFIG_PARAM="-Djava.util.logging.config.file=$logging_config" 32 | fi 33 | 34 | if [ -z "$JICOFO_MAX_MEMORY" ]; then JICOFO_MAX_MEMORY=3072m; fi 35 | 36 | exec java -Xmx$JICOFO_MAX_MEMORY -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp -Djdk.tls.ephemeralDHKeySize=2048 $LOGGING_CONFIG_PARAM $JAVA_SYS_PROPS -cp $cp $mainClass $@ 37 | -------------------------------------------------------------------------------- /jicofo/src/main/kotlin/org/jitsi/jicofo/conference/SourcesToAddOrRemove.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2015-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.conference 19 | 20 | import org.jitsi.jicofo.conference.source.ConferenceSourceMap 21 | import org.jitsi.utils.OrderedJsonObject 22 | 23 | /** An action -- add or remove. */ 24 | enum class AddOrRemove { 25 | Add, 26 | Remove 27 | } 28 | 29 | /** Holds a [ConferenceSourceMap] together with an action specifying if the sources are to be added or removed. */ 30 | data class SourcesToAddOrRemove( 31 | val action: AddOrRemove, 32 | val sources: ConferenceSourceMap 33 | ) { 34 | val debugState: OrderedJsonObject 35 | get() = OrderedJsonObject().apply { 36 | put("action", action.toString()) 37 | put("sources", sources.toJson()) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /debian/po/templates.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the jicofo package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: jicofo\n" 10 | "Report-Msgid-Bugs-To: jicofo@packages.debian.org\n" 11 | "POT-Creation-Date: 2016-11-21 19:43+0000\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=CHARSET\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #. Type: string 21 | #. Description 22 | #: ../templates:1001 23 | msgid "Hostname:" 24 | msgstr "" 25 | 26 | #. Type: string 27 | #. Description 28 | #: ../templates:1001 29 | msgid "The jicofo package needs the DNS hostname of your instance." 30 | msgstr "" 31 | 32 | #. Type: string 33 | #. Description 34 | #: ../templates:2001 35 | msgid "Jicofo username:" 36 | msgstr "" 37 | 38 | #. Type: string 39 | #. Description 40 | #: ../templates:2001 41 | msgid "The jicofo needs an authenticated admin user to connect to xmpp server." 42 | msgstr "" 43 | 44 | #. Type: password 45 | #. Description 46 | #: ../templates:3001 47 | msgid "Jicofo user password:" 48 | msgstr "" 49 | 50 | #. Type: password 51 | #. Description 52 | #: ../templates:3001 53 | msgid "The secret used to connect to xmpp server as jicofo user." 54 | msgstr "" 55 | -------------------------------------------------------------------------------- /jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/TopologySelectionStrategy.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2018 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.jicofo.bridge 17 | 18 | import org.jitsi.jicofo.bridge.colibri.Colibri2CascadeRepair 19 | import org.jitsi.jicofo.bridge.colibri.Colibri2Session 20 | import org.jitsi.jicofo.bridge.colibri.ColibriV2SessionManager 21 | 22 | /** 23 | * Represents a strategy for selecting bridge topologies. 24 | */ 25 | abstract class TopologySelectionStrategy { 26 | abstract fun connectNode(cascade: ColibriV2SessionManager, node: Colibri2Session): TopologySelectionResult 27 | abstract fun repairMesh( 28 | cascade: ColibriV2SessionManager, 29 | disconnected: Set> 30 | ): Set 31 | } 32 | 33 | data class TopologySelectionResult( 34 | val existingNode: Colibri2Session?, 35 | val meshId: String 36 | ) 37 | -------------------------------------------------------------------------------- /jicofo/src/test/kotlin/org/jitsi/jicofo/util/ListConferenceStore.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2021 - present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.util 19 | 20 | import org.jitsi.jicofo.ConferenceStore 21 | import org.jitsi.jicofo.PinnedConference 22 | import org.jitsi.jicofo.conference.JitsiMeetConference 23 | import org.jxmpp.jid.EntityBareJid 24 | import java.time.Duration 25 | 26 | class ListConferenceStore : ConferenceStore, MutableList by ArrayList() { 27 | override fun getAllConferences() = this 28 | override fun getConference(jid: EntityBareJid) = find { it.roomName == jid } 29 | override fun getPinnedConferences(): List = listOf() 30 | override fun pinConference(roomName: EntityBareJid, jvbVersion: String, duration: Duration) { } 31 | override fun unpinConference(roomName: EntityBareJid) {} 32 | } 33 | -------------------------------------------------------------------------------- /debian/manpage.1: -------------------------------------------------------------------------------- 1 | .\" Hey, EMACS: -*- nroff -*- 2 | .\" First parameter, NAME, should be all caps 3 | .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection 4 | .\" other parameters are allowed: see man(7), man(1) 5 | .TH JICOFO SECTION "November 28, 2014" 6 | .\" Please adjust this date whenever revising the manpage. 7 | .\" 8 | .\" Some roff macros, for reference: 9 | .\" .nh disable hyphenation 10 | .\" .hy enable hyphenation 11 | .\" .ad l left justify 12 | .\" .ad b justify to both left and right margins 13 | .\" .nf disable filling 14 | .\" .fi enable filling 15 | .\" .br insert line break 16 | .\" .sp insert n+1 empty lines 17 | .\" for manpage-specific macros, see man(7) 18 | .SH NAME 19 | jicofo \- conference focus for Jitsi Meet application. 20 | .SH SYNOPSIS 21 | .B /usr/share/jicofo/jicofo.sh 22 | .RI [ options ] ... 23 | .br 24 | .B /etc/init.d/jicofo (start|stop|restart) 25 | .SH DESCRIPTION 26 | This manual page documents briefly the 27 | .B jicofo 28 | service. 29 | .PP 30 | .\" TeX users may be more comfortable with the \fB\fP and 31 | .\" \fI\fP escape sequences to invode bold face and italics, 32 | .\" respectively. 33 | \fBjicofo\fP is a conference focus for Jitsi Meet application. 34 | .SH AUTHOR 35 | jicofo was written by Jitsi.org and Bluejimp.com. 36 | .PP 37 | This manual page was written by Pawel Domas , 38 | for the Debian project (and may be used by others). 39 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/metrics/JicofoMetricsContainer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2022 - present 8x8, Inc 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.metrics 19 | 20 | import org.jitsi.config.JitsiConfig 21 | import org.jitsi.jicofo.TaskPools 22 | import org.jitsi.metaconfig.config 23 | import org.jitsi.metrics.MetricsContainer 24 | import org.jitsi.metrics.MetricsUpdater 25 | import java.time.Duration 26 | 27 | class JicofoMetricsContainer private constructor() : MetricsContainer(namespace = "jitsi_jicofo") { 28 | val metricsUpdater = MetricsUpdater(TaskPools.scheduledPool, updateInterval) 29 | 30 | companion object { 31 | private val updateInterval: Duration by config { 32 | "jicofo.metrics.update-interval".from(JitsiConfig.newConfig) 33 | } 34 | 35 | @JvmStatic 36 | val instance = JicofoMetricsContainer() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /jicofo/src/main/java/org/jitsi/jicofo/util/ErrorResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2018 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.jicofo.util; 17 | 18 | import org.jivesoftware.smack.packet.*; 19 | 20 | /** 21 | * Utility for Error IQ. 22 | */ 23 | public class ErrorResponse 24 | { 25 | /** 26 | * A convenience method for creating error responses. 27 | * 28 | * @param request the {@link IQ.Type#get IQ.Type.get} or 29 | * {@link IQ.Type#set IQ.Type.set} IQ packet. 30 | * @param condition the {@link StanzaError.Condition} for the error. 31 | * @param text human readable error description. 32 | * @return ErrorIQ 33 | */ 34 | static public ErrorIQ create( 35 | IQ request, StanzaError.Condition condition, String text) 36 | { 37 | return IQ.createErrorResponse( 38 | request, 39 | StanzaError.from(condition, text).build()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /jicofo/src/test/kotlin/org/jitsi/jicofo/mock/MockXmppProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2022 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.jicofo.mock 17 | 18 | import io.mockk.every 19 | import io.mockk.mockk 20 | import org.jitsi.jicofo.xmpp.XmppProvider 21 | import org.jivesoftware.smack.AbstractXMPPConnection 22 | import org.jxmpp.jid.EntityBareJid 23 | 24 | class MockXmppProvider(val xmppConnection: AbstractXMPPConnection = MockXmppConnection().xmppConnection) { 25 | val chatRooms = mutableMapOf() 26 | val xmppProvider = mockk(relaxed = true) { 27 | every { registered } returns true 28 | every { findOrCreateRoom(any(), any()) } answers { getRoom(arg(0)).chatRoom } 29 | every { xmppConnection } returns this@MockXmppProvider.xmppConnection 30 | } 31 | 32 | fun getRoom(jid: EntityBareJid): MockChatRoom = chatRooms.computeIfAbsent(jid) { MockChatRoom(this.xmppProvider) } 33 | } 34 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/metrics/GlobalMetrics.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2023 - present 8x8, Inc 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.metrics 19 | 20 | import java.lang.management.ManagementFactory 21 | import org.jitsi.jicofo.metrics.JicofoMetricsContainer.Companion.instance as metricsContainer 22 | 23 | class GlobalMetrics { 24 | companion object { 25 | @JvmField 26 | val threadCount = metricsContainer.registerLongGauge( 27 | "threads", 28 | "The current number of JVM threads" 29 | ) 30 | 31 | val xmppDisconnects = metricsContainer.registerCounter( 32 | "xmpp_disconnects", 33 | "The number of times one of the XMPP connections has disconnected." 34 | ) 35 | 36 | fun update() { 37 | threadCount.set(ManagementFactory.getThreadMXBean().threadCount.toLong()) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /jicofo/src/main/java/org/jitsi/jicofo/auth/AuthenticationListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2015-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.auth; 19 | 20 | import org.jxmpp.jid.*; 21 | 22 | /** 23 | * Interface used to listen to authentication notification fired by 24 | * {@link AuthenticationAuthority}. 25 | * 26 | * @author Pawel Domas 27 | */ 28 | public interface AuthenticationListener 29 | { 30 | /** 31 | * Called by {@link AuthenticationAuthority} when the user identified by 32 | * given userJid gets confirmed identity by external authentication 33 | * component. 34 | * @param userJid the real user JID(not MUC JID which can be faked). 35 | * @param authenticatedIdentity the identity of the user confirmed by 36 | */ 37 | void jidAuthenticated(Jid userJid, 38 | String authenticatedIdentity, 39 | String sessionId); 40 | } 41 | -------------------------------------------------------------------------------- /jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/colibri/ColibriAllocation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2018 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.jicofo.bridge.colibri 17 | 18 | import org.jitsi.jicofo.conference.source.ConferenceSourceMap 19 | import org.jitsi.xmpp.extensions.jingle.IceUdpTransportPacketExtension 20 | 21 | /** 22 | * Describes the resources allocated on a bridge for a single endpoint. 23 | */ 24 | data class ColibriAllocation( 25 | /** The sources advertised by the bridge. */ 26 | val sources: ConferenceSourceMap, 27 | /** Encodes the bridge-side transport information (ICE candidates, DTLS fingerprint, etc.). */ 28 | val transport: IceUdpTransportPacketExtension, 29 | /** The region of the bridge */ 30 | val region: String?, 31 | /** An identifier for the session */ 32 | val bridgeSessionId: String?, 33 | /** The SCTP port (used for both local and remote port) advertised by the bridge, if any */ 34 | val sctpPort: Int? 35 | ) 36 | -------------------------------------------------------------------------------- /jicofo-selector/src/test/kotlin/org/jitsi/jicofo/JicofoSelectorKotestProjectConfig.kt: -------------------------------------------------------------------------------- 1 | import io.kotest.core.config.AbstractProjectConfig 2 | import org.jitsi.jicofo.metrics.JicofoMetricsContainer 3 | import org.jitsi.metaconfig.MetaconfigSettings 4 | 5 | /* 6 | * Copyright @ 2020 - present 8x8, Inc. 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | 21 | /** 22 | * Note: jicofo and jicofo-selector still share packages, so the class name is change to avoid conflicts. 23 | */ 24 | class JicofoSelectorKotestProjectConfig : AbstractProjectConfig() { 25 | override suspend fun beforeProject() = super.beforeProject().also { 26 | // The only purpose of config caching is performance. We always want caching disabled in tests (so we can 27 | // freely modify the config without affecting other tests executing afterwards). 28 | MetaconfigSettings.cacheEnabled = false 29 | 30 | JicofoMetricsContainer.instance.checkForNameConflicts = false 31 | JicofoMetricsContainer.instance.metricsUpdater.disablePeriodicUpdates = true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/SingleMeshTopologyStrategy.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2018 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.jicofo.bridge 17 | 18 | import org.jitsi.jicofo.bridge.colibri.Colibri2CascadeRepair 19 | import org.jitsi.jicofo.bridge.colibri.Colibri2Session 20 | import org.jitsi.jicofo.bridge.colibri.ColibriV2SessionManager 21 | 22 | /** Put all bridge nodes into a single mesh, named "0". */ 23 | class SingleMeshTopologyStrategy : TopologySelectionStrategy() { 24 | override fun connectNode(cascade: ColibriV2SessionManager, node: Colibri2Session): TopologySelectionResult = 25 | TopologySelectionResult(cascade.sessions.values.firstOrNull(), "0") 26 | 27 | override fun repairMesh( 28 | cascade: ColibriV2SessionManager, 29 | disconnected: Set> 30 | ): Set { 31 | assert(false) { 32 | "Single Mesh policy should never result in disconnected meshes" 33 | } 34 | return setOf() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/xmpp/muc/ChatRoomListener.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jitsi, the OpenSource Java VoIP and Instant Messaging client. 3 | * 4 | * Copyright @ 2015-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.xmpp.muc 19 | 20 | /** Listener for events fired from a [org.jitsi.impl.protocol.xmpp.ChatRoom] **/ 21 | interface ChatRoomListener { 22 | fun memberJoined(member: ChatRoomMember) {} 23 | fun memberKicked(member: ChatRoomMember) {} 24 | fun memberLeft(member: ChatRoomMember) {} 25 | fun memberPresenceChanged(member: ChatRoomMember) {} 26 | 27 | fun roomDestroyed(reason: String? = null) {} 28 | fun startMutedChanged(startAudioMuted: Boolean, startVideoMuted: Boolean) {} 29 | fun localRoleChanged(newRole: MemberRole) {} 30 | fun numAudioSendersChanged(numAudioSenders: Int) {} 31 | fun numVideoSendersChanged(numVideoSenders: Int) {} 32 | } 33 | 34 | /** A class with the default kotlin method implementations (to avoid using @JvmDefault) **/ 35 | open class DefaultChatRoomListener : ChatRoomListener 36 | -------------------------------------------------------------------------------- /resources/collect-dump-logs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # script that creates an archive in current folder 4 | # containing the heap and thread dump and the current log file 5 | 6 | JAVA_HEAPDUMP_PATH="/tmp/java_*.hprof" 7 | STAMP=`date +%Y-%m-%d-%H%M` 8 | PID_PATH="/var/run/jicofo.pid" 9 | JICOFO_UID=`id -u jicofo` 10 | RUNNING="" 11 | unset PID 12 | 13 | #Find any crashes in /var/crash from our user in the past 20 minutes, if they exist 14 | CRASH_FILES=$(find /var/crash -name '*.crash' -uid $JICOFO_UID -mmin -20 -type f) 15 | 16 | [ -e $PID_PATH ] && PID=$(cat $PID_PATH) 17 | if [ ! -z $PID ]; then 18 | ps -p $PID | grep -q java 19 | [ $? -eq 0 ] && RUNNING="true" 20 | fi 21 | if [ ! -z $RUNNING ]; then 22 | echo "Jicofo pid $PID" 23 | THREADS_FILE="/tmp/stack-${STAMP}-${PID}.threads" 24 | HEAP_FILE="/tmp/heap-${STAMP}-${PID}.bin" 25 | sudo -u jicofo jstack ${PID} > ${THREADS_FILE} 26 | sudo -u jicofo jmap -dump:live,format=b,file=${HEAP_FILE} ${PID} 27 | tar zcvf jicofo-dumps-${STAMP}-${PID}.tgz ${THREADS_FILE} ${HEAP_FILE} ${CRASH_FILES} /var/log/jitsi/jicofo* /tmp/hs_err_* 28 | rm ${HEAP_FILE} ${THREADS_FILE} 29 | else 30 | ls $JAVA_HEAPDUMP_PATH >/dev/null 2>&1 31 | if [ $? -eq 0 ]; then 32 | echo "Jicofo not running, but previous heap dump found." 33 | tar zcvf jicofo-dumps-${STAMP}-crash.tgz $JAVA_HEAPDUMP_PATH ${CRASH_FILES} /var/log/jitsi/jicofo* /tmp/hs_err_* 34 | rm ${JAVA_HEAPDUMP_PATH} 35 | else 36 | echo "Jicofo not running, no previous dump found. Including logs only." 37 | tar zcvf jicofo-dumps-${STAMP}-crash.tgz ${CRASH_FILES} /var/log/jitsi/jicofo* /tmp/hs_err_* 38 | fi 39 | fi 40 | -------------------------------------------------------------------------------- /jicofo-selector/src/test/kotlin/org/jitsi/jicofo/bridge/UtilTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2018 - present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.bridge 19 | 20 | import io.kotest.core.spec.style.ShouldSpec 21 | import io.kotest.matchers.shouldBe 22 | import org.jitsi.xmpp.extensions.colibri.ColibriStatsExtension 23 | 24 | class UtilTest : ShouldSpec() { 25 | init { 26 | context("ColibriStatsExtension.getDouble test") { 27 | with(ColibriStatsExtension()) { 28 | addStat("int", 5) 29 | addStat("double", 5.0) 30 | addStat("valid-string", "5") 31 | addStat("invalid-string", "five") 32 | 33 | getDouble("int") shouldBe 5.toDouble() 34 | getDouble("double") shouldBe 5.toDouble() 35 | getDouble("valid-string") shouldBe 5.toDouble() 36 | getDouble("invalid-string") shouldBe null 37 | getDouble("non-existent-key") shouldBe null 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /jicofo/src/test/kotlin/org/jitsi/jicofo/OctoConfigTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2020 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.jicofo 17 | 18 | import io.kotest.core.spec.style.ShouldSpec 19 | import io.kotest.matchers.shouldBe 20 | import org.jitsi.config.withNewConfig 21 | 22 | class OctoConfigTest : ShouldSpec() { 23 | init { 24 | context("Enabled flag") { 25 | // Note that there's a corresponding flag in the JVB and these two 26 | // MUST be in sync (otherwise bridges will crash because they won't 27 | // know how to deal with octo channels). 28 | context("default") { 29 | OctoConfig.config.enabled shouldBe false 30 | } 31 | context("disabled") { 32 | withNewConfig("jicofo.octo.enabled=false") { 33 | OctoConfig.config.enabled shouldBe false 34 | } 35 | } 36 | context("enabled") { 37 | withNewConfig("jicofo.octo.enabled=true") { 38 | OctoConfig.config.enabled shouldBe true 39 | } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /jicofo/src/main/kotlin/org/jitsi/jicofo/ConferenceStore.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2021 - present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo 19 | 20 | import org.jitsi.jicofo.conference.JitsiMeetConference 21 | import org.jxmpp.jid.EntityBareJid 22 | import java.time.Duration 23 | 24 | interface ConferenceStore { 25 | /** Get a list of all conferences. */ 26 | fun getAllConferences(): List 27 | 28 | /** Get a conference for a specific [Jid] (i.e. name). */ 29 | fun getConference(jid: EntityBareJid): JitsiMeetConference? 30 | 31 | fun getPinnedConferences(): List 32 | fun pinConference(roomName: EntityBareJid, jvbVersion: String, duration: Duration) 33 | fun unpinConference(roomName: EntityBareJid) 34 | 35 | fun addListener(listener: Listener) {} 36 | fun removeListener(listener: Listener) {} 37 | 38 | interface Listener { 39 | fun conferenceEnded(name: EntityBareJid) 40 | } 41 | } 42 | 43 | data class PinnedConference( 44 | val conferenceId: String, 45 | val jvbVersion: String, 46 | val expiresAt: String 47 | ) 48 | -------------------------------------------------------------------------------- /jicofo/src/main/kotlin/org/jitsi/jicofo/health/HealthConfig.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2020 - present 8x8, Inc 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.health 19 | 20 | import org.jitsi.config.JitsiConfig.Companion.legacyConfig 21 | import org.jitsi.config.JitsiConfig.Companion.newConfig 22 | import org.jitsi.metaconfig.config 23 | import java.time.Duration 24 | 25 | class HealthConfig private constructor() { 26 | val enabled: Boolean by config { 27 | "org.jitsi.jicofo.health.ENABLE_HEALTH_CHECKS".from(legacyConfig) 28 | "jicofo.health.enabled".from(newConfig) 29 | } 30 | 31 | val interval: Duration by config { 32 | "jicofo.health.interval".from(newConfig) 33 | } 34 | 35 | val timeout: Duration by config { 36 | "jicofo.health.timeout".from(newConfig) 37 | } 38 | 39 | val maxCheckDuration: Duration by config { 40 | "jicofo.health.max-check-duration".from(newConfig) 41 | } 42 | 43 | val roomNamePrefix: String by config { 44 | "jicofo.health.room-name-prefix".from(newConfig) 45 | } 46 | 47 | companion object { 48 | @JvmField 49 | val config = HealthConfig() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/xmpp/JingleUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2021-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.xmpp 19 | 20 | import org.jitsi.jicofo.xmpp.jingle.JingleSession 21 | import org.jitsi.xmpp.extensions.jingle.ContentPacketExtension 22 | import org.jitsi.xmpp.extensions.jingle.JingleAction 23 | import org.jitsi.xmpp.extensions.jingle.JingleIQ 24 | import org.jivesoftware.smack.packet.IQ 25 | import org.jxmpp.jid.Jid 26 | 27 | fun createSessionInitiate(from: Jid, to: Jid, sid: String, contents: List) = 28 | JingleIQ(JingleAction.SESSION_INITIATE, sid).apply { 29 | this.from = from 30 | this.to = to 31 | initiator = from 32 | type = IQ.Type.set 33 | contents.forEach { addContent(it) } 34 | } 35 | 36 | fun createTransportReplace(from: Jid, session: JingleSession, contents: List) = 37 | JingleIQ(JingleAction.TRANSPORT_REPLACE, session.sid).apply { 38 | this.from = from 39 | this.to = session.remoteJid 40 | initiator = from 41 | type = IQ.Type.set 42 | contents.forEach { addContent(it) } 43 | } 44 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/util/WeakValueMap.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2022 - present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.util 19 | 20 | import java.lang.ref.WeakReference 21 | import java.util.concurrent.ConcurrentHashMap 22 | 23 | /** 24 | * Self-cleaning map with weak values. 25 | * TODO: maybe move to jitsi-utils 26 | */ 27 | class WeakValueMap( 28 | private val cleanInterval: Int = 100 29 | ) { 30 | private val map: MutableMap> = ConcurrentHashMap>() 31 | private var i = 0 32 | 33 | fun get(key: K) = map[key]?.get().also { 34 | maybeClean() 35 | } 36 | fun put(key: K, value: V) { 37 | map[key] = WeakReference(value) 38 | maybeClean() 39 | } 40 | fun containsKey(key: K): Boolean = (map[key]?.get() != null).also { 41 | maybeClean() 42 | } 43 | fun remove(key: K) = map.remove(key)?.get() 44 | fun values(): List { 45 | clean() 46 | return map.values.mapNotNull { it.get() }.toList() 47 | } 48 | 49 | private fun maybeClean() = ((i++ % cleanInterval == 0)) && clean() 50 | private fun clean() = map.values.removeIf { it.get() == null } 51 | } 52 | -------------------------------------------------------------------------------- /lib/logging.properties: -------------------------------------------------------------------------------- 1 | 2 | handlers= java.util.logging.ConsoleHandler 3 | 4 | # Handlers with XMPP debug enabled: 5 | #handlers= java.util.logging.ConsoleHandler, org.jitsi.impl.protocol.xmpp.log.XmppPacketsFileHandler 6 | 7 | # Handlers with syslog enabled: 8 | #handlers= java.util.logging.ConsoleHandler, com.agafua.syslog.SyslogHandler 9 | #handlers= java.util.logging.ConsoleHandler, io.sentry.jul.SentryHandler 10 | 11 | java.util.logging.ConsoleHandler.level = ALL 12 | java.util.logging.ConsoleHandler.formatter = org.jitsi.utils.logging2.JitsiLogFormatter 13 | java.util.logging.ConsoleHandler.filter = org.jitsi.impl.protocol.xmpp.log.ExcludeXmppPackets 14 | 15 | org.jitsi.utils.logging2.JitsiLogFormatter.programname=Jicofo 16 | .level=INFO 17 | 18 | # To enable XMPP packets logging add XmppPacketsFileHandler to the handlers property 19 | org.jitsi.impl.protocol.xmpp.log.PacketDebugger.level=ALL 20 | org.jitsi.impl.protocol.xmpp.log.XmppPacketsFileHandler.pattern=/var/log/jitsi/jicofo-xmpp.log 21 | org.jitsi.impl.protocol.xmpp.log.XmppPacketsFileHandler.append=true 22 | org.jitsi.impl.protocol.xmpp.log.XmppPacketsFileHandler.limit=200000000 23 | org.jitsi.impl.protocol.xmpp.log.XmppPacketsFileHandler.count=3 24 | 25 | # Syslog (uncomment handler to use) 26 | com.agafua.syslog.SyslogHandler.transport = udp 27 | com.agafua.syslog.SyslogHandler.facility = local0 28 | com.agafua.syslog.SyslogHandler.port = 514 29 | com.agafua.syslog.SyslogHandler.hostname = localhost 30 | com.agafua.syslog.SyslogHandler.formatter = org.jitsi.utils.logging2.JitsiLogFormatter 31 | com.agafua.syslog.SyslogHandler.escapeNewlines = false 32 | com.agafua.syslog.SyslogHandler.filter = org.jitsi.impl.protocol.xmpp.log.ExcludeXmppPackets 33 | 34 | # Sentry (uncomment handler to use) 35 | io.sentry.jul.SentryHandler.level=WARNING 36 | 37 | # uncomment to see how Jicofo talks to the JVB 38 | #org.jitsi.impl.protocol.xmpp.colibri.level=ALL 39 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/JicofoConfig.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2020 - present 8x8, Inc 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo 19 | 20 | import org.jitsi.config.JitsiConfig.Companion.legacyConfig 21 | import org.jitsi.config.JitsiConfig.Companion.newConfig 22 | import org.jitsi.metaconfig.config 23 | import org.jitsi.metaconfig.optionalconfig 24 | import java.time.Duration 25 | 26 | class JicofoConfig private constructor() { 27 | val localRegion: String? by optionalconfig { 28 | "org.jitsi.jicofo.BridgeSelector.LOCAL_REGION".from(legacyConfig) 29 | "$BASE.local-region".from(newConfig) 30 | } 31 | 32 | val enableSctp: Boolean by config { 33 | "$BASE.sctp.enabled".from(newConfig) 34 | } 35 | 36 | fun enableSctp() = enableSctp 37 | 38 | fun localRegion() = localRegion 39 | 40 | // TODO this logically should be in VisitorsConfig, but that's in jicofo and this is needed by ChatRoom 41 | // in jicofo-common 42 | val vnodeJoinLatencyInterval: Duration by config { 43 | "jicofo.visitors.vnode-join-latency-interval".from(newConfig) 44 | } 45 | 46 | companion object { 47 | const val BASE = "jicofo" 48 | 49 | @JvmField 50 | val config = JicofoConfig() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /jicofo-common/src/main/java/org/jitsi/impl/protocol/xmpp/log/XmppPacketsFileHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2018 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.impl.protocol.xmpp.log; 17 | 18 | import java.io.*; 19 | import java.text.*; 20 | import java.time.*; 21 | import java.util.*; 22 | import java.util.logging.*; 23 | 24 | /** 25 | * Logs XMPP traffic to a file if configured in the logging.properties file. 26 | */ 27 | public class XmppPacketsFileHandler extends FileHandler 28 | { 29 | /** 30 | * A constructor. 31 | */ 32 | public XmppPacketsFileHandler() 33 | throws IOException, SecurityException 34 | { 35 | setFilter(new IncludeXmppPackets()); 36 | setFormatter(new Formatter()); 37 | setLevel(Level.ALL); 38 | } 39 | 40 | /** 41 | * The formatter used by {@link XmppPacketsFileHandler}. 42 | */ 43 | static class Formatter extends java.util.logging.Formatter 44 | { 45 | final SimpleDateFormat stampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); 46 | 47 | @Override 48 | public String format(LogRecord record) 49 | { 50 | Date when = Date.from(Instant.ofEpochMilli(record.getMillis())); 51 | String msg = record.getMessage(); 52 | 53 | return String.format("%s: %s%n", stampFormat.format(when), msg); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/util/PendingCount.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2015-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.util 19 | 20 | import org.jitsi.utils.ms 21 | import org.jitsi.utils.stats.RateTracker 22 | import java.time.Clock 23 | import java.time.Duration 24 | 25 | /** Keep a count of some events which have been triggered to happen within some interval, but which 26 | * may or may not happen, and thus should expire from the count after some time if they have not. 27 | * The intended use case is visitors who have been invited to a visitor ChatRoom. 28 | */ 29 | class PendingCount( 30 | timeout: Duration, 31 | clock: Clock = Clock.systemUTC() 32 | ) { 33 | private var occurredEvents = 0L 34 | 35 | private var tracker = object : RateTracker(timeout, 100.ms, clock) { 36 | override fun bucketExpired(count: Long) { 37 | occurredEvents = (occurredEvents - count).coerceAtLeast(0) 38 | } 39 | } 40 | 41 | @Synchronized 42 | fun eventPending() { 43 | tracker.update(1) 44 | } 45 | 46 | @Synchronized 47 | fun eventOccurred() { 48 | occurredEvents = (occurredEvents + 1).coerceAtMost(tracker.getAccumulatedCount()) 49 | } 50 | 51 | @Synchronized 52 | fun getCount(): Long = tracker.getAccumulatedCount() - occurredEvents 53 | } 54 | -------------------------------------------------------------------------------- /jicofo/src/main/kotlin/org/jitsi/jicofo/visitors/VisitorsConfig.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2022 - present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.visitors 19 | 20 | import org.jitsi.config.JitsiConfig.Companion.newConfig 21 | import org.jitsi.metaconfig.config 22 | import java.time.Duration 23 | 24 | class VisitorsConfig private constructor() { 25 | val enabled: Boolean by config { 26 | "jicofo.visitors.enabled".from(newConfig) 27 | } 28 | val maxParticipants: Int by config { 29 | "jicofo.visitors.max-participants".from(newConfig) 30 | } 31 | val maxVisitorsPerNode: Int by config { 32 | "jicofo.visitors.max-visitors-per-node".from(newConfig) 33 | } 34 | 35 | val notificationInterval: Duration by config { 36 | "jicofo.visitors.notification-interval".from(newConfig) 37 | } 38 | 39 | val autoEnableBroadcast: Boolean by config { 40 | "jicofo.visitors.auto-enable-broadcast".from(newConfig) 41 | } 42 | 43 | val requireMucConfigFlag: Boolean by config { 44 | "jicofo.visitors.require-muc-config-flag".from(newConfig) 45 | } 46 | 47 | val enableLiveRoom: Boolean by config { 48 | "jicofo.visitors.enable-live-room".from(newConfig) 49 | } 50 | 51 | companion object { 52 | @JvmField 53 | val config = VisitorsConfig() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /jicofo/src/main/kotlin/org/jitsi/jicofo/jigasi/JigasiConfig.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2020 - present 8x8, Inc 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.jigasi 19 | 20 | import org.jitsi.config.JitsiConfig.Companion.legacyConfig 21 | import org.jitsi.config.JitsiConfig.Companion.newConfig 22 | import org.jitsi.jicofo.xmpp.XmppConnectionEnum 23 | import org.jitsi.metaconfig.config 24 | import org.jitsi.metaconfig.optionalconfig 25 | import org.jxmpp.jid.EntityBareJid 26 | import org.jxmpp.jid.impl.JidCreate 27 | 28 | class JigasiConfig private constructor() { 29 | val breweryJid: EntityBareJid? by optionalconfig { 30 | "org.jitsi.jicofo.jigasi.BREWERY".from(legacyConfig).convertFrom { 31 | JidCreate.entityBareFrom(it) 32 | } 33 | "jicofo.jigasi.brewery-jid".from(newConfig).convertFrom { 34 | JidCreate.entityBareFrom(it) 35 | } 36 | } 37 | 38 | val xmppConnectionName: XmppConnectionEnum by config { 39 | "jicofo.jigasi.xmpp-connection-name".from(newConfig) 40 | } 41 | 42 | val privateAddressConnectivity: Boolean by config { 43 | "jicofo.jigasi.use-private-address-connectivity".from(newConfig) 44 | } 45 | 46 | fun xmppConnectionName() = xmppConnectionName 47 | 48 | companion object { 49 | @JvmField 50 | val config = JigasiConfig() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/colibri/ParticipantInfo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2021 - present 8x8, Inc 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.bridge.colibri 19 | 20 | import org.jitsi.utils.OrderedJsonObject 21 | 22 | /** 23 | * Represents the information for a specific participant/endpoint needed for colibri2. 24 | */ 25 | class ParticipantInfo( 26 | parameters: ParticipantAllocationParameters, 27 | var session: Colibri2Session 28 | ) { 29 | val id = parameters.id 30 | val statsId = parameters.statsId 31 | val useSctp = parameters.useSctp 32 | val medias = parameters.medias 33 | val supportsPrivateAddresses = parameters.supportsPrivateAddresses 34 | val useSsrcRewriting = parameters.useSsrcRewriting 35 | val visitor = parameters.visitor 36 | 37 | var audioMuted = parameters.forceMuteAudio 38 | var videoMuted = parameters.forceMuteVideo 39 | var sources = parameters.sources 40 | 41 | fun toJson() = OrderedJsonObject().apply { 42 | put("id", id) 43 | put("stats_id", statsId.toString()) 44 | put("sources", sources.toJson()) 45 | put("bridge", session.bridge.jid.resourceOrNull.toString()) 46 | put("audio_muted", audioMuted) 47 | put("video_muted", videoMuted) 48 | put("private_addresses", supportsPrivateAddresses) 49 | put("ssrc_rewriting", useSsrcRewriting) 50 | put("visitor", visitor) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /jicofo/src/assembly/archive.xml: -------------------------------------------------------------------------------- 1 | 5 | archive 6 | 7 | zip 8 | 9 | true 10 | ${project.artifactId}-${project.version} 11 | 12 | 13 | lib 14 | runtime 15 | false 16 | false 17 | 18 | 19 | 20 | 21 | ${project.basedir}/target/${project.artifactId}-${project.version}.jar 22 | ${file.separator} 23 | jicofo.jar 24 | 25 | 26 | 27 | 28 | ${project.build.directory} 29 | ${file.separator} 30 | 31 | *.jar 32 | 33 | 34 | *-with-dependencies.jar 35 | ${project.artifactId}-${project.version}.jar 36 | 37 | 38 | 39 | ${project.basedir}/lib 40 | lib 41 | 42 | **/*.jar 43 | 44 | 45 | 46 | ${project.parent.basedir}/resources/ 47 | ${file.separator} 48 | 755 49 | 50 | jicofo.sh 51 | jicofo.bat 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/BridgeMucDetector.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2018-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.bridge 19 | 20 | import org.jitsi.jicofo.xmpp.BaseBrewery 21 | import org.jitsi.jicofo.xmpp.XmppProvider 22 | import org.jitsi.utils.logging2.LoggerImpl 23 | import org.jitsi.xmpp.extensions.colibri.ColibriStatsExtension 24 | import org.jxmpp.jid.EntityBareJid 25 | import org.jxmpp.jid.EntityFullJid 26 | import org.jxmpp.jid.Jid 27 | 28 | /** 29 | * Detects jitsi-videobridge instances through a MUC. 30 | * 31 | * @author Boris Grozev 32 | */ 33 | class BridgeMucDetector( 34 | xmppProvider: XmppProvider, 35 | /** 36 | * The [BridgeSelector] instance which will be notified when new jitsi-videobridge instances are detected, or when 37 | * they update their status. 38 | */ 39 | private val bridgeSelector: BridgeSelector, 40 | breweryJid: EntityBareJid 41 | ) : BaseBrewery( 42 | xmppProvider, 43 | breweryJid, 44 | ColibriStatsExtension.ELEMENT, 45 | ColibriStatsExtension.NAMESPACE, 46 | LoggerImpl(BridgeMucDetector::class.simpleName).apply { addContext("type", "bridge") } 47 | ) { 48 | 49 | override fun onInstanceStatusChanged(jid: EntityFullJid, stats: ColibriStatsExtension) { 50 | bridgeSelector.addJvbAddress(jid, stats) 51 | } 52 | 53 | override fun notifyInstanceOffline(jid: Jid) = bridgeSelector.removeJvbAddress(jid) 54 | } 55 | -------------------------------------------------------------------------------- /jicofo/src/main/kotlin/org/jitsi/jicofo/ktor/RestConfig.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2022 - present 8x8, Inc 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.ktor 19 | 20 | import org.jitsi.config.JitsiConfig 21 | import org.jitsi.metaconfig.config 22 | 23 | class RestConfig private constructor() { 24 | val port: Int by config { 25 | "jicofo.rest.port".from(JitsiConfig.newConfig) 26 | } 27 | 28 | val host: String by config { 29 | "jicofo.rest.host".from(JitsiConfig.newConfig) 30 | } 31 | 32 | private val enabledProp: Boolean by config { 33 | "jicofo.rest.enabled".from(JitsiConfig.newConfig) 34 | } 35 | 36 | val enabled = enabledProp && port > 0 37 | 38 | val enablePrometheus: Boolean by config { 39 | "jicofo.rest.prometheus.enabled".from(JitsiConfig.newConfig) 40 | } 41 | 42 | val enableConferenceRequest: Boolean by config { 43 | "jicofo.rest.conference-request.enabled".from(JitsiConfig.newConfig) 44 | } 45 | 46 | val enableMoveEndpoints: Boolean by config { 47 | "jicofo.rest.move-endpoints.enabled".from(JitsiConfig.newConfig) 48 | } 49 | 50 | val enableDebug: Boolean by config { 51 | "jicofo.rest.debug.enabled".from(JitsiConfig.newConfig) 52 | } 53 | 54 | val pinEnabled: Boolean by config { 55 | "jicofo.rest.pin.enabled".from(JitsiConfig.newConfig) 56 | } 57 | 58 | companion object { 59 | @JvmField 60 | val config = RestConfig() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /jicofo/src/test/kotlin/org/jitsi/jicofo/mock/MockXmppConnection.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2022 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.jicofo.mock 17 | 18 | import io.mockk.every 19 | import io.mockk.mockk 20 | import org.jivesoftware.smack.AbstractXMPPConnection 21 | import org.jivesoftware.smack.packet.IQ 22 | import org.jivesoftware.smack.packet.Stanza 23 | import org.jivesoftware.smack.packet.StanzaFactory 24 | import org.jivesoftware.smack.packet.id.StanzaIdSource 25 | 26 | open class MockXmppConnection { 27 | val xmppConnection: AbstractXMPPConnection = mockk(relaxed = true) { 28 | every { createStanzaCollectorAndSend(any()) } answers { 29 | val request = arg(0) 30 | mockk(relaxed = true) { 31 | every { nextResult() } answers { handleIq(request) } 32 | } 33 | } 34 | 35 | every { trySendStanza(any()) } answers { 36 | val request = arg(0) 37 | if (request is IQ) handleIq(request) 38 | true 39 | } 40 | 41 | every { sendStanza(any()) } answers { 42 | val request = arg(0) 43 | if (request is IQ) handleIq(request) 44 | } 45 | 46 | val stanzaIdSource = object : StanzaIdSource { 47 | private var stanzaId = 0 48 | override fun getNewStanzaId() = "${stanzaId++}" 49 | } 50 | every { stanzaFactory } returns StanzaFactory(stanzaIdSource) 51 | } 52 | 53 | open fun handleIq(iq: IQ): IQ? = null 54 | } 55 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/TaskPools.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2020 - present 8x8, Inc 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo 19 | 20 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings 21 | import org.jitsi.utils.concurrent.CustomizableThreadFactory 22 | import java.util.concurrent.ExecutorService 23 | import java.util.concurrent.Executors 24 | import java.util.concurrent.ScheduledExecutorService 25 | 26 | @SuppressFBWarnings("NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR") 27 | class TaskPools { 28 | companion object { 29 | private val defaultIoPool: ExecutorService = 30 | Executors.newCachedThreadPool(CustomizableThreadFactory("Jicofo Global IO Pool", false)) 31 | 32 | @JvmStatic 33 | var ioPool: ExecutorService = defaultIoPool 34 | 35 | fun resetIoPool() { 36 | ioPool = defaultIoPool 37 | } 38 | 39 | private val defaultScheduledPool: ScheduledExecutorService = Executors.newScheduledThreadPool( 40 | 3, 41 | CustomizableThreadFactory("Jicofo Global Scheduled Pool", true) 42 | ) 43 | 44 | @JvmStatic 45 | var scheduledPool: ScheduledExecutorService = defaultScheduledPool 46 | 47 | fun resetScheduledPool() { 48 | scheduledPool = defaultScheduledPool 49 | } 50 | 51 | @JvmStatic 52 | fun shutdown() { 53 | ioPool.shutdown() 54 | scheduledPool.shutdown() 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/xmpp/muc/MemberRole.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jitsi, the OpenSource Java VoIP and Instant Messaging client. 3 | * 4 | * Copyright @ 2015-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.xmpp.muc 19 | 20 | import org.jivesoftware.smackx.muc.MUCAffiliation 21 | import org.jivesoftware.smackx.muc.MUCRole 22 | 23 | /** Indicates roles that a chat room member detains in its containing chat room. */ 24 | enum class MemberRole { 25 | /** A role implying the full set of chat room permissions */ 26 | OWNER, 27 | 28 | /** A role implying moderator permissions. */ 29 | MODERATOR, 30 | 31 | /** A role implying the ability to send to a chat room */ 32 | PARTICIPANT, 33 | 34 | /** A role implying only the ability to watch a chat room. */ 35 | VISITOR; 36 | 37 | companion object { 38 | @JvmStatic 39 | fun fromSmack(mucRole: MUCRole?, mucAffiliation: MUCAffiliation?) = when (mucAffiliation) { 40 | MUCAffiliation.admin -> MODERATOR 41 | MUCAffiliation.owner -> OWNER 42 | else -> when (mucRole) { 43 | MUCRole.moderator -> MODERATOR 44 | MUCRole.participant -> PARTICIPANT 45 | else -> VISITOR 46 | } 47 | } 48 | } 49 | } 50 | 51 | /** Has sufficient rights to moderate (i.e. is MODERATOR or OWNER). */ 52 | fun MemberRole?.hasModeratorRights() = this != null && this <= MemberRole.MODERATOR 53 | fun MemberRole?.hasOwnerRights() = this != null && this <= MemberRole.OWNER 54 | -------------------------------------------------------------------------------- /jicofo/src/test/kotlin/org/jitsi/jicofo/ConferenceIqHandlerTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2021-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo 19 | 20 | import io.kotest.core.spec.style.ShouldSpec 21 | import io.kotest.matchers.types.shouldBeInstanceOf 22 | import io.mockk.every 23 | import io.mockk.mockk 24 | import org.jitsi.jicofo.xmpp.ConferenceIqHandler 25 | import org.jitsi.xmpp.extensions.jitsimeet.ConferenceIq 26 | import org.jivesoftware.smack.packet.IQ 27 | import org.jxmpp.jid.impl.JidCreate 28 | 29 | class ConferenceIqHandlerTest : ShouldSpec() { 30 | private val conferenceIqHandler = ConferenceIqHandler( 31 | xmppProvider = mockk(relaxed = true), 32 | focusManager = mockk { 33 | every { getConference(any()) } returns null 34 | every { conferenceRequest(any(), any()) } returns true 35 | }, 36 | focusAuthJid = "", 37 | authAuthority = null, 38 | jigasiEnabled = false, 39 | visitorsManager = mockk(relaxed = true) 40 | ) 41 | 42 | init { 43 | context("Handling a ConferenceIQ") { 44 | val conferenceIq = ConferenceIq().apply { 45 | this.from = from 46 | room = JidCreate.entityBareFrom("testRoom@example.com") 47 | to = JidCreate.from("example.com") 48 | type = IQ.Type.set 49 | } 50 | 51 | conferenceIqHandler.handleConferenceIq(conferenceIq).shouldBeInstanceOf() 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/IntraRegionBridgeSelectionStrategy.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2018 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.jicofo.bridge 17 | 18 | /** 19 | * Implements a [BridgeSelectionStrategy] which selects bridges in a single 20 | * region, but uses multiple bridges for load balancing. 21 | */ 22 | class IntraRegionBridgeSelectionStrategy : BridgeSelectionStrategy() { 23 | override fun doSelect( 24 | bridges: List, 25 | conferenceBridges: Map, 26 | participantProperties: ParticipantProperties, 27 | ): Bridge? { 28 | val participantRegion = participantProperties.region 29 | if (bridges.isEmpty()) { 30 | return null 31 | } 32 | if (conferenceBridges.isEmpty()) { 33 | // Try to match the participant region for the initial selection 34 | return notLoadedInRegion(bridges, conferenceBridges, participantProperties, participantRegion) 35 | ?: leastLoaded(bridges, conferenceBridges, participantProperties) 36 | } 37 | val conferenceRegion = conferenceBridges.keys.first().region 38 | return notLoadedAlreadyInConferenceInRegion(bridges, conferenceBridges, participantProperties, conferenceRegion) 39 | ?: notLoadedInRegion(bridges, conferenceBridges, participantProperties, conferenceRegion) 40 | ?: leastLoadedAlreadyInConferenceInRegion( 41 | bridges, 42 | conferenceBridges, 43 | participantProperties, 44 | conferenceRegion 45 | ) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /jicofo-selector/spotbugs-exclude.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /resources/build_deb_package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | set -e 4 | 5 | # Make sure you have the following environment variables set, example: 6 | # export DEBFULLNAME="Jitsi Team" 7 | # export DEBEMAIL="dev@jitsi.org" 8 | # You need package devscripts installed (command dch). 9 | 10 | echo "===================================================================" 11 | echo " Building DEB packages... " 12 | echo "===================================================================" 13 | 14 | SCRIPT_FOLDER=$(dirname "$0") 15 | cd "$SCRIPT_FOLDER/.." 16 | 17 | # We are still using old version numbers 18 | VERSIONMAJ="1.0" 19 | VERSION=$VERSIONMAJ-$BUILD_NUMBER 20 | 21 | # TODO: uncoment the following and move to the new numbering scheme 22 | ## Let's get version from maven 23 | #MVNVER=$(xmllint --xpath "/*[local-name()='project']/*[local-name()='version']/text()" pom.xml) 24 | #TAG_NAME="v${MVNVER/-SNAPSHOT/}" 25 | 26 | #echo "Current tag name: $TAG_NAME" 27 | 28 | #if ! git rev-parse "$TAG_NAME" >/dev/null 2>&1 29 | #then 30 | # git tag -a "$TAG_NAME" -m "Tagged automatically by Jenkins" 31 | # git push origin "$TAG_NAME" 32 | #else 33 | # echo "Tag: $TAG_NAME already exists." 34 | #fi 35 | 36 | #VERSION_FULL=$(git describe --match "v[0-9\.]*" --long) 37 | #echo "Full version: ${VERSION_FULL}" 38 | 39 | #VERSION=${VERSION_FULL:1} 40 | echo "Package version: ${VERSION}" 41 | 42 | REV=$(git log --pretty=format:'%h' -n 1) 43 | dch -v "$VERSION-1" "Build from git. $REV" 44 | dch -D unstable -r "" 45 | 46 | # sets the version in the pom file so it will propagte to resulting jar 47 | mvn versions:set -DnewVersion="${VERSION}" 48 | 49 | # Install jicofo-common and jicofo-selector to the local maven repo because they 50 | # are currently not published anywhere else. 51 | mvn install 52 | 53 | # We need to make sure all dependencies are downloaded before start building 54 | # the debian package 55 | mvn dependency:resolve 56 | 57 | # now build the deb 58 | dpkg-buildpackage -tc -us -uc -A 59 | 60 | # clean the current changes as dch had changed the change log 61 | git checkout debian/changelog 62 | git checkout pom.xml 63 | 64 | echo "Here are the resulting files in $(pwd ..)" 65 | echo "-----" 66 | ls -l ../{*.changes,*.deb,*.buildinfo} 67 | echo "-----" 68 | 69 | # Let's try deploying 70 | cd .. 71 | ([ ! -x deploy.sh ] || ./deploy.sh "jicofo" ${BUILD_NUMBER} ) 72 | -------------------------------------------------------------------------------- /jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/SingleBridgeSelectionStrategy.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2018 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.jicofo.bridge 17 | 18 | import org.jitsi.utils.logging2.Logger 19 | import org.jitsi.utils.logging2.LoggerImpl 20 | 21 | /** 22 | * A [BridgeSelectionStrategy] implementation which keeps all 23 | * participants in a conference on the same bridge. 24 | */ 25 | class SingleBridgeSelectionStrategy : BridgeSelectionStrategy() { 26 | /** 27 | * {@inheritDoc} 28 | * 29 | * Always selects the bridge already used by the conference. 30 | */ 31 | override fun doSelect( 32 | bridges: List, 33 | conferenceBridges: Map, 34 | participantProperties: ParticipantProperties 35 | ): Bridge? { 36 | if (conferenceBridges.isEmpty()) { 37 | return leastLoadedInRegion(bridges, emptyMap(), participantProperties, participantProperties.region) 38 | ?: leastLoaded(bridges, emptyMap(), participantProperties) 39 | } else if (conferenceBridges.size != 1) { 40 | logger.error("Unexpected number of bridges with SingleBridgeSelectionStrategy: ${conferenceBridges.size}") 41 | return null 42 | } 43 | val bridge = conferenceBridges.keys.first() 44 | if (!bridge.isOperational) { 45 | logger.error("The conference already has a bridge, but it is not operational: bridge=$bridge") 46 | return null 47 | } 48 | return bridge 49 | } 50 | 51 | companion object { 52 | /** 53 | * The logger. 54 | */ 55 | private val logger: Logger = LoggerImpl(SingleBridgeSelectionStrategy::class.java.name) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/xmpp/muc/RoomMetadata.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2024-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.xmpp.muc 19 | 20 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties 21 | import com.fasterxml.jackson.core.JsonParser 22 | import com.fasterxml.jackson.core.JsonProcessingException 23 | import com.fasterxml.jackson.databind.JsonMappingException 24 | import com.fasterxml.jackson.databind.MapperFeature 25 | import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper 26 | import com.fasterxml.jackson.module.kotlin.readValue 27 | 28 | /** 29 | * The JSON structure included in the MUC config form from the room_metadata prosody module in jitsi-meet. Includes 30 | * only the fields that we need here in jicofo. 31 | */ 32 | @JsonIgnoreProperties(ignoreUnknown = true) 33 | data class RoomMetadata( 34 | val type: String, 35 | val metadata: Metadata? 36 | ) { 37 | @JsonIgnoreProperties(ignoreUnknown = true) 38 | data class Metadata(val visitors: Visitors?, val startMuted: StartMuted?) { 39 | @JsonIgnoreProperties(ignoreUnknown = true) 40 | data class Visitors(val live: Boolean?) 41 | 42 | @JsonIgnoreProperties(ignoreUnknown = true) 43 | data class StartMuted(val audio: Boolean?, val video: Boolean?) 44 | } 45 | 46 | companion object { 47 | private val mapper = jacksonObjectMapper().apply { 48 | enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) 49 | enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION) 50 | } 51 | 52 | @Throws(JsonProcessingException::class, JsonMappingException::class) 53 | fun parse(string: String): RoomMetadata { 54 | return mapper.readValue(string) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /jicofo/src/main/kotlin/org/jitsi/jicofo/ktor/exception/Exceptions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2024 - present 8x8, Inc 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.ktor.exception 19 | 20 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings 21 | import io.ktor.http.ContentType 22 | import io.ktor.http.HttpStatusCode 23 | import io.ktor.server.application.ApplicationCall 24 | import io.ktor.server.response.respondText 25 | 26 | sealed class JicofoKtorException(message: String?) : RuntimeException(message) 27 | open class BadRequest(message: String? = null) : JicofoKtorException("Bad request: ${message ?: ""}") 28 | class NotFound(message: String?) : JicofoKtorException("Not found: ${message ?: ""}") 29 | class Forbidden(message: String? = null) : JicofoKtorException("Forbidden: ${message ?: ""}") 30 | class InternalError(message: String? = null) : JicofoKtorException("Internal error: ${message ?: ""}") 31 | 32 | @SuppressFBWarnings(value = ["NP_METHOD_PARAMETER_TIGHTENS_ANNOTATION"], justification = "False positive") 33 | class MissingParameter(parameter: String) : BadRequest("Missing parameter: $parameter") 34 | 35 | object ExceptionHandler { 36 | suspend fun handle(call: ApplicationCall, cause: Throwable) { 37 | when (cause) { 38 | is BadRequest -> call.respond(HttpStatusCode.BadRequest, cause.message) 39 | is Forbidden -> call.respond(HttpStatusCode.Forbidden) 40 | is InternalError -> call.respond(HttpStatusCode.InternalServerError, cause.message) 41 | else -> call.respond(HttpStatusCode.InternalServerError, cause.message) 42 | } 43 | } 44 | 45 | private suspend fun ApplicationCall.respond(status: HttpStatusCode, message: String? = null) { 46 | respondText(ContentType.Text.Plain, status) { message ?: "Error" } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /jicofo/src/test/kotlin/org/jitsi/jicofo/JicofoConfigTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2020 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.jicofo 17 | 18 | import io.kotest.core.spec.style.ShouldSpec 19 | import io.kotest.matchers.shouldBe 20 | import org.jitsi.config.withLegacyConfig 21 | import org.jitsi.config.withNewConfig 22 | import org.jitsi.jicofo.JicofoConfig.Companion.config 23 | 24 | class JicofoConfigTest : ShouldSpec() { 25 | init { 26 | context("Local region config") { 27 | context("With no config") { 28 | config.localRegion shouldBe null 29 | } 30 | context("With legacy config") { 31 | withLegacyConfig("org.jitsi.jicofo.BridgeSelector.LOCAL_REGION=legacy") { 32 | config.localRegion shouldBe "legacy" 33 | } 34 | } 35 | context("With new config") { 36 | withNewConfig("jicofo { local-region=new }") { 37 | config.localRegion shouldBe "new" 38 | } 39 | } 40 | context("With both new and legacy") { 41 | withNewConfig("jicofo { local-region=new }") { 42 | withLegacyConfig("org.jitsi.jicofo.BridgeSelector.LOCAL_REGION=legacy") { 43 | config.localRegion shouldBe "legacy" 44 | } 45 | } 46 | } 47 | } 48 | 49 | context("SCTP") { 50 | context("Should be enabled by default") { 51 | config.enableSctp shouldBe true 52 | } 53 | context("Should be disabled with new config") { 54 | withNewConfig("jicofo { sctp { enabled=false } }") { 55 | config.enableSctp shouldBe false 56 | } 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/xmpp/jingle/JingleStats.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2015-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package org.jitsi.jicofo.xmpp.jingle 20 | 21 | import org.jitsi.jicofo.metrics.JicofoMetricsContainer 22 | import org.jitsi.metrics.CounterMetric 23 | import org.jitsi.xmpp.extensions.jingle.JingleAction 24 | import org.json.simple.JSONObject 25 | import java.util.concurrent.ConcurrentHashMap 26 | 27 | class JingleStats { 28 | companion object { 29 | private val stanzasReceivedByAction: MutableMap = ConcurrentHashMap() 30 | private val stanzasSentByAction: MutableMap = ConcurrentHashMap() 31 | 32 | @JvmStatic 33 | fun stanzaReceived(action: JingleAction) { 34 | stanzasReceivedByAction.computeIfAbsent(action) { 35 | JicofoMetricsContainer.instance.registerCounter( 36 | "jingle_${action.name.lowercase()}_received", 37 | "Number of ${action.name} stanzas received" 38 | ) 39 | }.inc() 40 | } 41 | 42 | @JvmStatic 43 | fun stanzaSent(action: JingleAction) { 44 | stanzasSentByAction.computeIfAbsent(action) { 45 | JicofoMetricsContainer.instance.registerCounter( 46 | "jingle_${action.name.lowercase()}_sent", 47 | "Number of ${action.name} stanzas sent." 48 | ) 49 | }.inc() 50 | } 51 | 52 | @JvmStatic 53 | fun toJson() = JSONObject().apply { 54 | this["sent"] = stanzasSentByAction.map { it.key to it.value.get() }.toMap() 55 | this["received"] = stanzasReceivedByAction.map { it.key to it.value.get() }.toMap() 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /jicofo-common/spotbugs-exclude.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /jicofo/spotbugs-exclude.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /jicofo/src/test/kotlin/org/jitsi/jicofo/ConferenceConfigTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2020 - present 8x8, Inc 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo 19 | 20 | import io.kotest.core.spec.style.ShouldSpec 21 | import io.kotest.matchers.shouldBe 22 | import org.jitsi.config.withNewConfig 23 | 24 | class ConferenceConfigTest : ShouldSpec() { 25 | init { 26 | context("source-signaling-delays") { 27 | context("With no delay configured") { 28 | for (i in 0..100) { 29 | ConferenceConfig.config.getSourceSignalingDelayMs(i) shouldBe 0 30 | } 31 | } 32 | context("With delay configured") { 33 | withNewConfig( 34 | """ 35 | jicofo.conference.source-signaling-delays { 36 | // Intentionally out of order 37 | 200 = 1000, 38 | 100 = 500, 39 | 300 = 2000 40 | } 41 | """.trimIndent() 42 | ) { 43 | ConferenceConfig.config.apply { 44 | getSourceSignalingDelayMs(0) shouldBe 0 45 | getSourceSignalingDelayMs(50) shouldBe 0 46 | getSourceSignalingDelayMs(99) shouldBe 0 47 | getSourceSignalingDelayMs(100) shouldBe 500 48 | getSourceSignalingDelayMs(199) shouldBe 500 49 | getSourceSignalingDelayMs(200) shouldBe 1000 50 | getSourceSignalingDelayMs(299) shouldBe 1000 51 | getSourceSignalingDelayMs(300) shouldBe 2000 52 | getSourceSignalingDelayMs(5000) shouldBe 2000 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /jicofo/src/main/kotlin/org/jitsi/jicofo/util/RateLimit.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2015-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package org.jitsi.jicofo.util 20 | 21 | import org.jitsi.utils.secs 22 | import java.time.Clock 23 | import java.time.Duration 24 | import java.time.Instant 25 | import java.util.Deque 26 | import java.util.LinkedList 27 | 28 | /** 29 | * Rate limiting which works as follows: 30 | * - must be at least [minInterval] gap between the requests 31 | * - no more than [maxRequests] requests within the [interval] 32 | */ 33 | class RateLimit( 34 | /** Never accept a request unless at least [minInterval] has passed since the last request */ 35 | private val minInterval: Duration = 10.secs, 36 | /** Accept at most [maxRequests] per [interval]. */ 37 | private val maxRequests: Int = 3, 38 | /** Accept at most [maxRequests] per [interval]. */ 39 | private val interval: Duration = 60.secs, 40 | private val clock: Clock = Clock.systemUTC() 41 | ) { 42 | /** Stores the timestamps of requests that have been received. */ 43 | private val requests: Deque = LinkedList() 44 | 45 | /** Return true if the request should be accepted and false otherwise. */ 46 | fun accept(): Boolean { 47 | val now = clock.instant() 48 | val previousRequest = requests.peekLast() 49 | if (previousRequest == null) { 50 | requests.add(now) 51 | return true 52 | } 53 | 54 | if (Duration.between(previousRequest, now) < minInterval) { 55 | return false 56 | } 57 | 58 | // Allow only [maxRequests] requests within the last [interval] 59 | requests.removeIf { Duration.between(it, now) > interval } 60 | if (requests.size >= maxRequests) { 61 | return false 62 | } 63 | requests.add(now) 64 | return true 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /doc/health-checks.md: -------------------------------------------------------------------------------- 1 | The REST API allows querying Jicofo whether it deems itself in a healthy state (i.e. the application is operational and the functionality it provides should perform as expected) at the time of the query or not. Videobridge will run an internal test in response to the request to determine its current health status. 2 | 3 | The default port for the REST API is `8888` to have Videobridge and Jicofo running on the same machine with their defaults without them clashing. 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 28 | 29 |
HTTP MethodResourceResponse
GET/about/health 15 |
    16 |
  • 200 OK if Jicofo ran its internal test to determine its current health status and the test completed successfully. The response is with Content-Type: application/json but no JSON value is provided i.e. Content-Length: 0.
  • 17 |
  • 503 Service Unavailable if Jicofo is currently unable to run its health check. The reasons for the failure include (but are not limited to): 18 |
      19 |
    • The REST API server has been brought up but the core conference-related functionality of Jicofo is still initializing. Videbridge could be considered healthy if the client chooses to interpret the HTTP status in such a fashion since the application may pass though the described transitional state under normal operation.
    • 20 |
    • The core conference-related functionality of Jicofo has commenced a shutdown procedure but the REST API server has not shut down yet. Videbridge could be considered healthy if the client chooses to interpret the HTTP status in such a fashion since the application may pass though the described transitional state under normal operation.
    • 21 |
    • The core conference-related functionality of Jicofo is in an unhealthy state and the REST API server is operating as expected.
    • 22 |
    23 |
  • 24 |
  • 500 Internal Server Error or any other 5xx status code if Jicofo ran its health check and determined that it is in an unhealthy state.
  • 25 |
  • No response (within a client-defined time frame) if Jicofo has entered a deadlock, an infinite loop, or a similar condition which prevents the completion of the health check. The server is to be considered in an unhealthy state by the client.
  • 26 |
27 |
30 | -------------------------------------------------------------------------------- /doc/conference-request.md: -------------------------------------------------------------------------------- 1 | # 2 | Conference-request refers to an initial exchange between a client and jicofo, which happens before the client joins 3 | the conference MUC. Its purposes are to: 4 | 1. Notify jicofo that it should join a certain MUC. 5 | 2. Have the client block until jicofo has created/joined the MUC (in case the client is not allowed to create a MUC). 6 | 3. Allow the first client in a conference to specify certain conference-wide options to jicofo. 7 | 4. Allow jicofo to redirect the client to a visitor node. 8 | 5. Allow external authentication with an XMPP domain or [Shibboleth](./shibboleth.md) 9 | 10 | # Format 11 | The request and response share a similar format. 12 | 13 | ## HTTP format 14 | For HTTP, the conference request is encoded as JSON and POSTed to /conference-request/v1. Note that authentication is 15 | not supported over HTTP due to the lack of an associated JID. 16 | 17 | The reason HTTP support was added is to prevent unnecessary connections to prosody when visitor redirection is used 18 | (i.e. a visitor sends an HTTP request, then logs into the XMPP server specified in the response) 19 | 20 | ### Example request 21 | ```js 22 | { 23 | "room": "conferenceName@example.com", // The JID of the conference 24 | "properties": { 25 | "rtcstatsEnabled": true 26 | } 27 | } 28 | ``` 29 | ### Example response 30 | ```js 31 | { 32 | "ready": true, // Jicofo has joined the room and the client can proceed. 33 | "focusJid": "jicofo@v1.example.com", // Indicates jicofo's JID, which can be trusted by the client 34 | "vnode": "v1", // Redirect to a visitor node with id "v1" 35 | "properties": { 36 | "sipGatewayEnabled": true // Signal jigasi support 37 | } 38 | } 39 | ``` 40 | 41 | ## XMPP format 42 | For XMPP, the conference request is encoded in a 43 | [ConferenceIq](https://github.com/jitsi/jitsi-xmpp-extensions/blob/master/src/main/java/org/jitsi/xmpp/extensions/jitsimeet/ConferenceIq.java). 44 | 45 | ### Example request 46 | ```xml 47 | 48 | 49 | 50 | 51 | 52 | ``` 53 | 54 | ### Example response 55 | ```xml 56 | 57 | 58 | " 59 | 60 | 61 | ``` 62 | -------------------------------------------------------------------------------- /debian/init.d: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # 3 | # INIT script for Jitsi Conference Focus 4 | # Version: 1.0 4-Dec-2014 pawel.domas@jitsi.org 5 | # 6 | ### BEGIN INIT INFO 7 | # Provides: jicofo 8 | # Required-Start: $local_fs $remote_fs 9 | # Required-Stop: $local_fs $remote_fs 10 | # Default-Start: 2 3 4 5 11 | # Default-Stop: 0 1 6 12 | # Short-Description: Jitsi conference Focus 13 | # Description: Conference focus for Jitsi Meet application. 14 | ### END INIT INFO 15 | 16 | . /lib/lsb/init-functions 17 | 18 | # Include jicofo defaults if available 19 | if [ -f /etc/jitsi/jicofo/config ]; then 20 | . /etc/jitsi/jicofo/config 21 | fi 22 | 23 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 24 | DAEMON=/usr/share/jicofo/jicofo.sh 25 | DAEMON_DIR=/usr/share/jicofo/ 26 | NAME=jicofo 27 | USER=jicofo 28 | PIDFILE=/var/run/jicofo.pid 29 | LOGFILE=/var/log/jitsi/jicofo.log 30 | DESC=jicofo 31 | 32 | 33 | if [ ! -x $DAEMON ] ;then 34 | echo "Daemon not executable: $DAEMON" 35 | exit 1 36 | fi 37 | 38 | set -e 39 | 40 | stop() { 41 | if [ -f $PIDFILE ]; then 42 | PID=$(cat $PIDFILE) 43 | fi 44 | echo -n "Stopping $NAME: " 45 | if [ $PID ]; then 46 | kill $PID || true 47 | rm $PIDFILE || true 48 | echo "$NAME stopped." 49 | else 50 | echo "$NAME doesn't seem to be running." 51 | fi 52 | } 53 | 54 | start() { 55 | if [ -f $PIDFILE ]; then 56 | echo "$NAME seems to be already running, we found pidfile $PIDFILE." 57 | exit 1 58 | fi 59 | echo -n "Starting $NAME: " 60 | export JICOFO_AUTH_PASSWORD JICOFO_MAX_MEMORY 61 | start-stop-daemon --start --quiet --background --chuid $USER --make-pidfile --pidfile $PIDFILE \ 62 | --exec /bin/bash -- -c "cd $DAEMON_DIR; JAVA_SYS_PROPS=\"$JAVA_SYS_PROPS\" exec $DAEMON $JICOFO_OPTS < /dev/null >> $LOGFILE 2>&1" 63 | echo "$NAME started." 64 | } 65 | 66 | reload() { 67 | echo 'Not yet implemented.' 68 | } 69 | 70 | status() { 71 | status_of_proc -p $PIDFILE java "$NAME" && exit 0 || exit $? 72 | } 73 | 74 | case "$1" in 75 | start) 76 | start 77 | ;; 78 | stop) 79 | stop 80 | ;; 81 | restart) 82 | stop 83 | start 84 | ;; 85 | reload) 86 | reload 87 | ;; 88 | force-reload) 89 | reload 90 | ;; 91 | status) 92 | status 93 | ;; 94 | *) 95 | N=/etc/init.d/$NAME 96 | echo "Usage: $N {start|stop|restart|reload|status}" >&2 97 | exit 1 98 | ;; 99 | esac 100 | 101 | exit 0 102 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/xmpp/XmppCapsStats.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2022-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.xmpp 19 | 20 | import org.jitsi.utils.OrderedJsonObject 21 | import org.jitsi.utils.logging2.createLogger 22 | 23 | class XmppCapsStats { 24 | companion object { 25 | /** 26 | * Maps a nodeVer string (the "node" and "ver" attributes from a caps extension (XEP-0115) joined by "#") to 27 | * the associated set of features and a counter for the number of participants with that nodeVer. 28 | */ 29 | private val map: MutableMap = mutableMapOf() 30 | private const val MAX_ENTRIES = 1000 31 | private val logger = createLogger() 32 | 33 | @JvmStatic 34 | val stats: OrderedJsonObject 35 | get() = OrderedJsonObject().apply { 36 | synchronized(map) { 37 | map.forEach { (nodeVer, e) -> 38 | this[nodeVer] = e.json() 39 | } 40 | } 41 | } 42 | 43 | fun update(nodeVer: String, features: Set) { 44 | synchronized(map) { 45 | if (map.size < MAX_ENTRIES) { 46 | map.computeIfAbsent(nodeVer) { FeaturesAndCount(features) }.count++ 47 | } else { 48 | map[nodeVer]?.let { 49 | it.count++ 50 | } ?: logger.warn("Too many entries. Ignoring nodeVer=$nodeVer, features=$features") 51 | } 52 | } 53 | } 54 | } 55 | 56 | private class FeaturesAndCount(val features: Set) { 57 | /** The number of participants seen with this set of features. */ 58 | var count = 0 59 | fun json() = OrderedJsonObject().apply { 60 | this["count"] = count 61 | this["features"] = features.map { it.name } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /jicofo/src/main/kotlin/org/jitsi/jicofo/conference/JitsiMeetConfig.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2022-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.conference 19 | 20 | import org.jitsi.utils.OrderedJsonObject 21 | import org.jitsi.utils.logging2.createLogger 22 | import java.lang.Boolean.parseBoolean 23 | 24 | /** Configuration for a conference included in a conference request */ 25 | class JitsiMeetConfig(properties: Map) { 26 | private val logger = createLogger() 27 | 28 | val rtcStatsEnabled = properties.getBoolean(RTCSTATS_ENABLED, default = true) 29 | val startAudioMuted: Int? = properties.getInt(START_AUDIO_MUTED) 30 | val startVideoMuted: Int? = properties.getInt(START_VIDEO_MUTED) 31 | 32 | private fun Map.getInt(key: String): Int? = get(key).let { 33 | try { 34 | return if (it.isNullOrBlank()) null else Integer.parseInt(it) 35 | } catch (e: NumberFormatException) { 36 | logger.warn("Failed to parse the value of $key as an integer.") 37 | return null 38 | } 39 | } 40 | 41 | val debugState: OrderedJsonObject 42 | get() = OrderedJsonObject().apply { 43 | put("rtcstatsEnabled", rtcStatsEnabled) 44 | put("startAudioMuted", startAudioMuted ?: "null") 45 | put("startVideoMuted", startVideoMuted ?: "null") 46 | } 47 | 48 | // Needed for createLogger() to work. 49 | companion object 50 | } 51 | 52 | private fun Map.getBoolean(key: String, default: Boolean): Boolean = get(key).let { 53 | return if (it.isNullOrBlank()) default else parseBoolean(it) 54 | } 55 | 56 | /** The name of the start muted property for audio. */ 57 | const val START_AUDIO_MUTED = "startAudioMuted" 58 | 59 | /** The name of the start muted property for video. */ 60 | const val START_VIDEO_MUTED = "startVideoMuted" 61 | 62 | /** The name of the rtcstats enabled property. */ 63 | const val RTCSTATS_ENABLED = "rtcstatsEnabled" 64 | -------------------------------------------------------------------------------- /jicofo/src/main/kotlin/org/jitsi/jicofo/jibri/JibriStats.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2018 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | package org.jitsi.jicofo.jibri 18 | 19 | import org.jitsi.jicofo.metrics.JicofoMetricsContainer 20 | 21 | /** 22 | * Counts total stats (failures by session type). 23 | */ 24 | class JibriStats { 25 | companion object { 26 | @JvmField 27 | val sipFailures = JicofoMetricsContainer.instance.registerCounter( 28 | "jibri_sip_failures", 29 | "Number of failures for a SIP jibri" 30 | ) 31 | 32 | @JvmField 33 | val recordingFailures = JicofoMetricsContainer.instance.registerCounter( 34 | "jibri_recording_failures", 35 | "Number of failures for a recording jibri" 36 | ) 37 | 38 | @JvmField 39 | val liveStreamingFailures = JicofoMetricsContainer.instance.registerCounter( 40 | "jibri_live_streaming_failures", 41 | "Number of failures for a live-streaming jibri" 42 | ) 43 | 44 | @JvmField 45 | val liveStreamingActive = JicofoMetricsContainer.instance.registerLongGauge( 46 | "jibri_live_streaming_active", 47 | "Current number of active jibris in live-streaming mode" 48 | ) 49 | 50 | @JvmField 51 | val recordingActive = JicofoMetricsContainer.instance.registerLongGauge( 52 | "jibri_recording_active", 53 | "Current number of active jibris in recording mode" 54 | ) 55 | 56 | @JvmField 57 | val sipActive = JicofoMetricsContainer.instance.registerLongGauge( 58 | "jibri_sip_active", 59 | "Current number of active jibris in SIP mode" 60 | ) 61 | 62 | @JvmStatic 63 | fun sessionFailed(type: JibriSession.Type) = when (type) { 64 | JibriSession.Type.SIP_CALL -> sipFailures.inc() 65 | JibriSession.Type.LIVE_STREAMING -> liveStreamingFailures.inc() 66 | JibriSession.Type.RECORDING -> recordingFailures.inc() 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/VisitorSelectionStrategy.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2018 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.jitsi.jicofo.bridge 18 | 19 | /** 20 | * A bridge selection strategy that selects distinct sets of bridges for visitors and participants, when possible. 21 | * The selection strategy for each category of participant is specified independently and invoked recursively. 22 | */ 23 | class VisitorSelectionStrategy : BridgeSelectionStrategy() { 24 | private val participantSelectionStrategy = checkNotNull(BridgeConfig.config.participantSelectionStrategy) { 25 | "participant-selection-strategy must be set when VisitorSelectionStrategy is used" 26 | } 27 | 28 | private val visitorSelectionStrategy = checkNotNull(BridgeConfig.config.visitorSelectionStrategy) { 29 | "visitor-selection-strategy must be set when VisitorSelectionStrategy is used" 30 | } 31 | 32 | override fun doSelect( 33 | bridges: List, 34 | conferenceBridges: Map, 35 | participantProperties: ParticipantProperties 36 | ): Bridge? { 37 | val eligibleBridges = bridges.filter { 38 | val conferenceBridge = conferenceBridges[it] 39 | // The bridge is not in the conference, or it's used for the same type of endpoint 40 | conferenceBridge == null || conferenceBridge.visitor == participantProperties.visitor 41 | } 42 | val conferenceBridgesOfType = conferenceBridges.filter { it.value.visitor == participantProperties.visitor } 43 | 44 | val strategy = if (participantProperties.visitor) { 45 | visitorSelectionStrategy 46 | } else { 47 | participantSelectionStrategy 48 | } 49 | 50 | return strategy.doSelect(eligibleBridges, conferenceBridgesOfType, participantProperties) 51 | /* If there are no available bridges filtered by participant type, allow mixing. */ 52 | ?: strategy.doSelect(bridges, conferenceBridges, participantProperties) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/SplitBridgeSelectionStrategy.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2018 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.jicofo.bridge 17 | 18 | import org.jitsi.utils.logging2.Logger 19 | import org.jitsi.utils.logging2.LoggerImpl 20 | 21 | /** 22 | * Implements a [BridgeSelectionStrategy] which "splits" a conference to as many bridges as possible (always 23 | * selects a bridge not in the conference or with the fewest endpoints from that conference). For testing purposes only. 24 | */ 25 | class SplitBridgeSelectionStrategy : BridgeSelectionStrategy() { 26 | /** 27 | * {@inheritDoc} 28 | * 29 | * Always selects the bridge already used by the conference. 30 | */ 31 | override fun doSelect( 32 | bridges: List, 33 | conferenceBridges: Map, 34 | participantProperties: ParticipantProperties 35 | ): Bridge? { 36 | // If there's any bridge not yet in this conference, use that; otherwise 37 | // find the bridge with the fewest participants 38 | val bridgeNotYetInConf = bridges.firstOrNull { !conferenceBridges.containsKey(it) } 39 | 40 | return bridgeNotYetInConf ?: conferenceBridges.entries 41 | .filter { bridges.contains(it.key) } 42 | .minByOrNull { it.value.participantCount }?.key 43 | } 44 | 45 | override fun select( 46 | bridges: List, 47 | conferenceBridges: Map, 48 | participantProperties: ParticipantProperties, 49 | allowMultiBridge: Boolean 50 | ): Bridge? { 51 | if (!allowMultiBridge) { 52 | logger.warn( 53 | "Force-enabling octo for SplitBridgeSelectionStrategy. To suppress this warning, enable octo" + 54 | " in jicofo.conf." 55 | ) 56 | } 57 | return super.select(bridges, conferenceBridges, participantProperties, true) 58 | } 59 | 60 | companion object { 61 | private val logger: Logger = LoggerImpl(SplitBridgeSelectionStrategy::class.java.name) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /jicofo/src/main/kotlin/org/jitsi/jicofo/jibri/JibriConfig.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2020 - present 8x8, Inc 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.jibri 19 | 20 | import org.jitsi.config.JitsiConfig.Companion.legacyConfig 21 | import org.jitsi.config.JitsiConfig.Companion.newConfig 22 | import org.jitsi.jicofo.xmpp.XmppConnectionEnum 23 | import org.jitsi.metaconfig.config 24 | import org.jitsi.metaconfig.optionalconfig 25 | import org.jxmpp.jid.EntityBareJid 26 | import org.jxmpp.jid.impl.JidCreate 27 | import java.time.Duration 28 | 29 | class JibriConfig private constructor() { 30 | val breweryJid: EntityBareJid? by optionalconfig { 31 | "org.jitsi.jicofo.jibri.BREWERY".from(legacyConfig).convertFrom { 32 | JidCreate.entityBareFrom(it) 33 | } 34 | "jicofo.jibri.brewery-jid".from(newConfig).convertFrom { 35 | JidCreate.entityBareFrom(it) 36 | } 37 | } 38 | 39 | val sipBreweryJid: EntityBareJid? by optionalconfig { 40 | "org.jitsi.jicofo.jibri.SIP_BREWERY".from(legacyConfig).convertFrom { 41 | JidCreate.entityBareFrom(it) 42 | } 43 | "jicofo.jibri-sip.brewery-jid".from(newConfig).convertFrom { 44 | JidCreate.entityBareFrom(it) 45 | } 46 | } 47 | 48 | val pendingTimeout: Duration by config { 49 | "org.jitsi.jicofo.jibri.PENDING_TIMEOUT".from(legacyConfig).convertFrom { Duration.ofSeconds(it) } 50 | "jicofo.jibri.pending-timeout".from(newConfig) 51 | } 52 | 53 | val numRetries: Int by config { 54 | "org.jitsi.jicofo.NUM_JIBRI_RETRIES".from(legacyConfig) 55 | "jicofo.jibri.num-retries".from(newConfig) 56 | } 57 | 58 | val xmppConnectionName: XmppConnectionEnum by config { 59 | "jicofo.jibri.xmpp-connection-name".from(newConfig) 60 | } 61 | 62 | val privateAddressConnectivity: Boolean by config { 63 | "jicofo.jibri.use-private-address-connectivity".from(newConfig) 64 | } 65 | 66 | companion object { 67 | @JvmField 68 | val config = JibriConfig() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /jicofo/src/main/kotlin/org/jitsi/jicofo/xmpp/ConfigurationChangeHandler.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2021-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.xmpp 19 | 20 | import org.jitsi.jicofo.ConferenceStore 21 | import org.jitsi.jicofo.TaskPools 22 | import org.jitsi.utils.logging2.createLogger 23 | import org.jivesoftware.smack.StanzaListener 24 | import org.jivesoftware.smack.filter.MessageTypeFilter 25 | import org.jivesoftware.smack.packet.Message 26 | import org.jivesoftware.smack.packet.Stanza 27 | import org.jivesoftware.smackx.muc.packet.MUCUser 28 | import org.jxmpp.jid.EntityBareJid 29 | 30 | /** 31 | * Handle MUC Notification of Configuration Changes messages. 32 | * See https://xmpp.org/extensions/xep-0045.html#roomconfig-notify 33 | */ 34 | class ConfigurationChangeHandler( 35 | private val xmppProvider: XmppProvider, 36 | private val conferenceStore: ConferenceStore 37 | ) : XmppProvider.Listener, StanzaListener { 38 | private val logger = createLogger() 39 | 40 | init { 41 | xmppProvider.xmppConnection.addSyncStanzaListener(this, MessageTypeFilter.GROUPCHAT) 42 | } 43 | 44 | override fun processStanza(stanza: Stanza) { 45 | if (stanza !is Message) { 46 | logger.error("Not a message") 47 | return 48 | } 49 | 50 | MUCUser.from(stanza)?.let { mucUser -> 51 | // Code 104 is for MUC configuration form changes 52 | if (mucUser.status?.any { it.code == 104 } == true) { 53 | val roomJid = stanza.from 54 | if (roomJid !is EntityBareJid) { 55 | logger.info("An occupant sending status 104?") 56 | return 57 | } 58 | logger.info("Configuration changed for $roomJid") 59 | conferenceStore.getConference(roomJid)?.let { conference -> 60 | TaskPools.ioPool.submit { conference.mucConfigurationChanged() } 61 | } ?: run { 62 | logger.info("Configuration changed for unknown conference.") 63 | } 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/xmpp/Features.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2020 - present 8x8, Inc 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.xmpp 19 | 20 | enum class Features(val value: String) { 21 | AUDIO("urn:xmpp:jingle:apps:rtp:audio"), 22 | VIDEO("urn:xmpp:jingle:apps:rtp:video"), 23 | 24 | /** DTLS/SCTP feature name. */ 25 | SCTP("urn:xmpp:jingle:transports:dtls-sctp:1"), 26 | 27 | /** RTX (RFC4588) support. */ 28 | RTX("urn:ietf:rfc:4588"), 29 | 30 | /** RTCP REMB. */ 31 | REMB("http://jitsi.org/remb"), 32 | 33 | /** Transport-wide congestion control. */ 34 | TCC("http://jitsi.org/tcc"), 35 | 36 | /** Source name signalling. */ 37 | SOURCE_NAMES("http://jitsi.org/source-name"), 38 | SSRC_REWRITING_V1("http://jitsi.org/ssrc-rewriting-1"), 39 | RECEIVE_MULTIPLE_STREAMS("http://jitsi.org/receive-multiple-video-streams"), 40 | 41 | /** Jingle sources encoded as JSON. */ 42 | JSON_SOURCES("http://jitsi.org/json-encoded-sources"), 43 | OPUS_RED("http://jitsi.org/opus-red"), 44 | AUDIO_MUTE("http://jitsi.org/protocol/audio-mute"), 45 | 46 | // The ones below are not used in jicofo, but are defined here to improve the xmpp-caps statistics and avoid logs 47 | // about unknown features. 48 | JIBRI("http://jitsi.org/protocol/jibri"), 49 | JIGASI("http://jitsi.org/protocol/jigasi"), 50 | LIPSYNC("http://jitsi.org/meet/lipsync"), 51 | E2EE("https://jitsi.org/meet/e2ee"), 52 | XMPP_CAPS("http://jabber.org/protocol/caps"), 53 | JINGLE("urn:xmpp:jingle:1"), 54 | JINGLE_RTP("urn:xmpp:jingle:apps:rtp:1"), 55 | JINGLE_ICE("urn:xmpp:jingle:transports:ice-udp:1"), 56 | DTLS("urn:xmpp:jingle:apps:dtls:0"), 57 | RTCPMUX("urn:ietf:rfc:5761"), 58 | BUNDLE("urn:ietf:rfc:5888"), 59 | RAYO("urn:xmpp:rayo:client:1"), 60 | VISITORS_V1("http://jitsi.org/visitors-1"); 61 | 62 | companion object { 63 | val defaultFeatures = setOf(AUDIO, VIDEO, SCTP) 64 | 65 | /** Parse a string and silently return null if not recognized. */ 66 | fun parseString(s: String): Features? = values().find { it.value == s } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/xmpp/Util.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2018 - present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.xmpp 19 | 20 | import org.jitsi.utils.logging2.LoggerImpl 21 | import org.jitsi.xmpp.util.XmlStringBuilderUtil.Companion.toStringOpt 22 | import org.jivesoftware.smack.AbstractXMPPConnection 23 | import org.jivesoftware.smack.SmackException 24 | import org.jivesoftware.smack.XMPPConnection 25 | import org.jivesoftware.smack.packet.IQ 26 | import org.jivesoftware.smack.packet.Stanza 27 | import org.jxmpp.jid.DomainBareJid 28 | import org.jxmpp.jid.Jid 29 | import org.jxmpp.jid.impl.JidCreate 30 | import org.jxmpp.stringprep.XmppStringprepException 31 | 32 | private val logger = LoggerImpl("org.jitsi.jicofo.xmpp.Util") 33 | 34 | /** 35 | * Reads the original jid as encoded in the resource part by mod_client_proxy, returns the original jid if it format 36 | * is not as expected. 37 | */ 38 | fun parseJidFromClientProxyJid( 39 | /** 40 | * The JID of the client_proxy component. 41 | */ 42 | clientProxy: DomainBareJid?, 43 | /** 44 | * The JID to parse. 45 | */ 46 | jid: Jid 47 | ): Jid { 48 | clientProxy ?: return jid 49 | 50 | if (clientProxy == jid.asDomainBareJid()) { 51 | jid.resourceOrNull?.let { resource -> 52 | return try { 53 | JidCreate.from(resource.toString()) 54 | } catch (e: XmppStringprepException) { 55 | jid 56 | } 57 | } 58 | } 59 | return jid 60 | } 61 | 62 | fun XMPPConnection.tryToSendStanza(stanza: Stanza) = try { 63 | sendStanza(stanza) 64 | } catch (e: SmackException.NotConnectedException) { 65 | logger.error("No connection - unable to send packet: ${stanza.toXML().toStringOpt()}", e) 66 | } catch (e: InterruptedException) { 67 | logger.error("Failed to send packet: ${stanza.toXML().toStringOpt()}", e) 68 | } 69 | 70 | @Throws(SmackException.NotConnectedException::class) 71 | fun AbstractXMPPConnection.sendIqAndGetResponse(iq: IQ): IQ? = createStanzaCollectorAndSend(iq).let { 72 | try { 73 | it.nextResult() 74 | } finally { 75 | it.cancel() 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /jicofo/src/test/kotlin/org/jitsi/jicofo/codec/JingleOfferFactoryTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2015-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.codec 19 | 20 | import io.kotest.core.spec.IsolationMode 21 | import io.kotest.core.spec.style.ShouldSpec 22 | import io.kotest.matchers.shouldBe 23 | import io.kotest.matchers.shouldNotBe 24 | import org.jitsi.xmpp.extensions.jingle.ContentPacketExtension 25 | import org.jitsi.xmpp.extensions.jingle.PayloadTypePacketExtension 26 | import org.jitsi.xmpp.extensions.jingle.RtpDescriptionPacketExtension 27 | import org.jitsi.jicofo.codec.JingleOfferFactory.INSTANCE as jingleOfferFactory 28 | 29 | class JingleOfferFactoryTest : ShouldSpec() { 30 | override fun isolationMode(): IsolationMode? = IsolationMode.SingleInstance 31 | init { 32 | context("With default options") { 33 | val offer = jingleOfferFactory.createOffer(OfferOptions()) 34 | 35 | offer.find { it.name == "audio" } shouldNotBe null 36 | offer.find { it.name == "data" } shouldNotBe null 37 | 38 | val videoContent = offer.find { it.name == "video" } 39 | videoContent shouldNotBe null 40 | videoContent!!.containsRtx() shouldBe true 41 | } 42 | context("Without audio and data") { 43 | val offer = jingleOfferFactory.createOffer(OfferOptions(audio = false, sctp = false)) 44 | 45 | offer.find { it.name == "audio" } shouldBe null 46 | offer.find { it.name == "video" } shouldNotBe null 47 | offer.find { it.name == "data" } shouldBe null 48 | } 49 | context("Without RTX") { 50 | val offer = jingleOfferFactory.createOffer(OfferOptions(rtx = false)) 51 | val videoContent = offer.find { it.name == "video" } 52 | 53 | videoContent shouldNotBe null 54 | videoContent!!.containsRtx() shouldBe false 55 | } 56 | } 57 | 58 | private fun ContentPacketExtension.containsRtx() = 59 | getChildExtensionsOfType(RtpDescriptionPacketExtension::class.java).any { 60 | it.getChildExtensionsOfType(PayloadTypePacketExtension::class.java).any { it.name == "rtx" } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/xmpp/jingle/JingleIqRequestHandler.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2015-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.xmpp.jingle 19 | 20 | import org.jitsi.jicofo.util.WeakValueMap 21 | import org.jitsi.jicofo.xmpp.AbstractIqHandler 22 | import org.jitsi.jicofo.xmpp.IqProcessingResult 23 | import org.jitsi.jicofo.xmpp.IqProcessingResult.RejectedWithError 24 | import org.jitsi.jicofo.xmpp.IqRequest 25 | import org.jitsi.utils.logging2.createLogger 26 | import org.jitsi.xmpp.extensions.jingle.JingleIQ 27 | import org.jivesoftware.smack.AbstractXMPPConnection 28 | import org.jivesoftware.smack.packet.IQ 29 | import org.jivesoftware.smack.packet.StanzaError 30 | 31 | /** 32 | * Maintain a weak map of [JingleSession]s and route incoming Jingle IQs to the associated session. 33 | * @author Pawel Domas 34 | */ 35 | class JingleIqRequestHandler( 36 | connections: Set 37 | ) : AbstractIqHandler(connections, JingleIQ.ELEMENT, JingleIQ.NAMESPACE, setOf(IQ.Type.set)) { 38 | private val logger = createLogger() 39 | 40 | /** The list of active Jingle sessions. */ 41 | private val sessions = WeakValueMap() 42 | 43 | override fun handleRequest(request: IqRequest): IqProcessingResult { 44 | val session = sessions.get(request.iq.sid) 45 | if (session == null) { 46 | logger.warn("No session found for SID ${request.iq.sid}") 47 | return RejectedWithError( 48 | IQ.createErrorResponse( 49 | request.iq, 50 | StanzaError.getBuilder(StanzaError.Condition.bad_request).build() 51 | ) 52 | ) 53 | } 54 | 55 | return session.processIq(request.iq) 56 | } 57 | 58 | fun registerSession(session: JingleSession) { 59 | val existingSession = sessions.get(session.sid) 60 | if (existingSession != null) { 61 | logger.warn("Replacing existing session with SID ${session.sid}") 62 | } 63 | sessions.put(session.sid, session) 64 | } 65 | 66 | fun removeSession(session: JingleSession) { 67 | sessions.remove(session.sid) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /jicofo/src/main/java/org/jitsi/jicofo/auth/ExternalJWTAuthority.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2015-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.auth; 19 | 20 | import org.jxmpp.jid.*; 21 | 22 | import java.time.*; 23 | 24 | /** 25 | * Special case of XMPPDomainAuthAuthority where the user is 26 | * authenticated in Prosody with JWT token authentication method. The name of 27 | * XMPP domain should be passed to the constructor, which will happen when 28 | * {@link AuthConfig#getLoginUrl()} is set to 29 | * "EXT_JWT:auth.server.net", where 'auth.server.net' is the Prosody domain with 30 | * JWT token authentication enabled. 31 | * In order to obtain JWT, the user visits external "login" service from where 32 | * is redirected back to the app with the token. That's why 33 | * {@link #isExternal()} is overridden to return true. 34 | * 35 | * @author Pawel Domas 36 | */ 37 | public class ExternalJWTAuthority 38 | extends XMPPDomainAuthAuthority 39 | { 40 | /** 41 | * Creates new instance of {@link ExternalJWTAuthority}. 42 | * @param domain the name of the Prosody domain with JWT authentication 43 | * enabled. 44 | */ 45 | public ExternalJWTAuthority(DomainBareJid domain) 46 | { 47 | // For external JWT type of authentication we do not want to persist 48 | // the session IDs longer than the duration of the conference. 49 | // Also session duration is limited to 1 minuted. This is how long it 50 | // can be used for "on the fly" user role upgrade. That is the case when 51 | // the user starts from anonymous domain and then authenticates in 52 | // the popup window. 53 | super(false /* enable auto login */, 54 | Duration.ofMinutes(1) /* limit session duration to 1 minute */, 55 | domain); 56 | } 57 | 58 | @Override 59 | public String createLoginUrl( 60 | String machineUID, EntityFullJid peerFullJid, 61 | EntityBareJid roomName, boolean popup) 62 | { 63 | // Login URL is configured/generated in the client 64 | return null; 65 | } 66 | 67 | @Override 68 | public boolean isExternal() 69 | { 70 | return true; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/xmpp/muc/ChatRoomMember.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jitsi, the OpenSource Java VoIP and Instant Messaging client. 3 | * 4 | * Copyright @ 2015-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.xmpp.muc 19 | 20 | import org.jitsi.jicofo.xmpp.Features 21 | import org.jitsi.utils.OrderedJsonObject 22 | import org.jivesoftware.smack.packet.Presence 23 | import org.jxmpp.jid.EntityFullJid 24 | import org.jxmpp.jid.Jid 25 | 26 | /** 27 | * This interface represents chat room participants. Instances are retrieved 28 | * through implementations of the ChatRoom interface and offer methods 29 | * that allow querying member properties, such as, moderation permissions, 30 | * associated chat room and other. 31 | * 32 | * @author Emil Ivov 33 | * @author Boris Grozev 34 | */ 35 | interface ChatRoomMember { 36 | val chatRoom: ChatRoom 37 | 38 | /** The ID of this member. Set to the resource part of the occupant JID. */ 39 | val name: String 40 | 41 | /** The role of this chat room member in its containing room. */ 42 | val role: MemberRole 43 | 44 | /** Returns the JID of the user (outside the MUC), i.e. the "real" JID. It may not always be known. */ 45 | val jid: Jid? 46 | 47 | /** Get the latest [SourceInfo]s advertised by this chat member in presence. */ 48 | val sourceInfos: Set 49 | 50 | /** The occupant JID of the member in the chat room */ 51 | val occupantJid: EntityFullJid 52 | 53 | /** The last [Presence] packet received for this member (or null it if no presence has been received yet) */ 54 | val presence: Presence? 55 | 56 | val isJigasi: Boolean 57 | val isTranscriber: Boolean 58 | val isJibri: Boolean 59 | val isAudioMuted: Boolean 60 | val isVideoMuted: Boolean 61 | 62 | /** Gets the region (e.g. "us-east") of this [ChatRoomMember]. */ 63 | val region: String? 64 | 65 | /** The statistics id if any. */ 66 | val statsId: String? 67 | 68 | /** The supported video codecs if any */ 69 | val videoCodecs: List? 70 | 71 | /** 72 | * The list of features advertised as XMPP capabilities. Note that although the features are cached (XEP-0115), 73 | * the first time [features] is accessed it may block waiting for a disco#info response! 74 | */ 75 | val features: Set 76 | 77 | val debugState: OrderedJsonObject 78 | } 79 | -------------------------------------------------------------------------------- /jicofo/src/test/kotlin/org/jitsi/jicofo/mock/MockChatRoom.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2022 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.jicofo.mock 17 | 18 | import io.mockk.every 19 | import io.mockk.mockk 20 | import org.jitsi.jicofo.xmpp.Features 21 | import org.jitsi.jicofo.xmpp.XmppProvider 22 | import org.jitsi.jicofo.xmpp.muc.ChatRoom 23 | import org.jitsi.jicofo.xmpp.muc.ChatRoomListener 24 | import org.jitsi.jicofo.xmpp.muc.ChatRoomMember 25 | import org.jitsi.utils.OrderedJsonObject 26 | import org.jivesoftware.smack.packet.ExtensionElement 27 | import java.lang.IllegalArgumentException 28 | import javax.xml.namespace.QName 29 | 30 | class MockChatRoom(val xmppProvider: XmppProvider) { 31 | val chatRoomListeners = mutableListOf() 32 | val memberList = mutableListOf() 33 | val chatRoom = mockk(relaxed = true) { 34 | every { addListener(capture(chatRoomListeners)) } returns Unit 35 | every { members } returns memberList 36 | every { memberCount } answers { memberList.size } 37 | every { xmppProvider } returns this@MockChatRoom.xmppProvider 38 | every { debugState } returns OrderedJsonObject() 39 | every { getChatMember(any()) } answers { memberList.find { it.occupantJid == arg(0) } } 40 | } 41 | 42 | fun addMember(id: String): ChatRoomMember { 43 | val member = mockk(relaxed = true) { 44 | every { name } returns id 45 | every { chatRoom } returns this@MockChatRoom.chatRoom 46 | every { features } returns Features.defaultFeatures 47 | every { debugState } returns OrderedJsonObject() 48 | every { presence } returns mockk { 49 | every { status } returns null 50 | every { getExtension(any()) } returns null 51 | every { getExtension(any()) } returns null 52 | every { getExtension(any>()) } returns null 53 | } 54 | } 55 | memberList.add(member) 56 | chatRoomListeners.forEach { it.memberJoined(member) } 57 | return member 58 | } 59 | 60 | fun removeMember(member: ChatRoomMember) { 61 | if (!memberList.contains(member)) throw IllegalArgumentException("not a member") 62 | memberList.remove(member) 63 | chatRoomListeners.forEach { it.memberLeft(member) } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /jicofo-common/src/test/kotlin/org/jitsi/jicofo/xmpp/muc/RoomMetadataTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2024-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.xmpp.muc 19 | 20 | import io.kotest.assertions.throwables.shouldThrow 21 | import io.kotest.core.spec.style.ShouldSpec 22 | import io.kotest.matchers.shouldBe 23 | import io.kotest.matchers.types.shouldBeInstanceOf 24 | 25 | class RoomMetadataTest : ShouldSpec() { 26 | init { 27 | context("Valid") { 28 | context("With visitors.live set") { 29 | val parsed = RoomMetadata.parse( 30 | """ 31 | { 32 | "type": "room_metadata", 33 | "metadata": { 34 | "visitors": { 35 | "live": true, 36 | "anotherField": 123 37 | }, 38 | "anotherField": {} 39 | } 40 | } 41 | """.trimIndent() 42 | ) 43 | parsed.shouldBeInstanceOf() 44 | parsed.metadata!!.visitors!!.live shouldBe true 45 | } 46 | context("With no visitors included") { 47 | 48 | val parsed = RoomMetadata.parse( 49 | """ 50 | { 51 | "type": "room_metadata", 52 | "metadata": { 53 | "key": { 54 | "key2": "value2" 55 | }, 56 | "anotherField": {} 57 | } 58 | } 59 | """.trimIndent() 60 | ) 61 | parsed.shouldBeInstanceOf() 62 | parsed.metadata!!.visitors shouldBe null 63 | } 64 | } 65 | context("Invalid") { 66 | context("Missing type") { 67 | shouldThrow { 68 | RoomMetadata.parse( 69 | """ 70 | { "key": 123 } 71 | """.trimIndent() 72 | ) 73 | } 74 | } 75 | context("Invalid JSON") { 76 | shouldThrow { 77 | RoomMetadata.parse("{") 78 | } 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /jicofo/src/main/kotlin/org/jitsi/jicofo/jibri/JibriDetectorMetrics.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2023-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.jibri 19 | 20 | import org.jitsi.jicofo.metrics.JicofoMetricsContainer 21 | import org.jitsi.utils.OrderedJsonObject 22 | 23 | class JibriDetectorMetrics { 24 | companion object { 25 | private val jibriInstanceCount = JicofoMetricsContainer.instance.registerLongGauge( 26 | "jibri_instances", 27 | "Current number of jibri instances", 28 | 0 29 | ) 30 | private val jibriInstanceAvailableCount = JicofoMetricsContainer.instance.registerLongGauge( 31 | "jibri_instances_available", 32 | "Current number of available (not in use) jibri instances", 33 | 0 34 | ) 35 | private val sipJibriInstanceCount = JicofoMetricsContainer.instance.registerLongGauge( 36 | "sip_jibri_instances", 37 | "Current number of SIP jibri instances", 38 | 0 39 | ) 40 | private val sipJibriInstanceAvailableCount = JicofoMetricsContainer.instance.registerLongGauge( 41 | "sip_jibri_instances_available", 42 | "Current number of available (not in use) SIP jibri instances", 43 | 0 44 | ) 45 | 46 | fun updateMetrics(jibriDetector: JibriDetector?, sipJibriDetector: JibriDetector?) { 47 | jibriDetector?.instanceCount?.toLong()?.let { 48 | jibriInstanceCount.set(it) 49 | } 50 | jibriDetector?.getInstanceCount { it.status.isAvailable }?.toLong()?.let { 51 | jibriInstanceAvailableCount.set(it) 52 | } 53 | sipJibriDetector?.instanceCount?.toLong()?.let { 54 | sipJibriInstanceCount.set(it) 55 | } 56 | sipJibriDetector?.getInstanceCount { it.status.isAvailable }?.toLong()?.let { 57 | sipJibriInstanceAvailableCount.set(it) 58 | } 59 | } 60 | 61 | fun appendStats(o: OrderedJsonObject) { 62 | o["jibri_detector"] = OrderedJsonObject().apply { 63 | put("count", jibriInstanceCount.get()) 64 | put("available", jibriInstanceAvailableCount.get()) 65 | } 66 | o["sip_jibri_detector"] = OrderedJsonObject().apply { 67 | put("count", sipJibriInstanceCount.get()) 68 | put("available", sipJibriInstanceAvailableCount.get()) 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /jicofo-common/src/main/kotlin/org/jitsi/jicofo/xmpp/muc/SourceInfo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2021 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.jicofo.xmpp.muc 17 | 18 | import org.jitsi.jicofo.conference.source.VideoType 19 | import org.jitsi.utils.MediaType 20 | import org.json.simple.JSONObject 21 | import org.json.simple.parser.JSONParser 22 | 23 | /** The information about a source contained in a jitsi-meet SourceInfo extension. */ 24 | data class SourceInfo( 25 | val name: String, 26 | val muted: Boolean, 27 | val videoType: VideoType?, 28 | val mediaType: MediaType? 29 | ) 30 | 31 | /** 32 | * Parse the JSON string encoded in a SourceInfo XML extension as a set of [SourceInfo]s. 33 | * @throws ParseException if the string is not valid JSON. 34 | * @throws IllegalArgumentException if the JSON in not in the expected format. 35 | */ 36 | @kotlin.jvm.Throws(IllegalArgumentException::class) 37 | fun parseSourceInfoJson(s: String): Set { 38 | val json = JSONParser().parse(s) as? JSONObject ?: throw IllegalArgumentException("Illegal SourceInfo JSON: $s") 39 | 40 | return json.map { 41 | val name = it.key as? String ?: throw IllegalArgumentException("Invalid source name ${it.key}") 42 | val sourceJson = it.value as? JSONObject ?: throw IllegalArgumentException("Invalid source value: ${it.value}") 43 | val muted = sourceJson["muted"] as? Boolean ?: true 44 | val videoType = when (val videoTypeValue = sourceJson["videoType"]) { 45 | null -> null 46 | !is String -> throw IllegalArgumentException("Invalid videoType: $it") 47 | else -> VideoType.parseString(videoTypeValue) 48 | } 49 | 50 | val mediaTypeField = sourceJson["mediaType"] as? String 51 | val mediaType = when { 52 | "audio".equals(mediaTypeField, ignoreCase = true) -> MediaType.AUDIO 53 | "video".equals(mediaTypeField, ignoreCase = true) -> MediaType.VIDEO 54 | else -> parseMediaTypeFromName(name) 55 | } 56 | 57 | SourceInfo(name, muted, videoType, mediaType) 58 | }.toSet() 59 | } 60 | 61 | /** 62 | * If not explicitly signaled the media type is encoded in the name as in `abcd-a0` (an audio source) and `abcd-v5` 63 | * (a video source). 64 | */ 65 | private fun parseMediaTypeFromName(name: String): MediaType? = name.indexOf('-').let { indexOfDash -> 66 | if (indexOfDash < 0 || indexOfDash == name.length - 1) { 67 | null 68 | } else { 69 | when (name[indexOfDash + 1]) { 70 | 'a' -> MediaType.AUDIO 71 | 'v' -> MediaType.VIDEO 72 | else -> null 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /jicofo/src/test/kotlin/org/jitsi/jicofo/util/RateLimitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2018 - present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.util 19 | 20 | import io.kotest.core.spec.style.ShouldSpec 21 | import io.kotest.matchers.shouldBe 22 | import org.jitsi.utils.secs 23 | import org.jitsi.utils.time.FakeClock 24 | 25 | class RateLimitTest : ShouldSpec() { 26 | init { 27 | context("RateLimit test") { 28 | val clock = FakeClock() 29 | val rateLimit = RateLimit(clock = clock) 30 | 31 | should("allow 1st request") { 32 | rateLimit.accept() shouldBe true 33 | } 34 | should("not allow next request immediately") { 35 | rateLimit.accept() shouldBe false 36 | } 37 | clock.elapse(5.secs) 38 | should("not allow next request after 5 seconds") { 39 | rateLimit.accept() shouldBe false 40 | } 41 | clock.elapse(6.secs) 42 | should("allow 2nd request after 11 seconds") { 43 | rateLimit.accept() shouldBe true 44 | } 45 | should("not allow 3rd request after 11 seconds") { 46 | rateLimit.accept() shouldBe false 47 | } 48 | clock.elapse(10.secs) 49 | should("allow 3rd request after 21 seconds") { 50 | rateLimit.accept() shouldBe true 51 | } 52 | clock.elapse(11.secs) 53 | should("not allow more than 3 request within the last minute (31 second)") { 54 | rateLimit.accept() shouldBe false 55 | } 56 | clock.elapse(10.secs) 57 | should("not allow more than 3 request within the last minute (41 second)") { 58 | rateLimit.accept() shouldBe false 59 | } 60 | clock.elapse(10.secs) 61 | should("not allow more than 3 request within the last minute (51 second)") { 62 | rateLimit.accept() shouldBe false 63 | } 64 | clock.elapse(10.secs) 65 | should("allow the 4th request after 60 seconds have passed since the 1st (61 second)") { 66 | rateLimit.accept() shouldBe true 67 | } 68 | clock.elapse(5.secs) 69 | should("not allow the 5th request in 66th second") { 70 | rateLimit.accept() shouldBe false 71 | } 72 | clock.elapse(5.secs) 73 | should("allow the 5th request in 71st second") { 74 | rateLimit.accept() shouldBe true 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /jicofo/src/test/kotlin/org/jitsi/jicofo/auth/AuthConfigTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2020 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.jicofo.auth 17 | 18 | import io.kotest.assertions.throwables.shouldThrow 19 | import io.kotest.core.spec.IsolationMode 20 | import io.kotest.core.spec.style.ShouldSpec 21 | import io.kotest.matchers.shouldBe 22 | import org.jitsi.config.withLegacyConfig 23 | import org.jitsi.config.withNewConfig 24 | import java.time.Duration 25 | 26 | class AuthConfigTest : ShouldSpec() { 27 | override fun isolationMode() = IsolationMode.InstancePerLeaf 28 | 29 | init { 30 | context("Default values") { 31 | AuthConfig.config.apply { 32 | enableAutoLogin shouldBe true 33 | shouldThrow { loginUrl } 34 | type shouldBe AuthConfig.Type.NONE 35 | authenticationLifetime shouldBe Duration.ofHours(24) 36 | } 37 | } 38 | context("With legacy config") { 39 | withLegacyConfig( 40 | """ 41 | org.jitsi.jicofo.auth.URL=XMPP:test@example.com 42 | org.jitsi.jicofo.auth.DISABLE_AUTOLOGIN=true 43 | org.jitsi.jicofo.auth.AUTH_LIFETIME=60000 44 | """ 45 | ) { 46 | AuthConfig.config.apply { 47 | enableAutoLogin shouldBe false 48 | loginUrl shouldBe "test@example.com" 49 | type shouldBe AuthConfig.Type.XMPP 50 | authenticationLifetime shouldBe Duration.ofMillis(60000) 51 | } 52 | } 53 | } 54 | context("With new config") { 55 | withNewConfig( 56 | """ 57 | jicofo.authentication { 58 | enabled = true 59 | type = JWT 60 | login-url = login 61 | enable-auto-login = false 62 | authentication-lifetime = 5 minutes 63 | } 64 | """ 65 | ) { 66 | AuthConfig.config.apply { 67 | enableAutoLogin shouldBe false 68 | loginUrl shouldBe "login" 69 | type shouldBe AuthConfig.Type.JWT 70 | authenticationLifetime shouldBe Duration.ofMinutes(5) 71 | } 72 | } 73 | withNewConfig( 74 | """ 75 | jicofo.authentication { 76 | enabled = false 77 | } 78 | """ 79 | ) { 80 | AuthConfig.config.apply { 81 | type shouldBe AuthConfig.Type.NONE 82 | } 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /jicofo/src/main/java/org/jitsi/jicofo/auth/ErrorFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2015-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.auth; 19 | 20 | import org.jitsi.xmpp.extensions.jitsimeet.*; 21 | import org.jivesoftware.smack.packet.*; 22 | 23 | /** 24 | * Utility class for creating XMPP error responses. 25 | * 26 | * @author Pawel Domas 27 | */ 28 | public class ErrorFactory 29 | { 30 | /** 31 | * Creates 'not-authorized' XMPP error response to given query. 32 | * 33 | * @param query the IQ for which error response wil be created. 34 | * @param msg optional error description to be included in the response. 35 | * 36 | * @return XMPP 'not-authorized' error response to given query. 37 | */ 38 | public static IQ createNotAuthorizedError(IQ query, String msg) 39 | { 40 | final StanzaError error 41 | = StanzaError.from(StanzaError.Condition.not_authorized, msg).build(); 42 | 43 | return IQ.createErrorResponse(query, error); 44 | } 45 | 46 | /** 47 | * Creates 'not-acceptable' XMPP error response with application specific 48 | * 'session-invalid' error extension. 49 | * 50 | * @param query the IQ for which error response will be created. 51 | * 52 | * @return XMPP 'not-acceptable' error response to given query 53 | * with application specific 'session-invalid' extension. 54 | */ 55 | public static IQ createSessionInvalidResponse(IQ query) 56 | { 57 | final StanzaError error 58 | = StanzaError.from( 59 | StanzaError.Condition.not_acceptable, 60 | "invalid session") 61 | // session-invalid application specific error 62 | .addExtension(new SessionInvalidPacketExtension()) 63 | .build(); 64 | 65 | return IQ.createErrorResponse(query, error); 66 | } 67 | 68 | /** 69 | * Creates 'not-acceptable' XMPP error response to given query. 70 | * 71 | * @param query the IQ for which error response will be created. 72 | * 73 | * @param errorMessage application specific error message included in 74 | * error response. 75 | * 76 | * @return 'not-acceptable' XMPP error response to given query with 77 | * included errorMessage. 78 | */ 79 | public static IQ createNotAcceptableError(IQ query, String errorMessage) 80 | { 81 | // not acceptable 82 | final StanzaError error 83 | = StanzaError.from(StanzaError.Condition.not_acceptable, errorMessage) 84 | .build(); 85 | 86 | return IQ.createErrorResponse(query, error); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /jicofo/src/main/kotlin/org/jitsi/jicofo/ConferenceRequest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2022 - present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo 19 | 20 | import com.fasterxml.jackson.annotation.JsonInclude 21 | import com.fasterxml.jackson.core.JsonProcessingException 22 | import com.fasterxml.jackson.databind.JsonMappingException 23 | import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper 24 | import org.jitsi.xmpp.extensions.jitsimeet.ConferenceIq 25 | import org.jxmpp.jid.impl.JidCreate 26 | import org.jxmpp.stringprep.XmppStringprepException 27 | import kotlin.jvm.Throws 28 | 29 | /** 30 | * The initial request to create or join a conference, a generic version of [ConferenceIq]. 31 | */ 32 | @JsonInclude(JsonInclude.Include.NON_EMPTY) 33 | class ConferenceRequest( 34 | var room: String? = null, 35 | var ready: Boolean? = null, 36 | var focusJid: String? = null, 37 | var sessionId: String? = null, 38 | var machineUid: String? = null, 39 | var identity: String? = null, 40 | var vnode: String? = null, 41 | val properties: MutableMap = mutableMapOf() 42 | ) { 43 | @Throws(XmppStringprepException::class) 44 | fun toConferenceIq() = ConferenceIq().apply { 45 | this@ConferenceRequest.room?.let { 46 | room = JidCreate.entityBareFrom(it) 47 | } 48 | this@ConferenceRequest.ready?.let { 49 | isReady = it 50 | } 51 | this@ConferenceRequest.focusJid?.let { 52 | focusJid = it 53 | } 54 | this@ConferenceRequest.sessionId?.let { 55 | sessionId = it 56 | } 57 | this@ConferenceRequest.machineUid?.let { 58 | machineUID = it 59 | } 60 | this@ConferenceRequest.identity?.let { 61 | identity = it 62 | } 63 | this@ConferenceRequest.vnode?.let { 64 | vnode = it 65 | } 66 | this@ConferenceRequest.properties.forEach { (k, v) -> addProperty(k, v) } 67 | } 68 | 69 | fun toJson(): String = mapper.writeValueAsString(this) 70 | 71 | companion object { 72 | fun fromConferenceIq(iq: ConferenceIq) = ConferenceRequest( 73 | room = iq.room.toString(), 74 | ready = iq.isReady, 75 | focusJid = iq.focusJid, 76 | sessionId = iq.sessionId, 77 | machineUid = iq.machineUID, 78 | identity = iq.identity, 79 | vnode = iq.vnode, 80 | properties = iq.propertiesMap 81 | ) 82 | 83 | private val mapper = jacksonObjectMapper() 84 | 85 | @JvmStatic 86 | @Throws(JsonProcessingException::class, JsonMappingException::class) 87 | fun parseJson(s: String): ConferenceRequest = mapper.readValue(s, ConferenceRequest::class.java) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /jicofo/src/main/kotlin/org/jitsi/jicofo/xmpp/JibriIqHandler.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2017-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.xmpp 19 | 20 | import org.jitsi.jicofo.ConferenceStore 21 | import org.jitsi.jicofo.JicofoServices 22 | import org.jitsi.jicofo.jibri.BaseJibri 23 | import org.jitsi.jicofo.xmpp.IqProcessingResult.AcceptedWithNoResponse 24 | import org.jitsi.jicofo.xmpp.IqProcessingResult.AcceptedWithResponse 25 | import org.jitsi.jicofo.xmpp.IqProcessingResult.RejectedWithError 26 | import org.jitsi.utils.logging2.createLogger 27 | import org.jitsi.xmpp.extensions.jibri.JibriIq 28 | import org.jitsi.xmpp.util.XmlStringBuilderUtil.Companion.toStringOpt 29 | import org.jivesoftware.smack.AbstractXMPPConnection 30 | import org.jivesoftware.smack.iqrequest.IQRequestHandler 31 | import org.jivesoftware.smack.packet.IQ 32 | import org.jivesoftware.smack.packet.StanzaError 33 | 34 | /** 35 | * A Smack [IQRequestHandler] for "jibri" IQs. Terminates all "jibri" IQs received by Smack, but delegates their 36 | * handling to specific [BaseJibri] instances. 37 | */ 38 | class JibriIqHandler( 39 | connections: Set, 40 | private val conferenceStore: ConferenceStore 41 | ) : 42 | AbstractIqHandler( 43 | connections, 44 | JibriIq.ELEMENT, 45 | JibriIq.NAMESPACE, 46 | setOf(IQ.Type.set), 47 | IQRequestHandler.Mode.sync 48 | ) { 49 | val logger = createLogger() 50 | 51 | /** 52 | * {@inheritDoc} 53 | * Pass the request to the first [BaseJibri] that wants it. 54 | * 55 | * Note that this is synchronized to ensure correct use of the synchronized list (and we want to avoid using a 56 | * copy on write list for performance reasons). 57 | */ 58 | override fun handleRequest(request: IqRequest): IqProcessingResult { 59 | // TODO: we should be able to recognize the conference for a jibri IQ simply based on the `to` address. 60 | conferenceStore.getAllConferences().forEach { conference -> 61 | when (val result = conference.handleJibriRequest(request)) { 62 | is AcceptedWithResponse, is AcceptedWithNoResponse, is RejectedWithError -> return result 63 | else -> Unit // Proceed to the next conference 64 | } 65 | } 66 | 67 | // No conference accepted the request. 68 | logger.warn("Jibri IQ not accepted by any conference: ${request.iq.toStringOpt()}") 69 | if (JicofoServices.jicofoServicesSingleton?.jibriDetector == null && 70 | JicofoServices.jicofoServicesSingleton?.sipJibriDetector == null 71 | ) { 72 | logger.warn("No jibri detectors configured.") 73 | } 74 | return RejectedWithError(request, StanzaError.Condition.item_not_found) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /jicofo/src/test/kotlin/org/jitsi/jicofo/conference/source/SsrcGroupTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2021-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.conference.source 19 | 20 | import io.kotest.assertions.throwables.shouldThrow 21 | import io.kotest.core.spec.style.ShouldSpec 22 | import io.kotest.matchers.shouldBe 23 | import org.jitsi.utils.MediaType 24 | import org.jitsi.xmpp.extensions.colibri.SourcePacketExtension 25 | import org.jitsi.xmpp.extensions.jingle.SourceGroupPacketExtension 26 | 27 | class SsrcGroupTest : ShouldSpec() { 28 | init { 29 | context("SsrcGroupSemantics") { 30 | context("Parsing") { 31 | SsrcGroupSemantics.fromString("sim") shouldBe SsrcGroupSemantics.Sim 32 | SsrcGroupSemantics.fromString("SIM") shouldBe SsrcGroupSemantics.Sim 33 | SsrcGroupSemantics.fromString("sIM") shouldBe SsrcGroupSemantics.Sim 34 | SsrcGroupSemantics.fromString("fiD") shouldBe SsrcGroupSemantics.Fid 35 | 36 | shouldThrow { 37 | SsrcGroupSemantics.fromString("invalid") 38 | } 39 | } 40 | context("To string") { 41 | SsrcGroupSemantics.Sim.toString() shouldBe "SIM" 42 | SsrcGroupSemantics.Fid.toString() shouldBe "FID" 43 | } 44 | } 45 | context("From XML") { 46 | val packetExtension = SourceGroupPacketExtension().apply { 47 | semantics = "sim" 48 | addSources( 49 | listOf( 50 | SourcePacketExtension().apply { ssrc = 1 }, 51 | SourcePacketExtension().apply { ssrc = 2 }, 52 | SourcePacketExtension().apply { ssrc = 3 } 53 | ) 54 | ) 55 | } 56 | 57 | val ssrcGroup = SsrcGroup.fromPacketExtension(packetExtension) 58 | ssrcGroup shouldBe SsrcGroup(SsrcGroupSemantics.Sim, listOf(1, 2, 3)) 59 | } 60 | context("To XML") { 61 | val ssrcGroup = SsrcGroup(SsrcGroupSemantics.Sim, listOf(1, 2, 3)) 62 | val packetExtension = ssrcGroup.toPacketExtension() 63 | packetExtension.semantics shouldBe "SIM" 64 | packetExtension.sources.size shouldBe 3 65 | packetExtension.sources.map { Source(MediaType.VIDEO, it) }.toList() shouldBe listOf( 66 | Source(1, MediaType.VIDEO), 67 | Source(2, MediaType.VIDEO), 68 | Source(3, MediaType.VIDEO) 69 | ) 70 | } 71 | context("Compact JSON") { 72 | SsrcGroup(SsrcGroupSemantics.Sim, listOf(1, 2, 3)).compactJson shouldBe """ 73 | ["s",1,2,3] 74 | """.trimIndent() 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /jicofo/src/test/kotlin/org/jitsi/jicofo/conference/source/SourceTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2021-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.conference.source 19 | 20 | import io.kotest.core.spec.style.ShouldSpec 21 | import io.kotest.matchers.shouldBe 22 | import io.kotest.matchers.shouldNotBe 23 | import org.jitsi.utils.MediaType 24 | import org.jitsi.xmpp.extensions.colibri.SourcePacketExtension 25 | import org.jitsi.xmpp.extensions.jingle.ParameterPacketExtension 26 | import org.jitsi.xmpp.extensions.jitsimeet.SSRCInfoPacketExtension 27 | 28 | class SourceTest : ShouldSpec() { 29 | init { 30 | context("From XML") { 31 | val packetExtension = SourcePacketExtension().apply { 32 | ssrc = 1 33 | name = "name-1" 34 | videoType = "camera" 35 | addChildExtension(ParameterPacketExtension("msid", "msid")) 36 | } 37 | 38 | Source(MediaType.VIDEO, packetExtension) shouldBe 39 | Source(1, MediaType.VIDEO, name = "name-1", msid = "msid", videoType = VideoType.Camera) 40 | } 41 | context("To XML") { 42 | val msidValue = "msid-value" 43 | val nameValue = "source-name-value" 44 | val videoType = VideoType.Desktop 45 | val source = Source(1, MediaType.VIDEO, name = nameValue, msid = msidValue, videoType = videoType) 46 | val owner = "abcdabcd" 47 | val extension = source.toPacketExtension(owner = owner) 48 | 49 | extension.ssrc shouldBe 1 50 | extension.name shouldBe nameValue 51 | extension.videoType shouldBe videoType.toString() 52 | val parameters = extension.getChildExtensionsOfType(ParameterPacketExtension::class.java) 53 | parameters.filter { it.name == "msid" && it.value == msidValue }.size shouldBe 1 54 | 55 | val ssrcInfo = extension.getFirstChildOfType(SSRCInfoPacketExtension::class.java) 56 | ssrcInfo shouldNotBe null 57 | ssrcInfo.owner.toString() shouldBe owner 58 | } 59 | context("Compact JSON") { 60 | Source(1, MediaType.VIDEO, name = "test-name", msid = "msid").compactJson shouldBe 61 | """ 62 | {"s":1,"n":"test-name","m":"msid"} 63 | """.trimIndent() 64 | Source( 65 | 1, 66 | MediaType.VIDEO, 67 | videoType = VideoType.Desktop, 68 | name = "test-name", 69 | msid = "msid" 70 | ).compactJson shouldBe 71 | """ 72 | {"s":1,"n":"test-name","m":"msid","v":"d"} 73 | """.trimIndent() 74 | Source(1, MediaType.AUDIO).compactJson shouldBe """ 75 | {"s":1} 76 | """.trimIndent() 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /jicofo/src/test/kotlin/org/jitsi/jicofo/xmpp/muc/MemberRoleTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2018 - present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.xmpp.muc 19 | 20 | import io.kotest.assertions.withClue 21 | import io.kotest.core.spec.style.ShouldSpec 22 | import io.kotest.matchers.booleans.shouldBeFalse 23 | import io.kotest.matchers.booleans.shouldBeTrue 24 | import io.kotest.matchers.ints.shouldBeGreaterThan 25 | import io.kotest.matchers.shouldBe 26 | import org.jitsi.jicofo.xmpp.muc.MemberRole.MODERATOR 27 | import org.jitsi.jicofo.xmpp.muc.MemberRole.OWNER 28 | import org.jitsi.jicofo.xmpp.muc.MemberRole.PARTICIPANT 29 | import org.jitsi.jicofo.xmpp.muc.MemberRole.VISITOR 30 | import org.jivesoftware.smackx.muc.MUCAffiliation 31 | import org.jivesoftware.smackx.muc.MUCRole 32 | 33 | class MemberRoleTest : ShouldSpec() { 34 | init { 35 | context("Order") { 36 | (VISITOR > PARTICIPANT) shouldBe true 37 | (PARTICIPANT > MODERATOR) shouldBe true 38 | 39 | VISITOR.compareTo(PARTICIPANT) shouldBeGreaterThan 0 40 | PARTICIPANT.compareTo(MODERATOR) shouldBeGreaterThan 0 41 | } 42 | context("hasModeratorRights") { 43 | VISITOR.hasModeratorRights().shouldBeFalse() 44 | PARTICIPANT.hasModeratorRights().shouldBeFalse() 45 | MODERATOR.hasModeratorRights().shouldBeTrue() 46 | OWNER.hasModeratorRights().shouldBeTrue() 47 | } 48 | context("hasOwnerRights") { 49 | VISITOR.hasOwnerRights().shouldBeFalse() 50 | PARTICIPANT.hasOwnerRights().shouldBeFalse() 51 | MODERATOR.hasOwnerRights().shouldBeFalse() 52 | OWNER.hasOwnerRights().shouldBeTrue() 53 | } 54 | context("From Smack role and affiliation") { 55 | enumPairsWithNull().forEach { (mucRole, mucAffiliation) -> 56 | withClue("mucRole=$mucRole, mucAffiliation=$mucAffiliation") { 57 | MemberRole.fromSmack(mucRole, mucAffiliation) shouldBe when (mucAffiliation) { 58 | MUCAffiliation.admin -> MODERATOR 59 | MUCAffiliation.owner -> OWNER 60 | else -> when (mucRole) { 61 | MUCRole.moderator -> MODERATOR 62 | MUCRole.participant -> PARTICIPANT 63 | else -> VISITOR 64 | } 65 | } 66 | } 67 | } 68 | } 69 | } 70 | 71 | private inline fun , reified U : Enum> enumPairsWithNull(): List> = 72 | enumValuesAndNull().flatMap { l -> enumValuesAndNull().map { r -> l to r } } 73 | private inline fun > enumValuesAndNull(): List = 74 | mutableListOf(null).apply { addAll(enumValues()) } 75 | } 76 | -------------------------------------------------------------------------------- /jicofo-common/src/main/java/org/jitsi/impl/protocol/xmpp/log/PacketDebugger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright @ 2018 - present 8x8, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.jitsi.impl.protocol.xmpp.log; 17 | 18 | import edu.umd.cs.findbugs.annotations.*; 19 | import org.jitsi.utils.logging2.*; 20 | import org.jitsi.xmpp.util.*; 21 | import org.jivesoftware.smack.*; 22 | import org.jivesoftware.smack.debugger.*; 23 | import org.jivesoftware.smack.packet.*; 24 | 25 | import java.lang.*; 26 | 27 | /** 28 | * Implements {@link SmackDebugger} in order to get info about XMPP traffic. 29 | */ 30 | public class PacketDebugger 31 | extends AbstractDebugger 32 | { 33 | /** 34 | * The logger used by this class. 35 | */ 36 | private final static Logger logger = new LoggerImpl(PacketDebugger.class.getName()); 37 | 38 | /** 39 | * Whether XMPP logging is enabled. We don't want to insert a debugger into Smack when it's not going to actually 40 | * log anything. 41 | */ 42 | public static boolean isEnabled() 43 | { 44 | return logger.isDebugEnabled(); 45 | } 46 | 47 | /** 48 | * An ID to log to identify the connection. 49 | */ 50 | @NonNull 51 | private final String id; 52 | 53 | /** 54 | * Creates new {@link PacketDebugger} 55 | * {@inheritDoc} 56 | */ 57 | @SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD") 58 | public PacketDebugger(XMPPConnection connection, @NonNull String id) 59 | { 60 | super(connection); 61 | this.id = id; 62 | 63 | // Change the static value only if an instance is created. 64 | AbstractDebugger.printInterpreted = true; 65 | } 66 | 67 | @Override 68 | public void onIncomingStreamElement(TopLevelStreamElement streamElement) 69 | { 70 | logger.debug( 71 | () -> "RCV PKT (" + id + "): " + XmlStringBuilderUtil.Companion.toStringOpt(streamElement.toXML()) 72 | ); 73 | } 74 | 75 | @Override 76 | public void onOutgoingStreamElement(TopLevelStreamElement streamElement) 77 | { 78 | logger.debug( 79 | () -> "SENT PKT (" + id + "): " + XmlStringBuilderUtil.Companion.toStringOpt(streamElement.toXML()) 80 | ); 81 | } 82 | 83 | // It's fine to do non-atomic as it's only 1 thread doing write operation 84 | /** 85 | * {@inheritDoc} 86 | */ 87 | @Override 88 | protected void log(String logMessage) 89 | { 90 | if (logger.isDebugEnabled() && !logMessage.startsWith("RECV (") && !logMessage.startsWith("SENT (")) 91 | { 92 | logger.debug(logMessage); 93 | } 94 | } 95 | 96 | /** 97 | * {@inheritDoc} 98 | */ 99 | @Override 100 | protected void log(String logMessage, Throwable throwable) 101 | { 102 | logger.warn("Smack: " + logMessage, throwable); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /jicofo/src/main/kotlin/org/jitsi/jicofo/util/RateLimitedStat.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jicofo, the Jitsi Conference Focus. 3 | * 4 | * Copyright @ 2015-Present 8x8, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package org.jitsi.jicofo.util 19 | 20 | import org.jitsi.jicofo.TaskPools.Companion.scheduledPool 21 | import java.time.Clock 22 | import java.time.Duration 23 | import java.time.Instant 24 | import java.util.concurrent.Future 25 | import java.util.concurrent.TimeUnit 26 | 27 | /** A statistic whose value is only reported at a limited rate. */ 28 | class RateLimitedStat 29 | @JvmOverloads 30 | constructor( 31 | private val changeInterval: Duration, 32 | private val onChanged: (Int) -> Unit, 33 | initialValue: Int = 0, 34 | private val clock: Clock = Clock.systemUTC() 35 | ) { 36 | private val lock = Any() 37 | 38 | private var _value = initialValue 39 | 40 | private var lastChanged: Instant? = null 41 | 42 | private var updateTask: Future<*>? = null 43 | 44 | var value: Int 45 | get() = synchronized(lock) { _value } 46 | set(newValue) { 47 | val report: Boolean 48 | synchronized(lock) { 49 | _value = newValue 50 | 51 | report = valueUpdated() 52 | } 53 | 54 | if (report) { 55 | reportChanged() 56 | } 57 | } 58 | 59 | fun adjustValue(delta: Int) { 60 | val report: Boolean 61 | synchronized(lock) { 62 | _value += delta 63 | 64 | report = valueUpdated() 65 | } 66 | 67 | if (report) { 68 | reportChanged() 69 | } 70 | } 71 | 72 | /** Call this inside the synchronization block after the value is updated. If it returns true, 73 | * call [reportChanged] immediately, *outside* the synchronization block. 74 | */ 75 | private fun valueUpdated(): Boolean { 76 | val now = Instant.now() 77 | if (updateTask != null) { 78 | return false 79 | } 80 | 81 | lastChanged?.let { 82 | if (Duration.between(it, now) < changeInterval) { 83 | val notificationTime = it.plus(changeInterval) 84 | val delay = Duration.between(now, notificationTime) 85 | updateTask = scheduledPool.schedule( 86 | { this.reportChanged() }, 87 | delay.toMillis(), 88 | TimeUnit.MILLISECONDS 89 | ) 90 | return false 91 | } 92 | } 93 | return true 94 | } 95 | 96 | private fun reportChanged() { 97 | val value: Int 98 | synchronized(lock) { 99 | value = this._value 100 | lastChanged = clock.instant() 101 | updateTask = null 102 | } 103 | onChanged(value) 104 | } 105 | 106 | fun stop() = updateTask?.cancel(false) 107 | } 108 | --------------------------------------------------------------------------------