Skip to content

Commit dac770c

Browse files
authored
Parse SDP encoding names and fmtp names case-insensitively (#518)
RFC 4855 treats RTP encoding names and fmtp parameter names as case-insensitive. Accept mixed-case rtpmap/fmtp while keeping operator== exact for stored and configured media types, and expose equals_media_type for SDP-derived comparisons. Signed-off-by: Gareth Sylvester-Bradley <garethsb@nvidia.com>
1 parent d047c9e commit dac770c

8 files changed

Lines changed: 180 additions & 28 deletions

File tree

Development/cmake/NmosCppLibraries.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,6 +1093,7 @@ set(NMOS_CPP_NMOS_SOURCES
10931093
nmos/manifest_api.cpp
10941094
nmos/mdns.cpp
10951095
nmos/mdns_api.cpp
1096+
nmos/media_type.cpp
10961097
nmos/node_api.cpp
10971098
nmos/node_api_target_handler.cpp
10981099
nmos/node_behaviour.cpp

Development/nmos-cpp-node/node_implementation.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2036,7 +2036,7 @@ nmos::transport_file_parser make_node_implementation_transport_file_parser()
20362036

20372037
const auto validate_sdp_parameters = [](const web::json::value& receiver, const nmos::sdp_parameters& sdp_params)
20382038
{
2039-
if (nmos::media_types::video_jxsv == nmos::get_media_type(sdp_params))
2039+
if (equals_media_type(nmos::media_types::video_jxsv, nmos::get_media_type(sdp_params)))
20402040
{
20412041
nmos::validate_video_jxsv_sdp_parameters(receiver, sdp_params);
20422042
}

Development/nmos/media_type.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#include "nmos/media_type.h"
2+
3+
#include <boost/algorithm/string/predicate.hpp>
4+
5+
namespace nmos
6+
{
7+
bool equals_media_type(const media_type& lhs, const media_type& rhs)
8+
{
9+
return boost::algorithm::iequals(lhs.name, rhs.name);
10+
}
11+
}

Development/nmos/media_type.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ namespace nmos
1212
// and https://specs.amwa.tv/is-04/releases/v1.2.0/APIs/schemas/with-refs/receiver_video.html
1313
// etc.
1414
DEFINE_STRING_ENUM(media_type)
15+
16+
// RFC 4855: media subtype names (and thus media type strings that embed an
17+
// RTP encoding name) are case-insensitive. operator== remains exact-string
18+
// identity for stored / configured values.
19+
bool equals_media_type(const media_type& lhs, const media_type& rhs);
20+
1521
namespace media_types
1622
{
1723
// Video media types

Development/nmos/sdp_utils.cpp

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <limits>
44
#include <boost/algorithm/string/case_conv.hpp>
5+
#include <boost/algorithm/string/predicate.hpp>
56
#include <boost/asio/ip/address.hpp>
67
#include <boost/range/adaptor/filtered.hpp>
78
#include <boost/range/adaptor/transformed.hpp>
@@ -955,6 +956,31 @@ namespace nmos
955956
return media_type{ sdp_params.media_type.name + U("/") + sdp_params.rtpmap.encoding_name };
956957
}
957958

959+
namespace details
960+
{
961+
// Find the specified fmtp parameter name case-insensitive in the specified fmtp list per RFC 4855
962+
sdp_parameters::fmtp_t::const_iterator find_fmtp(const sdp_parameters::fmtp_t& fmtp, const utility::string_t& name)
963+
{
964+
return std::find_if(fmtp.begin(), fmtp.end(), [&](const sdp_parameters::fmtp_t::value_type& param)
965+
{
966+
return boost::algorithm::iequals(param.first, name);
967+
});
968+
}
969+
sdp_parameters::fmtp_t::iterator find_fmtp(sdp_parameters::fmtp_t& fmtp, const utility::string_t& name)
970+
{
971+
return std::find_if(fmtp.begin(), fmtp.end(), [&](const sdp_parameters::fmtp_t::value_type& param)
972+
{
973+
return boost::algorithm::iequals(param.first, name);
974+
});
975+
}
976+
977+
// RTP encoding names are case-insensitive per RFC 4855
978+
bool equals_encoding_name(const utility::string_t& lhs, const utility::string_t& rhs)
979+
{
980+
return boost::algorithm::iequals(lhs, rhs);
981+
}
982+
}
983+
958984
web::json::value make_session_description(const sdp_parameters& sdp_params, const web::json::value& transport_params, bst::optional<bool> source_filters)
959985
{
960986
return make_session_description(sdp_params, transport_params, make_rtpmap(sdp_params), make_fmtp(sdp_params), source_filters);
@@ -1573,7 +1599,10 @@ namespace nmos
15731599
if (0 == params.channel_count) params.channel_count = 1;
15741600

15751601
const auto& encoding_name = sdp_params.rtpmap.encoding_name;
1576-
params.bit_depth = !encoding_name.empty() && U('L') == encoding_name.front() ? utility::istringstreamed<uint32_t>(encoding_name.substr(1)) : 0;
1602+
// RTP encoding names are case-insensitive per RFC 4855 (e.g. "L24" / "l24")
1603+
params.bit_depth = !encoding_name.empty() && details::equals_encoding_name(encoding_name.substr(0, 1), U("L"))
1604+
? utility::istringstreamed<uint32_t>(encoding_name.substr(1))
1605+
: 0;
15771606

15781607
params.sample_rate = sdp_params.rtpmap.clock_rate;
15791608

@@ -1674,21 +1703,58 @@ namespace nmos
16741703

16751704
namespace details
16761705
{
1706+
bool is_audio_L_encoding_name(const utility::string_t& encoding_name)
1707+
{
1708+
return !encoding_name.empty() && equals_encoding_name(encoding_name.substr(0, 1), U("L"));
1709+
}
1710+
1711+
// Check the specified media type case-insensitive against enum values in the specified string constraint per RFC 4855
1712+
// cf. nmos::match_string_constraint
1713+
bool match_media_type_constraint(const utility::string_t& value, const web::json::value& constraint)
1714+
{
1715+
// first check the enum constraint if present, like nmos::details::match_enum_constraint but with equals_media_type
1716+
if (constraint.has_field(nmos::fields::constraint_enum))
1717+
{
1718+
const auto& enum_values = nmos::fields::constraint_enum(constraint).as_array();
1719+
const media_type actual{ value };
1720+
if (enum_values.end() == std::find_if(enum_values.begin(), enum_values.end(), [&](const web::json::value& enum_value)
1721+
{
1722+
return enum_value.is_string() && equals_media_type(nmos::media_type{ enum_value.as_string() }, actual);
1723+
}))
1724+
{
1725+
return false;
1726+
}
1727+
}
1728+
// then use nmos::match_string_constraint to check the pattern constraint if present
1729+
if (constraint.has_field(nmos::fields::constraint_pattern))
1730+
{
1731+
if (!nmos::match_string_constraint(value, web::json::value_of({
1732+
{ nmos::fields::constraint_pattern, nmos::fields::constraint_pattern(constraint) }
1733+
})))
1734+
{
1735+
return false;
1736+
}
1737+
}
1738+
return true;
1739+
}
1740+
16771741
nmos::format get_format(const sdp_parameters& sdp_params)
16781742
{
1679-
if (sdp::media_types::video == sdp_params.media_type && U("raw") == sdp_params.rtpmap.encoding_name) return nmos::formats::video;
1680-
if (sdp::media_types::audio == sdp_params.media_type && U("L") == sdp_params.rtpmap.encoding_name.substr(0, 1)) return nmos::formats::audio;
1681-
if (sdp::media_types::video == sdp_params.media_type && U("smpte291") == sdp_params.rtpmap.encoding_name) return nmos::formats::data;
1682-
if (sdp::media_types::video == sdp_params.media_type && U("SMPTE2022-6") == sdp_params.rtpmap.encoding_name) return nmos::formats::mux;
1743+
const auto& encoding_name = sdp_params.rtpmap.encoding_name;
1744+
if (sdp::media_types::video == sdp_params.media_type && equals_encoding_name(encoding_name, U("raw"))) return nmos::formats::video;
1745+
if (sdp::media_types::audio == sdp_params.media_type && is_audio_L_encoding_name(encoding_name)) return nmos::formats::audio;
1746+
if (sdp::media_types::video == sdp_params.media_type && equals_encoding_name(encoding_name, U("smpte291"))) return nmos::formats::data;
1747+
if (sdp::media_types::video == sdp_params.media_type && equals_encoding_name(encoding_name, U("SMPTE2022-6"))) return nmos::formats::mux;
16831748
throw sdp_processing_error("unsupported media type/encoding name");
16841749
}
16851750

16861751
format_parameters get_format_parameters(const sdp_parameters& sdp_params)
16871752
{
1688-
if (sdp::media_types::video == sdp_params.media_type && U("raw") == sdp_params.rtpmap.encoding_name) return get_video_raw_parameters(sdp_params);
1689-
if (sdp::media_types::audio == sdp_params.media_type && U("L") == sdp_params.rtpmap.encoding_name.substr(0, 1)) return get_audio_L_parameters(sdp_params);
1690-
if (sdp::media_types::video == sdp_params.media_type && U("smpte291") == sdp_params.rtpmap.encoding_name) return get_video_smpte291_parameters(sdp_params);
1691-
if (sdp::media_types::video == sdp_params.media_type && U("SMPTE2022-6") == sdp_params.rtpmap.encoding_name) return get_video_SMPTE2022_6_parameters(sdp_params);
1753+
const auto& encoding_name = sdp_params.rtpmap.encoding_name;
1754+
if (sdp::media_types::video == sdp_params.media_type && equals_encoding_name(encoding_name, U("raw"))) return get_video_raw_parameters(sdp_params);
1755+
if (sdp::media_types::audio == sdp_params.media_type && is_audio_L_encoding_name(encoding_name)) return get_audio_L_parameters(sdp_params);
1756+
if (sdp::media_types::video == sdp_params.media_type && equals_encoding_name(encoding_name, U("smpte291"))) return get_video_smpte291_parameters(sdp_params);
1757+
if (sdp::media_types::video == sdp_params.media_type && equals_encoding_name(encoding_name, U("SMPTE2022-6"))) return get_video_SMPTE2022_6_parameters(sdp_params);
16921758
throw sdp_processing_error("unsupported media type/encoding name");
16931759
}
16941760

@@ -1723,7 +1789,7 @@ namespace nmos
17231789
{
17241790
// General Constraints
17251791

1726-
{ nmos::caps::format::media_type, [](CAPS_ARGS) { return nmos::match_string_constraint(get_media_type(sdp).name, con); } },
1792+
{ nmos::caps::format::media_type, [](CAPS_ARGS) { return match_media_type_constraint(get_media_type(sdp).name, con); } },
17271793
// hm, how best to match (rational) nmos::caps::format::grain_rate against (double) framerate e.g. for video/SMPTE2022-6?
17281794
// is 23.976 a match for 24000/1001? how about 23.98, or 23.9? or even 23?!
17291795
{ nmos::caps::format::grain_rate, [](CAPS_ARGS) { auto exactframerate = get_exactframerate(&format); return nmos::rational{} == exactframerate || nmos::match_rational_constraint(exactframerate, con); } },
@@ -1781,7 +1847,10 @@ namespace nmos
17811847
if (!media_types_or_null.is_null())
17821848
{
17831849
const auto& media_types = media_types_or_null.as_array();
1784-
const auto found = std::find(media_types.begin(), media_types.end(), web::json::value::string(media_type.name));
1850+
const auto found = std::find_if(media_types.begin(), media_types.end(), [&](const web::json::value& candidate)
1851+
{
1852+
return candidate.is_string() && equals_media_type(nmos::media_type{ candidate.as_string() }, media_type);
1853+
});
17851854
if (media_types.end() == found) throw details::sdp_processing_error("unsupported encoding name");
17861855
}
17871856
const auto& constraint_sets_or_null = nmos::fields::constraint_sets(caps);

Development/nmos/sdp_utils.h

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -653,20 +653,12 @@ namespace nmos
653653
}
654654
};
655655

656-
inline sdp_parameters::fmtp_t::const_iterator find_fmtp(const sdp_parameters::fmtp_t& fmtp, const utility::string_t& name)
657-
{
658-
return std::find_if(fmtp.begin(), fmtp.end(), [&](const sdp_parameters::fmtp_t::value_type& param)
659-
{
660-
return param.first == name;
661-
});
662-
}
663-
inline sdp_parameters::fmtp_t::iterator find_fmtp(sdp_parameters::fmtp_t& fmtp, const utility::string_t& name)
664-
{
665-
return std::find_if(fmtp.begin(), fmtp.end(), [&](const sdp_parameters::fmtp_t::value_type& param)
666-
{
667-
return param.first == name;
668-
});
669-
}
656+
// Find the specified fmtp parameter name case-insensitive in the specified fmtp list per RFC 4855
657+
sdp_parameters::fmtp_t::const_iterator find_fmtp(const sdp_parameters::fmtp_t& fmtp, const utility::string_t& name);
658+
sdp_parameters::fmtp_t::iterator find_fmtp(sdp_parameters::fmtp_t& fmtp, const utility::string_t& name);
659+
660+
// RTP encoding names are case-insensitive per RFC 4855
661+
bool equals_encoding_name(const utility::string_t& lhs, const utility::string_t& rhs);
670662

671663
// type-erased format-specific parameters
672664
// e.g. can hold a video_raw_parameters, an audio_L_parameters, etc.
@@ -688,6 +680,9 @@ namespace nmos
688680
// Check the specified SDP interlace and segmented parameters against the specified interlace_mode constraint
689681
bool match_interlace_mode_constraint(bool interlace, bool segmented, const web::json::value& constraint);
690682

683+
// Check the specified media type case-insensitive against enum values in the specified string constraint per RFC 4855
684+
bool match_media_type_constraint(const utility::string_t& value, const web::json::value& constraint);
685+
691686
// Check the specified SDP parameters and format-specific parameters against the specified constraint set
692687
// using the specified parameter constraint functions
693688
bool match_sdp_parameters_constraint_set(const sdp_parameter_constraints& constraints, const sdp_parameters& sdp_params, const format_parameters& format_params, const web::json::value& constraint_set);

Development/nmos/test/sdp_utils_test.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,75 @@ BST_TEST_CASE(testSdpParametersVideoRaw)
840840
}
841841
}
842842

843+
////////////////////////////////////////////////////////////////////////////////////////////
844+
// RFC 4855: RTP encoding names and fmtp parameter names are case-insensitive.
845+
BST_TEST_CASE(testSdpEncodingNameAndFmtpCaseInsensitive)
846+
{
847+
using web::json::value_of;
848+
849+
nmos::sdp_parameters mixed_case_video{
850+
U("mixed-case"),
851+
sdp::media_types::video,
852+
{
853+
96,
854+
U("RAW"),
855+
90000
856+
},
857+
{
858+
{ U("sampling"), U("YCbCr-4:2:2") },
859+
{ U("depth"), U("10") },
860+
{ U("width"), U("1920") },
861+
{ U("height"), U("1080") },
862+
{ U("exactframerate"), U("50") },
863+
{ U("colorimetry"), U("BT709") },
864+
{ U("pm"), U("2110GPM") },
865+
{ U("ssn"), U("ST2110-20:2017") },
866+
{ U("tp"), U("2110TPN") }
867+
}
868+
};
869+
870+
const auto video = nmos::get_video_raw_parameters(mixed_case_video);
871+
BST_REQUIRE_EQUAL(sdp::packing_modes::general.name, video.pm.name);
872+
BST_REQUIRE_EQUAL(sdp::smpte_standard_numbers::ST2110_20_2017.name, video.ssn.name);
873+
BST_REQUIRE_EQUAL(sdp::type_parameters::type_N.name, video.tp.name);
874+
BST_REQUIRE_EQUAL(1920u, video.width);
875+
BST_REQUIRE_EQUAL(1080u, video.height);
876+
877+
auto video_receiver = value_of({
878+
{ nmos::fields::format, nmos::formats::video.name },
879+
{ nmos::fields::caps, value_of({
880+
{ nmos::fields::media_types, value_of({ nmos::media_types::video_raw.name }) }
881+
}) }
882+
});
883+
BST_REQUIRE_NO_THROW(nmos::validate_sdp_parameters(video_receiver, mixed_case_video));
884+
885+
nmos::sdp_parameters mixed_case_audio{
886+
U("mixed-case-audio"),
887+
sdp::media_types::audio,
888+
{
889+
97,
890+
U("l24"),
891+
48000,
892+
2
893+
},
894+
{
895+
{ U("CHANNEL-ORDER"), U("SMPTE2110.(ST)") }
896+
}
897+
};
898+
899+
const auto audio = nmos::get_audio_L_parameters(mixed_case_audio);
900+
BST_REQUIRE_EQUAL(24u, audio.bit_depth);
901+
BST_REQUIRE_EQUAL(U("SMPTE2110.(ST)"), audio.channel_order);
902+
903+
auto audio_receiver = value_of({
904+
{ nmos::fields::format, nmos::formats::audio.name },
905+
{ nmos::fields::caps, value_of({
906+
{ nmos::fields::media_types, value_of({ nmos::media_types::audio_L24.name }) }
907+
}) }
908+
});
909+
BST_REQUIRE_NO_THROW(nmos::validate_sdp_parameters(audio_receiver, mixed_case_audio));
910+
}
911+
843912
////////////////////////////////////////////////////////////////////////////////////////////
844913
BST_TEST_CASE(testSdpParametersAudioL)
845914
{

Development/nmos/video_jxsv.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ namespace nmos
317317
#define CAPS_ARGS const sdp_parameters& sdp, const format_parameters& format, const web::json::value& con
318318
static const std::map<utility::string_t, std::function<bool(CAPS_ARGS)>> jxsv_constraints
319319
{
320-
{ nmos::caps::format::media_type, [](CAPS_ARGS) { return nmos::match_string_constraint(get_media_type(sdp).name, con); } },
320+
{ nmos::caps::format::media_type, [](CAPS_ARGS) { return nmos::details::match_media_type_constraint(get_media_type(sdp).name, con); } },
321321
{ nmos::caps::format::grain_rate, [](CAPS_ARGS) { auto jxsv = get_jxsv(&format); return jxsv && (nmos::rational{} == jxsv->exactframerate || nmos::match_rational_constraint(jxsv->exactframerate, con)); } },
322322
{ nmos::caps::format::profile, [](CAPS_ARGS) { auto jxsv = get_jxsv(&format); return jxsv && (jxsv->profile.empty() || nmos::match_string_constraint(jxsv->profile.name, con)); } },
323323
{ nmos::caps::format::level, [](CAPS_ARGS) { auto jxsv = get_jxsv(&format); return jxsv && (jxsv->level.empty() || nmos::match_string_constraint(jxsv->level.name, con)); } },
@@ -342,7 +342,8 @@ namespace nmos
342342
{
343343
// this function can only be used to validate SDP data for "video/jxsv"; logic error otherwise
344344
const auto media_type = get_media_type(sdp_params);
345-
if (nmos::media_types::video_jxsv != media_type) throw std::invalid_argument("unexpected media type/encoding name");
345+
if (!equals_media_type(nmos::media_types::video_jxsv, media_type))
346+
throw std::invalid_argument("unexpected media type/encoding name");
346347

347348
nmos::details::validate_sdp_parameters(details::jxsv_constraints, sdp_params, nmos::formats::video, get_video_jxsv_parameters(sdp_params), receiver);
348349
}

0 commit comments

Comments
 (0)