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
1 change: 1 addition & 0 deletions Development/cmake/NmosCppLibraries.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,7 @@ set(NMOS_CPP_NMOS_SOURCES
nmos/manifest_api.cpp
nmos/mdns.cpp
nmos/mdns_api.cpp
nmos/media_type.cpp
nmos/node_api.cpp
nmos/node_api_target_handler.cpp
nmos/node_behaviour.cpp
Expand Down
2 changes: 1 addition & 1 deletion Development/nmos-cpp-node/node_implementation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2036,7 +2036,7 @@ nmos::transport_file_parser make_node_implementation_transport_file_parser()

const auto validate_sdp_parameters = [](const web::json::value& receiver, const nmos::sdp_parameters& sdp_params)
{
if (nmos::media_types::video_jxsv == nmos::get_media_type(sdp_params))
if (equals_media_type(nmos::media_types::video_jxsv, nmos::get_media_type(sdp_params)))
{
nmos::validate_video_jxsv_sdp_parameters(receiver, sdp_params);
}
Expand Down
11 changes: 11 additions & 0 deletions Development/nmos/media_type.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include "nmos/media_type.h"

#include <boost/algorithm/string/predicate.hpp>

namespace nmos
{
bool equals_media_type(const media_type& lhs, const media_type& rhs)
{
return boost::algorithm::iequals(lhs.name, rhs.name);
}
}
6 changes: 6 additions & 0 deletions Development/nmos/media_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ namespace nmos
// and https://specs.amwa.tv/is-04/releases/v1.2.0/APIs/schemas/with-refs/receiver_video.html
// etc.
DEFINE_STRING_ENUM(media_type)

// RFC 4855: media subtype names (and thus media type strings that embed an
// RTP encoding name) are case-insensitive. operator== remains exact-string
// identity for stored / configured values.
bool equals_media_type(const media_type& lhs, const media_type& rhs);

namespace media_types
{
// Video media types
Expand Down
91 changes: 80 additions & 11 deletions Development/nmos/sdp_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <limits>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/asio/ip/address.hpp>
#include <boost/range/adaptor/filtered.hpp>
#include <boost/range/adaptor/transformed.hpp>
Expand Down Expand Up @@ -955,6 +956,31 @@ namespace nmos
return media_type{ sdp_params.media_type.name + U("/") + sdp_params.rtpmap.encoding_name };
}

namespace details
{
// Find the specified fmtp parameter name case-insensitive in the specified fmtp list per RFC 4855
sdp_parameters::fmtp_t::const_iterator find_fmtp(const sdp_parameters::fmtp_t& fmtp, const utility::string_t& name)
{
return std::find_if(fmtp.begin(), fmtp.end(), [&](const sdp_parameters::fmtp_t::value_type& param)
{
return boost::algorithm::iequals(param.first, name);
});
}
sdp_parameters::fmtp_t::iterator find_fmtp(sdp_parameters::fmtp_t& fmtp, const utility::string_t& name)
{
return std::find_if(fmtp.begin(), fmtp.end(), [&](const sdp_parameters::fmtp_t::value_type& param)
{
return boost::algorithm::iequals(param.first, name);
});
}

// RTP encoding names are case-insensitive per RFC 4855
bool equals_encoding_name(const utility::string_t& lhs, const utility::string_t& rhs)
{
return boost::algorithm::iequals(lhs, rhs);
}
}

web::json::value make_session_description(const sdp_parameters& sdp_params, const web::json::value& transport_params, bst::optional<bool> source_filters)
{
return make_session_description(sdp_params, transport_params, make_rtpmap(sdp_params), make_fmtp(sdp_params), source_filters);
Expand Down Expand Up @@ -1573,7 +1599,10 @@ namespace nmos
if (0 == params.channel_count) params.channel_count = 1;

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

params.sample_rate = sdp_params.rtpmap.clock_rate;

Expand Down Expand Up @@ -1674,21 +1703,58 @@ namespace nmos

namespace details
{
bool is_audio_L_encoding_name(const utility::string_t& encoding_name)
{
return !encoding_name.empty() && equals_encoding_name(encoding_name.substr(0, 1), U("L"));
}

// Check the specified media type case-insensitive against enum values in the specified string constraint per RFC 4855
// cf. nmos::match_string_constraint
bool match_media_type_constraint(const utility::string_t& value, const web::json::value& constraint)
{
// first check the enum constraint if present, like nmos::details::match_enum_constraint but with equals_media_type
if (constraint.has_field(nmos::fields::constraint_enum))
{
const auto& enum_values = nmos::fields::constraint_enum(constraint).as_array();
const media_type actual{ value };
if (enum_values.end() == std::find_if(enum_values.begin(), enum_values.end(), [&](const web::json::value& enum_value)
{
return enum_value.is_string() && equals_media_type(nmos::media_type{ enum_value.as_string() }, actual);
}))
{
return false;
}
}
// then use nmos::match_string_constraint to check the pattern constraint if present
if (constraint.has_field(nmos::fields::constraint_pattern))
{
if (!nmos::match_string_constraint(value, web::json::value_of({
{ nmos::fields::constraint_pattern, nmos::fields::constraint_pattern(constraint) }
})))
{
return false;
}
}
return true;
}

nmos::format get_format(const sdp_parameters& sdp_params)
{
if (sdp::media_types::video == sdp_params.media_type && U("raw") == sdp_params.rtpmap.encoding_name) return nmos::formats::video;
if (sdp::media_types::audio == sdp_params.media_type && U("L") == sdp_params.rtpmap.encoding_name.substr(0, 1)) return nmos::formats::audio;
if (sdp::media_types::video == sdp_params.media_type && U("smpte291") == sdp_params.rtpmap.encoding_name) return nmos::formats::data;
if (sdp::media_types::video == sdp_params.media_type && U("SMPTE2022-6") == sdp_params.rtpmap.encoding_name) return nmos::formats::mux;
const auto& encoding_name = sdp_params.rtpmap.encoding_name;
if (sdp::media_types::video == sdp_params.media_type && equals_encoding_name(encoding_name, U("raw"))) return nmos::formats::video;
if (sdp::media_types::audio == sdp_params.media_type && is_audio_L_encoding_name(encoding_name)) return nmos::formats::audio;
if (sdp::media_types::video == sdp_params.media_type && equals_encoding_name(encoding_name, U("smpte291"))) return nmos::formats::data;
if (sdp::media_types::video == sdp_params.media_type && equals_encoding_name(encoding_name, U("SMPTE2022-6"))) return nmos::formats::mux;
throw sdp_processing_error("unsupported media type/encoding name");
}

format_parameters get_format_parameters(const sdp_parameters& sdp_params)
{
if (sdp::media_types::video == sdp_params.media_type && U("raw") == sdp_params.rtpmap.encoding_name) return get_video_raw_parameters(sdp_params);
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);
if (sdp::media_types::video == sdp_params.media_type && U("smpte291") == sdp_params.rtpmap.encoding_name) return get_video_smpte291_parameters(sdp_params);
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);
const auto& encoding_name = sdp_params.rtpmap.encoding_name;
if (sdp::media_types::video == sdp_params.media_type && equals_encoding_name(encoding_name, U("raw"))) return get_video_raw_parameters(sdp_params);
if (sdp::media_types::audio == sdp_params.media_type && is_audio_L_encoding_name(encoding_name)) return get_audio_L_parameters(sdp_params);
if (sdp::media_types::video == sdp_params.media_type && equals_encoding_name(encoding_name, U("smpte291"))) return get_video_smpte291_parameters(sdp_params);
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);
throw sdp_processing_error("unsupported media type/encoding name");
}

Expand Down Expand Up @@ -1723,7 +1789,7 @@ namespace nmos
{
// General Constraints

{ nmos::caps::format::media_type, [](CAPS_ARGS) { return nmos::match_string_constraint(get_media_type(sdp).name, con); } },
{ nmos::caps::format::media_type, [](CAPS_ARGS) { return match_media_type_constraint(get_media_type(sdp).name, con); } },
// hm, how best to match (rational) nmos::caps::format::grain_rate against (double) framerate e.g. for video/SMPTE2022-6?
// is 23.976 a match for 24000/1001? how about 23.98, or 23.9? or even 23?!
{ nmos::caps::format::grain_rate, [](CAPS_ARGS) { auto exactframerate = get_exactframerate(&format); return nmos::rational{} == exactframerate || nmos::match_rational_constraint(exactframerate, con); } },
Expand Down Expand Up @@ -1781,7 +1847,10 @@ namespace nmos
if (!media_types_or_null.is_null())
{
const auto& media_types = media_types_or_null.as_array();
const auto found = std::find(media_types.begin(), media_types.end(), web::json::value::string(media_type.name));
const auto found = std::find_if(media_types.begin(), media_types.end(), [&](const web::json::value& candidate)
{
return candidate.is_string() && equals_media_type(nmos::media_type{ candidate.as_string() }, media_type);
});
if (media_types.end() == found) throw details::sdp_processing_error("unsupported encoding name");
}
const auto& constraint_sets_or_null = nmos::fields::constraint_sets(caps);
Expand Down
23 changes: 9 additions & 14 deletions Development/nmos/sdp_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -653,20 +653,12 @@ namespace nmos
}
};

inline sdp_parameters::fmtp_t::const_iterator find_fmtp(const sdp_parameters::fmtp_t& fmtp, const utility::string_t& name)
{
return std::find_if(fmtp.begin(), fmtp.end(), [&](const sdp_parameters::fmtp_t::value_type& param)
{
return param.first == name;
});
}
inline sdp_parameters::fmtp_t::iterator find_fmtp(sdp_parameters::fmtp_t& fmtp, const utility::string_t& name)
{
return std::find_if(fmtp.begin(), fmtp.end(), [&](const sdp_parameters::fmtp_t::value_type& param)
{
return param.first == name;
});
}
// Find the specified fmtp parameter name case-insensitive in the specified fmtp list per RFC 4855
sdp_parameters::fmtp_t::const_iterator find_fmtp(const sdp_parameters::fmtp_t& fmtp, const utility::string_t& name);
sdp_parameters::fmtp_t::iterator find_fmtp(sdp_parameters::fmtp_t& fmtp, const utility::string_t& name);

// RTP encoding names are case-insensitive per RFC 4855
bool equals_encoding_name(const utility::string_t& lhs, const utility::string_t& rhs);

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

// Check the specified media type case-insensitive against enum values in the specified string constraint per RFC 4855
bool match_media_type_constraint(const utility::string_t& value, const web::json::value& constraint);

// Check the specified SDP parameters and format-specific parameters against the specified constraint set
// using the specified parameter constraint functions
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);
Expand Down
69 changes: 69 additions & 0 deletions Development/nmos/test/sdp_utils_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,75 @@ BST_TEST_CASE(testSdpParametersVideoRaw)
}
}

////////////////////////////////////////////////////////////////////////////////////////////
// RFC 4855: RTP encoding names and fmtp parameter names are case-insensitive.
BST_TEST_CASE(testSdpEncodingNameAndFmtpCaseInsensitive)
{
using web::json::value_of;

nmos::sdp_parameters mixed_case_video{
U("mixed-case"),
sdp::media_types::video,
{
96,
U("RAW"),
90000
},
{
{ U("sampling"), U("YCbCr-4:2:2") },
{ U("depth"), U("10") },
{ U("width"), U("1920") },
{ U("height"), U("1080") },
{ U("exactframerate"), U("50") },
{ U("colorimetry"), U("BT709") },
{ U("pm"), U("2110GPM") },
{ U("ssn"), U("ST2110-20:2017") },
{ U("tp"), U("2110TPN") }
}
};

const auto video = nmos::get_video_raw_parameters(mixed_case_video);
BST_REQUIRE_EQUAL(sdp::packing_modes::general.name, video.pm.name);
BST_REQUIRE_EQUAL(sdp::smpte_standard_numbers::ST2110_20_2017.name, video.ssn.name);
BST_REQUIRE_EQUAL(sdp::type_parameters::type_N.name, video.tp.name);
BST_REQUIRE_EQUAL(1920u, video.width);
BST_REQUIRE_EQUAL(1080u, video.height);

auto video_receiver = value_of({
{ nmos::fields::format, nmos::formats::video.name },
{ nmos::fields::caps, value_of({
{ nmos::fields::media_types, value_of({ nmos::media_types::video_raw.name }) }
}) }
});
BST_REQUIRE_NO_THROW(nmos::validate_sdp_parameters(video_receiver, mixed_case_video));

nmos::sdp_parameters mixed_case_audio{
U("mixed-case-audio"),
sdp::media_types::audio,
{
97,
U("l24"),
48000,
2
},
{
{ U("CHANNEL-ORDER"), U("SMPTE2110.(ST)") }
}
};

const auto audio = nmos::get_audio_L_parameters(mixed_case_audio);
BST_REQUIRE_EQUAL(24u, audio.bit_depth);
BST_REQUIRE_EQUAL(U("SMPTE2110.(ST)"), audio.channel_order);

auto audio_receiver = value_of({
{ nmos::fields::format, nmos::formats::audio.name },
{ nmos::fields::caps, value_of({
{ nmos::fields::media_types, value_of({ nmos::media_types::audio_L24.name }) }
}) }
});
BST_REQUIRE_NO_THROW(nmos::validate_sdp_parameters(audio_receiver, mixed_case_audio));
}

////////////////////////////////////////////////////////////////////////////////////////////
BST_TEST_CASE(testSdpParametersAudioL)
{
Expand Down
5 changes: 3 additions & 2 deletions Development/nmos/video_jxsv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ namespace nmos
#define CAPS_ARGS const sdp_parameters& sdp, const format_parameters& format, const web::json::value& con
static const std::map<utility::string_t, std::function<bool(CAPS_ARGS)>> jxsv_constraints
{
{ nmos::caps::format::media_type, [](CAPS_ARGS) { return nmos::match_string_constraint(get_media_type(sdp).name, con); } },
{ nmos::caps::format::media_type, [](CAPS_ARGS) { return nmos::details::match_media_type_constraint(get_media_type(sdp).name, con); } },
{ 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)); } },
{ nmos::caps::format::profile, [](CAPS_ARGS) { auto jxsv = get_jxsv(&format); return jxsv && (jxsv->profile.empty() || nmos::match_string_constraint(jxsv->profile.name, con)); } },
{ nmos::caps::format::level, [](CAPS_ARGS) { auto jxsv = get_jxsv(&format); return jxsv && (jxsv->level.empty() || nmos::match_string_constraint(jxsv->level.name, con)); } },
Expand All @@ -342,7 +342,8 @@ namespace nmos
{
// this function can only be used to validate SDP data for "video/jxsv"; logic error otherwise
const auto media_type = get_media_type(sdp_params);
if (nmos::media_types::video_jxsv != media_type) throw std::invalid_argument("unexpected media type/encoding name");
if (!equals_media_type(nmos::media_types::video_jxsv, media_type))
throw std::invalid_argument("unexpected media type/encoding name");

nmos::details::validate_sdp_parameters(details::jxsv_constraints, sdp_params, nmos::formats::video, get_video_jxsv_parameters(sdp_params), receiver);
}
Expand Down
Loading