text: // Copyright 2017 The Fuchsia 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 SRC_CONNECTIVITY_WLAN_LIB_MLME_CPP_INCLUDE_WLAN_MLME_MAC_FRAME_H_
  #define SRC_CONNECTIVITY_WLAN_LIB_MLME_CPP_INCLUDE_WLAN_MLME_MAC_FRAME_H_
  
  #include <lib/fit/function.h>
  #include <lib/zx/time.h>
  #include <zircon/compiler.h>
  #include <zircon/types.h>
  
  #include <cstdint>
  #include <memory>
  
  #include <wlan/common/bitfield.h>
  #include <wlan/common/mac_frame.h>
  #include <wlan/common/macaddr.h>
  #include <wlan/mlme/frame_validation.h>
  #include <wlan/mlme/packet.h>
  
  namespace wlan {
  
  template <typename H, typename B>
  class Frame;
  template <typename H, typename B>
  class FrameView;
  
  template <typename H, typename B>
  class VerifiedFrameType {
   public:
    static VerifiedFrameType<H, B> Valid(FrameView<H, B> frame) {
      ZX_DEBUG_ASSERT(!frame.IsEmpty());
      return VerifiedFrameType<H, B>(frame);
    }
  
    static VerifiedFrameType<H, B> Invalid() { return VerifiedFrameType<H, B>(FrameView<H, B>()); }
  
    FrameView<H, B> CheckLength() const {
      if (!IsValid()) {
        return frame_;
      }
  
      if (!frame_.HasValidLen()) {
        return FrameView<H, B>();
      }
      return frame_;
    }
  
    bool IsValid() const { return !frame_.IsEmpty(); }
    operator bool() const { return IsValid(); }
  
   private:
    explicit VerifiedFrameType(FrameView<H, B> frame) : frame_(frame) {}
  
    FrameView<H, B> frame_;
  };
  
  // A temporary representation of a frame.
  template <typename Header, typename Body = UnknownBody>
  class FrameView {
   public:
    explicit FrameView(const Packet* pkt, size_t offset = 0) : data_offset_(offset), pkt_(pkt) {
      ZX_DEBUG_ASSERT(pkt_ != nullptr);
      ZX_DEBUG_ASSERT(pkt->len() >= offset);
    }
  
    FrameView() : data_offset_(0), pkt_(nullptr) {}
  
    static VerifiedFrameType<Header, Body> CheckType(const Packet* pkt, size_t offset = 0) {
      if (!is_valid_frame_type<Header, Body>(pkt, offset)) {
        return VerifiedFrameType<Header, Body>::Invalid();
      }
  
      return VerifiedFrameType<Header, Body>::Valid(FrameView<Header, Body>(pkt, offset));
    }
  
    template <typename NewBody>
    VerifiedFrameType<Header, NewBody> CheckBodyType() const {
      return FrameView<Header, NewBody>::CheckType(pkt_, data_offset_);
    }
  
    // Skips the header on the underlying data of the frame.
    // Method has no effect when called on empty frames.
    FrameView<Body, UnknownBody> SkipHeader() const {
      if (IsEmpty()) {
        return {};
      }
  
      ZX_DEBUG_ASSERT(body_offset() <= pkt_->len());
      return FrameView<Body, UnknownBody>(pkt_, body_offset());
    }
  
    // Verifies and treats the underlying data as a different frame type.
    template <typename NewHeader, typename NewBody = UnknownBody>
    VerifiedFrameType<NewHeader, NewBody> As() const {
      return FrameView<NewHeader, NewBody>::CheckType(pkt_, data_offset_);
    }
  
    // Advances the underlying data by `len` bytes and returns a frame of unknown
    // type. Returns an empty frame if the underlying data is too short.
    FrameView<UnknownBody, UnknownBody> AdvanceBy(size_t len) const {
      if (IsEmpty() || pkt_->len() - data_offset_ < len) {
        return {};
      }
      return FrameView<UnknownBody, UnknownBody>(pkt_, data_offset_ + len);
    }
  
    Frame<Header, Body> IntoOwned(std::unique_ptr<Packet> pkt) const {
      ZX_DEBUG_ASSERT(pkt != nullptr && pkt.get() == pkt_);
      return Frame<Header, Body>(data_offset_, std::move(pkt));
    }
  
    const uint8_t* data() const {
      ZX_DEBUG_ASSERT(pkt_ != nullptr);
  
      return pkt_->data() + data_offset_;
    }
  
    cpp20::span<const uint8_t> body_data() const {
      ZX_DEBUG_ASSERT(pkt_ != nullptr);
      if (body_len() == 0) {
        return {};
      }
  
      auto body = pkt_->field<uint8_t>(body_offset());
      return {body, body_len()};
    }
  
    const Header* hdr() const {
      ZX_DEBUG_ASSERT(pkt_ != nullptr);
  
      return pkt_->field<Header>(hdr_offset());
    }
  
    const Body* body() const {
      ZX_DEBUG_ASSERT(pkt_ != nullptr);
  
      return pkt_->field<Body>(body_offset());
    }
  
    size_t body_len() const {
      ZX_DEBUG_ASSERT(pkt_ != nullptr);
  
      size_t offset = body_offset();
      ZX_DEBUG_ASSERT(offset <= pkt_->len());
      if (pkt_->len() < offset) {
        return 0;
      }
      return pkt_->len() - offset;
    }
  
    size_t len() const {
      ZX_DEBUG_ASSERT(pkt_ != nullptr);
  
      if (pkt_->len() < hdr_offset()) {
        return 0;
      }
      return pkt_->len() - hdr_offset();
    }
  
    bool has_rx_info() const {
      ZX_DEBUG_ASSERT(pkt_ != nullptr);
  
      if (!is_mac_hdr<Header>::value) {
        return false;
      }
  
      return pkt_->has_ctrl_data<wlan_rx_info_t>();
    }
  
    const wlan_rx_info_t* rx_info() const {
      ZX_DEBUG_ASSERT(pkt_ != nullptr);
      ZX_DEBUG_ASSERT(has_rx_info());
      static_assert(is_mac_hdr<Header>::value, "only MAC frame can carry rx_info");
  
      return pkt_->ctrl_data<wlan_rx_info_t>();
    }
  
    bool HasValidLen() const {
      ZX_DEBUG_ASSERT(pkt_ != nullptr);
  
      return is_valid_frame_length<Header, Body>(pkt_, data_offset_);
    }
  
    bool IsEmpty() const { return pkt_ == nullptr; }
    operator bool() const { return !IsEmpty(); }
  
    // Advances the frame such that it points to the beginning of its previous
    // Body. Example:
    //     DataFrameView<> data_frame(packet_ptr);
    //     FrameView<LlcHeader> llc_frame = data_frame.NextFrame<LlcHeader>();
    //     FrameView<Eapol> eapol_frame = llc_frame.NextFrame<Eapol>();
    // TODO(hahnr): This should be called NextHdr instead.
    template <typename NextH = Body, typename NextB = UnknownBody>
    FrameView<NextH, NextB> NextFrame() const {
      ZX_DEBUG_ASSERT(pkt_ != nullptr);
  
      return FrameView<NextH, NextB>(pkt_, body_offset());
    }
  
    size_t hdr_offset() const { return data_offset_; }
  
    size_t body_offset() const {
      auto padding_func = get_packet_padding_func<Header>(pkt_);
      return hdr_offset() + padding_func(hdr()->len());
    }
  
   private:
    size_t data_offset_ = 0;
    const Packet* pkt_;
  };
  
  template <typename Header, typename Body = UnknownBody>
  class Frame {
   public:
    explicit Frame(std::unique_ptr<Packet> pkt) : data_offset_(0), pkt_(std::move(pkt)) {
      ZX_DEBUG_ASSERT(pkt_ != nullptr);
    }
  
    Frame(size_t offset, std::unique_ptr<Packet> pkt) : data_offset_(offset), pkt_(std::move(pkt)) {
      ZX_DEBUG_ASSERT(pkt_ != nullptr);
    }
  
    Frame() : data_offset_(0), pkt_(nullptr) {}
  
    Header* hdr() {
      ZX_DEBUG_ASSERT(!IsEmpty());
  
      auto hdr = pkt_->mut_field<Header>(View().hdr_offset());
      ZX_DEBUG_ASSERT(hdr != nullptr);
      return hdr;
    }
  
    const Header* hdr() const { return View().hdr(); }
  
    Body* body() {
      ZX_DEBUG_ASSERT(!IsEmpty());
  
      auto body = pkt_->mut_field<Body>(View().body_offset());
      ZX_DEBUG_ASSERT(body != nullptr);
      return body;
    }
  
    const Body* body() const { return View().body(); }
  
    cpp20::span<const uint8_t> body_data() const { return View().body_data(); }
  
    size_t body_len() const { return View().body_len(); }
  
    size_t len() const { return View().len(); }
  
    // `true` if the frame was 'taken' and should no longer be used.
    bool IsEmpty() const { return pkt_ == nullptr; }
  
    // Returns the Frame's underlying Packet. The Frame will no longer own the
    // Packet and will be `empty` from that moment on and should no longer be
    // used.
    std::unique_ptr<Packet> Take() {
      ZX_DEBUG_ASSERT(!IsEmpty());
      return std::move(pkt_);
    }
  
    FrameView<Header, Body> View() const {
      ZX_DEBUG_ASSERT(!IsEmpty());
  
      return FrameView<Header, Body>(pkt_.get(), data_offset_);
    }
  
   private:
    size_t data_offset_;
    std::unique_ptr<Packet> pkt_;
  };
  
  // Frame which contains a known header but unknown payload.
  using EthFrame = Frame<EthernetII>;
  template <typename T = UnknownBody>
  using MgmtFrame = Frame<MgmtFrameHeader, T>;
  template <typename T = UnknownBody>
  using CtrlFrame = Frame<CtrlFrameHdr, T>;
  template <typename T = UnknownBody>
  using DataFrame = Frame<DataFrameHeader, T>;
  
  using EthFrameView = FrameView<EthernetII>;
  template <typename T = UnknownBody>
  using MgmtFrameView = FrameView<MgmtFrameHeader, T>;
  template <typename T = UnknownBody>
  using CtrlFrameView = FrameView<CtrlFrameHdr, T>;
  template <typename T = UnknownBody>
  using DataFrameView = FrameView<DataFrameHeader, T>;
  
  // TODO(hahnr): This isn't a great location for these definitions.
  using aid_t = size_t;
  static constexpr aid_t kGroupAdressedAid = 0;
  static constexpr aid_t kMaxBssClients = 2008;
  static constexpr aid_t kUnknownAid = kMaxBssClients + 1;
  
  void FillEtherLlcHeader(LlcHeader* llc, uint16_t protocol_id);
  
  }  // namespace wlan
  
  #endif  // SRC_CONNECTIVITY_WLAN_LIB_MLME_CPP_INCLUDE_WLAN_MLME_MAC_FRAME_H_
  