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 <limits>
8 #include <utility>
9
10 #include "cast/streaming/rtp_defines.h"
11 #include "gmock/gmock.h"
12 #include "gtest/gtest.h"
13 #include "util/json/json_serialization.h"
14
15 using ::testing::ElementsAre;
16
17 namespace openscreen {
18 namespace cast {
19
20 namespace {
21
22 constexpr char kValidOffer[] = R"({
23 "castMode": "mirroring",
24 "receiverGetStatus": true,
25 "supportedStreams": [
26 {
27 "index": 0,
28 "type": "video_source",
29 "codecName": "h264",
30 "rtpProfile": "cast",
31 "rtpPayloadType": 101,
32 "ssrc": 19088743,
33 "maxFrameRate": "60000/1000",
34 "timeBase": "1/90000",
35 "maxBitRate": 5000000,
36 "profile": "main",
37 "level": "4",
38 "targetDelay": 200,
39 "aesKey": "040d756791711fd3adb939066e6d8690",
40 "aesIvMask": "9ff0f022a959150e70a2d05a6c184aed",
41 "resolutions": [
42 {
43 "width": 1280,
44 "height": 720
45 },
46 {
47 "width": 640,
48 "height": 360
49 },
50 {
51 "width": 640,
52 "height": 480
53 }
54 ]
55 },
56 {
57 "index": 1,
58 "type": "video_source",
59 "codecName": "vp8",
60 "rtpProfile": "cast",
61 "rtpPayloadType": 100,
62 "ssrc": 19088744,
63 "maxFrameRate": "30000/1001",
64 "targetDelay": 1000,
65 "timeBase": "1/90000",
66 "maxBitRate": 5000000,
67 "profile": "main",
68 "level": "5",
69 "aesKey": "bbf109bf84513b456b13a184453b66ce",
70 "aesIvMask": "edaf9e4536e2b66191f560d9c04b2a69"
71 },
72 {
73 "index": 2,
74 "type": "audio_source",
75 "codecName": "opus",
76 "targetDelay": 300,
77 "rtpProfile": "cast",
78 "rtpPayloadType": 96,
79 "ssrc": 4294967295,
80 "bitRate": 124000,
81 "timeBase": "1/48000",
82 "channels": 2,
83 "aesKey": "51027e4e2347cbcb49d57ef10177aebc",
84 "aesIvMask": "7f12a19be62a36c04ae4116caaeff6d1"
85 }
86 ]
87 })";
88
ExpectFailureOnParse(absl::string_view body,absl::optional<Error::Code> expected=absl::nullopt)89 void ExpectFailureOnParse(
90 absl::string_view body,
91 absl::optional<Error::Code> expected = absl::nullopt) {
92 ErrorOr<Json::Value> root = json::Parse(body);
93 ASSERT_TRUE(root.is_value()) << root.error();
94 ErrorOr<Offer> error_or_offer = Offer::Parse(std::move(root.value()));
95 EXPECT_TRUE(error_or_offer.is_error());
96 if (expected) {
97 EXPECT_EQ(expected, error_or_offer.error().code());
98 }
99 }
100
ExpectEqualsValidOffer(const Offer & offer)101 void ExpectEqualsValidOffer(const Offer& offer) {
102 EXPECT_EQ(CastMode::kMirroring, offer.cast_mode);
103 EXPECT_EQ(true, offer.supports_wifi_status_reporting);
104
105 // Verify list of video streams.
106 EXPECT_EQ(2u, offer.video_streams.size());
107 const auto& video_streams = offer.video_streams;
108
109 const bool flipped = video_streams[0].stream.index != 0;
110 const VideoStream& vs_one = flipped ? video_streams[1] : video_streams[0];
111 const VideoStream& vs_two = flipped ? video_streams[0] : video_streams[1];
112
113 EXPECT_EQ(0, vs_one.stream.index);
114 EXPECT_EQ(1, vs_one.stream.channels);
115 EXPECT_EQ(Stream::Type::kVideoSource, vs_one.stream.type);
116 EXPECT_EQ(VideoCodec::kH264, vs_one.codec);
117 EXPECT_EQ(RtpPayloadType::kVideoH264, vs_one.stream.rtp_payload_type);
118 EXPECT_EQ(19088743u, vs_one.stream.ssrc);
119 EXPECT_EQ((SimpleFraction{60000, 1000}), vs_one.max_frame_rate);
120 EXPECT_EQ(90000, vs_one.stream.rtp_timebase);
121 EXPECT_EQ(5000000, vs_one.max_bit_rate);
122 EXPECT_EQ("main", vs_one.profile);
123 EXPECT_EQ("4", vs_one.level);
124 EXPECT_THAT(vs_one.stream.aes_key,
125 ElementsAre(0x04, 0x0d, 0x75, 0x67, 0x91, 0x71, 0x1f, 0xd3, 0xad,
126 0xb9, 0x39, 0x06, 0x6e, 0x6d, 0x86, 0x90));
127 EXPECT_THAT(vs_one.stream.aes_iv_mask,
128 ElementsAre(0x9f, 0xf0, 0xf0, 0x22, 0xa9, 0x59, 0x15, 0x0e, 0x70,
129 0xa2, 0xd0, 0x5a, 0x6c, 0x18, 0x4a, 0xed));
130
131 const auto& resolutions = vs_one.resolutions;
132 EXPECT_EQ(3u, resolutions.size());
133 const Resolution& r_one = resolutions[0];
134 EXPECT_EQ(1280, r_one.width);
135 EXPECT_EQ(720, r_one.height);
136
137 const Resolution& r_two = resolutions[1];
138 EXPECT_EQ(640, r_two.width);
139 EXPECT_EQ(360, r_two.height);
140
141 const Resolution& r_three = resolutions[2];
142 EXPECT_EQ(640, r_three.width);
143 EXPECT_EQ(480, r_three.height);
144
145 EXPECT_EQ(1, vs_two.stream.index);
146 EXPECT_EQ(1, vs_two.stream.channels);
147 EXPECT_EQ(Stream::Type::kVideoSource, vs_two.stream.type);
148 EXPECT_EQ(VideoCodec::kVp8, vs_two.codec);
149 EXPECT_EQ(RtpPayloadType::kVideoVp8, vs_two.stream.rtp_payload_type);
150 EXPECT_EQ(19088744u, vs_two.stream.ssrc);
151 EXPECT_EQ((SimpleFraction{30000, 1001}), vs_two.max_frame_rate);
152 EXPECT_EQ(90000, vs_two.stream.rtp_timebase);
153 EXPECT_EQ(5000000, vs_two.max_bit_rate);
154 EXPECT_EQ("main", vs_two.profile);
155 EXPECT_EQ("5", vs_two.level);
156 EXPECT_THAT(vs_two.stream.aes_key,
157 ElementsAre(0xbb, 0xf1, 0x09, 0xbf, 0x84, 0x51, 0x3b, 0x45, 0x6b,
158 0x13, 0xa1, 0x84, 0x45, 0x3b, 0x66, 0xce));
159 EXPECT_THAT(vs_two.stream.aes_iv_mask,
160 ElementsAre(0xed, 0xaf, 0x9e, 0x45, 0x36, 0xe2, 0xb6, 0x61, 0x91,
161 0xf5, 0x60, 0xd9, 0xc0, 0x4b, 0x2a, 0x69));
162
163 const auto& resolutions_two = vs_two.resolutions;
164 EXPECT_EQ(0u, resolutions_two.size());
165
166 // Verify list of audio streams.
167 EXPECT_EQ(1u, offer.audio_streams.size());
168 const AudioStream& as = offer.audio_streams[0];
169 EXPECT_EQ(2, as.stream.index);
170 EXPECT_EQ(Stream::Type::kAudioSource, as.stream.type);
171 EXPECT_EQ(AudioCodec::kOpus, as.codec);
172 EXPECT_EQ(RtpPayloadType::kAudioOpus, as.stream.rtp_payload_type);
173 EXPECT_EQ(std::numeric_limits<Ssrc>::max(), as.stream.ssrc);
174 EXPECT_EQ(124000, as.bit_rate);
175 EXPECT_EQ(2, as.stream.channels);
176
177 EXPECT_THAT(as.stream.aes_key,
178 ElementsAre(0x51, 0x02, 0x7e, 0x4e, 0x23, 0x47, 0xcb, 0xcb, 0x49,
179 0xd5, 0x7e, 0xf1, 0x01, 0x77, 0xae, 0xbc));
180 EXPECT_THAT(as.stream.aes_iv_mask,
181 ElementsAre(0x7f, 0x12, 0xa1, 0x9b, 0xe6, 0x2a, 0x36, 0xc0, 0x4a,
182 0xe4, 0x11, 0x6c, 0xaa, 0xef, 0xf6, 0xd1));
183 }
184
185 } // namespace
186
TEST(OfferTest,ErrorOnEmptyOffer)187 TEST(OfferTest, ErrorOnEmptyOffer) {
188 ExpectFailureOnParse("{}");
189 }
190
TEST(OfferTest,ErrorOnMissingMandatoryFields)191 TEST(OfferTest, ErrorOnMissingMandatoryFields) {
192 // It's okay if castMode is omitted, but if supportedStreams is omitted we
193 // should fail here.
194 ExpectFailureOnParse(R"({
195 "castMode": "mirroring"
196 })");
197 }
198
TEST(OfferTest,CanParseValidButStreamlessOffer)199 TEST(OfferTest, CanParseValidButStreamlessOffer) {
200 ErrorOr<Json::Value> root = json::Parse(R"({
201 "castMode": "mirroring",
202 "supportedStreams": []
203 })");
204 ASSERT_TRUE(root.is_value()) << root.error();
205 EXPECT_TRUE(Offer::Parse(std::move(root.value())).is_value());
206 }
207
TEST(OfferTest,ErrorOnMissingAudioStreamMandatoryField)208 TEST(OfferTest, ErrorOnMissingAudioStreamMandatoryField) {
209 ExpectFailureOnParse(R"({
210 "castMode": "mirroring",
211 "supportedStreams": [{
212 "index": 2,
213 "codecName": "opus",
214 "rtpProfile": "cast",
215 "rtpPayloadType": 96,
216 "ssrc": 19088743,
217 "bitRate": 124000,
218 "timeBase": "1/48000",
219 "channels": 2
220 }]})");
221
222 ExpectFailureOnParse(R"({
223 "castMode": "mirroring",
224 "supportedStreams": [{
225 "index": 2,
226 "type": "audio_source",
227 "codecName": "opus",
228 "rtpProfile": "cast",
229 "rtpPayloadType": 96,
230 "bitRate": 124000,
231 "timeBase": "1/48000",
232 "channels": 2
233 }]})");
234 }
235
TEST(OfferTest,CanParseValidButMinimalAudioOffer)236 TEST(OfferTest, CanParseValidButMinimalAudioOffer) {
237 ErrorOr<Json::Value> root = json::Parse(R"({
238 "castMode": "mirroring",
239 "supportedStreams": [{
240 "index": 2,
241 "type": "audio_source",
242 "codecName": "opus",
243 "rtpProfile": "cast",
244 "rtpPayloadType": 96,
245 "ssrc": 19088743,
246 "bitRate": 124000,
247 "timeBase": "1/48000",
248 "channels": 2,
249 "aesKey": "51027e4e2347cbcb49d57ef10177aebc",
250 "aesIvMask": "7f12a19be62a36c04ae4116caaeff6d1"
251 }]
252 })");
253 ASSERT_TRUE(root.is_value());
254 EXPECT_TRUE(Offer::Parse(std::move(root.value())).is_value());
255 }
256
TEST(OfferTest,CanParseValidZeroBitRateAudioOffer)257 TEST(OfferTest, CanParseValidZeroBitRateAudioOffer) {
258 ErrorOr<Json::Value> root = json::Parse(R"({
259 "castMode": "mirroring",
260 "supportedStreams": [{
261 "index": 2,
262 "type": "audio_source",
263 "codecName": "opus",
264 "rtpProfile": "cast",
265 "rtpPayloadType": 96,
266 "ssrc": 19088743,
267 "bitRate": 0,
268 "timeBase": "1/48000",
269 "channels": 5,
270 "aesKey": "51029e4e2347cbcb49d57ef10177aebd",
271 "aesIvMask": "7f12a19be62a36c04ae4116caaeff5d2"
272 }]
273 })");
274 ASSERT_TRUE(root.is_value()) << root.error();
275 const auto offer = Offer::Parse(std::move(root.value()));
276 EXPECT_TRUE(offer.is_value()) << offer.error();
277 }
278
TEST(OfferTest,ErrorOnInvalidRtpTimebase)279 TEST(OfferTest, ErrorOnInvalidRtpTimebase) {
280 ExpectFailureOnParse(R"({
281 "castMode": "mirroring",
282 "supportedStreams": [{
283 "index": 2,
284 "type": "audio_source",
285 "codecName": "opus",
286 "rtpProfile": "cast",
287 "rtpPayloadType": 96,
288 "ssrc": 19088743,
289 "bitRate": 124000,
290 "timeBase": "1/10000000",
291 "channels": 2,
292 "aesKey": "51027e4e2347cbcb49d57ef10177aebc",
293 "aesIvMask": "7f12a19be62a36c04ae4116caaeff6d1"
294 }]
295 })");
296
297 ExpectFailureOnParse(R"({
298 "castMode": "mirroring",
299 "supportedStreams": [{
300 "index": 2,
301 "type": "audio_source",
302 "codecName": "opus",
303 "rtpProfile": "cast",
304 "rtpPayloadType": 96,
305 "ssrc": 19088743,
306 "bitRate": 124000,
307 "timeBase": "0",
308 "channels": 2,
309 "aesKey": "51027e4e2347cbcb49d57ef10177aebc",
310 "aesIvMask": "7f12a19be62a36c04ae4116caaeff6d1"
311 }]
312 })");
313
314 ExpectFailureOnParse(R"({
315 "castMode": "mirroring",
316 "supportedStreams": [{
317 "index": 2,
318 "type": "audio_source",
319 "codecName": "opus",
320 "rtpProfile": "cast",
321 "rtpPayloadType": 96,
322 "ssrc": 19088743,
323 "bitRate": 124000,
324 "timeBase": "1/1",
325 "channels": 2,
326 "aesKey": "51027e4e2347cbcb49d57ef10177aebc",
327 "aesIvMask": "7f12a19be62a36c04ae4116caaeff6d1"
328 }]
329 })");
330
331 ExpectFailureOnParse(R"({
332 "castMode": "mirroring",
333 "supportedStreams": [{
334 "index": 2,
335 "type": "audio_source",
336 "codecName": "opus",
337 "rtpProfile": "cast",
338 "rtpPayloadType": 96,
339 "ssrc": 19088743,
340 "bitRate": 124000,
341 "timeBase": "really fast plz, kthx",
342 "channels": 2,
343 "aesKey": "51027e4e2347cbcb49d57ef10177aebc",
344 "aesIvMask": "7f12a19be62a36c04ae4116caaeff6d1"
345 }]
346 })");
347 }
348
TEST(OfferTest,ErrorOnMissingVideoStreamMandatoryField)349 TEST(OfferTest, ErrorOnMissingVideoStreamMandatoryField) {
350 ExpectFailureOnParse(R"({
351 "castMode": "mirroring",
352 "supportedStreams": [{
353 "index": 2,
354 "codecName": "video_source",
355 "rtpProfile": "h264",
356 "rtpPayloadType": 101,
357 "ssrc": 19088743,
358 "bitRate": 124000,
359 "timeBase": "1/48000"
360 }]
361 })");
362
363 ExpectFailureOnParse(R"({
364 "castMode": "mirroring",
365 "supportedStreams": [{
366 "index": 2,
367 "type": "video_source",
368 "codecName": "h264",
369 "rtpProfile": "cast",
370 "rtpPayloadType": 101,
371 "bitRate": 124000,
372 "timeBase": "1/48000",
373 "maxBitRate": 10000
374 }]
375 })");
376
377 ExpectFailureOnParse(R"({
378 "castMode": "mirroring",
379 "supportedStreams": [{
380 "index": 2,
381 "type": "video_source",
382 "codecName": "vp8",
383 "rtpProfile": "cast",
384 "rtpPayloadType": 100,
385 "ssrc": 19088743,
386 "timeBase": "1/48000",
387 "resolutions": [],
388 "maxBitRate": 10000
389 }]
390 })");
391
392 ExpectFailureOnParse(R"({
393 "castMode": "mirroring",
394 "supportedStreams": [{
395 "index": 2,
396 "type": "video_source",
397 "codecName": "vp8",
398 "rtpProfile": "cast",
399 "rtpPayloadType": 100,
400 "ssrc": 19088743,
401 "timeBase": "1/48000",
402 "resolutions": [],
403 "maxBitRate": 10000,
404 "aesKey": "51027e4e2347cbcb49d57ef10177aebc"
405 }]
406 })");
407
408 ExpectFailureOnParse(R"({
409 "castMode": "mirroring",
410 "supportedStreams": [{
411 "index": 2,
412 "type": "video_source",
413 "codecName": "vp8",
414 "rtpProfile": "cast",
415 "rtpPayloadType": 100,
416 "ssrc": 19088743,
417 "timeBase": "1/48000",
418 "resolutions": [],
419 "maxBitRate": 10000,
420 "aesIvMask": "7f12a19be62a36c04ae4116caaeff6d1"
421 }]
422 })");
423 }
424
TEST(OfferTest,CanParseValidButMinimalVideoOffer)425 TEST(OfferTest, CanParseValidButMinimalVideoOffer) {
426 ErrorOr<Json::Value> root = json::Parse(R"({
427 "castMode": "mirroring",
428 "supportedStreams": [{
429 "index": 2,
430 "type": "video_source",
431 "codecName": "vp8",
432 "rtpProfile": "cast",
433 "rtpPayloadType": 100,
434 "ssrc": 19088743,
435 "timeBase": "1/48000",
436 "resolutions": [],
437 "maxBitRate": 10000,
438 "aesKey": "51027e4e2347cbcb49d57ef10177aebc",
439 "aesIvMask": "7f12a19be62a36c04ae4116caaeff6d1"
440 }]
441 })");
442
443 ASSERT_TRUE(root.is_value());
444 EXPECT_TRUE(Offer::Parse(std::move(root.value())).is_value());
445 }
446
TEST(OfferTest,CanParseValidOffer)447 TEST(OfferTest, CanParseValidOffer) {
448 ErrorOr<Json::Value> root = json::Parse(kValidOffer);
449 ASSERT_TRUE(root.is_value());
450 ErrorOr<Offer> offer = Offer::Parse(std::move(root.value()));
451
452 ExpectEqualsValidOffer(offer.value());
453 }
454
TEST(OfferTest,ParseAndToJsonResultsInSameOffer)455 TEST(OfferTest, ParseAndToJsonResultsInSameOffer) {
456 ErrorOr<Json::Value> root = json::Parse(kValidOffer);
457 ASSERT_TRUE(root.is_value());
458 ErrorOr<Offer> offer = Offer::Parse(std::move(root.value()));
459
460 ExpectEqualsValidOffer(offer.value());
461
462 auto eoj = offer.value().ToJson();
463 EXPECT_TRUE(eoj.is_value()) << eoj.error();
464 ErrorOr<Offer> reparsed_offer = Offer::Parse(std::move(eoj.value()));
465 ExpectEqualsValidOffer(reparsed_offer.value());
466 }
467
468 // We don't want to enforce that a given offer must have both audio and
469 // video, so we don't assert on either.
TEST(OfferTest,ToJsonSucceedsWithMissingStreams)470 TEST(OfferTest, ToJsonSucceedsWithMissingStreams) {
471 ErrorOr<Json::Value> root = json::Parse(kValidOffer);
472 ASSERT_TRUE(root.is_value());
473 ErrorOr<Offer> offer = Offer::Parse(std::move(root.value()));
474 ExpectEqualsValidOffer(offer.value());
475 const Offer valid_offer = std::move(offer.value());
476
477 Offer missing_audio_streams = valid_offer;
478 missing_audio_streams.audio_streams.clear();
479 EXPECT_TRUE(missing_audio_streams.ToJson().is_value());
480
481 Offer missing_video_streams = valid_offer;
482 missing_video_streams.audio_streams.clear();
483 EXPECT_TRUE(missing_video_streams.ToJson().is_value());
484 }
485
TEST(OfferTest,ToJsonFailsWithInvalidStreams)486 TEST(OfferTest, ToJsonFailsWithInvalidStreams) {
487 ErrorOr<Json::Value> root = json::Parse(kValidOffer);
488 ASSERT_TRUE(root.is_value());
489 ErrorOr<Offer> offer = Offer::Parse(std::move(root.value()));
490 ExpectEqualsValidOffer(offer.value());
491 const Offer valid_offer = std::move(offer.value());
492
493 Offer video_stream_invalid = valid_offer;
494 video_stream_invalid.video_streams[0].max_frame_rate.denominator = 0;
495 EXPECT_TRUE(video_stream_invalid.ToJson().is_error());
496
497 Offer audio_stream_invalid = valid_offer;
498 video_stream_invalid.audio_streams[0].bit_rate = 0;
499 EXPECT_TRUE(video_stream_invalid.ToJson().is_error());
500 }
501
TEST(OfferTest,FailsIfUnencrypted)502 TEST(OfferTest, FailsIfUnencrypted) {
503 // Video stream missing AES fields.
504 ExpectFailureOnParse(R"({
505 "castMode": "mirroring",
506 "supportedStreams": [{
507 "index": 2,
508 "type": "video_source",
509 "codecName": "vp8",
510 "rtpProfile": "cast",
511 "rtpPayloadType": 100,
512 "ssrc": 19088743,
513 "timeBase": "1/48000",
514 "resolutions": [],
515 "maxBitRate": 10000,
516 "aesIvMask": "7f12a19be62a36c04ae4116caaeff6d1"
517 }]
518 })",
519 Error::Code::kUnencryptedOffer);
520
521 ExpectFailureOnParse(R"({
522 "castMode": "mirroring",
523 "supportedStreams": [{
524 "index": 2,
525 "type": "video_source",
526 "codecName": "vp8",
527 "rtpProfile": "cast",
528 "rtpPayloadType": 100,
529 "ssrc": 19088743,
530 "timeBase": "1/48000",
531 "resolutions": [],
532 "maxBitRate": 10000,
533 "aesKey": "51027e4e2347cbcb49d57ef10177aebc"
534 }]
535 })",
536 Error::Code::kUnencryptedOffer);
537
538 // Audio stream missing AES fields.
539 ExpectFailureOnParse(R"({
540 "castMode": "mirroring",
541 "supportedStreams": [{
542 "index": 2,
543 "type": "audio_source",
544 "codecName": "opus",
545 "rtpProfile": "cast",
546 "rtpPayloadType": 96,
547 "ssrc": 19088743,
548 "bitRate": 124000,
549 "timeBase": "1/48000",
550 "channels": 2,
551 "aesIvMask": "7f12a19be62a36c04ae4116caaeff6d1"
552 }]
553 })",
554 Error::Code::kUnencryptedOffer);
555
556 ExpectFailureOnParse(R"({
557 "castMode": "mirroring",
558 "supportedStreams": [{
559 "index": 2,
560 "type": "audio_source",
561 "codecName": "opus",
562 "rtpProfile": "cast",
563 "rtpPayloadType": 96,
564 "ssrc": 19088743,
565 "bitRate": 124000,
566 "timeBase": "1/48000",
567 "channels": 2,
568 "aesKey": "51027e4e2347cbcb49d57ef10177aebc"
569 }]
570 })",
571 Error::Code::kUnencryptedOffer);
572
573 // And finally, fields provided but not properly formatted.
574 ExpectFailureOnParse(R"({
575 "castMode": "mirroring",
576 "supportedStreams": [{
577 "index": 2,
578 "type": "audio_source",
579 "codecName": "opus",
580 "rtpProfile": "cast",
581 "rtpPayloadType": 96,
582 "ssrc": 19088743,
583 "bitRate": 124000,
584 "timeBase": "1/48000",
585 "channels": 2,
586 "aesKey": "51027e4e2347$bcb49d57ef10177aebc",
587 "aesIvMask": "7f12a19be62a36c04ae4116caaeff6d1"
588 }]
589 })",
590 Error::Code::kUnencryptedOffer);
591 }
592
593 } // namespace cast
594 } // namespace openscreen
595