Skip to content

Commit

Permalink
feat: Add a "export" extension to colibri2.
Browse files Browse the repository at this point in the history
  • Loading branch information
bgrozev committed Sep 22, 2024
1 parent 3816e5a commit adc8b79
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.packet.*;

import java.util.*;

public class ConferenceModifyIQ
extends AbstractConferenceModificationIQ<ConferenceModifyIQ>
{
Expand Down Expand Up @@ -95,6 +97,10 @@ private ConferenceModifyIQ(Builder b)
rtcstatsEnabled = b.rtcstatsEnabled;
create = b.create;
expire = b.expire;
if (b.exports != null)
{
addExtension(b.exports);
}

if (b.meetingId == null)
{
Expand Down Expand Up @@ -170,6 +176,12 @@ public boolean getExpire()
return expire;
}

@Nullable
public Exports getExports()
{
return getExtension(Exports.class);
}

@Contract("_ -> new")
public static @NotNull Builder builder(XMPPConnection connection)
{
Expand All @@ -196,6 +208,7 @@ public static final class Builder
private boolean expire = EXPIRE_DEFAULT;
private String conferenceName;
private String meetingId;
private Exports exports = null;

private Builder(IqData iqCommon)
{
Expand All @@ -218,6 +231,22 @@ public Builder setRtcstatsEnabled(boolean rtcstatsEnabled)
return this;
}

public Builder setEmptyExports()
{
exports = new Exports();
return this;
}

public Builder addExport(@NotNull Export export)
{
if (exports == null)
{
exports = new Exports();
}
exports.addExport(export);
return this;
}

public Builder setConferenceName(String name)
{
conferenceName = name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ private static void doRegisterProviders()
/* Original colibri does something weird with these elements' namespaces, so register them here. */
ProviderManager.addExtensionProvider(ForceMute.ELEMENT, ForceMute.NAMESPACE, new ForceMute.Provider());
ProviderManager.addExtensionProvider(InitialLastN.ELEMENT, InitialLastN.NAMESPACE, new InitialLastNProvider());
ProviderManager.addExtensionProvider(Export.ELEMENT, Export.NAMESPACE, new ExportProvider());
ProviderManager.addExtensionProvider(Exports.ELEMENT, Exports.NAMESPACE, new ExportsProvider());
ProviderManager.addExtensionProvider(Capability.ELEMENT, Capability.NAMESPACE, new Capability.Provider());
ProviderManager.addExtensionProvider(Sctp.ELEMENT, Sctp.NAMESPACE, new Sctp.Provider());
ProviderManager.addExtensionProvider(Colibri2Error.ELEMENT,
Expand Down
71 changes: 71 additions & 0 deletions src/main/kotlin/org/jitsi/xmpp/extensions/colibri2/Export.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright @ 2024 - present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.xmpp.extensions.colibri2

import org.jitsi.xmpp.extensions.AbstractPacketExtension
import org.jitsi.xmpp.extensions.DefaultPacketExtensionProvider
import org.jivesoftware.smack.packet.XmlEnvironment
import org.jivesoftware.smack.parsing.SmackParsingException
import org.jivesoftware.smack.xml.XmlPullParser
import org.jivesoftware.smack.xml.XmlPullParserException
import java.io.IOException
import java.net.URI
import java.net.URISyntaxException

class Export(
val url: URI,
audio: Boolean = false,
video: Boolean = false
) : AbstractPacketExtension(NAMESPACE, ELEMENT) {
init {
setAttribute(URL_ATTR_NAME, url)
if (audio) {
setAttribute(AUDIO_ATTR_NAME, true)
}
if (video) {
setAttribute(VIDEO_ATTR_NAME, true)
}
}

val audio: Boolean
get() = getAttributeAsString(AUDIO_ATTR_NAME)?.toBoolean() ?: false
val video: Boolean
get() = getAttributeAsString(VIDEO_ATTR_NAME)?.toBoolean() ?: false

companion object {
const val ELEMENT = "export"
const val NAMESPACE = ConferenceModifyIQ.NAMESPACE
const val URL_ATTR_NAME = "url"
const val AUDIO_ATTR_NAME = "audio"
const val VIDEO_ATTR_NAME = "video"
}
}

class ExportProvider : DefaultPacketExtensionProvider<Export>(Export::class.java) {
@Throws(XmlPullParserException::class, IOException::class, SmackParsingException::class)
override fun parse(parser: XmlPullParser, depth: Int, xml: XmlEnvironment?): Export {
val url = parser.getAttributeValue("", Export.URL_ATTR_NAME)
?: throw SmackParsingException.RequiredAttributeMissingException("Missing 'url' attribute")
val audio = parser.getAttributeValue("", Export.AUDIO_ATTR_NAME)?.toBoolean() ?: false
val video = parser.getAttributeValue("", Export.VIDEO_ATTR_NAME)?.toBoolean() ?: false

try {
return Export(URI(url), audio, video)
} catch (e: URISyntaxException) {
throw SmackParsingException("Invalid 'url': ${e.message}")
}
}
}
32 changes: 32 additions & 0 deletions src/main/kotlin/org/jitsi/xmpp/extensions/colibri2/Exports.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright @ 2024 - present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.xmpp.extensions.colibri2

import org.jitsi.xmpp.extensions.AbstractPacketExtension
import org.jitsi.xmpp.extensions.DefaultPacketExtensionProvider

class Exports : AbstractPacketExtension(NAMESPACE, ELEMENT) {

fun getExports(): List<Export> = getChildExtensionsOfType(Export::class.java)
fun addExport(export: Export) = addChildExtension(export)

companion object {
const val ELEMENT = "exports"
const val NAMESPACE = ConferenceModifyIQ.NAMESPACE
}
}

class ExportsProvider : DefaultPacketExtensionProvider<Exports>(Exports::class.java)
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import org.jitsi.xmpp.extensions.colibri2.Colibri2Relay
import org.jitsi.xmpp.extensions.colibri2.ConferenceModifiedIQ
import org.jitsi.xmpp.extensions.colibri2.ConferenceModifyIQ
import org.jitsi.xmpp.extensions.colibri2.Endpoints
import org.jitsi.xmpp.extensions.colibri2.Export
import org.jitsi.xmpp.extensions.colibri2.ForceMute
import org.jitsi.xmpp.extensions.colibri2.InitialLastN
import org.jitsi.xmpp.extensions.colibri2.Media
Expand All @@ -37,6 +38,7 @@ import org.jivesoftware.smackx.muc.MUCRole
import org.json.simple.JSONArray
import org.json.simple.JSONObject
import java.lang.IllegalArgumentException
import java.net.URI

object Colibri2JSONDeserializer {
private fun deserializeMedia(media: JSONObject): Media {
Expand Down Expand Up @@ -356,6 +358,26 @@ object Colibri2JSONDeserializer {
setRtcstatsEnabled(it)
}
}

conferenceModify["exports"]?.let {
if (it is JSONArray) {
var added = false
it.forEach { export ->
if (export is JSONObject) {
addExport(
Export(
URI(export[Export.URL_ATTR_NAME] as String),
audio = export[Export.AUDIO_ATTR_NAME]?.toString()?.toBoolean() ?: false,
video = export[Export.VIDEO_ATTR_NAME]?.toString()?.toBoolean() ?: false
)
)
added = true
}
}
// An empty array is distinct from no value specified.
if (!added) setEmptyExports()
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import org.jitsi.xmpp.extensions.colibri2.Colibri2Endpoint
import org.jitsi.xmpp.extensions.colibri2.Colibri2Relay
import org.jitsi.xmpp.extensions.colibri2.ConferenceModifiedIQ
import org.jitsi.xmpp.extensions.colibri2.ConferenceModifyIQ
import org.jitsi.xmpp.extensions.colibri2.Export
import org.jitsi.xmpp.extensions.colibri2.Exports
import org.jitsi.xmpp.extensions.colibri2.ForceMute
import org.jitsi.xmpp.extensions.colibri2.InitialLastN
import org.jitsi.xmpp.extensions.colibri2.Media
Expand Down Expand Up @@ -231,6 +233,16 @@ object Colibri2JSONSerializer {
}
}

private fun serializeExport(export: Export) = JSONObject().apply {
put("url", export.url.toString())
if (export.audio) put("audio", true)
if (export.video) put("video", true)
}

private fun serializeExports(exports: Exports) = JSONArray().apply {
exports.getExports().forEach { add(serializeExport(it)) }
}

@JvmStatic
fun serializeConferenceModify(iq: ConferenceModifyIQ): JSONObject {
return serializeAbstractConferenceModificationIQ(iq).apply {
Expand All @@ -246,6 +258,10 @@ object Colibri2JSONSerializer {
put(ConferenceModifyIQ.RTCSTATS_ENABLED_ATTR_NAME, iq.isRtcstatsEnabled)
}

iq.exports?.let {
put("exports", serializeExports(it))
}

put(ConferenceModifyIQ.MEETING_ID_ATTR_NAME, iq.meetingId)

iq.conferenceName?.let { put(ConferenceModifyIQ.NAME_ATTR_NAME, it) }
Expand Down
64 changes: 64 additions & 0 deletions src/test/kotlin/org/jitsi/xmpp/extensions/colibri2/ExportTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright @ 2024 - present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.xmpp.extensions.colibri2

import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.ShouldSpec
import io.kotest.matchers.shouldBe
import org.jivesoftware.smack.parsing.SmackParsingException
import org.jivesoftware.smack.util.PacketParserUtils
import java.net.URI

class ExportTest : ShouldSpec() {
init {
IqProviderUtils.registerProviders()
val provider = ExportProvider()
val url = "ws://example.com"

context("Parsing a valid extension") {
context("Without audio/video") {
val export = provider.parse(PacketParserUtils.getParserFor("<export url='$url'/>"))
export.url shouldBe URI(url)
export.audio shouldBe false
export.video shouldBe false
}
context("With audio") {
val export = provider.parse(PacketParserUtils.getParserFor("<export url='$url' audio='true'/>"))
export.url shouldBe URI(url)
export.audio shouldBe true
export.video shouldBe false
}
context("With video") {
val export = provider.parse(
PacketParserUtils.getParserFor("<export url='$url' audio='false' video='true'/>")
)
export.url shouldBe URI(url)
export.audio shouldBe false
export.video shouldBe true
}
}
context("Parsing with missing url") {
shouldThrow<SmackParsingException> {
provider.parse(PacketParserUtils.getParserFor("<export/>"))
}
}
context("Parsing with invalid url") {
shouldThrow<SmackParsingException> {
provider.parse(PacketParserUtils.getParserFor("<export url='in val id'/>"))
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,10 @@ private val expectedMappings = listOf(
<transport ice-controlling="true"/>
<capability name="source-names"/>
</endpoint>
<exports>
<export url='wss://example.com/audio' audio='true'/>
<export url='wss://example.com/video' video='true'/>
</exports>
</conference-modify>
</iq>
""",
Expand Down Expand Up @@ -278,6 +282,10 @@ private val expectedMappings = listOf(
"transport": { "ice-controlling": true },
"capabilities": [ "source-names" ]
}
],
"exports": [
{ "url": "wss://example.com/audio", "audio": true },
{ "url": "wss://example.com/video", "video": true }
]
}
""",
Expand Down

0 comments on commit adc8b79

Please sign in to comment.