blob: 06e830a8989c15dfd7bd6a53691da3fe18a246f3 [file] [log] [blame]
// Copyright 2018 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 GARNET_LIB_WLAN_MLME_INCLUDE_WLAN_MLME_FRAME_VALIDATION_H_
#define GARNET_LIB_WLAN_MLME_INCLUDE_WLAN_MLME_FRAME_VALIDATION_H_
#include <wlan/common/action_frame.h>
#include <wlan/common/mac_frame.h>
#include <wlan/mlme/packet.h>
#include <wlan/protocol/mac.h>
#include <zircon/types.h>
namespace wlan {
struct UnknownBody : public EmptyHdr {
} __PACKED;
template <typename H> struct is_mac_hdr {
static constexpr bool value = std::is_same<H, MgmtFrameHeader>::value ||
std::is_same<H, DataFrameHeader>::value ||
std::is_same<H, CtrlFrameHdr>::value;
};
namespace internal {
template <unsigned int N, typename T> T align(T t) {
static_assert(N > 1 && !(N & (N - 1)), "alignment must be with a power of 2");
return (t + (N - 1)) & ~(N - 1);
}
static bool IsAmsduSubframeHeader(const DataFrameHeader* hdr, size_t available_buf_len) {
// Before accessing dynamic fields, verify full header length.
if (available_buf_len < hdr->len()) { return false; }
auto qos_ctrl = hdr->qos_ctrl();
if (qos_ctrl == nullptr) { return false; }
return qos_ctrl->amsdu_present() == 1;
}
template <typename H, typename B> struct FrameTypeValidator {};
template <typename H, typename B> struct MacSubtypeValidator {
static bool is_valid(const uint8_t* buf, size_t len) {
auto fc = reinterpret_cast<const FrameControl*>(buf);
return fc->subtype() == B::Subtype();
}
};
template <typename H> struct MacSubtypeValidator<H, UnknownBody> {
static bool is_valid(const uint8_t* buf, size_t len) { return true; }
};
template <> struct MacSubtypeValidator<DataFrameHeader, NullDataHdr> {
static bool is_valid(const uint8_t* buf, size_t len) {
auto fc = reinterpret_cast<const FrameControl*>(buf);
return fc->subtype() == DataSubtype::kNull || fc->subtype() == DataSubtype::kQosnull;
}
};
template <> struct MacSubtypeValidator<DataFrameHeader, LlcHeader> {
static bool is_valid(const uint8_t* buf, size_t len) {
auto hdr = reinterpret_cast<const DataFrameHeader*>(buf);
if (hdr->fc.subtype() == DataSubtype::kDataSubtype) { return true; }
if (hdr->fc.subtype() != DataSubtype::kQosdata) { return false; }
return !IsAmsduSubframeHeader(hdr, len);
}
};
template <> struct MacSubtypeValidator<DataFrameHeader, AmsduSubframeHeader> {
static bool is_valid(const uint8_t* buf, size_t len) {
auto hdr = reinterpret_cast<const DataFrameHeader*>(buf);
if (hdr->fc.subtype() != DataSubtype::kQosdata) { return false; }
return IsAmsduSubframeHeader(hdr, len);
}
};
template <typename H, typename B> bool IsValidMacFrameType(const uint8_t* buf, size_t len) {
if (len < sizeof(H)) { return false; }
auto fc = reinterpret_cast<const FrameControl*>(buf);
return fc->type() == H::Type() && MacSubtypeValidator<H, B>::is_valid(buf, len);
}
template <typename B> struct FrameTypeValidator<MgmtFrameHeader, B> {
static bool is_valid(const uint8_t* buf, size_t len) {
return IsValidMacFrameType<MgmtFrameHeader, B>(buf, len);
}
};
template <typename B> struct FrameTypeValidator<DataFrameHeader, B> {
static bool is_valid(const uint8_t* buf, size_t len) {
return IsValidMacFrameType<DataFrameHeader, B>(buf, len);
}
};
template <typename B> struct FrameTypeValidator<CtrlFrameHdr, B> {
static bool is_valid(const uint8_t* buf, size_t len) {
return IsValidMacFrameType<CtrlFrameHdr, B>(buf, len);
}
};
template <typename B> struct FrameTypeValidator<EthernetII, B> {
static bool is_valid(const uint8_t* buf, size_t len) { return true; }
};
template <typename B> struct FrameTypeValidator<ActionFrame, B> {
static bool is_valid(const uint8_t* buf, size_t len) {
if (len < sizeof(ActionFrame)) { return false; }
auto hdr = reinterpret_cast<const ActionFrame*>(buf);
return hdr->category == B::ActionCategory();
}
};
template <typename B> struct FrameTypeValidator<ActionFrameBlockAck, B> {
static bool is_valid(const uint8_t* buf, size_t len) {
if (len < sizeof(ActionFrameBlockAck)) { return false; }
auto hdr = reinterpret_cast<const ActionFrameBlockAck*>(buf);
return hdr->action == B::BlockAckAction();
}
};
template <typename B> struct FrameTypeValidator<AmsduSubframeHeader, B> {
static bool is_valid(const uint8_t* buf, size_t len) {
// An AMSDU header itself carries no information to be identified as such.
// A data frame however provides type checks for AMSDU subframe headers.
return true;
}
};
template <> struct FrameTypeValidator<LlcHeader, EapolHdr> {
static bool is_valid(const uint8_t* buf, size_t len) {
if (len < sizeof(LlcHeader)) { return false; }
auto hdr = reinterpret_cast<const LlcHeader*>(buf);
return be16toh(hdr->protocol_id) == kEapolProtocolId;
}
};
} // namespace internal
typedef size_t (*add_padding_func)(size_t);
template <typename H> add_padding_func get_packet_padding_func(const Packet* pkt) {
auto rx = pkt->ctrl_data<wlan_rx_info_t>();
if (is_mac_hdr<H>::value && rx != nullptr &&
rx->rx_flags & WLAN_RX_INFO_FLAGS_FRAME_BODY_PADDING_4) {
return internal::align<4>;
}
return [](size_t v) { return v; };
}
// Check if the given buffer is long enough to hold a header of type H.
// Note: The expected length of the header can be variable and depends on the content of the buffer.
template <typename H> bool is_valid_hdr_length(const uint8_t* buf, size_t len) {
if (buf == nullptr) { return false; }
if (std::is_base_of<EmptyHdr, H>::value) { return true; }
if (len < sizeof(H)) { return false; }
auto hdr = reinterpret_cast<const H*>(buf);
ZX_DEBUG_ASSERT(hdr->len() >= sizeof(H));
return len >= hdr->len();
}
template <typename H, typename B>
bool is_valid_frame_length(const uint8_t* buf, size_t len, add_padding_func padding) {
if (buf == nullptr || !is_valid_hdr_length<H>(buf, len)) { return false; }
ZX_DEBUG_ASSERT(len >= sizeof(H));
auto hdr = reinterpret_cast<const H*>(buf);
size_t body_offset = padding(hdr->len());
if (body_offset > len) { return false; }
return is_valid_hdr_length<B>(buf + body_offset, len - body_offset);
}
template <typename H, typename B> bool is_valid_frame_length(const Packet* pkt, size_t offset) {
if (pkt == nullptr || offset > pkt->len()) { return false; }
const uint8_t* buf = pkt->data() + offset;
size_t len = pkt->len() - offset;
auto padding = get_packet_padding_func<H>(pkt);
return is_valid_frame_length<H, B>(buf, len, padding);
}
template <typename H, typename B> bool is_valid_frame_type(const uint8_t* buf, size_t len) {
if (buf == nullptr) { return false; }
return internal::FrameTypeValidator<H, B>::is_valid(buf, len);
}
template <typename H, typename B> bool is_valid_frame_type(const Packet* pkt, size_t offset) {
if (pkt == nullptr || offset > pkt->len()) { return false; }
const uint8_t* buf = pkt->data() + offset;
size_t len = pkt->len() - offset;
return is_valid_frame_type<H, B>(buf, len);
}
} // namespace wlan
#endif // GARNET_LIB_WLAN_MLME_INCLUDE_WLAN_MLME_FRAME_VALIDATION_H_