Support more types of nl80211 attributes

This CL includes the following changes:
1.
  Improve the interface design of NL80211Attribute classes.
  Add a function GetAttributeValue() to NL80211NestedAttr class.
2.
  Support more types of nl80211 attributes. This includes all
  size of interger types, string, flag, and raw buffer.
3.
  Guarantee payload size alignment by padding 0s.
4.
  Support FLAG attribute by adding a function AddAttribute(int id).
  We don't instanciate any flag attribute because it has no payload.
5.
  Add corresponding unit tests.

BUG=29454786
TEST=compile
TEST=run unittests

Change-Id: I3653a3babfc1df5dde42eecf4410fd636d26b956
This commit is contained in:
Ningyuan Wang 2016-06-30 10:36:47 -07:00
parent b2f7d37a15
commit 96f218d3f9
3 changed files with 378 additions and 101 deletions

View File

@ -16,16 +16,25 @@
#include "net/nl80211_attribute.h"
#include "android-base/logging.h"
#include <android-base/logging.h>
using std::string;
using std::vector;
namespace android {
namespace wificond {
void BaseNL80211Attr::InitAttributeHeader(int attribute_id,
// Explicit instantiation
template class NL80211Attr<uint8_t>;
template class NL80211Attr<uint16_t>;
template class NL80211Attr<uint32_t>;
template class NL80211Attr<vector<uint8_t>>;
template class NL80211Attr<string>;
// For BaseNL80211Attr
void BaseNL80211Attr::InitHeaderAndResize(int attribute_id,
int payload_length) {
data_.resize(NLA_HDRLEN, 0);
data_.resize(NLA_HDRLEN + NLA_ALIGN(payload_length), 0);
nlattr* header = reinterpret_cast<nlattr*>(data_.data());
header->nla_type = attribute_id;
header->nla_len = NLA_HDRLEN + payload_length;
@ -36,13 +45,68 @@ int BaseNL80211Attr::GetAttributeId() const {
return header->nla_type;
}
bool BaseNL80211Attr::IsValid() const {
if (data_.size() < NLA_HDRLEN) {
return false;
}
const nlattr* header = reinterpret_cast<const nlattr*>(data_.data());
return NLA_ALIGN(header->nla_len) == data_.size();
}
const vector<uint8_t>& BaseNL80211Attr::GetConstData() const {
return data_;
}
// For NL80211NestedAttr:
// For NL80211Attr<std::vector<uint8_t>>
NL80211Attr<vector<uint8_t>>::NL80211Attr(int id,
const vector<uint8_t>& raw_buffer) {
size_t size = raw_buffer.size();
InitHeaderAndResize(id, size);
memcpy(data_.data() + NLA_HDRLEN, raw_buffer.data(), raw_buffer.size());
}
NL80211Attr<vector<uint8_t>>::NL80211Attr(
const vector<uint8_t>& data) {
data_ = data;
}
vector<uint8_t> NL80211Attr<vector<uint8_t>>::GetValue() const {
const nlattr* header = reinterpret_cast<const nlattr*>(data_.data());
return vector<uint8_t>(
data_.data() + NLA_HDRLEN,
data_.data() + header->nla_len);
}
// For NL80211Attr<std::string>
NL80211Attr<string>::NL80211Attr(int id, const string& str) {
size_t size = str.size();
// This string is storaged as a null-terminated string.
// Buffer is initialized with 0s so we only need to make a space for
// the null terminator.
InitHeaderAndResize(id, size + 1);
char* storage = reinterpret_cast<char*>(data_.data() + NLA_HDRLEN);
str.copy(storage, size);
}
NL80211Attr<string>::NL80211Attr(const vector<uint8_t>& data) {
data_ = data;
}
string NL80211Attr<string>::GetValue() const {
const nlattr* header = reinterpret_cast<const nlattr*>(data_.data());
size_t str_length = header->nla_len - NLA_HDRLEN;
// Remove trailing zeros.
while (str_length > 0 &&
*(data_.data() + NLA_HDRLEN + str_length - 1) == 0) {
str_length--;
}
return string(reinterpret_cast<const char*>(data_.data() + NLA_HDRLEN),
str_length);
}
// For NL80211NestedAttr
NL80211NestedAttr::NL80211NestedAttr(int id) {
InitAttributeHeader(id, 0);
InitHeaderAndResize(id, 0);
}
NL80211NestedAttr::NL80211NestedAttr(const vector<uint8_t>& data) {
@ -53,46 +117,68 @@ void NL80211NestedAttr::AddAttribute(const BaseNL80211Attr& attribute) {
const vector<uint8_t>& append_data = attribute.GetConstData();
// Append the data of |attribute| to |this|.
data_.insert(data_.end(), append_data.begin(), append_data.end());
uint8_t* head_ptr = &data_.front();
nlattr* header = reinterpret_cast<nlattr*>(head_ptr);
nlattr* header = reinterpret_cast<nlattr*>(data_.data());
// We don't need to worry about padding for nested attribute.
// Because as long as all sub attributes have padding, the payload is aligned.
header->nla_len += append_data.size();
}
bool NL80211NestedAttr::HasAttribute(int id, AttributeType type) const {
return GetAttribute(id, type, nullptr);
void NL80211NestedAttr::AddFlagAttribute(int attribute_id) {
// We only need to append a header for flag attribute.
nlattr flag_header;
flag_header.nla_type = attribute_id;
flag_header.nla_len = NLA_HDRLEN;
vector<uint8_t> append_data(
reinterpret_cast<uint8_t*>(&flag_header),
reinterpret_cast<uint8_t*>(&flag_header) + sizeof(nlattr));
append_data.resize(NLA_HDRLEN, 0);
// Append the data of |attribute| to |this|.
data_.insert(data_.end(), append_data.begin(), append_data.end());
nlattr* header = reinterpret_cast<nlattr*>(data_.data());
header->nla_len += NLA_HDRLEN;
}
bool NL80211NestedAttr::HasAttribute(int id) const {
return GetAttributeInternal(id, nullptr, nullptr);
}
bool NL80211NestedAttr::GetAttribute(int id,
AttributeType type,
BaseNL80211Attr* attribute) const {
NL80211NestedAttr* attribute) const {
uint8_t* start = nullptr;
uint8_t* end = nullptr;
if (!GetAttributeInternal(id, &start, &end) ||
start == nullptr ||
end == nullptr) {
return false;
}
*attribute = NL80211NestedAttr(vector<uint8_t>(start, end));
if (!attribute->IsValid()) {
return false;
}
return true;
}
bool NL80211NestedAttr::GetAttributeInternal(int id,
uint8_t** start,
uint8_t** end) const {
// Skip the top level attribute header.
const uint8_t* ptr = data_.data() + NLA_HDRLEN;
const uint8_t* end_ptr = data_.data() + data_.size();
while (ptr + NLA_HDRLEN <= end_ptr) {
const nlattr* header = reinterpret_cast<const nlattr*>(ptr);
if (header->nla_type == id) {
if (ptr + header->nla_len > end_ptr) {
if (ptr + NLA_ALIGN(header->nla_len) > end_ptr) {
LOG(ERROR) << "Failed to get attribute: broken nl80211 atrribute.";
return false;
}
if (attribute != nullptr) {
switch(type) {
case kNested:
*attribute = NL80211NestedAttr(
vector<uint8_t>(ptr, ptr + header->nla_len));
break;
case kUInt32:
*attribute = NL80211Attr<uint32_t>(
vector<uint8_t>(ptr, ptr + header->nla_len));
break;
// TODO(nywang): write cases for other types of attributes.
default:
return false;
}
if (start != nullptr && end != nullptr) {
*start = const_cast<uint8_t*>(ptr);
*end = const_cast<uint8_t*>(ptr + NLA_ALIGN(header->nla_len));
}
return true;
}
ptr += header->nla_len;
ptr += NLA_ALIGN(header->nla_len);
}
return false;
}

View File

@ -18,6 +18,8 @@
#define WIFICOND_NL80211_ATTRIBUTE_H_
#include <memory>
#include <string>
#include <type_traits>
#include <vector>
#include <linux/netlink.h>
@ -29,19 +31,17 @@ namespace wificond {
class BaseNL80211Attr {
public:
enum AttributeType {
kNested,
kUInt32
};
virtual ~BaseNL80211Attr() = default;
const std::vector<uint8_t>& GetConstData() const;
int GetAttributeId() const;
// This is used when we initialize a NL80211 attribute from an existing
// buffer.
virtual bool IsValid() const;
protected:
BaseNL80211Attr() = default;
void InitAttributeHeader(int attribute_id, int payload_length);
void InitHeaderAndResize(int attribute_id, int payload_length);
std::vector<uint8_t> data_;
};
@ -49,26 +49,75 @@ class BaseNL80211Attr {
template <typename T>
class NL80211Attr : public BaseNL80211Attr {
public:
explicit NL80211Attr(int id, T value) {
InitAttributeHeader(id, sizeof(T));
data_.resize(data_.size() + sizeof(T));
T* storage = reinterpret_cast<T*>(data_.data() + data_.size() - sizeof(T));
NL80211Attr(int id, T value) {
static_assert(
std::is_integral<T>::value,
"Failed to create NL80211Attr class with non-integral type");
InitHeaderAndResize(id, sizeof(T));
T* storage = reinterpret_cast<T*>(data_.data() + NLA_HDRLEN);
*storage = value;
}
// Caller is a responsible for ensuring that |data| is:
// 1) Is at least NLA_HDRLEN long
// 2) That *data when interpreted as a nlattr is internally consistent
// (e.g. data.size() == NLA_HDLLEN + nlattr.nla_len)
// 1) Is at least NLA_HDRLEN long.
// 2) That *data when interpreted as a nlattr is internally consistent.
// (e.g. data.size() == NLA_ALIGN(nlattr.nla_len)
// and nla_len == NLA_HDRLEN + payload size
explicit NL80211Attr(const std::vector<uint8_t>& data) {
data_ = data;
}
~NL80211Attr() override = default;
bool IsValid() const override {
if (!BaseNL80211Attr::IsValid()) {
return false;
}
// If BaseNL80211Attr::IsValid() == true, at least we have enough valid
// buffer for header.
const nlattr* header = reinterpret_cast<const nlattr*>(data_.data());
// Buffer size = header size + payload size + padding size
// nla_len = header size + payload size
if (NLA_ALIGN(sizeof(T)) + NLA_HDRLEN != data_.size() ||
sizeof(T) + NLA_HDRLEN != header->nla_len ) {
return false;
}
return true;
}
T GetValue() const {
return *reinterpret_cast<const T*>(data_.data() + NLA_HDRLEN);
}
}; // class NL80211Attr
}; // class NL80211Attr for POD-types
template <>
class NL80211Attr<std::vector<uint8_t>> : public BaseNL80211Attr {
public:
NL80211Attr(int id, const std::vector<uint8_t>& raw_buffer);
explicit NL80211Attr(const std::vector<uint8_t>& data);
~NL80211Attr() override = default;
std::vector<uint8_t> GetValue() const;
}; // class NL80211Attr for raw data
template <>
class NL80211Attr<std::string> : public BaseNL80211Attr {
public:
NL80211Attr(int id, const std::string& str);
// We parse string attribute buffer in the same way kernel does.
// All trailing zeros are trimmed when retrieving a std::string from
// byte array.
explicit NL80211Attr(const std::vector<uint8_t>& data);
~NL80211Attr() override = default;
std::string GetValue() const;
}; // class NL80211Attr for string
// Force the compiler not to instantiate these templates because
// they will be instantiated in nl80211_attribute.cpp file. This helps
// reduce compile time as well as object file size.
extern template class NL80211Attr<uint8_t>;
extern template class NL80211Attr<uint16_t>;
extern template class NL80211Attr<uint32_t>;
extern template class NL80211Attr<std::vector<uint8_t>>;
extern template class NL80211Attr<std::string>;
class NL80211NestedAttr : public BaseNL80211Attr {
public:
@ -77,16 +126,55 @@ class NL80211NestedAttr : public BaseNL80211Attr {
~NL80211NestedAttr() override = default;
void AddAttribute(const BaseNL80211Attr& attribute);
bool HasAttribute(int id, AttributeType type) const;
// For NLA_FLAG attribute
void AddFlagAttribute(int attribute_id);
bool HasAttribute(int id) const;
// Access an attribute nested within |this|.
// The result is returned by writing the attribute object to |*attribute|.
// Deeper nested attributes are not included. This means if A is nested within
// |this|, and B is nested within A, this function can't be used to access B.
// The reason is that we may have multiple attributes having the same
// attribute id, nested within different level of |this|.
bool GetAttribute(int id,
AttributeType type,
BaseNL80211Attr* attribute) const;
bool GetAttribute(int id, NL80211NestedAttr* attribute) const;
template <typename T>
bool GetAttributeValue(int id, T* value) const {
std::vector<uint8_t> empty_vec;
// All data in |attribute| created here will be overwritten by
// GetAttribute(). So we use an empty vector to initialize it,
// regardless of the fact that an empty buffer is not qualified
// for creating a valid attribute.
NL80211Attr<T> attribute(empty_vec);
if (!GetAttribute(id, &attribute)) {
return false;
}
*value = attribute.GetValue();
return true;
}
template <typename T>
bool GetAttribute(int id, NL80211Attr<T>* attribute) const {
uint8_t* start = nullptr;
uint8_t* end = nullptr;
if (!GetAttributeInternal(id, &start, &end) ||
start == nullptr ||
end == nullptr) {
return false;
}
*attribute = NL80211Attr<T>(std::vector<uint8_t>(start, end));
if (!attribute->IsValid()) {
return false;
}
return true;
}
private:
// |*start| and |*end| are the start and end pointers of buffer where
// |id| atrribute locates.
bool GetAttributeInternal(int id,
uint8_t** start,
uint8_t** end) const;
};
} // namespace wificond

View File

@ -22,78 +22,181 @@
#include "net/nl80211_attribute.h"
using std::string;
namespace android {
namespace wificond {
namespace {
const uint32_t kScanFrequency1 = 2500;
const uint32_t kScanFrequency2 = 5000;
const uint32_t kRSSIThreshold = 80;
const uint32_t kRSSIHysteresis = 10;
const uint32_t kU8Value1 = 200;
const uint32_t kU16Value1 = 5000;
const uint32_t kU32Value1 = 250000;
const uint32_t kU32Value2 = 500000;
const std::string kIFName = "wlan0";
const uint8_t kMacAddress[] = {
0xc0, 0x3f, 0x0e, 0x77, 0xe8, 0x7f
};
// This header contains invalid buffer length
const uint8_t kBrokenBuffer[] = {
0xff, 0xff, // nla_len = 0xffff
0x01, 0x11, // nla_type
0x15, 0x12, // payload
0x00, 0x00 // padding
};
const uint8_t kValidU32AttrBuffer[] = {
0x08, 0x00, // nla_len = 8
0x01, 0x00, // nla_type
0xf1, 0x12, 0x12, 0x2a // payload
};
const uint8_t kBufferContainsStringWithTrailingZero[] = {
0x0a, 0x00, // nla_len = 10
0x01, 0x00, // nla_type
'w', 'l', 'a', 'n', '0', '\0',
0x00, 0x00 // padding
};
const uint8_t kBufferContainsStringWithTrailingZeros[] = {
0x0c, 0x00, // nla_len = 12
0x01, 0x00, // nla_type
'w', 'l', 'a', 'n', '0', '\0', '\0', '\0'
};
const uint8_t kBufferContainsStringWithoutTrailingZero[] = {
0x09, 0x00, // nla_len = 9
0x01, 0x00, // nla_type
'w', 'l', 'a', 'n', '0',
0x00, 0x00, 0x00 // padding
};
} // namespace
TEST(NL80211AttributeTest, AttributeScanFrequenciesListTest) {
NL80211NestedAttr scan_freq(NL80211_ATTR_SCAN_FREQUENCIES);
// Use 1,2,3 .. for anonymous attributes.
NL80211Attr<uint32_t> freq1(1, kScanFrequency1);
NL80211Attr<uint32_t> freq2(2, kScanFrequency2);
scan_freq.AddAttribute(freq1);
scan_freq.AddAttribute(freq2);
EXPECT_EQ(scan_freq.GetAttributeId(), NL80211_ATTR_SCAN_FREQUENCIES);
EXPECT_TRUE(scan_freq.HasAttribute(1, BaseNL80211Attr::kUInt32));
EXPECT_TRUE(scan_freq.HasAttribute(2, BaseNL80211Attr::kUInt32));
NL80211Attr<uint32_t> attr_u32(0, 0);
EXPECT_TRUE(scan_freq.GetAttribute(1, BaseNL80211Attr::kUInt32, &attr_u32));
EXPECT_EQ(attr_u32.GetValue(), kScanFrequency1);
EXPECT_TRUE(scan_freq.GetAttribute(2, BaseNL80211Attr::kUInt32, &attr_u32));
EXPECT_EQ(attr_u32.GetValue(), kScanFrequency2);
TEST(NL80211AttributeTest,U8AttributesSeriallizeCorrectly) {
NL80211Attr<uint8_t> u8_attr(1, kU8Value1);
EXPECT_EQ(u8_attr.GetValue(), kU8Value1);
}
TEST(NL80211AttributeTest, AttributeCQMTest) {
NL80211NestedAttr cqm(NL80211_ATTR_CQM);
TEST(NL80211AttributeTest,U16AttributesSeriallizeCorrectly) {
NL80211Attr<uint16_t> u16_attr(1, kU16Value1);
EXPECT_EQ(u16_attr.GetValue(), kU16Value1);
}
NL80211Attr<uint32_t> rssi_thold(NL80211_ATTR_CQM_RSSI_THOLD,
kRSSIThreshold);
NL80211Attr<uint32_t> rssi_hyst(NL80211_ATTR_CQM_RSSI_HYST,
kRSSIHysteresis);
NL80211Attr<uint32_t> rssi_threshold_event(
NL80211_ATTR_CQM_RSSI_THRESHOLD_EVENT,
NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW);
cqm.AddAttribute(rssi_thold);
cqm.AddAttribute(rssi_hyst);
cqm.AddAttribute(rssi_threshold_event);
TEST(NL80211AttributeTest,U32AttributesSeriallizeCorrectly) {
NL80211Attr<uint32_t> u32_attr(1, kU32Value1);
EXPECT_EQ(u32_attr.GetValue(), kU32Value1);
}
EXPECT_EQ(cqm.GetAttributeId(), NL80211_ATTR_CQM);
EXPECT_TRUE(cqm.HasAttribute(NL80211_ATTR_CQM_RSSI_THOLD,
BaseNL80211Attr::kUInt32));
EXPECT_TRUE(cqm.HasAttribute(NL80211_ATTR_CQM_RSSI_HYST,
BaseNL80211Attr::kUInt32));
EXPECT_TRUE(cqm.HasAttribute(NL80211_ATTR_CQM_RSSI_THRESHOLD_EVENT,
BaseNL80211Attr::kUInt32));
TEST(NL80211AttributeTest,StringAttributesSeriallizeCorrectly) {
NL80211Attr<std::string> str_attr(1, kIFName);
EXPECT_EQ(str_attr.GetValue(), kIFName);
}
NL80211Attr<uint32_t> attr_u32(0, 0);
EXPECT_TRUE(cqm.GetAttribute(NL80211_ATTR_CQM_RSSI_THOLD,
BaseNL80211Attr::kUInt32,
&attr_u32));
EXPECT_EQ(attr_u32.GetValue(), kRSSIThreshold);
TEST(NL80211AttributeTest, ByteVectorsSeriallizeCorrectly) {
std::vector<uint8_t> mac_address(
kMacAddress,
kMacAddress + sizeof(kMacAddress));
NL80211Attr<std::vector<uint8_t>> byte_vector_attr(1, mac_address);
EXPECT_EQ(byte_vector_attr.GetValue(), mac_address);
}
EXPECT_TRUE(cqm.GetAttribute(NL80211_ATTR_CQM_RSSI_HYST,
BaseNL80211Attr::kUInt32,
&attr_u32));
EXPECT_EQ(attr_u32.GetValue(), kRSSIHysteresis);
TEST(NL80211AttributeTest, CanGetNestedAttributes) {
NL80211NestedAttr nested_attr(1);
NL80211Attr<uint32_t> u32_attr_1(1, kU32Value1);
NL80211Attr<uint32_t> u32_attr_2(2, kU32Value2);
EXPECT_TRUE(cqm.GetAttribute(NL80211_ATTR_CQM_RSSI_THRESHOLD_EVENT,
BaseNL80211Attr::kUInt32,
&attr_u32));
EXPECT_EQ(attr_u32.GetValue(), NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW);
nested_attr.AddAttribute(u32_attr_1);
nested_attr.AddAttribute(u32_attr_2);
EXPECT_TRUE(nested_attr.HasAttribute(1));
EXPECT_TRUE(nested_attr.HasAttribute(2));
uint32_t attr_value;
EXPECT_TRUE(nested_attr.GetAttributeValue(1, &attr_value));
EXPECT_EQ(attr_value, kU32Value1);
EXPECT_TRUE(nested_attr.GetAttributeValue(2, &attr_value));
EXPECT_EQ(attr_value, kU32Value2);
}
TEST(NL80211AttributeTest, CannotGetDoubleNestedAttributes) {
NL80211NestedAttr nested_attr(1);
NL80211NestedAttr deeper_nested_attr(2);
NL80211Attr<uint32_t> u32_attr_1(3, kU32Value1);
deeper_nested_attr.AddAttribute(u32_attr_1);
nested_attr.AddAttribute(deeper_nested_attr);
EXPECT_FALSE(nested_attr.HasAttribute(3));
}
TEST(NL80211AttributeTest, CannotGetMissingAttribute) {
NL80211NestedAttr nested_attr(1);
NL80211Attr<uint32_t> u32_attr_1(1, kU32Value1);
nested_attr.AddAttribute(u32_attr_1);
uint32_t attr_value;
EXPECT_FALSE(nested_attr.HasAttribute(2));
EXPECT_FALSE(nested_attr.GetAttributeValue(2, &attr_value));
}
TEST(NL80211AttributeTest, CannotGetAttributeWithWrongType) {
NL80211NestedAttr nested_attr(1);
NL80211Attr<uint32_t> u32_attr_1(1, kU32Value1);
nested_attr.AddAttribute(u32_attr_1);
uint16_t attr_value;
EXPECT_TRUE(nested_attr.HasAttribute(1));
EXPECT_FALSE(nested_attr.GetAttributeValue(1, &attr_value));
}
TEST(NL80211AttributeTest, InvalidU32AttributeWithEmptyBuffer) {
std::vector<uint8_t> buffer;
NL80211Attr<uint32_t> invalid_attr(buffer);
EXPECT_FALSE(invalid_attr.IsValid());
}
TEST(NL80211AttributeTest, InvalidU32AttributeWithBrokenBuffer) {
std::vector<uint8_t> buffer(
kBrokenBuffer,
kBrokenBuffer + sizeof(kBrokenBuffer));
NL80211Attr<uint32_t> invalid_attr(buffer);
EXPECT_FALSE(invalid_attr.IsValid());
}
TEST(NL80211AttributeTest, InvalidU16AttributeWithU32Buffer) {
std::vector<uint8_t> buffer(
kValidU32AttrBuffer,
kValidU32AttrBuffer + sizeof(kValidU32AttrBuffer));
NL80211Attr<uint32_t> valid_attr(buffer);
NL80211Attr<uint16_t> invalid_attr(buffer);
EXPECT_TRUE(valid_attr.IsValid());
EXPECT_FALSE(invalid_attr.IsValid());
}
TEST(NL80211AttributeTest, InitStringAttributeWithTrailingZeroFromBuffer) {
std::vector<uint8_t> buffer(
kBufferContainsStringWithTrailingZero,
kBufferContainsStringWithTrailingZero +
sizeof(kBufferContainsStringWithTrailingZero));
NL80211Attr<std::string> str_attr(buffer);
EXPECT_EQ("wlan0", str_attr.GetValue());
}
TEST(NL80211AttributeTest, InitStringAttributeWithTrailingZerosFromBuffer) {
std::vector<uint8_t> buffer(
kBufferContainsStringWithTrailingZeros,
kBufferContainsStringWithTrailingZeros +
sizeof(kBufferContainsStringWithTrailingZeros));
NL80211Attr<std::string> str_attr(buffer);
EXPECT_EQ("wlan0", str_attr.GetValue());
}
TEST(NL80211AttributeTest, InitStringAttributeWithoutTrailingZeroFromBuffer) {
std::vector<uint8_t> buffer(
kBufferContainsStringWithoutTrailingZero,
kBufferContainsStringWithoutTrailingZero +
sizeof(kBufferContainsStringWithoutTrailingZero));
NL80211Attr<std::string> str_attr(buffer);
EXPECT_EQ("wlan0", str_attr.GetValue());
}
} // namespace wificond