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

#ifndef DISCOVERY_MDNS_MDNS_READER_H_
#define DISCOVERY_MDNS_MDNS_READER_H_

#include <utility>
#include <vector>

#include "discovery/mdns/mdns_records.h"
#include "platform/base/error.h"
#include "util/big_endian.h"

namespace openscreen {
namespace discovery {

struct Config;

class MdnsReader : public BigEndianReader {
 public:
  MdnsReader(const Config& config, const uint8_t* buffer, size_t length);

  using BigEndianReader::Read;

  // The following methods return true if the method was able to successfully
  // read the value to |out| and advances current() to point right past the read
  // data. Returns false if the method failed to read the value to |out|,
  // current() remains unchanged.
  bool Read(TxtRecordRdata::Entry* out);
  bool Read(DomainName* out);
  bool Read(RawRecordRdata* out);
  bool Read(SrvRecordRdata* out);
  bool Read(ARecordRdata* out);
  bool Read(AAAARecordRdata* out);
  bool Read(PtrRecordRdata* out);
  bool Read(TxtRecordRdata* out);
  bool Read(NsecRecordRdata* out);

  // Reads a DNS resource record with its RDATA.
  // The correct type of RDATA to be read is determined by the type
  // specified in the record.
  bool Read(MdnsRecord* out);
  bool Read(MdnsQuestion* out);

  // Reads multiple mDNS questions and records that are a part of
  // a mDNS message being read.
  ErrorOr<MdnsMessage> Read();

 private:
  struct NsecBitMapField {
    uint8_t window_block;
    uint8_t bitmap_length;
    const uint8_t* bitmap;
  };

  bool Read(IPAddress::Version version, IPAddress* out);
  bool Read(DnsType type, Rdata* out);
  bool Read(Header* out);
  bool Read(std::vector<DnsType>* types, int remaining_length);
  bool Read(NsecBitMapField* out);

  template <class ItemType>
  bool Read(uint16_t count, std::vector<ItemType>* out) {
    Cursor cursor(this);
    out->reserve(count);
    for (uint16_t i = 0; i < count; ++i) {
      ItemType entry;
      if (!Read(&entry)) {
        return false;
      }
      out->emplace_back(std::move(entry));
    }
    cursor.Commit();
    return true;
  }

  template <class RdataType>
  bool Read(Rdata* out) {
    RdataType rdata;
    if (Read(&rdata)) {
      *out = std::move(rdata);
      return true;
    }
    return false;
  }

  // Maximum allowed size for the rdata in any received record.
  const size_t kMaximumAllowedRdataSize;
};

}  // namespace discovery
}  // namespace openscreen

#endif  // DISCOVERY_MDNS_MDNS_READER_H_