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