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