xref: /aosp_15_r20/external/openscreen/cast/streaming/offer_messages.cc (revision 3f982cf4871df8771c9d4abe6e9a6f8d829b2736)
1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "cast/streaming/offer_messages.h"
6 
7 #include <inttypes.h>
8 
9 #include <algorithm>
10 #include <limits>
11 #include <string>
12 #include <utility>
13 
14 #include "absl/strings/match.h"
15 #include "absl/strings/numbers.h"
16 #include "absl/strings/str_split.h"
17 #include "cast/streaming/constants.h"
18 #include "platform/base/error.h"
19 #include "util/big_endian.h"
20 #include "util/enum_name_table.h"
21 #include "util/json/json_helpers.h"
22 #include "util/json/json_serialization.h"
23 #include "util/osp_logging.h"
24 #include "util/stringprintf.h"
25 
26 namespace openscreen {
27 namespace cast {
28 
29 namespace {
30 
31 constexpr char kSupportedStreams[] = "supportedStreams";
32 constexpr char kAudioSourceType[] = "audio_source";
33 constexpr char kVideoSourceType[] = "video_source";
34 constexpr char kStreamType[] = "type";
35 
CodecParameterIsValid(VideoCodec codec,const std::string & codec_parameter)36 bool CodecParameterIsValid(VideoCodec codec,
37                            const std::string& codec_parameter) {
38   if (codec_parameter.empty()) {
39     return true;
40   }
41   switch (codec) {
42     case VideoCodec::kVp8:
43       return absl::StartsWith(codec_parameter, "vp08");
44     case VideoCodec::kVp9:
45       return absl::StartsWith(codec_parameter, "vp09");
46     case VideoCodec::kAv1:
47       return absl::StartsWith(codec_parameter, "av01");
48     case VideoCodec::kHevc:
49       return absl::StartsWith(codec_parameter, "hev1");
50     case VideoCodec::kH264:
51       return absl::StartsWith(codec_parameter, "avc1");
52     case VideoCodec::kNotSpecified:
53       return false;
54   }
55   OSP_NOTREACHED();
56 }
57 
CodecParameterIsValid(AudioCodec codec,const std::string & codec_parameter)58 bool CodecParameterIsValid(AudioCodec codec,
59                            const std::string& codec_parameter) {
60   if (codec_parameter.empty()) {
61     return true;
62   }
63   switch (codec) {
64     case AudioCodec::kAac:
65       return absl::StartsWith(codec_parameter, "mp4a.");
66 
67     // Opus doesn't use codec parameters.
68     case AudioCodec::kOpus:  // fallthrough
69     case AudioCodec::kNotSpecified:
70       return false;
71   }
72   OSP_NOTREACHED();
73 }
74 
75 EnumNameTable<CastMode, 2> kCastModeNames{
76     {{"mirroring", CastMode::kMirroring}, {"remoting", CastMode::kRemoting}}};
77 
TryParseRtpPayloadType(const Json::Value & value,RtpPayloadType * out)78 bool TryParseRtpPayloadType(const Json::Value& value, RtpPayloadType* out) {
79   int t;
80   if (!json::TryParseInt(value, &t)) {
81     return false;
82   }
83 
84   uint8_t t_small = t;
85   if (t_small != t || !IsRtpPayloadType(t_small)) {
86     return false;
87   }
88 
89   *out = static_cast<RtpPayloadType>(t_small);
90   return true;
91 }
92 
TryParseRtpTimebase(const Json::Value & value,int * out)93 bool TryParseRtpTimebase(const Json::Value& value, int* out) {
94   std::string raw_timebase;
95   if (!json::TryParseString(value, &raw_timebase)) {
96     return false;
97   }
98 
99   // The spec demands a leading 1, so this isn't really a fraction.
100   const auto fraction = SimpleFraction::FromString(raw_timebase);
101   if (fraction.is_error() || !fraction.value().is_positive() ||
102       fraction.value().numerator() != 1) {
103     return false;
104   }
105 
106   *out = fraction.value().denominator();
107   return true;
108 }
109 
110 // For a hex byte, the conversion is 4 bits to 1 character, e.g.
111 // 0b11110001 becomes F1, so 1 byte is two characters.
112 constexpr int kHexDigitsPerByte = 2;
113 constexpr int kAesBytesSize = 16;
114 constexpr int kAesStringLength = kAesBytesSize * kHexDigitsPerByte;
TryParseAesHexBytes(const Json::Value & value,std::array<uint8_t,kAesBytesSize> * out)115 bool TryParseAesHexBytes(const Json::Value& value,
116                          std::array<uint8_t, kAesBytesSize>* out) {
117   std::string hex_string;
118   if (!json::TryParseString(value, &hex_string)) {
119     return false;
120   }
121 
122   constexpr int kHexDigitsPerScanField = 16;
123   constexpr int kNumScanFields = kAesStringLength / kHexDigitsPerScanField;
124   uint64_t quads[kNumScanFields];
125   int chars_scanned;
126   if (hex_string.size() == kAesStringLength &&
127       sscanf(hex_string.c_str(), "%16" SCNx64 "%16" SCNx64 "%n", &quads[0],
128              &quads[1], &chars_scanned) == kNumScanFields &&
129       chars_scanned == kAesStringLength &&
130       std::none_of(hex_string.begin(), hex_string.end(),
131                    [](char c) { return std::isspace(c); })) {
132     WriteBigEndian(quads[0], out->data());
133     WriteBigEndian(quads[1], out->data() + 8);
134     return true;
135   }
136 
137   return false;
138 }
139 
ToString(Stream::Type type)140 absl::string_view ToString(Stream::Type type) {
141   switch (type) {
142     case Stream::Type::kAudioSource:
143       return kAudioSourceType;
144     case Stream::Type::kVideoSource:
145       return kVideoSourceType;
146     default: {
147       OSP_NOTREACHED();
148     }
149   }
150 }
151 
TryParseResolutions(const Json::Value & value,std::vector<Resolution> * out)152 bool TryParseResolutions(const Json::Value& value,
153                          std::vector<Resolution>* out) {
154   out->clear();
155 
156   // Some legacy senders don't provide resolutions, so just return empty.
157   if (!value.isArray() || value.empty()) {
158     return false;
159   }
160 
161   for (Json::ArrayIndex i = 0; i < value.size(); ++i) {
162     Resolution resolution;
163     if (!Resolution::TryParse(value[i], &resolution)) {
164       out->clear();
165       return false;
166     }
167     out->push_back(std::move(resolution));
168   }
169 
170   return true;
171 }
172 
173 }  // namespace
174 
TryParse(const Json::Value & value,Stream::Type type,Stream * out)175 Error Stream::TryParse(const Json::Value& value,
176                        Stream::Type type,
177                        Stream* out) {
178   out->type = type;
179 
180   if (!json::TryParseInt(value["index"], &out->index) ||
181       !json::TryParseUint(value["ssrc"], &out->ssrc) ||
182       !TryParseRtpPayloadType(value["rtpPayloadType"],
183                               &out->rtp_payload_type) ||
184       !TryParseRtpTimebase(value["timeBase"], &out->rtp_timebase)) {
185     return Error(Error::Code::kJsonParseError,
186                  "Offer stream has missing or invalid mandatory field");
187   }
188 
189   if (!json::TryParseInt(value["channels"], &out->channels)) {
190     out->channels = out->type == Stream::Type::kAudioSource
191                         ? kDefaultNumAudioChannels
192                         : kDefaultNumVideoChannels;
193   } else if (out->channels <= 0) {
194     return Error(Error::Code::kJsonParseError, "Invalid channel count");
195   }
196 
197   if (!TryParseAesHexBytes(value["aesKey"], &out->aes_key) ||
198       !TryParseAesHexBytes(value["aesIvMask"], &out->aes_iv_mask)) {
199     return Error(Error::Code::kUnencryptedOffer,
200                  "Offer stream must have both a valid aesKey and aesIvMask");
201   }
202   if (out->rtp_timebase <
203           std::min(kDefaultAudioMinSampleRate, kRtpVideoTimebase) ||
204       out->rtp_timebase > kRtpVideoTimebase) {
205     return Error(Error::Code::kJsonParseError, "rtp_timebase (sample rate)");
206   }
207 
208   out->target_delay = kDefaultTargetPlayoutDelay;
209   int target_delay;
210   if (json::TryParseInt(value["targetDelay"], &target_delay)) {
211     auto d = std::chrono::milliseconds(target_delay);
212     if (kMinTargetPlayoutDelay <= d && d <= kMaxTargetPlayoutDelay) {
213       out->target_delay = d;
214     }
215   }
216 
217   json::TryParseBool(value["receiverRtcpEventLog"],
218                      &out->receiver_rtcp_event_log);
219   json::TryParseString(value["receiverRtcpDscp"], &out->receiver_rtcp_dscp);
220   json::TryParseString(value["codecParameter"], &out->codec_parameter);
221 
222   return Error::None();
223 }
224 
ToJson() const225 Json::Value Stream::ToJson() const {
226   OSP_DCHECK(IsValid());
227 
228   Json::Value root;
229   root["index"] = index;
230   root["type"] = std::string(ToString(type));
231   root["channels"] = channels;
232   root["rtpPayloadType"] = static_cast<int>(rtp_payload_type);
233   // rtpProfile is technically required by the spec, although it is always set
234   // to cast. We set it here to be compliant with all spec implementers.
235   root["rtpProfile"] = "cast";
236   static_assert(sizeof(ssrc) <= sizeof(Json::UInt),
237                 "this code assumes Ssrc fits in a Json::UInt");
238   root["ssrc"] = static_cast<Json::UInt>(ssrc);
239   root["targetDelay"] = static_cast<int>(target_delay.count());
240   root["aesKey"] = HexEncode(aes_key.data(), aes_key.size());
241   root["aesIvMask"] = HexEncode(aes_iv_mask.data(), aes_iv_mask.size());
242   root["receiverRtcpEventLog"] = receiver_rtcp_event_log;
243   root["receiverRtcpDscp"] = receiver_rtcp_dscp;
244   root["timeBase"] = "1/" + std::to_string(rtp_timebase);
245   root["codecParameter"] = codec_parameter;
246   return root;
247 }
248 
IsValid() const249 bool Stream::IsValid() const {
250   return channels >= 1 && index >= 0 && target_delay.count() > 0 &&
251          target_delay.count() <= std::numeric_limits<int>::max() &&
252          rtp_timebase >= 1;
253 }
254 
TryParse(const Json::Value & value,AudioStream * out)255 Error AudioStream::TryParse(const Json::Value& value, AudioStream* out) {
256   Error error =
257       Stream::TryParse(value, Stream::Type::kAudioSource, &out->stream);
258   if (!error.ok()) {
259     return error;
260   }
261 
262   std::string codec_name;
263   if (!json::TryParseInt(value["bitRate"], &out->bit_rate) ||
264       out->bit_rate < 0 ||
265       !json::TryParseString(value[kCodecName], &codec_name)) {
266     return Error(Error::Code::kJsonParseError, "Invalid audio stream field");
267   }
268   ErrorOr<AudioCodec> codec = StringToAudioCodec(codec_name);
269   if (!codec) {
270     return Error(Error::Code::kUnknownCodec,
271                  "Codec is not known, can't use stream");
272   }
273   out->codec = codec.value();
274   if (!CodecParameterIsValid(codec.value(), out->stream.codec_parameter)) {
275     return Error(Error::Code::kInvalidCodecParameter,
276                  StringPrintf("Invalid audio codec parameter (%s for codec %s)",
277                               out->stream.codec_parameter.c_str(),
278                               CodecToString(codec.value())));
279   }
280   return Error::None();
281 }
282 
ToJson() const283 Json::Value AudioStream::ToJson() const {
284   OSP_DCHECK(IsValid());
285 
286   Json::Value out = stream.ToJson();
287   out[kCodecName] = CodecToString(codec);
288   out["bitRate"] = bit_rate;
289   return out;
290 }
291 
IsValid() const292 bool AudioStream::IsValid() const {
293   return bit_rate >= 0 && stream.IsValid();
294 }
295 
TryParse(const Json::Value & value,VideoStream * out)296 Error VideoStream::TryParse(const Json::Value& value, VideoStream* out) {
297   Error error =
298       Stream::TryParse(value, Stream::Type::kVideoSource, &out->stream);
299   if (!error.ok()) {
300     return error;
301   }
302 
303   std::string codec_name;
304   if (!json::TryParseString(value[kCodecName], &codec_name)) {
305     return Error(Error::Code::kJsonParseError, "Video stream missing codec");
306   }
307   ErrorOr<VideoCodec> codec = StringToVideoCodec(codec_name);
308   if (!codec) {
309     return Error(Error::Code::kUnknownCodec,
310                  "Codec is not known, can't use stream");
311   }
312   out->codec = codec.value();
313   if (!CodecParameterIsValid(codec.value(), out->stream.codec_parameter)) {
314     return Error(Error::Code::kInvalidCodecParameter,
315                  StringPrintf("Invalid video codec parameter (%s for codec %s)",
316                               out->stream.codec_parameter.c_str(),
317                               CodecToString(codec.value())));
318   }
319 
320   out->max_frame_rate = SimpleFraction{kDefaultMaxFrameRate, 1};
321   std::string raw_max_frame_rate;
322   if (json::TryParseString(value["maxFrameRate"], &raw_max_frame_rate)) {
323     auto parsed = SimpleFraction::FromString(raw_max_frame_rate);
324     if (parsed.is_value() && parsed.value().is_positive()) {
325       out->max_frame_rate = parsed.value();
326     }
327   }
328 
329   TryParseResolutions(value["resolutions"], &out->resolutions);
330   json::TryParseString(value["profile"], &out->profile);
331   json::TryParseString(value["protection"], &out->protection);
332   json::TryParseString(value["level"], &out->level);
333   json::TryParseString(value["errorRecoveryMode"], &out->error_recovery_mode);
334   if (!json::TryParseInt(value["maxBitRate"], &out->max_bit_rate)) {
335     out->max_bit_rate = 4 << 20;
336   }
337 
338   return Error::None();
339 }
340 
ToJson() const341 Json::Value VideoStream::ToJson() const {
342   OSP_DCHECK(IsValid());
343 
344   Json::Value out = stream.ToJson();
345   out["codecName"] = CodecToString(codec);
346   out["maxFrameRate"] = max_frame_rate.ToString();
347   out["maxBitRate"] = max_bit_rate;
348   out["protection"] = protection;
349   out["profile"] = profile;
350   out["level"] = level;
351   out["errorRecoveryMode"] = error_recovery_mode;
352 
353   Json::Value rs;
354   for (auto resolution : resolutions) {
355     rs.append(resolution.ToJson());
356   }
357   out["resolutions"] = std::move(rs);
358   return out;
359 }
360 
IsValid() const361 bool VideoStream::IsValid() const {
362   return max_bit_rate > 0 && max_frame_rate.is_positive();
363 }
364 
365 // static
Parse(const Json::Value & root)366 ErrorOr<Offer> Offer::Parse(const Json::Value& root) {
367   Offer out;
368   Error error = TryParse(root, &out);
369   return error.ok() ? ErrorOr<Offer>(std::move(out))
370                     : ErrorOr<Offer>(std::move(error));
371 }
372 
373 // static
TryParse(const Json::Value & root,Offer * out)374 Error Offer::TryParse(const Json::Value& root, Offer* out) {
375   if (!root.isObject()) {
376     return Error(Error::Code::kJsonParseError, "null offer");
377   }
378   const ErrorOr<CastMode> cast_mode =
379       GetEnum(kCastModeNames, root["castMode"].asString());
380   Json::Value supported_streams = root[kSupportedStreams];
381   if (!supported_streams.isArray()) {
382     return Error(Error::Code::kJsonParseError, "supported streams in offer");
383   }
384 
385   std::vector<AudioStream> audio_streams;
386   std::vector<VideoStream> video_streams;
387   for (Json::ArrayIndex i = 0; i < supported_streams.size(); ++i) {
388     const Json::Value& fields = supported_streams[i];
389     std::string type;
390     if (!json::TryParseString(fields[kStreamType], &type)) {
391       return Error(Error::Code::kJsonParseError, "Missing stream type");
392     }
393 
394     Error error;
395     if (type == kAudioSourceType) {
396       AudioStream stream;
397       error = AudioStream::TryParse(fields, &stream);
398       if (error.ok()) {
399         audio_streams.push_back(std::move(stream));
400       }
401     } else if (type == kVideoSourceType) {
402       VideoStream stream;
403       error = VideoStream::TryParse(fields, &stream);
404       if (error.ok()) {
405         video_streams.push_back(std::move(stream));
406       }
407     }
408 
409     if (!error.ok()) {
410       if (error.code() == Error::Code::kUnknownCodec) {
411         OSP_VLOG << "Dropping audio stream due to unknown codec: " << error;
412         continue;
413       } else {
414         return error;
415       }
416     }
417   }
418 
419   *out = Offer{cast_mode.value(CastMode::kMirroring), std::move(audio_streams),
420                std::move(video_streams)};
421   return Error::None();
422 }
423 
ToJson() const424 Json::Value Offer::ToJson() const {
425   OSP_DCHECK(IsValid());
426   Json::Value root;
427   root["castMode"] = GetEnumName(kCastModeNames, cast_mode).value();
428   Json::Value streams;
429   for (auto& stream : audio_streams) {
430     streams.append(stream.ToJson());
431   }
432 
433   for (auto& stream : video_streams) {
434     streams.append(stream.ToJson());
435   }
436 
437   root[kSupportedStreams] = std::move(streams);
438   return root;
439 }
440 
IsValid() const441 bool Offer::IsValid() const {
442   return std::all_of(audio_streams.begin(), audio_streams.end(),
443                      [](const AudioStream& a) { return a.IsValid(); }) &&
444          std::all_of(video_streams.begin(), video_streams.end(),
445                      [](const VideoStream& v) { return v.IsValid(); });
446 }
447 }  // namespace cast
448 }  // namespace openscreen
449