// Copyright 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "cast/streaming/offer_messages.h" #include #include #include #include #include #include "absl/strings/match.h" #include "absl/strings/numbers.h" #include "absl/strings/str_split.h" #include "cast/streaming/constants.h" #include "platform/base/error.h" #include "util/big_endian.h" #include "util/enum_name_table.h" #include "util/json/json_helpers.h" #include "util/json/json_serialization.h" #include "util/osp_logging.h" #include "util/stringprintf.h" namespace openscreen { namespace cast { namespace { constexpr char kSupportedStreams[] = "supportedStreams"; constexpr char kAudioSourceType[] = "audio_source"; constexpr char kVideoSourceType[] = "video_source"; constexpr char kStreamType[] = "type"; ErrorOr ParseRtpPayloadType(const Json::Value& parent, const std::string& field) { auto t = json::ParseInt(parent, field); if (!t) { return t.error(); } uint8_t t_small = t.value(); if (t_small != t.value() || !IsRtpPayloadType(t_small)) { return Error(Error::Code::kParameterInvalid, "Received invalid RTP Payload Type."); } return static_cast(t_small); } ErrorOr ParseRtpTimebase(const Json::Value& parent, const std::string& field) { auto error_or_raw = json::ParseString(parent, field); if (!error_or_raw) { return error_or_raw.error(); } // The spec demands a leading 1, so this isn't really a fraction. const auto fraction = SimpleFraction::FromString(error_or_raw.value()); if (fraction.is_error() || !fraction.value().is_positive() || fraction.value().numerator() != 1) { return json::CreateParseError("RTP timebase"); } return fraction.value().denominator(); } // For a hex byte, the conversion is 4 bits to 1 character, e.g. // 0b11110001 becomes F1, so 1 byte is two characters. constexpr int kHexDigitsPerByte = 2; constexpr int kAesBytesSize = 16; constexpr int kAesStringLength = kAesBytesSize * kHexDigitsPerByte; ErrorOr> ParseAesHexBytes( const Json::Value& parent, const std::string& field) { auto hex_string = json::ParseString(parent, field); if (!hex_string) { return hex_string.error(); } constexpr int kHexDigitsPerScanField = 16; constexpr int kNumScanFields = kAesStringLength / kHexDigitsPerScanField; uint64_t quads[kNumScanFields]; int chars_scanned; if (hex_string.value().size() == kAesStringLength && sscanf(hex_string.value().c_str(), "%16" SCNx64 "%16" SCNx64 "%n", &quads[0], &quads[1], &chars_scanned) == kNumScanFields && chars_scanned == kAesStringLength && std::none_of(hex_string.value().begin(), hex_string.value().end(), [](char c) { return std::isspace(c); })) { std::array bytes; WriteBigEndian(quads[0], bytes.data()); WriteBigEndian(quads[1], bytes.data() + 8); return bytes; } return json::CreateParseError("AES hex string bytes"); } ErrorOr ParseStream(const Json::Value& value, Stream::Type type) { auto index = json::ParseInt(value, "index"); if (!index) { return index.error(); } // If channel is omitted, the default value is used later. auto channels = json::ParseInt(value, "channels"); if (channels.is_value() && channels.value() <= 0) { return json::CreateParameterError("channel"); } auto rtp_profile = json::ParseString(value, "rtpProfile"); if (!rtp_profile) { return rtp_profile.error(); } auto rtp_payload_type = ParseRtpPayloadType(value, "rtpPayloadType"); if (!rtp_payload_type) { return rtp_payload_type.error(); } auto ssrc = json::ParseUint(value, "ssrc"); if (!ssrc) { return ssrc.error(); } auto aes_key = ParseAesHexBytes(value, "aesKey"); auto aes_iv_mask = ParseAesHexBytes(value, "aesIvMask"); if (!aes_key || !aes_iv_mask) { return Error(Error::Code::kUnencryptedOffer, "Offer stream must have both a valid aesKey and aesIvMask"); } auto rtp_timebase = ParseRtpTimebase(value, "timeBase"); if (!rtp_timebase) { return rtp_timebase.error(); } if (rtp_timebase.value() < std::min(kDefaultAudioMinSampleRate, kRtpVideoTimebase) || rtp_timebase.value() > kRtpVideoTimebase) { return json::CreateParameterError("rtp_timebase (sample rate)"); } auto target_delay = json::ParseInt(value, "targetDelay"); std::chrono::milliseconds target_delay_ms = kDefaultTargetPlayoutDelay; if (target_delay) { auto d = std::chrono::milliseconds(target_delay.value()); if (kMinTargetPlayoutDelay <= d && d <= kMaxTargetPlayoutDelay) { target_delay_ms = d; } } auto receiver_rtcp_event_log = json::ParseBool(value, "receiverRtcpEventLog"); auto receiver_rtcp_dscp = json::ParseString(value, "receiverRtcpDscp"); return Stream{index.value(), type, channels.value(type == Stream::Type::kAudioSource ? kDefaultNumAudioChannels : kDefaultNumVideoChannels), rtp_payload_type.value(), ssrc.value(), target_delay_ms, aes_key.value(), aes_iv_mask.value(), receiver_rtcp_event_log.value({}), receiver_rtcp_dscp.value({}), rtp_timebase.value()}; } ErrorOr ParseAudioStream(const Json::Value& value) { auto stream = ParseStream(value, Stream::Type::kAudioSource); if (!stream) { return stream.error(); } auto bit_rate = json::ParseInt(value, "bitRate"); if (!bit_rate) { return bit_rate.error(); } auto codec_name = json::ParseString(value, kCodecName); if (!codec_name) { return codec_name.error(); } ErrorOr codec = StringToAudioCodec(codec_name.value()); if (!codec) { return Error(Error::Code::kUnknownCodec, "Codec is not known, can't use stream"); } // A bit rate of 0 is valid for some codec types, so we don't enforce here. if (bit_rate.value() < 0) { return json::CreateParameterError("bit rate"); } return AudioStream{stream.value(), codec.value(), bit_rate.value()}; } ErrorOr> ParseResolutions(const Json::Value& parent, const std::string& field) { std::vector resolutions; // Some legacy senders don't provide resolutions, so just return empty. const Json::Value& value = parent[field]; if (!value.isArray() || value.empty()) { return resolutions; } for (Json::ArrayIndex i = 0; i < value.size(); ++i) { Resolution resolution; if (!Resolution::ParseAndValidate(value[i], &resolution)) { return Error(Error::Code::kJsonParseError); } resolutions.push_back(std::move(resolution)); } return resolutions; } ErrorOr ParseVideoStream(const Json::Value& value) { auto stream = ParseStream(value, Stream::Type::kVideoSource); if (!stream) { return stream.error(); } auto codec_name = json::ParseString(value, kCodecName); if (!codec_name) { return codec_name.error(); } ErrorOr codec = StringToVideoCodec(codec_name.value()); if (!codec) { return Error(Error::Code::kUnknownCodec, "Codec is not known, can't use stream"); } auto resolutions = ParseResolutions(value, "resolutions"); if (!resolutions) { return resolutions.error(); } auto raw_max_frame_rate = json::ParseString(value, "maxFrameRate"); SimpleFraction max_frame_rate{kDefaultMaxFrameRate, 1}; if (raw_max_frame_rate.is_value()) { auto parsed = SimpleFraction::FromString(raw_max_frame_rate.value()); if (parsed.is_value() && parsed.value().is_positive()) { max_frame_rate = parsed.value(); } } auto profile = json::ParseString(value, "profile"); auto protection = json::ParseString(value, "protection"); auto max_bit_rate = json::ParseInt(value, "maxBitRate"); auto level = json::ParseString(value, "level"); auto error_recovery_mode = json::ParseString(value, "errorRecoveryMode"); return VideoStream{stream.value(), codec.value(), max_frame_rate, max_bit_rate.value(4 << 20), protection.value({}), profile.value({}), level.value({}), resolutions.value(), error_recovery_mode.value({})}; } absl::string_view ToString(Stream::Type type) { switch (type) { case Stream::Type::kAudioSource: return kAudioSourceType; case Stream::Type::kVideoSource: return kVideoSourceType; default: { OSP_NOTREACHED(); } } } EnumNameTable kCastModeNames{ {{"mirroring", CastMode::kMirroring}, {"remoting", CastMode::kRemoting}}}; } // namespace Json::Value Stream::ToJson() const { OSP_DCHECK(IsValid()); Json::Value root; root["index"] = index; root["type"] = std::string(ToString(type)); root["channels"] = channels; root["rtpPayloadType"] = static_cast(rtp_payload_type); // rtpProfile is technically required by the spec, although it is always set // to cast. We set it here to be compliant with all spec implementers. root["rtpProfile"] = "cast"; static_assert(sizeof(ssrc) <= sizeof(Json::UInt), "this code assumes Ssrc fits in a Json::UInt"); root["ssrc"] = static_cast(ssrc); root["targetDelay"] = static_cast(target_delay.count()); root["aesKey"] = HexEncode(aes_key); root["aesIvMask"] = HexEncode(aes_iv_mask); root["receiverRtcpEventLog"] = receiver_rtcp_event_log; root["receiverRtcpDscp"] = receiver_rtcp_dscp; root["timeBase"] = "1/" + std::to_string(rtp_timebase); return root; } bool Stream::IsValid() const { return channels >= 1 && index >= 0 && target_delay.count() > 0 && target_delay.count() <= std::numeric_limits::max() && rtp_timebase >= 1; } Json::Value AudioStream::ToJson() const { OSP_DCHECK(IsValid()); Json::Value out = stream.ToJson(); out[kCodecName] = CodecToString(codec); out["bitRate"] = bit_rate; return out; } bool AudioStream::IsValid() const { return bit_rate >= 0 && stream.IsValid(); } Json::Value VideoStream::ToJson() const { OSP_DCHECK(IsValid()); Json::Value out = stream.ToJson(); out["codecName"] = CodecToString(codec); out["maxFrameRate"] = max_frame_rate.ToString(); out["maxBitRate"] = max_bit_rate; out["protection"] = protection; out["profile"] = profile; out["level"] = level; out["errorRecoveryMode"] = error_recovery_mode; Json::Value rs; for (auto resolution : resolutions) { rs.append(resolution.ToJson()); } out["resolutions"] = std::move(rs); return out; } bool VideoStream::IsValid() const { return max_bit_rate > 0 && max_frame_rate.is_positive(); } // static ErrorOr Offer::Parse(const Json::Value& root) { if (!root.isObject()) { return json::CreateParseError("null offer"); } const ErrorOr cast_mode = GetEnum(kCastModeNames, root["castMode"].asString()); Json::Value supported_streams = root[kSupportedStreams]; if (!supported_streams.isArray()) { return json::CreateParseError("supported streams in offer"); } std::vector audio_streams; std::vector video_streams; for (Json::ArrayIndex i = 0; i < supported_streams.size(); ++i) { const Json::Value& fields = supported_streams[i]; auto type = json::ParseString(fields, kStreamType); if (!type) { return type.error(); } if (type.value() == kAudioSourceType) { auto stream = ParseAudioStream(fields); if (!stream) { if (stream.error().code() == Error::Code::kUnknownCodec) { OSP_DVLOG << "Dropping audio stream due to unknown codec: " << stream.error(); continue; } else { return stream.error(); } } audio_streams.push_back(std::move(stream.value())); } else if (type.value() == kVideoSourceType) { auto stream = ParseVideoStream(fields); if (!stream) { if (stream.error().code() == Error::Code::kUnknownCodec) { OSP_DVLOG << "Dropping video stream due to unknown codec: " << stream.error(); continue; } else { return stream.error(); } } video_streams.push_back(std::move(stream.value())); } } return Offer{cast_mode.value(CastMode::kMirroring), std::move(audio_streams), std::move(video_streams)}; } Json::Value Offer::ToJson() const { OSP_DCHECK(IsValid()); Json::Value root; root["castMode"] = GetEnumName(kCastModeNames, cast_mode).value(); Json::Value streams; for (auto& stream : audio_streams) { streams.append(stream.ToJson()); } for (auto& stream : video_streams) { streams.append(stream.ToJson()); } root[kSupportedStreams] = std::move(streams); return root; } bool Offer::IsValid() const { return std::all_of(audio_streams.begin(), audio_streams.end(), [](const AudioStream& a) { return a.IsValid(); }) && std::all_of(video_streams.begin(), video_streams.end(), [](const VideoStream& v) { return v.IsValid(); }); } } // namespace cast } // namespace openscreen