// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef DISCOVERY_MDNS_IMPL_MDNS_RESPONDER_H_
#define DISCOVERY_MDNS_IMPL_MDNS_RESPONDER_H_

#include <map>
#include <memory>
#include <vector>

#include "discovery/mdns/public/mdns_records.h"
#include "platform/api/time.h"
#include "platform/base/macros.h"
#include "util/alarm.h"

namespace openscreen {

struct IPEndpoint;
class TaskRunner;

namespace discovery {

struct Config;
class MdnsMessage;
class MdnsProbeManager;
class MdnsRandom;
class MdnsReceiver;
class MdnsRecordChangedCallback;
class MdnsSender;
class MdnsQuerier;

// This class is responsible for responding to any incoming mDNS Queries
// received via the OnMessageReceived() method. When responding, the generated
// MdnsMessage will contain the requested record(s) in the answers section, or
// an NSEC record to specify that the requested record was not found in the case
// of a query with DnsType aside from ANY. In the case where records are found,
// the additional records field may be populated with additional records, as
// specified in RFCs 6762 and 6763.
class MdnsResponder {
 public:
  // Class to handle querying for existing records.
  class RecordHandler {
   public:
    virtual ~RecordHandler();

    // Returns whether this service has one or more records matching the
    // provided name, type, and class.
    virtual bool HasRecords(const DomainName& name,
                            DnsType type,
                            DnsClass clazz) = 0;

    // Returns all records owned by this service with name, type, and class
    // matching the provided values.
    virtual std::vector<MdnsRecord::ConstRef> GetRecords(const DomainName& name,
                                                         DnsType type,
                                                         DnsClass clazz) = 0;

    // Enumerates all PTR records owned by this service.
    virtual std::vector<MdnsRecord::ConstRef> GetPtrRecords(DnsClass clazz) = 0;
  };

  // |record_handler|, |sender|, |receiver|, |task_runner|, |random_delay|, and
  // |config| are expected to persist for the duration of this instance's
  // lifetime.
  MdnsResponder(RecordHandler* record_handler,
                MdnsProbeManager* ownership_handler,
                MdnsSender* sender,
                MdnsReceiver* receiver,
                TaskRunner& task_runner,
                ClockNowFunctionPtr now_function,
                MdnsRandom* random_delay,
                const Config& config);
  ~MdnsResponder();

  OSP_DISALLOW_COPY_AND_ASSIGN(MdnsResponder);

 private:
  // Class which handles processing and responding to queries segmented into
  // multiple messages.
  class TruncatedQuery {
   public:
    // |responder| and |task_runner| are expected to persist for the duration of
    // this instance's lifetime.
    TruncatedQuery(MdnsResponder* responder,
                   TaskRunner& task_runner,
                   ClockNowFunctionPtr now_function,
                   IPEndpoint src,
                   const MdnsMessage& message,
                   const Config& config);
    TruncatedQuery(const TruncatedQuery& other) = delete;
    TruncatedQuery(TruncatedQuery&& other) noexcept = delete;

    TruncatedQuery& operator=(const TruncatedQuery& other) = delete;
    TruncatedQuery& operator=(TruncatedQuery&& other) noexcept = delete;

    // Sets the query associated with this instance. Must only be called if no
    // query has already been set, here or through the ctor.
    void SetQuery(const MdnsMessage& message);

    // Adds additional known answers.
    void AddKnownAnswers(const std::vector<MdnsRecord>& records);

    // Responds to the stored queries.
    void SendResponse();

    const IPEndpoint& src() const { return src_; }
    const std::vector<MdnsQuestion>& questions() const { return questions_; }
    const std::vector<MdnsRecord>& known_answers() const {
      return known_answers_;
    }

   private:
    void RescheduleSend();

    // The number of messages received so far associated with this known answer
    // query.
    int messages_received_so_far = 0;

    const int max_allowed_messages_;
    const int max_allowed_records_;
    const IPEndpoint src_;
    MdnsResponder* const responder_;

    std::vector<MdnsQuestion> questions_;
    std::vector<MdnsRecord> known_answers_;
    Alarm alarm_;
  };

  // Called when a new MdnsMessage is received.
  void OnMessageReceived(const MdnsMessage& message, const IPEndpoint& src);

  // Responds a truncated query for which all known answers have been received.
  void RespondToTruncatedQuery(TruncatedQuery* query);

  // Processes a message associated with a multi-packet truncated query.
  void ProcessMultiPacketTruncatedMessage(const MdnsMessage& message,
                                          const IPEndpoint& src);

  // Processes queries provided.
  void ProcessQueries(const IPEndpoint& src,
                      const std::vector<MdnsQuestion>& questions,
                      const std::vector<MdnsRecord>& known_answers);

  // Sends the response to the provided query.
  void SendResponse(const MdnsQuestion& question,
                    const std::vector<MdnsRecord>& known_answers,
                    std::function<void(const MdnsMessage&)> send_response,
                    bool is_exclusive_owner);

  // Set of all truncated queries received so far. Per RFC 6762 section 7.1,
  // matching of a query with additional known answers should be done based on
  // the source address.
  // NOTE: unique_ptrs used because TruncatedQuery is not movable.
  std::map<IPEndpoint, std::unique_ptr<TruncatedQuery>> truncated_queries_;

  RecordHandler* const record_handler_;
  MdnsProbeManager* const ownership_handler_;
  MdnsSender* const sender_;
  MdnsReceiver* const receiver_;
  TaskRunner& task_runner_;
  const ClockNowFunctionPtr now_function_;
  MdnsRandom* const random_delay_;
  const Config& config_;

  friend class MdnsResponderTest;
};

}  // namespace discovery
}  // namespace openscreen

#endif  // DISCOVERY_MDNS_IMPL_MDNS_RESPONDER_H_
