blob: b5a1d877f1f0e086ca55059aa947607678736082 [file] [log] [blame]
/*
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/sdpserializer.h"
#include <string>
#include <utility>
#include <vector>
#include "api/jsep.h"
#include "rtc_base/strings/string_builder.h"
using cricket::SimulcastDescription;
using cricket::SimulcastLayer;
using cricket::SimulcastLayerList;
namespace webrtc {
namespace {
// delimiters
const char kDelimiterComma[] = ",";
const char kDelimiterCommaChar = ',';
const char kDelimiterSemicolon[] = ";";
const char kDelimiterSemicolonChar = ';';
const char kDelimiterSpace[] = " ";
const char kDelimiterSpaceChar = ' ';
// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
const char kSimulcastPausedStream[] = "~";
const char kSimulcastPausedStreamChar = '~';
const char kSimulcastSendStreams[] = "send";
const char kSimulcastReceiveStreams[] = "recv";
RTCError ParseError(const std::string& message) {
return RTCError(RTCErrorType::SYNTAX_ERROR, message);
}
// These methods serialize simulcast according to the specification:
// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
rtc::StringBuilder& operator<<(rtc::StringBuilder& builder,
const SimulcastLayer& simulcast_layer) {
if (simulcast_layer.is_paused) {
builder << kSimulcastPausedStream;
}
builder << simulcast_layer.rid;
return builder;
}
rtc::StringBuilder& operator<<(
rtc::StringBuilder& builder,
const std::vector<SimulcastLayer>& layer_alternatives) {
bool first = true;
for (const SimulcastLayer& rid : layer_alternatives) {
if (!first) {
builder << kDelimiterComma;
}
builder << rid;
first = false;
}
return builder;
}
rtc::StringBuilder& operator<<(rtc::StringBuilder& builder,
const SimulcastLayerList& simulcast_layers) {
bool first = true;
for (auto alternatives : simulcast_layers) {
if (!first) {
builder << kDelimiterSemicolon;
}
builder << alternatives;
first = false;
}
return builder;
}
// These methods deserialize simulcast according to the specification:
// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
// sc-str-list = sc-alt-list *( ";" sc-alt-list )
// sc-alt-list = sc-id *( "," sc-id )
// sc-id-paused = "~"
// sc-id = [sc-id-paused] rid-id
// rid-id = 1*(alpha-numeric / "-" / "_") ; see: I-D.ietf-mmusic-rid
RTCErrorOr<SimulcastLayerList> ParseSimulcastLayerList(const std::string& str) {
std::vector<std::string> tokens;
rtc::tokenize_with_empty_tokens(str, kDelimiterSemicolonChar, &tokens);
if (tokens.empty()) {
return ParseError("Layer list cannot be empty.");
}
SimulcastLayerList result;
for (const std::string& token : tokens) {
if (token.empty()) {
return ParseError("Simulcast alternative layer list is empty.");
}
std::vector<std::string> rid_tokens;
rtc::tokenize_with_empty_tokens(token, kDelimiterCommaChar, &rid_tokens);
if (rid_tokens.empty()) {
return ParseError("Simulcast alternative layer list is malformed.");
}
std::vector<SimulcastLayer> layers;
for (const auto& rid_token : rid_tokens) {
if (rid_token.empty() || rid_token == kSimulcastPausedStream) {
return ParseError("Rid must not be empty.");
}
bool paused = rid_token[0] == kSimulcastPausedStreamChar;
std::string rid = paused ? rid_token.substr(1) : rid_token;
// TODO(amithi, bugs.webrtc.org/10073):
// Validate the rid format.
// See also: https://github.com/w3c/webrtc-pc/issues/2013
layers.push_back(SimulcastLayer(rid, paused));
}
result.AddLayerWithAlternatives(layers);
}
return std::move(result);
}
} // namespace
std::string SdpSerializer::SerializeSimulcastDescription(
const cricket::SimulcastDescription& simulcast) const {
rtc::StringBuilder sb;
std::string delimiter;
if (!simulcast.send_layers().empty()) {
sb << kSimulcastSendStreams << kDelimiterSpace << simulcast.send_layers();
delimiter = kDelimiterSpace;
}
if (!simulcast.receive_layers().empty()) {
sb << delimiter << kSimulcastReceiveStreams << kDelimiterSpace
<< simulcast.receive_layers();
}
return sb.str();
}
// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
// a:simulcast:<send> <streams> <recv> <streams>
// Formal Grammar
// sc-value = ( sc-send [SP sc-recv] ) / ( sc-recv [SP sc-send] )
// sc-send = %s"send" SP sc-str-list
// sc-recv = %s"recv" SP sc-str-list
// sc-str-list = sc-alt-list *( ";" sc-alt-list )
// sc-alt-list = sc-id *( "," sc-id )
// sc-id-paused = "~"
// sc-id = [sc-id-paused] rid-id
// rid-id = 1*(alpha-numeric / "-" / "_") ; see: I-D.ietf-mmusic-rid
RTCErrorOr<SimulcastDescription> SdpSerializer::DeserializeSimulcastDescription(
absl::string_view string) const {
std::vector<std::string> tokens;
rtc::tokenize(std::string(string), kDelimiterSpaceChar, &tokens);
if (tokens.size() != 2 && tokens.size() != 4) {
return ParseError("Must have one or two <direction, streams> pairs.");
}
bool bidirectional = tokens.size() == 4; // indicates both send and recv
// Tokens 0, 2 (if exists) should be send / recv
if ((tokens[0] != kSimulcastSendStreams &&
tokens[0] != kSimulcastReceiveStreams) ||
(bidirectional && tokens[2] != kSimulcastSendStreams &&
tokens[2] != kSimulcastReceiveStreams) ||
(bidirectional && tokens[0] == tokens[2])) {
return ParseError("Valid values: send / recv.");
}
// Tokens 1, 3 (if exists) should be alternative layer lists
RTCErrorOr<SimulcastLayerList> list1, list2;
list1 = ParseSimulcastLayerList(tokens[1]);
if (!list1.ok()) {
return list1.MoveError();
}
if (bidirectional) {
list2 = ParseSimulcastLayerList(tokens[3]);
if (!list2.ok()) {
return list2.MoveError();
}
}
// Set the layers so that list1 is for send and list2 is for recv
if (tokens[0] != kSimulcastSendStreams) {
std::swap(list1, list2);
}
// Set the layers according to which pair is send and which is recv
// At this point if the simulcast is unidirectional then
// either |list1| or |list2| will be in 'error' state indicating that
// the value should not be used.
SimulcastDescription simulcast;
if (list1.ok()) {
simulcast.send_layers() = list1.MoveValue();
}
if (list2.ok()) {
simulcast.receive_layers() = list2.MoveValue();
}
return std::move(simulcast);
}
} // namespace webrtc