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 "discovery/mdns/mdns_trackers.h"
6
7 #include <memory>
8 #include <utility>
9
10 #include "discovery/common/config.h"
11 #include "discovery/mdns/mdns_random.h"
12 #include "discovery/mdns/mdns_record_changed_callback.h"
13 #include "discovery/mdns/mdns_sender.h"
14 #include "gmock/gmock.h"
15 #include "gtest/gtest.h"
16 #include "platform/test/fake_clock.h"
17 #include "platform/test/fake_task_runner.h"
18 #include "platform/test/fake_udp_socket.h"
19
20 namespace openscreen {
21 namespace discovery {
22 namespace {
23
24 constexpr Clock::duration kOneSecond =
25 Clock::to_duration(std::chrono::seconds(1));
26 }
27
28 using testing::_;
29 using testing::Args;
30 using testing::DoAll;
31 using testing::Invoke;
32 using testing::Return;
33 using testing::StrictMock;
34 using testing::WithArgs;
35
ACTION_P2(VerifyMessageBytesWithoutId,expected_data,expected_size)36 ACTION_P2(VerifyMessageBytesWithoutId, expected_data, expected_size) {
37 const uint8_t* actual_data = reinterpret_cast<const uint8_t*>(arg0);
38 const size_t actual_size = arg1;
39 ASSERT_EQ(actual_size, expected_size);
40 // Start at bytes[2] to skip a generated message ID.
41 for (size_t i = 2; i < actual_size; ++i) {
42 EXPECT_EQ(actual_data[i], expected_data[i]);
43 }
44 }
45
ACTION_P(VerifyTruncated,is_truncated)46 ACTION_P(VerifyTruncated, is_truncated) {
47 EXPECT_EQ(arg0.is_truncated(), is_truncated);
48 }
49
ACTION_P(VerifyRecordCount,record_count)50 ACTION_P(VerifyRecordCount, record_count) {
51 EXPECT_EQ(arg0.answers().size(), static_cast<size_t>(record_count));
52 }
53
54 class MockMdnsSender : public MdnsSender {
55 public:
MockMdnsSender(UdpSocket * socket)56 explicit MockMdnsSender(UdpSocket* socket) : MdnsSender(socket) {}
57
58 MOCK_METHOD1(SendMulticast, Error(const MdnsMessage&));
59 MOCK_METHOD2(SendMessage, Error(const MdnsMessage&, const IPEndpoint&));
60 };
61
62 class MockRecordChangedCallback : public MdnsRecordChangedCallback {
63 public:
64 MOCK_METHOD(std::vector<PendingQueryChange>,
65 OnRecordChanged,
66 (const MdnsRecord&, RecordChangedEvent event),
67 (override));
68 };
69
70 class MdnsTrackerTest : public testing::Test {
71 public:
MdnsTrackerTest()72 MdnsTrackerTest()
73 : clock_(Clock::now()),
74 task_runner_(&clock_),
75 socket_(&task_runner_),
76 sender_(&socket_),
77 a_question_(DomainName{"testing", "local"},
78 DnsType::kANY,
79 DnsClass::kIN,
80 ResponseType::kMulticast),
81 a_record_(DomainName{"testing", "local"},
82 DnsType::kA,
83 DnsClass::kIN,
84 RecordType::kShared,
85 std::chrono::seconds(120),
86 ARecordRdata(IPAddress{172, 0, 0, 1})),
87 nsec_record_(
88 DomainName{"testing", "local"},
89 DnsType::kNSEC,
90 DnsClass::kIN,
91 RecordType::kShared,
92 std::chrono::seconds(120),
93 NsecRecordRdata(DomainName{"testing", "local"}, DnsType::kA)) {}
94
95 template <class TrackerType>
TrackerNoQueryAfterDestruction(TrackerType tracker)96 void TrackerNoQueryAfterDestruction(TrackerType tracker) {
97 tracker.reset();
98 // Advance fake clock by a long time interval to make sure if there's a
99 // scheduled task, it will run.
100 clock_.Advance(std::chrono::hours(1));
101 }
102
CreateRecordTracker(const MdnsRecord & record,DnsType type)103 std::unique_ptr<MdnsRecordTracker> CreateRecordTracker(
104 const MdnsRecord& record,
105 DnsType type) {
106 return std::make_unique<MdnsRecordTracker>(
107 record, type, &sender_, &task_runner_, &FakeClock::now, &random_,
108 [this](const MdnsRecordTracker* tracker, const MdnsRecord& record) {
109 expiration_called_ = true;
110 });
111 }
112
CreateRecordTracker(const MdnsRecord & record)113 std::unique_ptr<MdnsRecordTracker> CreateRecordTracker(
114 const MdnsRecord& record) {
115 return CreateRecordTracker(record, record.dns_type());
116 }
117
CreateQuestionTracker(const MdnsQuestion & question,MdnsQuestionTracker::QueryType query_type=MdnsQuestionTracker::QueryType::kContinuous)118 std::unique_ptr<MdnsQuestionTracker> CreateQuestionTracker(
119 const MdnsQuestion& question,
120 MdnsQuestionTracker::QueryType query_type =
121 MdnsQuestionTracker::QueryType::kContinuous) {
122 return std::make_unique<MdnsQuestionTracker>(question, &sender_,
123 &task_runner_, &FakeClock::now,
124 &random_, config_, query_type);
125 }
126
127 protected:
AdvanceThroughAllTtlFractions(std::chrono::seconds ttl)128 void AdvanceThroughAllTtlFractions(std::chrono::seconds ttl) {
129 constexpr double kTtlFractions[] = {0.83, 0.88, 0.93, 0.98, 1.00};
130 Clock::duration time_passed{0};
131 for (double fraction : kTtlFractions) {
132 Clock::duration time_till_refresh = Clock::to_duration(ttl * fraction);
133 Clock::duration delta = time_till_refresh - time_passed;
134 time_passed = time_till_refresh;
135 clock_.Advance(delta);
136 }
137 }
138
GetRecord(MdnsRecordTracker * tracker)139 const MdnsRecord& GetRecord(MdnsRecordTracker* tracker) {
140 return tracker->record_;
141 }
142
143 // clang-format off
144 const std::vector<uint8_t> kQuestionQueryBytes = {
145 0x00, 0x00, // ID = 0
146 0x00, 0x00, // FLAGS = None
147 0x00, 0x01, // Question count
148 0x00, 0x00, // Answer count
149 0x00, 0x00, // Authority count
150 0x00, 0x00, // Additional count
151 // Question
152 0x07, 't', 'e', 's', 't', 'i', 'n', 'g',
153 0x05, 'l', 'o', 'c', 'a', 'l',
154 0x00,
155 0x00, 0xFF, // TYPE = ANY (255)
156 0x00, 0x01, // CLASS = IN (1)
157 };
158
159 const std::vector<uint8_t> kRecordQueryBytes = {
160 0x00, 0x00, // ID = 0
161 0x00, 0x00, // FLAGS = None
162 0x00, 0x01, // Question count
163 0x00, 0x00, // Answer count
164 0x00, 0x00, // Authority count
165 0x00, 0x00, // Additional count
166 // Question
167 0x07, 't', 'e', 's', 't', 'i', 'n', 'g',
168 0x05, 'l', 'o', 'c', 'a', 'l',
169 0x00,
170 0x00, 0x01, // TYPE = A (1)
171 0x00, 0x01, // CLASS = IN (1)
172 };
173
174 // clang-format on
175 Config config_;
176 FakeClock clock_;
177 FakeTaskRunner task_runner_;
178 FakeUdpSocket socket_;
179 StrictMock<MockMdnsSender> sender_;
180 MdnsRandom random_;
181
182 MdnsQuestion a_question_;
183 MdnsRecord a_record_;
184 MdnsRecord nsec_record_;
185
186 bool expiration_called_ = false;
187 };
188
189 // Records are re-queried at 80%, 85%, 90% and 95% TTL as per RFC 6762
190 // Section 5.2 There are no subsequent queries to refresh the record after that,
191 // the record is expired after TTL has passed since the start of tracking.
192 // Random variance required is from 0% to 2%, making these times at most 82%,
193 // 87%, 92% and 97% TTL. Fake clock is advanced to 83%, 88%, 93% and 98% to make
194 // sure that task gets executed.
195 // https://tools.ietf.org/html/rfc6762#section-5.2
196
TEST_F(MdnsTrackerTest,RecordTrackerRecordAccessor)197 TEST_F(MdnsTrackerTest, RecordTrackerRecordAccessor) {
198 std::unique_ptr<MdnsRecordTracker> tracker = CreateRecordTracker(a_record_);
199 EXPECT_EQ(GetRecord(tracker.get()), a_record_);
200 }
201
TEST_F(MdnsTrackerTest,RecordTrackerQueryAfterDelayPerQuestionTracker)202 TEST_F(MdnsTrackerTest, RecordTrackerQueryAfterDelayPerQuestionTracker) {
203 std::unique_ptr<MdnsQuestionTracker> question = CreateQuestionTracker(
204 a_question_, MdnsQuestionTracker::QueryType::kOneShot);
205 std::unique_ptr<MdnsQuestionTracker> question2 = CreateQuestionTracker(
206 a_question_, MdnsQuestionTracker::QueryType::kOneShot);
207 EXPECT_CALL(sender_, SendMulticast(_)).Times(2);
208 clock_.Advance(kOneSecond);
209 clock_.Advance(kOneSecond);
210 testing::Mock::VerifyAndClearExpectations(&sender_);
211
212 std::unique_ptr<MdnsRecordTracker> tracker = CreateRecordTracker(a_record_);
213
214 // No queries without an associated tracker.
215 AdvanceThroughAllTtlFractions(a_record_.ttl());
216 testing::Mock::VerifyAndClearExpectations(&sender_);
217
218 // 4 queries with one associated tracker.
219 tracker = CreateRecordTracker(a_record_);
220 tracker->AddAssociatedQuery(question.get());
221 EXPECT_CALL(sender_, SendMulticast(_)).Times(4);
222 AdvanceThroughAllTtlFractions(a_record_.ttl());
223 testing::Mock::VerifyAndClearExpectations(&sender_);
224
225 // 8 queries with two associated trackers.
226 tracker = CreateRecordTracker(a_record_);
227 tracker->AddAssociatedQuery(question.get());
228 tracker->AddAssociatedQuery(question2.get());
229 EXPECT_CALL(sender_, SendMulticast(_)).Times(8);
230 AdvanceThroughAllTtlFractions(a_record_.ttl());
231 }
232
TEST_F(MdnsTrackerTest,RecordTrackerSendsMessage)233 TEST_F(MdnsTrackerTest, RecordTrackerSendsMessage) {
234 std::unique_ptr<MdnsQuestionTracker> question = CreateQuestionTracker(
235 a_question_, MdnsQuestionTracker::QueryType::kOneShot);
236 EXPECT_CALL(sender_, SendMulticast(_)).Times(1);
237 clock_.Advance(kOneSecond);
238 clock_.Advance(kOneSecond);
239 testing::Mock::VerifyAndClearExpectations(&sender_);
240
241 std::unique_ptr<MdnsRecordTracker> tracker = CreateRecordTracker(a_record_);
242 tracker->AddAssociatedQuery(question.get());
243
244 EXPECT_CALL(sender_, SendMulticast(_))
245 .Times(1)
246 .WillRepeatedly([this](const MdnsMessage& message) -> Error {
247 EXPECT_EQ(message.questions().size(), size_t{1});
248 EXPECT_EQ(message.questions()[0], a_question_);
249 return Error::None();
250 });
251
252 clock_.Advance(Clock::to_duration(a_record_.ttl() * 0.83));
253 }
254
TEST_F(MdnsTrackerTest,RecordTrackerNoQueryAfterDestruction)255 TEST_F(MdnsTrackerTest, RecordTrackerNoQueryAfterDestruction) {
256 std::unique_ptr<MdnsRecordTracker> tracker = CreateRecordTracker(a_record_);
257 TrackerNoQueryAfterDestruction(std::move(tracker));
258 }
259
TEST_F(MdnsTrackerTest,RecordTrackerNoQueryAfterLateTask)260 TEST_F(MdnsTrackerTest, RecordTrackerNoQueryAfterLateTask) {
261 std::unique_ptr<MdnsRecordTracker> tracker = CreateRecordTracker(a_record_);
262 // If task runner was too busy and callback happened too late, there should be
263 // no query and instead the record will expire.
264 // Check lower bound for task being late (TTL) and an arbitrarily long time
265 // interval to ensure the query is not sent a later time.
266 clock_.Advance(a_record_.ttl());
267 clock_.Advance(std::chrono::hours(1));
268 }
269
TEST_F(MdnsTrackerTest,RecordTrackerUpdateResetsTtl)270 TEST_F(MdnsTrackerTest, RecordTrackerUpdateResetsTtl) {
271 expiration_called_ = false;
272 std::unique_ptr<MdnsRecordTracker> tracker = CreateRecordTracker(a_record_);
273 // Advance time by 60% of record's TTL
274 Clock::duration advance_time = Clock::to_duration(a_record_.ttl() * 0.6);
275 clock_.Advance(advance_time);
276 // Now update the record, this must reset expiration time
277 EXPECT_EQ(tracker->Update(a_record_).value(),
278 MdnsRecordTracker::UpdateType::kTTLOnly);
279 // Advance time by 60% of record's TTL again
280 clock_.Advance(advance_time);
281 // Check that expiration callback was not called
282 EXPECT_FALSE(expiration_called_);
283 }
284
TEST_F(MdnsTrackerTest,RecordTrackerForceExpiration)285 TEST_F(MdnsTrackerTest, RecordTrackerForceExpiration) {
286 expiration_called_ = false;
287 std::unique_ptr<MdnsRecordTracker> tracker = CreateRecordTracker(a_record_);
288 tracker->ExpireSoon();
289 // Expire schedules expiration after 1 second.
290 clock_.Advance(std::chrono::seconds(1));
291 EXPECT_TRUE(expiration_called_);
292 }
293
TEST_F(MdnsTrackerTest,NsecRecordTrackerForceExpiration)294 TEST_F(MdnsTrackerTest, NsecRecordTrackerForceExpiration) {
295 expiration_called_ = false;
296 std::unique_ptr<MdnsRecordTracker> tracker =
297 CreateRecordTracker(nsec_record_, DnsType::kA);
298 tracker->ExpireSoon();
299 // Expire schedules expiration after 1 second.
300 clock_.Advance(std::chrono::seconds(1));
301 EXPECT_TRUE(expiration_called_);
302 }
303
TEST_F(MdnsTrackerTest,RecordTrackerExpirationCallback)304 TEST_F(MdnsTrackerTest, RecordTrackerExpirationCallback) {
305 expiration_called_ = false;
306 std::unique_ptr<MdnsRecordTracker> tracker = CreateRecordTracker(a_record_);
307 clock_.Advance(a_record_.ttl());
308 EXPECT_TRUE(expiration_called_);
309 }
310
TEST_F(MdnsTrackerTest,RecordTrackerExpirationCallbackAfterGoodbye)311 TEST_F(MdnsTrackerTest, RecordTrackerExpirationCallbackAfterGoodbye) {
312 expiration_called_ = false;
313 std::unique_ptr<MdnsRecordTracker> tracker = CreateRecordTracker(a_record_);
314 MdnsRecord goodbye_record(a_record_.name(), a_record_.dns_type(),
315 a_record_.dns_class(), a_record_.record_type(),
316 std::chrono::seconds(0), a_record_.rdata());
317
318 // After a goodbye record is received, expiration is schedule in a second.
319 EXPECT_EQ(tracker->Update(goodbye_record).value(),
320 MdnsRecordTracker::UpdateType::kGoodbye);
321
322 // Advance clock to just before the expiration time of 1 second.
323 clock_.Advance(std::chrono::microseconds(999999));
324 EXPECT_FALSE(expiration_called_);
325 // Advance clock to exactly the expiration time.
326 clock_.Advance(std::chrono::microseconds(1));
327 EXPECT_TRUE(expiration_called_);
328 }
329
TEST_F(MdnsTrackerTest,RecordTrackerInvalidPositiveRecordUpdate)330 TEST_F(MdnsTrackerTest, RecordTrackerInvalidPositiveRecordUpdate) {
331 std::unique_ptr<MdnsRecordTracker> tracker = CreateRecordTracker(a_record_);
332
333 MdnsRecord invalid_name(DomainName{"invalid"}, a_record_.dns_type(),
334 a_record_.dns_class(), a_record_.record_type(),
335 a_record_.ttl(), a_record_.rdata());
336 EXPECT_EQ(tracker->Update(invalid_name).error(),
337 Error::Code::kParameterInvalid);
338
339 MdnsRecord invalid_type(a_record_.name(), DnsType::kPTR,
340 a_record_.dns_class(), a_record_.record_type(),
341 a_record_.ttl(),
342 PtrRecordRdata{DomainName{"invalid"}});
343 EXPECT_EQ(tracker->Update(invalid_type).error(),
344 Error::Code::kParameterInvalid);
345
346 MdnsRecord invalid_class(a_record_.name(), a_record_.dns_type(),
347 DnsClass::kANY, a_record_.record_type(),
348 a_record_.ttl(), a_record_.rdata());
349 EXPECT_EQ(tracker->Update(invalid_class).error(),
350 Error::Code::kParameterInvalid);
351
352 // RDATA must match the old RDATA for goodbye records
353 MdnsRecord invalid_rdata(a_record_.name(), a_record_.dns_type(),
354 a_record_.dns_class(), a_record_.record_type(),
355 std::chrono::seconds(0),
356 ARecordRdata(IPAddress{172, 0, 0, 2}));
357 EXPECT_EQ(tracker->Update(invalid_rdata).error(),
358 Error::Code::kParameterInvalid);
359 }
360
TEST_F(MdnsTrackerTest,RecordTrackerUpdatePositiveResponseWithNegative)361 TEST_F(MdnsTrackerTest, RecordTrackerUpdatePositiveResponseWithNegative) {
362 // Check valid update.
363 std::unique_ptr<MdnsRecordTracker> tracker =
364 CreateRecordTracker(a_record_, DnsType::kA);
365 auto result = tracker->Update(nsec_record_);
366 ASSERT_TRUE(result.is_value());
367 EXPECT_EQ(result.value(), MdnsRecordTracker::UpdateType::kRdata);
368 EXPECT_EQ(GetRecord(tracker.get()), nsec_record_);
369
370 // Check invalid update.
371 MdnsRecord non_a_nsec_record(
372 nsec_record_.name(), nsec_record_.dns_type(), nsec_record_.dns_class(),
373 nsec_record_.record_type(), nsec_record_.ttl(),
374 NsecRecordRdata(DomainName{"testing", "local"}, DnsType::kAAAA));
375 tracker = CreateRecordTracker(a_record_, DnsType::kA);
376 auto response = tracker->Update(non_a_nsec_record);
377 ASSERT_TRUE(response.is_error());
378 EXPECT_EQ(GetRecord(tracker.get()), a_record_);
379 }
380
TEST_F(MdnsTrackerTest,RecordTrackerUpdateNegativeResponseWithNegative)381 TEST_F(MdnsTrackerTest, RecordTrackerUpdateNegativeResponseWithNegative) {
382 // Check valid update.
383 std::unique_ptr<MdnsRecordTracker> tracker =
384 CreateRecordTracker(nsec_record_, DnsType::kA);
385 MdnsRecord multiple_nsec_record(
386 nsec_record_.name(), nsec_record_.dns_type(), nsec_record_.dns_class(),
387 nsec_record_.record_type(), nsec_record_.ttl(),
388 NsecRecordRdata(DomainName{"testing", "local"}, DnsType::kA,
389 DnsType::kAAAA));
390 auto result = tracker->Update(multiple_nsec_record);
391 ASSERT_TRUE(result.is_value());
392 EXPECT_EQ(result.value(), MdnsRecordTracker::UpdateType::kRdata);
393 EXPECT_EQ(GetRecord(tracker.get()), multiple_nsec_record);
394
395 // Check invalid update.
396 tracker = CreateRecordTracker(nsec_record_, DnsType::kA);
397 MdnsRecord non_a_nsec_record(
398 nsec_record_.name(), nsec_record_.dns_type(), nsec_record_.dns_class(),
399 nsec_record_.record_type(), nsec_record_.ttl(),
400 NsecRecordRdata(DomainName{"testing", "local"}, DnsType::kAAAA));
401 auto response = tracker->Update(non_a_nsec_record);
402 EXPECT_TRUE(response.is_error());
403 EXPECT_EQ(GetRecord(tracker.get()), nsec_record_);
404 }
405
TEST_F(MdnsTrackerTest,RecordTrackerUpdateNegativeResponseWithPositive)406 TEST_F(MdnsTrackerTest, RecordTrackerUpdateNegativeResponseWithPositive) {
407 // Check valid update.
408 std::unique_ptr<MdnsRecordTracker> tracker =
409 CreateRecordTracker(nsec_record_, DnsType::kA);
410 auto result = tracker->Update(a_record_);
411 ASSERT_TRUE(result.is_value());
412 EXPECT_EQ(result.value(), MdnsRecordTracker::UpdateType::kRdata);
413 EXPECT_EQ(GetRecord(tracker.get()), a_record_);
414
415 // Check invalid update.
416 tracker = CreateRecordTracker(nsec_record_, DnsType::kA);
417 MdnsRecord aaaa_record(a_record_.name(), DnsType::kAAAA,
418 a_record_.dns_class(), a_record_.record_type(),
419 std::chrono::seconds(0),
420 AAAARecordRdata(IPAddress{0, 0, 0, 0, 0, 0, 0, 1}));
421 result = tracker->Update(aaaa_record);
422 EXPECT_TRUE(result.is_error());
423 EXPECT_EQ(GetRecord(tracker.get()), nsec_record_);
424 }
425
TEST_F(MdnsTrackerTest,RecordTrackerNoExpirationCallbackAfterDestruction)426 TEST_F(MdnsTrackerTest, RecordTrackerNoExpirationCallbackAfterDestruction) {
427 expiration_called_ = false;
428 std::unique_ptr<MdnsRecordTracker> tracker = CreateRecordTracker(a_record_);
429 tracker.reset();
430 clock_.Advance(a_record_.ttl());
431 EXPECT_FALSE(expiration_called_);
432 }
433
434 // Initial query is delayed for up to 120 ms as per RFC 6762 Section 5.2
435 // Subsequent queries happen no sooner than a second after the initial query and
436 // the interval between the queries increases at least by a factor of 2 for each
437 // next query up until it's capped at 1 hour.
438 // https://tools.ietf.org/html/rfc6762#section-5.2
439
TEST_F(MdnsTrackerTest,QuestionTrackerQuestionAccessor)440 TEST_F(MdnsTrackerTest, QuestionTrackerQuestionAccessor) {
441 std::unique_ptr<MdnsQuestionTracker> tracker =
442 CreateQuestionTracker(a_question_);
443 EXPECT_EQ(tracker->question(), a_question_);
444 }
445
TEST_F(MdnsTrackerTest,QuestionTrackerQueryAfterDelay)446 TEST_F(MdnsTrackerTest, QuestionTrackerQueryAfterDelay) {
447 std::unique_ptr<MdnsQuestionTracker> tracker =
448 CreateQuestionTracker(a_question_);
449
450 EXPECT_CALL(sender_, SendMulticast(_))
451 .WillOnce(
452 DoAll(WithArgs<0>(VerifyTruncated(false)), Return(Error::None())));
453 clock_.Advance(std::chrono::milliseconds(120));
454
455 std::chrono::seconds interval{1};
456 while (interval < std::chrono::hours(1)) {
457 EXPECT_CALL(sender_, SendMulticast(_))
458 .WillOnce(
459 DoAll(WithArgs<0>(VerifyTruncated(false)), Return(Error::None())));
460 clock_.Advance(interval);
461 interval *= 2;
462 }
463 }
464
TEST_F(MdnsTrackerTest,QuestionTrackerSendsMessage)465 TEST_F(MdnsTrackerTest, QuestionTrackerSendsMessage) {
466 std::unique_ptr<MdnsQuestionTracker> tracker =
467 CreateQuestionTracker(a_question_);
468
469 EXPECT_CALL(sender_, SendMulticast(_))
470 .WillOnce(DoAll(
471 WithArgs<0>(VerifyTruncated(false)),
472 [this](const MdnsMessage& message) -> Error {
473 EXPECT_EQ(message.questions().size(), size_t{1});
474 EXPECT_EQ(message.questions()[0], a_question_);
475 return Error::None();
476 },
477 Return(Error::None())));
478
479 clock_.Advance(std::chrono::milliseconds(120));
480 }
481
TEST_F(MdnsTrackerTest,QuestionTrackerNoQueryAfterDestruction)482 TEST_F(MdnsTrackerTest, QuestionTrackerNoQueryAfterDestruction) {
483 std::unique_ptr<MdnsQuestionTracker> tracker =
484 CreateQuestionTracker(a_question_);
485 TrackerNoQueryAfterDestruction(std::move(tracker));
486 }
487
TEST_F(MdnsTrackerTest,QuestionTrackerSendsMultipleMessages)488 TEST_F(MdnsTrackerTest, QuestionTrackerSendsMultipleMessages) {
489 std::unique_ptr<MdnsQuestionTracker> tracker =
490 CreateQuestionTracker(a_question_);
491
492 std::vector<std::unique_ptr<MdnsRecordTracker>> answers;
493 for (int i = 0; i < 100; i++) {
494 auto record = CreateRecordTracker(a_record_);
495 tracker->AddAssociatedRecord(record.get());
496 answers.push_back(std::move(record));
497 }
498
499 EXPECT_CALL(sender_, SendMulticast(_))
500 .WillOnce(DoAll(WithArgs<0>(VerifyTruncated(true)),
501 WithArgs<0>(VerifyRecordCount(49)),
502 Return(Error::None())))
503 .WillOnce(DoAll(WithArgs<0>(VerifyTruncated(true)),
504 WithArgs<0>(VerifyRecordCount(50)),
505 Return(Error::None())))
506 .WillOnce(DoAll(WithArgs<0>(VerifyTruncated(false)),
507 WithArgs<0>(VerifyRecordCount(1)),
508 Return(Error::None())));
509
510 clock_.Advance(std::chrono::milliseconds(120));
511 }
512
513 } // namespace discovery
514 } // namespace openscreen
515