Skip to content

Commit

Permalink
Merge pull request #13492 from nextcloud/check-media-direction-in-int…
Browse files Browse the repository at this point in the history
…ernal-signaling-messages

fix: Check media direction in internal signaling messages
  • Loading branch information
nickvergessen authored Oct 8, 2024
2 parents e57b4fe + f71cf01 commit d25f504
Show file tree
Hide file tree
Showing 2 changed files with 263 additions and 0 deletions.
95 changes: 95 additions & 0 deletions lib/Controller/SignalingController.php
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,23 @@ public function sendMessages(string $token, string $messages): DataResponse {
if (!$participant->hasModeratorPermissions(false)) {
break;
}
} elseif ($decodedMessage['type'] === 'offer' || $decodedMessage['type'] === 'answer') {
$room = $this->manager->getRoomForSession($this->userId, $message['sessionId']);
$participant = $this->participantService->getParticipantBySession($room, $message['sessionId']);

if (!($participant->getPermissions() & Attendee::PERMISSIONS_PUBLISH_AUDIO) && $decodedMessage['roomType'] === 'video'
&& $this->isTryingToPublishMedia($decodedMessage['payload']['sdp'], 'audio')) {
break;
}
if (!($participant->getPermissions() & Attendee::PERMISSIONS_PUBLISH_VIDEO) && $decodedMessage['roomType'] === 'video'
&& $this->isTryingToPublishMedia($decodedMessage['payload']['sdp'], 'video')) {
break;
}
if (!($participant->getPermissions() & Attendee::PERMISSIONS_PUBLISH_SCREEN) && $decodedMessage['roomType'] === 'screen'
&& ($this->isTryingToPublishMedia($decodedMessage['payload']['sdp'], 'audio')
|| $this->isTryingToPublishMedia($decodedMessage['payload']['sdp'], 'video'))) {
break;
}
}

$this->messages->addMessage($message['sessionId'], $decodedMessage['to'], json_encode($decodedMessage));
Expand All @@ -395,6 +412,84 @@ public function sendMessages(string $token, string $messages): DataResponse {
return new DataResponse($response);
}

/**
* Returns whether the SDP is trying to publish the given media based on the
* media direction.
*
* The SDP is trying to publish if the related media description contains a
* media direction of either "sendrecv" or "sendonly". If no media direction
* is provided in a media description the media direction in the session
* description is used instead. If that is not provided either then
* "sendrecv" is assumed.
*
* See https://www.rfc-editor.org/rfc/rfc8866.html#name-media-direction-attributes
*
* @param string $sdp the SDP to check
* @param string $media the media to check, either "audio" or "video"
* @return bool true if it is trying to publish, false otherwise
*/
private function isTryingToPublishMedia(string $sdp, string $media): bool {
$lines = preg_split('/\r\n|\n|\r/', $sdp);

$sessionMediaDirectionIndex = -1;
$mediaDirectionIndex = -1;
$mediaDescriptionIndex = -1;
$matchingMediaDescriptionIndex = -1;

for ($i = 0; $i < count($lines); $i++) {
if (strpos($lines[$i], 'a=sendrecv') === 0
|| strpos($lines[$i], 'a=sendonly') === 0
|| strpos($lines[$i], 'a=recvonly') === 0
|| strpos($lines[$i], 'a=inactive') === 0) {
$mediaDirectionIndex = $i;

if ($mediaDescriptionIndex < 0) {
$sessionMediaDirectionIndex = $mediaDirectionIndex;
}

if ($matchingMediaDescriptionIndex >= 0
&& $matchingMediaDescriptionIndex >= $mediaDescriptionIndex
&& $mediaDirectionIndex > $matchingMediaDescriptionIndex
&& (strpos($lines[$mediaDirectionIndex], 'a=sendrecv') === 0
|| strpos($lines[$mediaDirectionIndex], 'a=sendonly') === 0)) {
return true;
}
} elseif (strpos($lines[$i], 'm=') === 0) {
// No media direction in previous matching media description,
// fallback to media direction in the session description or, if
// not set, default to "sendrecv".
if ($matchingMediaDescriptionIndex >= 0
&& $matchingMediaDescriptionIndex >= $mediaDescriptionIndex
&& $mediaDirectionIndex < $matchingMediaDescriptionIndex
&& ($sessionMediaDirectionIndex < 0
|| strpos($lines[$sessionMediaDirectionIndex], 'a=sendrecv') === 0
|| strpos($lines[$sessionMediaDirectionIndex], 'a=sendonly') === 0)) {
return true;
}

$mediaDescriptionIndex = $i;

if (strpos($lines[$i], 'm=' . $media) === 0) {
$matchingMediaDescriptionIndex = $i;
}
}
}

// No media direction in last matching media description, fallback to
// media direction in the session description or, if not set, default to
// "sendrecv".
if ($matchingMediaDescriptionIndex >= 0
&& $matchingMediaDescriptionIndex >= $mediaDescriptionIndex
&& $mediaDirectionIndex < $matchingMediaDescriptionIndex
&& ($sessionMediaDirectionIndex < 0
|| strpos($lines[$sessionMediaDirectionIndex], 'a=sendrecv') === 0
|| strpos($lines[$sessionMediaDirectionIndex], 'a=sendonly') === 0)) {
return true;
}

return false;
}

/**
* Get signaling messages
*
Expand Down
168 changes: 168 additions & 0 deletions tests/php/Controller/SignalingControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,174 @@ private function recreateSignalingController() {
);
}

public static function dataIsTryingToPublishMedia(): array {
// For simplicity the SDP contains only the relevant fields and it is
// not a valid SDP
return [
// Audio publisher/receiver
[
"m=audio 42108 RTP/AVP 0\n" .
"a=sendrecv\n",
true, false,
],
// Audio publisher/receiver with data channel
[
"m=audio 42108 RTP/AVP 0\n" .
"a=sendrecv\n" .
"m=application 8 UDP/DTLS/SCTP webrtc-datachannel\n" .
"a=sendrecv\n",
true, false,
],
// Video publisher
[
"m=video 42108 RTP/AVP 0\n" .
"a=sendonly\n",
false, true,
],
// Video publisher with data channel
[
"m=video 42108 RTP/AVP 0\n" .
"a=sendonly\n" .
"m=application 8 UDP/DTLS/SCTP webrtc-datachannel\n" .
"a=sendrecv\n",
false, true,
],
// Audio and video publisher/receiver
[
"m=audio 42108 RTP/AVP 0\n" .
"a=sendrecv\n" .
"m=video 42108 RTP/AVP 0\n" .
"a=sendrecv\n",
true, true,
],
// Audio and video publisher/receiver with data channel
[
"m=audio 42108 RTP/AVP 0\n" .
"a=sendrecv\n" .
"m=video 42108 RTP/AVP 0\n" .
"a=sendrecv\n" .
"m=application 8 UDP/DTLS/SCTP webrtc-datachannel\n" .
"a=sendrecv\n",
true, true,
],
// Audio and video receiver with data channel
[
"m=audio 42108 RTP/AVP 0\n" .
"a=recvonly\n" .
"m=video 42108 RTP/AVP 0\n" .
"a=recvonly\n" .
"m=application 8 UDP/DTLS/SCTP webrtc-datachannel\n" .
"a=sendrecv\n",
false, false,
],
// Audio receiver and video inactive
[
"m=audio 42108 RTP/AVP 0\n" .
"a=recvonly\n" .
"m=video 42108 RTP/AVP 0\n" .
"a=inactive\n",
false, false,
],
// Audio receiver with session publisher/receiver direction
[
"a=sendrecv\n" .
"m=audio 42108 RTP/AVP 0\n" .
"a=recvonly\n",
false, false,
],
// Video inactive with session publisher direction and data channel
[
"a=sendonly\n" .
"m=video 42108 RTP/AVP 0\n" .
"a=inactive\n" .
"m=application 8 UDP/DTLS/SCTP webrtc-datachannel\n" .
"a=sendrecv\n",
false, false,
],
// Audio and video with session publisher/receiver direction
[
"a=sendrecv\n" .
"m=audio 42108 RTP/AVP 0\n" .
"m=video 42108 RTP/AVP 0\n",
true, true,
],
// Audio and video with session publisher direction and data channel
[
"a=sendonly\n" .
"m=audio 42108 RTP/AVP 0\n" .
"m=video 42108 RTP/AVP 0\n" .
"m=application 8 UDP/DTLS/SCTP webrtc-datachannel\n" .
"a=sendrecv\n",
true, true,
],
// Audio and video with session receiver direction and data channel
[
"a=recvonly\n" .
"m=audio 42108 RTP/AVP 0\n" .
"m=video 42108 RTP/AVP 0\n" .
"m=application 8 UDP/DTLS/SCTP webrtc-datachannel\n" .
"a=sendrecv\n",
false, false,
],
// Audio and video with implicit publisher/receiver direction
[
"m=audio 42108 RTP/AVP 0\n" .
"m=video 42108 RTP/AVP 0\n",
true, true,
],
// Audio and video with implicit publisher/receiver direction and
// data channel
[
"m=audio 42108 RTP/AVP 0\n" .
"m=video 42108 RTP/AVP 0\n" .
"m=application 8 UDP/DTLS/SCTP webrtc-datachannel\n",
true, true,
],
// No audio and video description with session publisher direction
[
"a=sendonly\n",
false, false,
],
// Several audio and video with mixed directions
[
"m=audio 42108 RTP/AVP 0\n" .
"a=inactive\n" .
"m=audio 42108 RTP/AVP 0\n" .
"a=sendrecv\n" .
"m=video 42108 RTP/AVP 0\n" .
"a=recvonly\n" .
"m=audio 42108 RTP/AVP 0\n" .
"a=recvonly\n" .
"m=video 42108 RTP/AVP 0\n" .
"a=recvonly\n" .
"m=video 42108 RTP/AVP 0\n" .
"a=inactive\n",
true, false,
],
// Several mixed directions in a single media description (not a
// valid SDP, but just in case)
[
"m=audio 42108 RTP/AVP 0\n" .
"a=inactive\n" .
"a=sendrecv\n" .
"a=recvonly\n" .
"m=video 42108 RTP/AVP 0\n" .
"a=sendrecv\n" .
"a=recvonly\n" .
"a=inactive\n",
true, true,
],
];
}

/**
* @dataProvider dataIsTryingToPublishMedia
*/
public function testIsTryingToPublishMedia(string $sdp, bool $expectedAudioResult, bool $expectedVideoResult) {
$this->assertSame($expectedAudioResult, self::invokePrivate($this->controller, 'isTryingToPublishMedia', [$sdp, 'audio']));
$this->assertSame($expectedVideoResult, self::invokePrivate($this->controller, 'isTryingToPublishMedia', [$sdp, 'video']));
}

private function validateBackendRandom($data, $random, $checksum) {
if (empty($random) || strlen($random) < 32) {
return false;
Expand Down

0 comments on commit d25f504

Please sign in to comment.