• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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