Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Development/sdp/sdp_grammar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ namespace sdp
};
}

converter array_converter(const converter& converter, const std::string& delimiter = " ")
converter array_converter(const converter& converter, const std::string& delimiter)
{
return{
[=](const web::json::value& v) {
Expand Down Expand Up @@ -211,7 +211,7 @@ namespace sdp
// in other RFCs and SMPTE standards are inconsistent, so allow additional whitespace
const converter named_values_converter = array_converter(key_value_converter('=', { sdp::fields::name, string_converter }, { sdp::fields::value, string_converter }), "; ", "[ \\t]*(;[ \\t]*|$)");

converter object_converter(const std::vector<std::pair<utility::string_t, converter>>& field_converters, const std::string& delimiter = " ")
converter object_converter(const std::vector<std::pair<utility::string_t, converter>>& field_converters, const std::string& delimiter)
{
return{
[=](const web::json::value& v) {
Expand Down
44 changes: 44 additions & 0 deletions Development/sdp/sdp_grammar.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,45 @@ namespace sdp
parser parse;
};

// converters for the value types used by this grammar, and generators for compound
// values, which may also be used to define additional attribute converters
// see https://tools.ietf.org/html/rfc4566#section-9
// none of these, or get_default_attribute_converters, default_attribute_converter
// or session_description below, are safe to use during static initialization;
// prefer a function-local static for application-defined grammars

// <byte-string>
extern const converter string_converter;

// a number, e.g. <frame rate> in "a=framerate:<frame rate>"
extern const converter number_converter;

// 1*DIGIT
extern const converter digits_converter;

// 1*DIGIT, represented as a numeric string, for values which may exceed the range
// of a number, e.g. <sess-id>
extern const converter big_digits_converter;

// a space-separated list of <byte-string>
extern const converter strings_converter;

// a semicolon-separated list of <name>[=<value>], e.g. <format specific parameters>
extern const converter named_values_converter;

// <key>[<separator><value>]
converter key_value_converter(char separator, const std::pair<utility::string_t, converter>& key_converter, const std::pair<utility::string_t, converter>& value_converter);

// a delimited list of values, represented as a json array
converter array_converter(const converter& converter, const std::string& delimiter = " ");

// identical to above except that parse_delimiter is a regex pattern
converter array_converter(const converter& converter, const std::string& format_delimiter, const std::string& parse_delimiter);

// a delimited sequence of values, represented as a json object with the specified field names
// an empty field name indicates the values of the converted json object are merged into the result
converter object_converter(const std::vector<std::pair<utility::string_t, converter>>& field_converters, const std::string& delimiter = " ");

// "An SDP session description consists of a number of lines of text of
// the form: <type>=<value>
// where <type> MUST be exactly one case-significant character and
Expand Down Expand Up @@ -80,7 +119,12 @@ namespace sdp
// See https://tools.ietf.org/html/rfc4566#section-6
attribute_converters get_default_attribute_converters();

// converter for an attribute with no specific converter, which treats the <att-value> as an opaque <byte-string>
// and an attribute with no <att-value> as a property attribute
extern const converter default_attribute_converter;

// construct a grammar with the specified attribute converters
// the converters and default_converter are captured by reference and must outlive the returned grammar
description session_description(const attribute_converters& converters, const converter& default_converter);
}

Expand Down
91 changes: 91 additions & 0 deletions Development/sdp/test/sdp_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "bst/test/test.h"
#include "sdp/json.h"
#include "sdp/sdp_grammar.h"

////////////////////////////////////////////////////////////////////////////////////////////
BST_TEST_CASE(testSdpRoundtrip)
Expand Down Expand Up @@ -1120,3 +1121,93 @@ a=mid:SECONDARY
} while (!expected.fail() && !actual.fail());
}
}

////////////////////////////////////////////////////////////////////////////////////////////
namespace
{
// an example application-defined attribute
// a=x-example-foo:<format> <bar>
const utility::string_t example_foo{ U("x-example-foo") };

namespace example_fields
{
const web::json::field_as_integer format{ U("format") };
const web::json::field_as_string bar{ U("bar") };
}

// session_description captures converters by reference, so they must outlive
// the returned grammar; a function-local static is the natural application pattern
const sdp::grammar::attribute_converters& example_attribute_converters()
{
static const auto converters = [] {
auto converters = sdp::grammar::get_default_attribute_converters();
converters[example_foo] = sdp::grammar::object_converter({
{ example_fields::format, sdp::grammar::digits_converter },
{ example_fields::bar, sdp::grammar::string_converter }
});
return converters;
}();
return converters;
}

const sdp::grammar::description& example_grammar()
{
static const auto grammar = sdp::grammar::session_description(
example_attribute_converters(),
sdp::grammar::default_attribute_converter
);
return grammar;
}
}

BST_TEST_CASE(testSdpApplicationDefinedAttributes)
{
const auto& grammar = example_grammar();

const std::string test_sdp = R"(v=0
o=- 3745911798 3745911798 IN IP4 192.168.9.142
s=Example Sender 1 (Video)
t=0 0
a=x-example-foo:0 session-level
m=video 50020 RTP/AVP 96
c=IN IP4 239.22.142.1/32
a=rtpmap:96 raw/90000
a=x-example-foo:96 media-level
a=x-example-bar:unknown attributes are still handled by the default converter
a=recvonly
)";

auto session_description = sdp::parse_session_description(test_sdp, grammar);

auto& session_attributes = sdp::fields::attributes(session_description).as_array();
auto session_foo = sdp::find_name(session_attributes, example_foo);
BST_REQUIRE(session_attributes.end() != session_foo);
BST_REQUIRE_EQUAL(0, example_fields::format(sdp::fields::value(*session_foo)));
BST_REQUIRE_EQUAL(U("session-level"), example_fields::bar(sdp::fields::value(*session_foo)));

auto& media_attributes = sdp::fields::attributes(sdp::fields::media_descriptions(session_description).at(0)).as_array();
auto media_foo = sdp::find_name(media_attributes, example_foo);
BST_REQUIRE(media_attributes.end() != media_foo);
BST_REQUIRE_EQUAL(96, example_fields::format(sdp::fields::value(*media_foo)));
BST_REQUIRE_EQUAL(U("media-level"), example_fields::bar(sdp::fields::value(*media_foo)));

auto media_bar = sdp::find_name(media_attributes, U("x-example-bar"));
BST_REQUIRE(media_attributes.end() != media_bar);
BST_REQUIRE_EQUAL(U("unknown attributes are still handled by the default converter"), sdp::fields::value(*media_bar).as_string());

auto recvonly = sdp::find_name(media_attributes, sdp::attributes::recvonly);
BST_REQUIRE(media_attributes.end() != recvonly);
BST_REQUIRE(sdp::fields::value(*recvonly).is_null());

auto test_sdp2 = sdp::make_session_description(session_description, grammar);
std::istringstream expected(test_sdp), actual(test_sdp2);
do
{
std::string expected_line, actual_line;
std::getline(expected, expected_line);
std::getline(actual, actual_line);
// CR cannot appear in a raw string literal, so remove it from the actual line
if (!actual_line.empty() && '\r' == actual_line.back()) actual_line.pop_back();
BST_CHECK_EQUAL(expected_line, actual_line);
} while (!expected.fail() && !actual.fail());
}
Loading