shill: Combines user- and kernel-bound nl80211 messages.

The UserBoundNlMessage and KernelBoundNlMessage classes are merged
into Nl80211Message.

As part of combining the two messages, the code was modified to use raw
socket writes instead of using libnl (in order to test kernel-bound
Nl80211Message writing) for sending a message.  Note, however, that
libnl code is still liberally used; there's just less use of it than
before.  Some (a little) effort was made to be consistent with the
RTNLHandler code toward combining the codebases, eventually.

BUG=chromium-os:36099, chromium-os:36638
TEST=unittests and manual tests (I added code to create an
Nl80211Message, send that message to the kernel, and display the output;
I hand inspected the output).

Change-Id: I7b04e49cee84745d1b02f1dd2b064f9e9a0aada2
Reviewed-on: https://gerrit.chromium.org/gerrit/40266
Commit-Queue: Wade Guthrie <wdg@chromium.org>
Reviewed-by: Wade Guthrie <wdg@chromium.org>
Tested-by: Wade Guthrie <wdg@chromium.org>
diff --git a/nl80211_message.cc b/nl80211_message.cc
new file mode 100644
index 0000000..3fb4826
--- /dev/null
+++ b/nl80211_message.cc
@@ -0,0 +1,1447 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This code is derived from the 'iw' source code.  The copyright and license
+// of that code is as follows:
+//
+// Copyright (c) 2007, 2008  Johannes Berg
+// Copyright (c) 2007  Andy Lutomirski
+// Copyright (c) 2007  Mike Kershaw
+// Copyright (c) 2008-2009  Luis R. Rodriguez
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+#include "shill/nl80211_message.h"
+
+#include <ctype.h>
+#include <endian.h>
+#include <errno.h>
+#include <linux/nl80211.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netlink/attr.h>
+#include <netlink/genl/ctrl.h>
+#include <netlink/genl/family.h>
+#include <netlink/genl/genl.h>
+#include <netlink/msg.h>
+#include <netlink/netlink.h>
+
+#include <iomanip>
+#include <string>
+
+#include <base/format_macros.h>
+#include <base/stl_util.h>
+#include <base/stringprintf.h>
+
+#include "shill/attribute_list.h"
+#include "shill/ieee80211.h"
+#include "shill/logging.h"
+#include "shill/netlink_socket.h"
+#include "shill/nl80211_attribute.h"
+#include "shill/scope_logger.h"
+
+using base::LazyInstance;
+using base::StringAppendF;
+using base::StringPrintf;
+using std::map;
+using std::string;
+using std::vector;
+
+namespace shill {
+
+namespace {
+LazyInstance<Nl80211MessageDataCollector> g_datacollector =
+    LAZY_INSTANCE_INITIALIZER;
+}  // namespace
+
+const char Nl80211Message::kBogusMacAddress[]="XX:XX:XX:XX:XX:XX";
+
+const uint8_t Nl80211Frame::kMinimumFrameByteCount = 26;
+const uint8_t Nl80211Frame::kFrameTypeMask = 0xfc;
+
+const uint32_t Nl80211Message::kIllegalMessage = 0;
+const unsigned int Nl80211Message::kEthernetAddressBytes = 6;
+map<uint16_t, string> *Nl80211Message::reason_code_string_ = NULL;
+map<uint16_t, string> *Nl80211Message::status_code_string_ = NULL;
+
+// The nl messages look like this:
+//
+//       XXXXXXXXXXXXXXXXXXX-nlmsg_total_size-XXXXXXXXXXXXXXXXXXX
+//       XXXXXXXXXXXXXXXXXXX-nlmsg_msg_size-XXXXXXXXXXXXXXXXXXX
+//               +-- gnhl                        nlmsg_tail(hdr) --+
+//               |                               nlmsg_next(hdr) --+
+//               v        XXXXXXXXXXXX-nlmsg_len-XXXXXXXXXXXXXX    V
+// -----+-----+-+----------------------------------------------+-++----
+//  ... |     | |                payload                       | ||
+//      |     | +------+-+--------+-+--------------------------+ ||
+//      | nl  | |      | |        | |      attribs             | ||
+//      | msg |p| genl |p| family |p+------+-+-------+-+-------+p|| ...
+//      | hdr |a| msg  |a| header |a| nl   |p| pay   |p|       |a||
+//      |     |d| hdr  |d|        |d| attr |a| load  |a| ...   |d||
+//      |     | |      | |        | |      |d|       |d|       | ||
+// -----+-----+-+----------------------------------------------+-++----
+//       ^       ^                   ^        ^
+//       |       |                   |        XXXXXXX <-- nla_len(nlattr)
+//       |       |                   |        +-- nla_data(nlattr)
+//       |       |                   X-nla_total_size-X
+//       |       |                   XXXXX-nlmsg_attrlen-XXXXXX
+//       |       +-- nlmsg_data(hdr) +-- nlmsg_attrdata()
+//       +-- msg = nlmsg_hdr(raw_message)
+
+//
+// Nl80211Message
+//
+
+// TODO(wdg): do the nlattr parsing inside here.
+bool Nl80211Message::Init(nlattr *tb[NL80211_ATTR_MAX + 1],
+                          nlmsghdr *msg) {
+  if (!tb) {
+    LOG(ERROR) << "Null |tb| parameter";
+    return false;
+  }
+
+  message_ = msg;
+
+  SLOG(WiFi, 6) << "NL Message " << sequence_number() << " <===";
+
+  for (int i = 0; i < NL80211_ATTR_MAX + 1; ++i) {
+    if (tb[i]) {
+      attributes_.CreateAndInitFromNlAttr(static_cast<nl80211_attrs>(i), tb[i]);
+    }
+  }
+
+  // Convert integer values provided by libnl (for example, from the
+  // NL80211_ATTR_STATUS_CODE or NL80211_ATTR_REASON_CODE attribute) into
+  // strings describing the status.
+  if (!reason_code_string_) {
+    reason_code_string_ = new map<uint16_t, string>;
+    (*reason_code_string_)[IEEE_80211::kReasonCodeUnspecified] =
+        "Unspecified reason";
+    (*reason_code_string_)[
+        IEEE_80211::kReasonCodePreviousAuthenticationInvalid] =
+        "Previous authentication no longer valid";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeSenderHasLeft] =
+        "Deauthentcated because sending STA is leaving (or has left) IBSS or "
+        "ESS";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeInactivity] =
+        "Disassociated due to inactivity";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeTooManySTAs] =
+        "Disassociated because AP is unable to handle all currently associated "
+        "STAs";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeNonAuthenticated] =
+        "Class 2 frame received from nonauthenticated STA";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeNonAssociated] =
+        "Class 3 frame received from nonassociated STA";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeDisassociatedHasLeft] =
+        "Disassociated because sending STA is leaving (or has left) BSS";
+    (*reason_code_string_)[
+        IEEE_80211::kReasonCodeReassociationNotAuthenticated] =
+        "STA requesting (re)association is not authenticated with responding "
+        "STA";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeUnacceptablePowerCapability] =
+        "Disassociated because the information in the Power Capability "
+        "element is unacceptable";
+    (*reason_code_string_)[
+        IEEE_80211::kReasonCodeUnacceptableSupportedChannelInfo] =
+        "Disassociated because the information in the Supported Channels "
+        "element is unacceptable";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeInvalidInfoElement] =
+        "Invalid information element, i.e., an information element defined in "
+        "this standard for which the content does not meet the specifications "
+        "in Clause 7";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeMICFailure] =
+        "Message integrity code (MIC) failure";
+    (*reason_code_string_)[IEEE_80211::kReasonCode4WayTimeout] =
+        "4-Way Handshake timeout";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeGroupKeyHandshakeTimeout] =
+        "Group Key Handshake timeout";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeDifferenIE] =
+        "Information element in 4-Way Handshake different from "
+        "(Re)Association Request/Probe Response/Beacon frame";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeGroupCipherInvalid] =
+        "Invalid group cipher";
+    (*reason_code_string_)[IEEE_80211::kReasonCodePairwiseCipherInvalid] =
+        "Invalid pairwise cipher";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeAkmpInvalid] =
+        "Invalid AKMP";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeUnsupportedRsnIeVersion] =
+        "Unsupported RSN information element version";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeInvalidRsnIeCaps] =
+        "Invalid RSN information element capabilities";
+    (*reason_code_string_)[IEEE_80211::kReasonCode8021XAuth] =
+        "IEEE 802.1X authentication failed";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeCipherSuiteRejected] =
+        "Cipher suite rejected because of the security policy";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeUnspecifiedQoS] =
+        "Disassociated for unspecified, QoS-related reason";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeQoSBandwidth] =
+        "Disassociated because QoS AP lacks sufficient bandwidth for this "
+        "QoS STA";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeiPoorConditions] =
+        "Disassociated because excessive number of frames need to be "
+        "acknowledged, but are not acknowledged due to AP transmissions "
+        "and/or poor channel conditions";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeOutsideTxop] =
+        "Disassociated because STA is transmitting outside the limits of its "
+        "TXOPs";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeStaLeaving] =
+        "Requested from peer STA as the STA is leaving the BSS (or resetting)";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeUnacceptableMechanism] =
+        "Requested from peer STA as it does not want to use the mechanism";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeSetupRequired] =
+        "Requested from peer STA as the STA received frames using the "
+        "mechanism for which a setup is required";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeTimeout] =
+        "Requested from peer STA due to timeout";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeCipherSuiteNotSupported] =
+        "Peer STA does not support the requested cipher suite";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeInvalid] = "<INVALID REASON>";
+  }
+
+  if (!status_code_string_) {
+    status_code_string_ = new map<uint16_t, string>;
+    (*status_code_string_)[IEEE_80211::kStatusCodeSuccessful] = "Successful";
+    (*status_code_string_)[IEEE_80211::kStatusCodeFailure] =
+        "Unspecified failure";
+    (*status_code_string_)[IEEE_80211::kStatusCodeAllCapabilitiesNotSupported] =
+        "Cannot support all requested capabilities in the capability "
+        "information field";
+    (*status_code_string_)[IEEE_80211::kStatusCodeCantConfirmAssociation] =
+        "Reassociation denied due to inability to confirm that association "
+        "exists";
+    (*status_code_string_)[IEEE_80211::kStatusCodeAssociationDenied] =
+        "Association denied due to reason outside the scope of this standard";
+    (*status_code_string_)[
+        IEEE_80211::kStatusCodeAuthenticationUnsupported] =
+        "Responding station does not support the specified authentication "
+        "algorithm";
+    (*status_code_string_)[IEEE_80211::kStatusCodeOutOfSequence] =
+        "Received an authentication frame with authentication transaction "
+        "sequence number out of expected sequence";
+    (*status_code_string_)[IEEE_80211::kStatusCodeChallengeFailure] =
+        "Authentication rejected because of challenge failure";
+    (*status_code_string_)[IEEE_80211::kStatusCodeFrameTimeout] =
+        "Authentication rejected due to timeout waiting for next frame in "
+        "sequence";
+    (*status_code_string_)[IEEE_80211::kStatusCodeMaxSta] =
+        "Association denied because AP is unable to handle additional "
+        "associated STA";
+    (*status_code_string_)[IEEE_80211::kStatusCodeDataRateUnsupported] =
+        "Association denied due to requesting station not supporting all of "
+        "the data rates in the BSSBasicRateSet parameter";
+    (*status_code_string_)[IEEE_80211::kStatusCodeShortPreambleUnsupported] =
+        "Association denied due to requesting station not supporting the "
+        "short preamble option";
+    (*status_code_string_)[IEEE_80211::kStatusCodePbccUnsupported] =
+        "Association denied due to requesting station not supporting the PBCC "
+        "modulation option";
+    (*status_code_string_)[
+        IEEE_80211::kStatusCodeChannelAgilityUnsupported] =
+        "Association denied due to requesting station not supporting the "
+        "channel agility option";
+    (*status_code_string_)[IEEE_80211::kStatusCodeNeedSpectrumManagement] =
+        "Association request rejected because Spectrum Management capability "
+        "is required";
+    (*status_code_string_)[
+        IEEE_80211::kStatusCodeUnacceptablePowerCapability] =
+        "Association request rejected because the information in the Power "
+        "Capability element is unacceptable";
+    (*status_code_string_)[
+        IEEE_80211::kStatusCodeUnacceptableSupportedChannelInfo] =
+        "Association request rejected because the information in the "
+        "Supported Channels element is unacceptable";
+    (*status_code_string_)[IEEE_80211::kStatusCodeShortTimeSlotRequired] =
+        "Association request rejected due to requesting station not "
+        "supporting the Short Slot Time option";
+    (*status_code_string_)[IEEE_80211::kStatusCodeDssOfdmRequired] =
+        "Association request rejected due to requesting station not "
+        "supporting the DSSS-OFDM option";
+    (*status_code_string_)[IEEE_80211::kStatusCodeQosFailure] =
+        "Unspecified, QoS related failure";
+    (*status_code_string_)[
+        IEEE_80211::kStatusCodeInsufficientBandwithForQsta] =
+        "Association denied due to QAP having insufficient bandwidth to handle "
+        "another QSTA";
+    (*status_code_string_)[IEEE_80211::kStatusCodePoorConditions] =
+        "Association denied due to poor channel conditions";
+    (*status_code_string_)[IEEE_80211::kStatusCodeQosNotSupported] =
+        "Association (with QoS BSS) denied due to requesting station not "
+        "supporting the QoS facility";
+    (*status_code_string_)[IEEE_80211::kStatusCodeDeclined] =
+        "The request has been declined";
+    (*status_code_string_)[IEEE_80211::kStatusCodeInvalidParameterValues] =
+        "The request has not been successful as one or more parameters have "
+        "invalid values";
+    (*status_code_string_)[IEEE_80211::kStatusCodeCannotBeHonored] =
+        "The TS has not been created because the request cannot be honored. "
+        "However, a suggested Tspec is provided so that the initiating QSTA "
+        "may attempt to send another TS with the suggested changes to the "
+        "TSpec";
+    (*status_code_string_)[IEEE_80211::kStatusCodeInvalidInfoElement] =
+        "Invalid Information Element";
+    (*status_code_string_)[IEEE_80211::kStatusCodeGroupCipherInvalid] =
+        "Invalid Group Cipher";
+    (*status_code_string_)[IEEE_80211::kStatusCodePairwiseCipherInvalid] =
+        "Invalid Pairwise Cipher";
+    (*status_code_string_)[IEEE_80211::kStatusCodeAkmpInvalid] = "Invalid AKMP";
+    (*status_code_string_)[IEEE_80211::kStatusCodeUnsupportedRsnIeVersion] =
+        "Unsupported RSN Information Element version";
+    (*status_code_string_)[IEEE_80211::kStatusCodeInvalidRsnIeCaps] =
+        "Invalid RSN Information Element Capabilities";
+    (*status_code_string_)[IEEE_80211::kStatusCodeCipherSuiteRejected] =
+        "Cipher suite is rejected per security policy";
+    (*status_code_string_)[IEEE_80211::kStatusCodeTsDelayNotMet] =
+        "The TS has not been created. However, the HC may be capable of "
+        "creating a TS, in response to a request, after the time indicated in "
+        "the TS Delay element";
+    (*status_code_string_)[IEEE_80211::kStatusCodeDirectLinkIllegal] =
+        "Direct link is not allowed in the BSS by policy";
+    (*status_code_string_)[IEEE_80211::kStatusCodeStaNotInBss] =
+        "Destination STA is not present within this BSS";
+    (*status_code_string_)[IEEE_80211::kStatusCodeStaNotInQsta] =
+        "The destination STA is not a QoS STA";
+    (*status_code_string_)[IEEE_80211::kStatusCodeExcessiveListenInterval] =
+        "Association denied because Listen Interval is too large";
+    (*status_code_string_)[IEEE_80211::kStatusCodeInvalid] = "<INVALID STATUS>";
+  }
+
+  return true;
+}
+
+// Helper function to provide a string for a MAC address.
+bool Nl80211Message::GetMacAttributeString(nl80211_attrs id,
+                                           string *value) const {
+  if (!value) {
+    LOG(ERROR) << "Null |value| parameter";
+    return false;
+  }
+
+  ByteString data;
+  if (!attributes().GetRawAttributeValue(id, &data)) {
+    value->assign(kBogusMacAddress);
+    return false;
+  }
+  value->assign(StringFromMacAddress(data.GetConstData()));
+
+  return true;
+}
+
+// Helper function to provide a string for NL80211_ATTR_SCAN_FREQUENCIES.
+bool Nl80211Message::GetScanFrequenciesAttribute(
+    nl80211_attrs id, vector<uint32_t> *value) const {
+  if (!value) {
+    LOG(ERROR) << "Null |value| parameter";
+    return false;
+  }
+
+  value->clear();
+  ByteString rawdata;
+  if (!attributes().GetRawAttributeValue(id, &rawdata) && !rawdata.IsEmpty())
+    return false;
+
+  nlattr *nst = NULL;
+  // |nla_for_each_attr| requires a non-const parameter even though it
+  // doesn't change the data.
+  nlattr *attr_data = reinterpret_cast<nlattr *>(rawdata.GetData());
+  int rem_nst;
+  int len = rawdata.GetLength();
+
+  nla_for_each_attr(nst, attr_data, len, rem_nst) {
+    value->push_back(Nl80211Attribute::NlaGetU32(nst));
+  }
+  return true;
+}
+
+// Helper function to provide a string for NL80211_ATTR_SCAN_SSIDS.
+bool Nl80211Message::GetScanSsidsAttribute(
+    nl80211_attrs id, vector<string> *value) const {
+  if (!value) {
+    LOG(ERROR) << "Null |value| parameter";
+    return false;
+  }
+
+  ByteString rawdata;
+  if (!attributes().GetRawAttributeValue(id, &rawdata) || rawdata.IsEmpty())
+    return false;
+
+  nlattr *nst = NULL;
+  // |nla_for_each_attr| requires a non-const parameter even though it
+  // doesn't change the data.
+  nlattr *data = reinterpret_cast<nlattr *>(rawdata.GetData());
+  int rem_nst;
+  int len = rawdata.GetLength();
+
+  nla_for_each_attr(nst, data, len, rem_nst) {
+    value->push_back(StringFromSsid(nla_len(nst),
+                                    reinterpret_cast<const uint8_t *>(
+                                      nla_data(nst))).c_str());
+  }
+  return true;
+}
+
+// Protected members.
+
+string Nl80211Message::GetHeaderString() const {
+  char ifname[IF_NAMESIZE] = "";
+  uint32_t ifindex = UINT32_MAX;
+  bool ifindex_exists = attributes().GetU32AttributeValue(NL80211_ATTR_IFINDEX,
+                                                          &ifindex);
+
+  uint32_t wifi = UINT32_MAX;
+  bool wifi_exists = attributes().GetU32AttributeValue(NL80211_ATTR_WIPHY,
+                                                       &wifi);
+
+  string output;
+  if (ifindex_exists && wifi_exists) {
+    StringAppendF(&output, "%s (phy #%" PRIu32 "): ",
+                  (if_indextoname(ifindex, ifname) ? ifname : "<unknown>"),
+                  wifi);
+  } else if (ifindex_exists) {
+    StringAppendF(&output, "%s: ",
+                  (if_indextoname(ifindex, ifname) ? ifname : "<unknown>"));
+  } else if (wifi_exists) {
+    StringAppendF(&output, "phy #%" PRIu32 "u: ", wifi);
+  }
+
+  return output;
+}
+
+string Nl80211Message::StringFromFrame(nl80211_attrs attr_name) const {
+  string output;
+  ByteString frame_data;
+  if (attributes().GetRawAttributeValue(attr_name,
+                                        &frame_data) && !frame_data.IsEmpty()) {
+    Nl80211Frame frame(frame_data);
+    frame.ToString(&output);
+  } else {
+    output.append(" [no frame]");
+  }
+
+  return output;
+}
+
+// static
+string Nl80211Message::StringFromKeyType(nl80211_key_type key_type) {
+  switch (key_type) {
+  case NL80211_KEYTYPE_GROUP:
+    return "Group";
+  case NL80211_KEYTYPE_PAIRWISE:
+    return "Pairwise";
+  case NL80211_KEYTYPE_PEERKEY:
+    return "PeerKey";
+  default:
+    return "<Unknown Key Type>";
+  }
+}
+
+// static
+string Nl80211Message::StringFromMacAddress(const uint8_t *arg) {
+  string output;
+
+  if (!arg) {
+    output = kBogusMacAddress;
+    LOG(ERROR) << "|arg| parameter is NULL.";
+  } else {
+    StringAppendF(&output, "%02x", arg[0]);
+
+    for (unsigned int i = 1; i < kEthernetAddressBytes ; ++i) {
+      StringAppendF(&output, ":%02x", arg[i]);
+    }
+  }
+  return output;
+}
+
+// static
+string Nl80211Message::StringFromRegInitiator(__u8 initiator) {
+  switch (initiator) {
+  case NL80211_REGDOM_SET_BY_CORE:
+    return "the wireless core upon initialization";
+  case NL80211_REGDOM_SET_BY_USER:
+    return "a user";
+  case NL80211_REGDOM_SET_BY_DRIVER:
+    return "a driver";
+  case NL80211_REGDOM_SET_BY_COUNTRY_IE:
+    return "a country IE";
+  default:
+    return "<Unknown Reg Initiator>";
+  }
+}
+
+// static
+string Nl80211Message::StringFromSsid(const uint8_t len,
+                                          const uint8_t *data) {
+  string output;
+  if (!data) {
+    StringAppendF(&output, "<Error from %s, NULL parameter>", __func__);
+    LOG(ERROR) << "|data| parameter is NULL.";
+    return output;
+  }
+
+  for (int i = 0; i < len; ++i) {
+    if (data[i] == ' ')
+      output.append(" ");
+    else if (isprint(data[i]))
+      StringAppendF(&output, "%c", static_cast<char>(data[i]));
+    else
+      StringAppendF(&output, "\\x%2x", data[i]);
+  }
+
+  return output;
+}
+
+// static
+string Nl80211Message::StringFromReason(uint16_t status) {
+  map<uint16_t, string>::const_iterator match;
+  match = reason_code_string_->find(status);
+  if (match == reason_code_string_->end()) {
+    string output;
+    if (status < IEEE_80211::kReasonCodeMax) {
+      StringAppendF(&output, "<Reserved Reason:%u>", status);
+    } else {
+      StringAppendF(&output, "<Unknown Reason:%u>", status);
+    }
+    return output;
+  }
+  return match->second;
+}
+
+// static
+string Nl80211Message::StringFromStatus(uint16_t status) {
+  map<uint16_t, string>::const_iterator match;
+  match = status_code_string_->find(status);
+  if (match == status_code_string_->end()) {
+    string output;
+    if (status < IEEE_80211::kStatusCodeMax) {
+      StringAppendF(&output, "<Reserved Status:%u>", status);
+    } else {
+      StringAppendF(&output, "<Unknown Status:%u>", status);
+    }
+    return output;
+  }
+  return match->second;
+}
+
+string Nl80211Message::GenericToString() const {
+  string output;
+  StringAppendF(&output, "Message %s (%d)\n",
+                message_type_string(), message_type());
+  StringAppendF(&output, "%s", attributes_.ToString().c_str());
+  return output;
+}
+
+ByteString Nl80211Message::Encode(uint16_t nlmsg_type) const {
+  // Build netlink header.
+  nlmsghdr header;
+  size_t nlmsghdr_with_pad = NLMSG_ALIGN(sizeof(header));
+  header.nlmsg_len = nlmsghdr_with_pad;
+  header.nlmsg_type = nlmsg_type;
+  header.nlmsg_flags = NLM_F_REQUEST;
+  header.nlmsg_seq = sequence_number();
+  header.nlmsg_pid = getpid();
+
+  // Build genl message header.
+  genlmsghdr genl_header;
+  size_t genlmsghdr_with_pad = NLMSG_ALIGN(sizeof(genl_header));
+  header.nlmsg_len += genlmsghdr_with_pad;
+  genl_header.cmd = message_type();
+  genl_header.version = 1;
+  genl_header.reserved = 0;
+
+  // Assemble attributes (padding is included by AttributeList::Encode).
+  ByteString attributes = attributes_.Encode();
+  header.nlmsg_len += attributes.GetLength();
+
+  // Now that we know the total message size, build the output ByteString.
+  ByteString result;
+
+  // Netlink header + pad.
+  result.Append(ByteString(reinterpret_cast<unsigned char *>(&header),
+                           sizeof(header)));
+  result.Resize(nlmsghdr_with_pad);  // Zero-fill pad space (if any).
+
+  // Genl message header + pad.
+  result.Append(ByteString(reinterpret_cast<unsigned char *>(&genl_header),
+                           sizeof(genl_header)));
+  result.Resize(nlmsghdr_with_pad + genlmsghdr_with_pad);  // Zero-fill.
+
+  // Attributes including pad.
+  result.Append(attributes);
+
+  return result;
+}
+
+Nl80211Frame::Nl80211Frame(const ByteString &raw_frame)
+  : frame_type_(kIllegalFrameType), reason_(UINT16_MAX), status_(UINT16_MAX),
+    frame_(raw_frame) {
+  const IEEE_80211::ieee80211_frame *frame =
+      reinterpret_cast<const IEEE_80211::ieee80211_frame *>(
+          frame_.GetConstData());
+
+  // Now, let's populate the other stuff.
+  if (frame_.GetLength() >= kMinimumFrameByteCount) {
+    mac_from_ =
+        Nl80211Message::StringFromMacAddress(&frame->destination_mac[0]);
+    mac_to_ = Nl80211Message::StringFromMacAddress(&frame->source_mac[0]);
+    frame_type_ = frame->frame_control & kFrameTypeMask;
+
+    switch (frame_type_) {
+    case kAssocResponseFrameType:
+    case kReassocResponseFrameType:
+      status_ = le16toh(frame->u.associate_response.status_code);
+      break;
+
+    case kAuthFrameType:
+      status_ = le16toh(frame->u.authentiate_message.status_code);
+      break;
+
+    case kDisassocFrameType:
+    case kDeauthFrameType:
+      reason_ = le16toh(frame->u.deauthentiate_message.reason_code);
+      break;
+
+    default:
+      break;
+    }
+  }
+}
+
+bool Nl80211Frame::ToString(string *output) const {
+  if (!output) {
+    LOG(ERROR) << "NULL |output|";
+    return false;
+  }
+
+  if (frame_.IsEmpty()) {
+    output->append(" [no frame]");
+    return true;
+  }
+
+  if (frame_.GetLength() < kMinimumFrameByteCount) {
+    output->append(" [invalid frame: ");
+  } else {
+    StringAppendF(output, " %s -> %s", mac_from_.c_str(), mac_to_.c_str());
+
+    switch (frame_.GetConstData()[0] & kFrameTypeMask) {
+    case kAssocResponseFrameType:
+      StringAppendF(output, "; AssocResponse status: %u: %s",
+                    status_,
+                    Nl80211Message::StringFromStatus(status_).c_str());
+      break;
+    case kReassocResponseFrameType:
+      StringAppendF(output, "; ReassocResponse status: %u: %s",
+                    status_,
+                    Nl80211Message::StringFromStatus(status_).c_str());
+      break;
+    case kAuthFrameType:
+      StringAppendF(output, "; Auth status: %u: %s",
+                    status_,
+                    Nl80211Message::StringFromStatus(status_).c_str());
+      break;
+
+    case kDisassocFrameType:
+      StringAppendF(output, "; Disassoc reason %u: %s",
+                    reason_,
+                    Nl80211Message::StringFromReason(reason_).c_str());
+      break;
+    case kDeauthFrameType:
+      StringAppendF(output, "; Deauth reason %u: %s",
+                    reason_,
+                    Nl80211Message::StringFromReason(reason_).c_str());
+      break;
+
+    default:
+      break;
+    }
+    output->append(" [frame: ");
+  }
+
+  const unsigned char *frame = frame_.GetConstData();
+  for (size_t i = 0; i < frame_.GetLength(); ++i) {
+    StringAppendF(output, "%02x, ", frame[i]);
+  }
+  output->append("]");
+
+  return true;
+}
+
+bool Nl80211Frame::IsEqual(const Nl80211Frame &other) const {
+  return frame_.Equals(other.frame_);
+}
+
+
+//
+// Specific Nl80211Message types.
+//
+
+const uint8_t AssociateMessage::kCommand = NL80211_CMD_ASSOCIATE;
+const char AssociateMessage::kCommandString[] = "NL80211_CMD_ASSOCIATE";
+
+string AssociateMessage::ToString() const {
+  string output(GetHeaderString());
+  output.append("assoc");
+  if (attributes().GetRawAttributeValue(NL80211_ATTR_FRAME, NULL))
+    output.append(StringFromFrame(NL80211_ATTR_FRAME));
+  else if (attributes().IsFlagAttributeTrue(NL80211_ATTR_TIMED_OUT))
+    output.append(": timed out");
+  else
+    output.append(": unknown event");
+  return output;
+}
+
+const uint8_t AuthenticateMessage::kCommand = NL80211_CMD_AUTHENTICATE;
+const char AuthenticateMessage::kCommandString[] = "NL80211_CMD_AUTHENTICATE";
+
+string AuthenticateMessage::ToString() const {
+  string output(GetHeaderString());
+  output.append("auth");
+  if (attributes().GetRawAttributeValue(NL80211_ATTR_FRAME, NULL)) {
+    output.append(StringFromFrame(NL80211_ATTR_FRAME));
+  } else {
+    output.append(attributes().IsFlagAttributeTrue(NL80211_ATTR_TIMED_OUT) ?
+                  ": timed out" : ": unknown event");
+  }
+  return output;
+}
+
+const uint8_t CancelRemainOnChannelMessage::kCommand =
+  NL80211_CMD_CANCEL_REMAIN_ON_CHANNEL;
+const char CancelRemainOnChannelMessage::kCommandString[] =
+  "NL80211_CMD_CANCEL_REMAIN_ON_CHANNEL";
+
+string CancelRemainOnChannelMessage::ToString() const {
+  string output(GetHeaderString());
+  uint32_t freq;
+  uint64_t cookie;
+  StringAppendF(&output,
+                "done with remain on freq %" PRIu32 " (cookie %" PRIx64 ")",
+                (attributes().GetU32AttributeValue(NL80211_ATTR_WIPHY_FREQ,
+                                                   &freq) ?  0 : freq),
+                (attributes().GetU64AttributeValue(NL80211_ATTR_COOKIE,
+                                                   &cookie) ?  0 : cookie));
+  return output;
+}
+
+const uint8_t ConnectMessage::kCommand = NL80211_CMD_CONNECT;
+const char ConnectMessage::kCommandString[] = "NL80211_CMD_CONNECT";
+
+string ConnectMessage::ToString() const {
+  string output(GetHeaderString());
+
+  uint16_t status = UINT16_MAX;
+
+  if (!attributes().GetU16AttributeValue(NL80211_ATTR_STATUS_CODE, &status)) {
+    output.append("unknown connect status");
+  } else if (status == 0) {
+    output.append("connected");
+  } else {
+    output.append("failed to connect");
+  }
+
+  if (attributes().GetRawAttributeValue(NL80211_ATTR_MAC, NULL)) {
+    string mac;
+    GetMacAttributeString(NL80211_ATTR_MAC, &mac);
+    StringAppendF(&output, " to %s", mac.c_str());
+  }
+  if (status)
+    StringAppendF(&output, ", status: %u: %s", status,
+                  StringFromStatus(status).c_str());
+  return output;
+}
+
+const uint8_t DeauthenticateMessage::kCommand = NL80211_CMD_DEAUTHENTICATE;
+const char DeauthenticateMessage::kCommandString[] =
+    "NL80211_CMD_DEAUTHENTICATE";
+
+string DeauthenticateMessage::ToString() const {
+  string output(GetHeaderString());
+  StringAppendF(&output, "deauth%s",
+                StringFromFrame(NL80211_ATTR_FRAME).c_str());
+  return output;
+}
+
+const uint8_t DeleteStationMessage::kCommand = NL80211_CMD_DEL_STATION;
+const char DeleteStationMessage::kCommandString[] = "NL80211_CMD_DEL_STATION";
+
+string DeleteStationMessage::ToString() const {
+  string mac;
+  GetMacAttributeString(NL80211_ATTR_MAC, &mac);
+  string output(GetHeaderString());
+  StringAppendF(&output, "del station %s", mac.c_str());
+  return output;
+}
+
+const uint8_t DisassociateMessage::kCommand = NL80211_CMD_DISASSOCIATE;
+const char DisassociateMessage::kCommandString[] = "NL80211_CMD_DISASSOCIATE";
+
+string DisassociateMessage::ToString() const {
+  string output(GetHeaderString());
+  StringAppendF(&output, "disassoc%s",
+                StringFromFrame(NL80211_ATTR_FRAME).c_str());
+  return output;
+}
+
+const uint8_t DisconnectMessage::kCommand = NL80211_CMD_DISCONNECT;
+const char DisconnectMessage::kCommandString[] = "NL80211_CMD_DISCONNECT";
+
+string DisconnectMessage::ToString() const {
+  string output(GetHeaderString());
+  StringAppendF(&output, "disconnected %s",
+                ((attributes().IsFlagAttributeTrue(
+                    NL80211_ATTR_DISCONNECTED_BY_AP)) ?
+                 "(by AP)" : "(local request)"));
+
+  uint16_t reason = UINT16_MAX;
+  if (attributes().GetU16AttributeValue(NL80211_ATTR_REASON_CODE, &reason)) {
+    StringAppendF(&output, " reason: %u: %s",
+                  reason, StringFromReason(reason).c_str());
+  }
+  return output;
+}
+
+const uint8_t FrameTxStatusMessage::kCommand = NL80211_CMD_FRAME_TX_STATUS;
+const char FrameTxStatusMessage::kCommandString[] =
+    "NL80211_CMD_FRAME_TX_STATUS";
+
+string FrameTxStatusMessage::ToString() const {
+  string output(GetHeaderString());
+  uint64_t cookie = UINT64_MAX;
+  attributes().GetU64AttributeValue(NL80211_ATTR_COOKIE, &cookie);
+
+  StringAppendF(&output, "mgmt TX status (cookie %" PRIx64 "): %s", cookie,
+                (attributes().IsFlagAttributeTrue(NL80211_ATTR_ACK) ?
+                 "acked" : "no ack"));
+  return output;
+}
+
+const uint8_t JoinIbssMessage::kCommand = NL80211_CMD_JOIN_IBSS;
+const char JoinIbssMessage::kCommandString[] = "NL80211_CMD_JOIN_IBSS";
+
+string JoinIbssMessage::ToString() const {
+  string mac;
+  GetMacAttributeString(NL80211_ATTR_MAC, &mac);
+  string output(GetHeaderString());
+  StringAppendF(&output, "IBSS %s joined", mac.c_str());
+  return output;
+}
+
+const uint8_t MichaelMicFailureMessage::kCommand =
+    NL80211_CMD_MICHAEL_MIC_FAILURE;
+const char MichaelMicFailureMessage::kCommandString[] =
+    "NL80211_CMD_MICHAEL_MIC_FAILURE";
+
+string MichaelMicFailureMessage::ToString() const {
+  string output(GetHeaderString());
+
+  output.append("Michael MIC failure event:");
+
+  if (attributes().GetRawAttributeValue(NL80211_ATTR_MAC, NULL)) {
+    string mac;
+    GetMacAttributeString(NL80211_ATTR_MAC, &mac);
+    StringAppendF(&output, " source MAC address %s", mac.c_str());
+  }
+
+  {
+    ByteString rawdata;
+    if (attributes().GetRawAttributeValue(NL80211_ATTR_KEY_SEQ,
+                                          &rawdata) &&
+        rawdata.GetLength() == Nl80211Message::kEthernetAddressBytes) {
+      const unsigned char *seq = rawdata.GetConstData();
+      StringAppendF(&output, " seq=%02x%02x%02x%02x%02x%02x",
+                    seq[0], seq[1], seq[2], seq[3], seq[4], seq[5]);
+    }
+  }
+  uint32_t key_type_val = UINT32_MAX;
+  if (attributes().GetU32AttributeValue(NL80211_ATTR_KEY_TYPE, &key_type_val)) {
+    nl80211_key_type key_type = static_cast<nl80211_key_type >(key_type_val);
+    StringAppendF(&output, " Key Type %s", StringFromKeyType(key_type).c_str());
+  }
+
+  uint8_t key_index = UINT8_MAX;
+  if (attributes().GetU8AttributeValue(NL80211_ATTR_KEY_IDX, &key_index)) {
+    StringAppendF(&output, " Key Id %u", key_index);
+  }
+
+  return output;
+}
+
+const uint8_t NewScanResultsMessage::kCommand = NL80211_CMD_NEW_SCAN_RESULTS;
+const char NewScanResultsMessage::kCommandString[] =
+    "NL80211_CMD_NEW_SCAN_RESULTS";
+
+string NewScanResultsMessage::ToString() const {
+  string output(GetHeaderString());
+  output.append("scan finished");
+
+  {
+    output.append("; frequencies: ");
+    vector<uint32_t> list;
+    if (GetScanFrequenciesAttribute(NL80211_ATTR_SCAN_FREQUENCIES, &list)) {
+      string str;
+      for (vector<uint32_t>::const_iterator i = list.begin();
+             i != list.end(); ++i) {
+        StringAppendF(&str, " %" PRIu32 ", ", *i);
+      }
+      output.append(str);
+    }
+  }
+
+  {
+    output.append("; SSIDs: ");
+    vector<string> list;
+    if (GetScanSsidsAttribute(NL80211_ATTR_SCAN_SSIDS, &list)) {
+      string str;
+      for (vector<string>::const_iterator i = list.begin();
+           i != list.end(); ++i) {
+        StringAppendF(&str, "\"%s\", ", i->c_str());
+      }
+      output.append(str);
+    }
+  }
+
+  return output;
+}
+
+const uint8_t NewStationMessage::kCommand = NL80211_CMD_NEW_STATION;
+const char NewStationMessage::kCommandString[] = "NL80211_CMD_NEW_STATION";
+
+string NewStationMessage::ToString() const {
+  string mac;
+  GetMacAttributeString(NL80211_ATTR_MAC, &mac);
+  string output(GetHeaderString());
+  StringAppendF(&output, "new station %s", mac.c_str());
+
+  return output;
+}
+
+const uint8_t NewWifiMessage::kCommand = NL80211_CMD_NEW_WIPHY;
+const char NewWifiMessage::kCommandString[] = "NL80211_CMD_NEW_WIPHY";
+
+string NewWifiMessage::ToString() const {
+  string output(GetHeaderString());
+  string wifi_name = "None";
+  attributes().GetStringAttributeValue(NL80211_ATTR_WIPHY_NAME, &wifi_name);
+  StringAppendF(&output, "renamed to %s", wifi_name.c_str());
+  return output;
+}
+
+const uint8_t NotifyCqmMessage::kCommand = NL80211_CMD_NOTIFY_CQM;
+const char NotifyCqmMessage::kCommandString[] = "NL80211_CMD_NOTIFY_CQM";
+
+string NotifyCqmMessage::ToString() const {
+  // TODO(wdg): use attributes().GetNestedAttributeValue()...
+  static const nla_policy kCqmValidationPolicy[NL80211_ATTR_CQM_MAX + 1] = {
+    { NLA_U32, 0, 0 },  // Who Knows?
+    { NLA_U32, 0, 0 },  // [NL80211_ATTR_CQM_RSSI_THOLD]
+    { NLA_U32, 0, 0 },  // [NL80211_ATTR_CQM_RSSI_HYST]
+    { NLA_U32, 0, 0 },  // [NL80211_ATTR_CQM_RSSI_THRESHOLD_EVENT]
+  };
+
+  string output(GetHeaderString());
+  output.append("connection quality monitor event: ");
+
+  const Nl80211RawAttribute *attribute =
+      attributes().GetRawAttribute(NL80211_ATTR_CQM);
+  if (!attribute) {
+    output.append("missing data!");
+    return output;
+  }
+
+  const nlattr *const_data = attribute->data();
+  // Note that |nla_parse_nested| doesn't change |const_data| but doesn't
+  // declare itself as 'const', either.  Hence the cast.
+  nlattr *cqm_attr = const_cast<nlattr *>(const_data);
+
+  nlattr *cqm[NL80211_ATTR_CQM_MAX + 1];
+  if (!cqm_attr ||
+      nla_parse_nested(cqm, NL80211_ATTR_CQM_MAX, cqm_attr,
+                       const_cast<nla_policy *>(kCqmValidationPolicy))) {
+    output.append("missing data!");
+    return output;
+  }
+  if (cqm[NL80211_ATTR_CQM_RSSI_THRESHOLD_EVENT]) {
+    nl80211_cqm_rssi_threshold_event rssi_event =
+        static_cast<nl80211_cqm_rssi_threshold_event>(
+          Nl80211Attribute::NlaGetU32(
+              cqm[NL80211_ATTR_CQM_RSSI_THRESHOLD_EVENT]));
+    if (rssi_event == NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH)
+      output.append("RSSI went above threshold");
+    else
+      output.append("RSSI went below threshold");
+  } else if (cqm[NL80211_ATTR_CQM_PKT_LOSS_EVENT] &&
+       attributes().GetRawAttributeValue(NL80211_ATTR_MAC, NULL)) {
+    string mac;
+    GetMacAttributeString(NL80211_ATTR_MAC, &mac);
+    StringAppendF(&output, "peer %s didn't ACK %" PRIu32 " packets",
+                  mac.c_str(),
+                  Nl80211Attribute::NlaGetU32(
+                      cqm[NL80211_ATTR_CQM_PKT_LOSS_EVENT]));
+  } else {
+    output.append("unknown event");
+  }
+  return output;
+}
+
+const uint8_t PmksaCandidateMessage::kCommand = NL80211_ATTR_PMKSA_CANDIDATE;
+const char PmksaCandidateMessage::kCommandString[] =
+  "NL80211_ATTR_PMKSA_CANDIDATE";
+
+string PmksaCandidateMessage::ToString() const {
+  string output(GetHeaderString());
+  output.append("PMKSA candidate found");
+  return output;
+}
+
+const uint8_t RegBeaconHintMessage::kCommand = NL80211_CMD_REG_BEACON_HINT;
+const char RegBeaconHintMessage::kCommandString[] =
+    "NL80211_CMD_REG_BEACON_HINT";
+
+string RegBeaconHintMessage::ToString() const {
+  string output(GetHeaderString());
+  uint32_t wiphy_idx = UINT32_MAX;
+  attributes().GetU32AttributeValue(NL80211_ATTR_WIPHY, &wiphy_idx);
+
+  const Nl80211RawAttribute *freq_before =
+      attributes().GetRawAttribute(NL80211_ATTR_FREQ_BEFORE);
+  if (!freq_before)
+    return "";
+  const nlattr *const_before = freq_before->data();
+  ieee80211_beacon_channel chan_before_beacon;
+  memset(&chan_before_beacon, 0, sizeof(chan_before_beacon));
+  if (ParseBeaconHintChan(const_before, &chan_before_beacon))
+    return "";
+
+  const Nl80211RawAttribute *freq_after =
+      attributes().GetRawAttribute(NL80211_ATTR_FREQ_AFTER);
+  if (!freq_after)
+    return "";
+
+  const nlattr *const_after = freq_after->data();
+  ieee80211_beacon_channel chan_after_beacon;
+  memset(&chan_after_beacon, 0, sizeof(chan_after_beacon));
+  if (ParseBeaconHintChan(const_after, &chan_after_beacon))
+    return "";
+
+  if (chan_before_beacon.center_freq != chan_after_beacon.center_freq)
+    return "";
+
+  /* A beacon hint is sent _only_ if something _did_ change */
+  output.append("beacon hint:");
+  StringAppendF(&output, "phy%" PRIu32 " %u MHz [%d]:",
+                wiphy_idx, chan_before_beacon.center_freq,
+                ChannelFromIeee80211Frequency(chan_before_beacon.center_freq));
+
+  if (chan_before_beacon.passive_scan && !chan_after_beacon.passive_scan)
+    output.append("\to active scanning enabled");
+  if (chan_before_beacon.no_ibss && !chan_after_beacon.no_ibss)
+    output.append("\to beaconing enabled");
+  return output;
+}
+
+int RegBeaconHintMessage::ParseBeaconHintChan(const nlattr *tb,
+                                              ieee80211_beacon_channel *chan)
+    const {
+  static const nla_policy kBeaconFreqPolicy[
+      NL80211_FREQUENCY_ATTR_MAX + 1] = {
+        {0, 0, 0},
+        { NLA_U32, 0, 0 },  // [NL80211_FREQUENCY_ATTR_FREQ]
+        {0, 0, 0},
+        { NLA_FLAG, 0, 0 },  // [NL80211_FREQUENCY_ATTR_PASSIVE_SCAN]
+        { NLA_FLAG, 0, 0 },  // [NL80211_FREQUENCY_ATTR_NO_IBSS]
+  };
+
+  if (!tb) {
+    LOG(ERROR) << "|tb| parameter is NULL.";
+    return -EINVAL;
+  }
+
+  if (!chan) {
+    LOG(ERROR) << "|chan| parameter is NULL.";
+    return -EINVAL;
+  }
+
+  nlattr *tb_freq[NL80211_FREQUENCY_ATTR_MAX + 1];
+
+  // Note that |nla_parse_nested| doesn't change its parameters but doesn't
+  // declare itself as 'const', either.  Hence the cast.
+  if (nla_parse_nested(tb_freq,
+                       NL80211_FREQUENCY_ATTR_MAX,
+                       const_cast<nlattr *>(tb),
+                       const_cast<nla_policy *>(kBeaconFreqPolicy)))
+    return -EINVAL;
+
+  chan->center_freq = Nl80211Attribute::NlaGetU32(
+      tb_freq[NL80211_FREQUENCY_ATTR_FREQ]);
+
+  if (tb_freq[NL80211_FREQUENCY_ATTR_PASSIVE_SCAN])
+    chan->passive_scan = true;
+  if (tb_freq[NL80211_FREQUENCY_ATTR_NO_IBSS])
+    chan->no_ibss = true;
+
+  return 0;
+}
+
+int RegBeaconHintMessage::ChannelFromIeee80211Frequency(int freq) {
+  // TODO(wdg): get rid of these magic numbers.
+  if (freq == 2484)
+    return 14;
+
+  if (freq < 2484)
+    return (freq - 2407) / 5;
+
+  /* FIXME: dot11ChannelStartingFactor (802.11-2007 17.3.8.3.2) */
+  return freq/5 - 1000;
+}
+
+const uint8_t RegChangeMessage::kCommand = NL80211_CMD_REG_CHANGE;
+const char RegChangeMessage::kCommandString[] = "NL80211_CMD_REG_CHANGE";
+
+string RegChangeMessage::ToString() const {
+  string output(GetHeaderString());
+  output.append("regulatory domain change: ");
+
+  uint8_t reg_type = UINT8_MAX;
+  attributes().GetU8AttributeValue(NL80211_ATTR_REG_TYPE, &reg_type);
+
+  uint32_t initiator = UINT32_MAX;
+  attributes().GetU32AttributeValue(NL80211_ATTR_REG_INITIATOR, &initiator);
+
+  uint32_t wifi = UINT32_MAX;
+  bool wifi_exists = attributes().GetU32AttributeValue(NL80211_ATTR_WIPHY,
+                                                       &wifi);
+
+  string alpha2 = "<None>";
+  attributes().GetStringAttributeValue(NL80211_ATTR_REG_ALPHA2, &alpha2);
+
+  switch (reg_type) {
+  case NL80211_REGDOM_TYPE_COUNTRY:
+    StringAppendF(&output, "set to %s by %s request",
+                  alpha2.c_str(), StringFromRegInitiator(initiator).c_str());
+    if (wifi_exists)
+      StringAppendF(&output, " on phy%" PRIu32, wifi);
+    break;
+
+  case NL80211_REGDOM_TYPE_WORLD:
+    StringAppendF(&output, "set to world roaming by %s request",
+                  StringFromRegInitiator(initiator).c_str());
+    break;
+
+  case NL80211_REGDOM_TYPE_CUSTOM_WORLD:
+    StringAppendF(&output,
+                  "custom world roaming rules in place on phy%" PRIu32
+                  " by %s request",
+                  wifi, StringFromRegInitiator(initiator).c_str());
+    break;
+
+  case NL80211_REGDOM_TYPE_INTERSECTION:
+    StringAppendF(&output, "intersection used due to a request made by %s",
+                  StringFromRegInitiator(initiator).c_str());
+    if (wifi_exists)
+      StringAppendF(&output, " on phy%" PRIu32, wifi);
+    break;
+
+  default:
+    output.append("unknown source");
+    break;
+  }
+  return output;
+}
+
+const uint8_t RemainOnChannelMessage::kCommand = NL80211_CMD_REMAIN_ON_CHANNEL;
+const char RemainOnChannelMessage::kCommandString[] =
+    "NL80211_CMD_REMAIN_ON_CHANNEL";
+
+string RemainOnChannelMessage::ToString() const {
+  string output(GetHeaderString());
+
+  uint32_t wifi_freq = UINT32_MAX;
+  attributes().GetU32AttributeValue(NL80211_ATTR_WIPHY_FREQ, &wifi_freq);
+
+  uint32_t duration = UINT32_MAX;
+  attributes().GetU32AttributeValue(NL80211_ATTR_DURATION, &duration);
+
+  uint64_t cookie = UINT64_MAX;
+  attributes().GetU64AttributeValue(NL80211_ATTR_COOKIE, &cookie);
+
+  StringAppendF(&output, "remain on freq %" PRIu32 " (%" PRIu32 "ms, cookie %"
+                PRIx64 ")",
+                wifi_freq, duration, cookie);
+  return output;
+}
+
+const uint8_t RoamMessage::kCommand = NL80211_CMD_ROAM;
+const char RoamMessage::kCommandString[] = "NL80211_CMD_ROAM";
+
+string RoamMessage::ToString() const {
+  string output(GetHeaderString());
+  output.append("roamed");
+
+  if (attributes().GetRawAttributeValue(NL80211_ATTR_MAC, NULL)) {
+    string mac;
+    GetMacAttributeString(NL80211_ATTR_MAC, &mac);
+    StringAppendF(&output, " to %s", mac.c_str());
+  }
+  return output;
+}
+
+const uint8_t ScanAbortedMessage::kCommand = NL80211_CMD_SCAN_ABORTED;
+const char ScanAbortedMessage::kCommandString[] = "NL80211_CMD_SCAN_ABORTED";
+
+string ScanAbortedMessage::ToString() const {
+  string output(GetHeaderString());
+  output.append("scan aborted");
+
+  {
+    output.append("; frequencies: ");
+    vector<uint32_t> list;
+    if (GetScanFrequenciesAttribute(NL80211_ATTR_SCAN_FREQUENCIES, &list)) {
+      string str;
+      for (vector<uint32_t>::const_iterator i = list.begin();
+           i != list.end(); ++i) {
+        StringAppendF(&str, " %" PRIu32 ", ", *i);
+      }
+      output.append(str);
+    }
+  }
+
+  {
+    output.append("; SSIDs: ");
+    vector<string> list;
+    if (GetScanSsidsAttribute(NL80211_ATTR_SCAN_SSIDS, &list)) {
+      string str;
+      for (vector<string>::const_iterator i = list.begin();
+           i != list.end(); ++i) {
+        StringAppendF(&str, "\"%s\", ", i->c_str());
+      }
+      output.append(str);
+    }
+  }
+
+  return output;
+}
+
+const uint8_t TriggerScanMessage::kCommand = NL80211_CMD_TRIGGER_SCAN;
+const char TriggerScanMessage::kCommandString[] = "NL80211_CMD_TRIGGER_SCAN";
+
+string TriggerScanMessage::ToString() const {
+  string output(GetHeaderString());
+  output.append("scan started");
+
+  {
+    output.append("; frequencies: ");
+    vector<uint32_t> list;
+    if (GetScanFrequenciesAttribute(NL80211_ATTR_SCAN_FREQUENCIES, &list)) {
+      string str;
+      for (vector<uint32_t>::const_iterator i = list.begin();
+             i != list.end(); ++i) {
+        StringAppendF(&str, "%" PRIu32 ", ", *i);
+      }
+      output.append(str);
+    }
+  }
+
+  {
+    output.append("; SSIDs: ");
+    vector<string> list;
+    if (GetScanSsidsAttribute(NL80211_ATTR_SCAN_SSIDS, &list)) {
+      string str;
+      for (vector<string>::const_iterator i = list.begin();
+           i != list.end(); ++i) {
+        StringAppendF(&str, "\"%s\", ", i->c_str());
+      }
+      output.append(str);
+    }
+  }
+
+  return output;
+}
+
+const uint8_t UnknownMessage::kCommand = 0xff;
+const char UnknownMessage::kCommandString[] = "<Unknown Message Type>";
+
+string UnknownMessage::ToString() const {
+  string output(GetHeaderString());
+  StringAppendF(&output, "unknown event %u", command_);
+  return output;
+}
+
+const uint8_t UnprotDeauthenticateMessage::kCommand =
+    NL80211_CMD_UNPROT_DEAUTHENTICATE;
+const char UnprotDeauthenticateMessage::kCommandString[] =
+    "NL80211_CMD_UNPROT_DEAUTHENTICATE";
+
+string UnprotDeauthenticateMessage::ToString() const {
+  string output(GetHeaderString());
+  StringAppendF(&output, "unprotected deauth %s",
+                StringFromFrame(NL80211_ATTR_FRAME).c_str());
+  return output;
+}
+
+const uint8_t UnprotDisassociateMessage::kCommand =
+    NL80211_CMD_UNPROT_DISASSOCIATE;
+const char UnprotDisassociateMessage::kCommandString[] =
+    "NL80211_CMD_UNPROT_DISASSOCIATE";
+
+string UnprotDisassociateMessage::ToString() const {
+  string output(GetHeaderString());
+  StringAppendF(&output, "unprotected disassoc %s",
+                StringFromFrame(NL80211_ATTR_FRAME).c_str());
+  return output;
+}
+
+//
+// Factory class.
+//
+
+Nl80211Message *Nl80211MessageFactory::CreateMessage(nlmsghdr *msg) {
+  if (!msg) {
+    LOG(ERROR) << "NULL |msg| parameter";
+    return NULL;
+  }
+
+  scoped_ptr<Nl80211Message> message;
+
+  genlmsghdr *gnlh =
+      reinterpret_cast<genlmsghdr *>(nlmsg_data(msg));
+
+  if (!gnlh) {
+    LOG(ERROR) << "NULL gnlh";
+    return NULL;
+  }
+
+  switch (gnlh->cmd) {
+    case AssociateMessage::kCommand:
+      message.reset(new AssociateMessage()); break;
+    case AuthenticateMessage::kCommand:
+      message.reset(new AuthenticateMessage()); break;
+    case CancelRemainOnChannelMessage::kCommand:
+      message.reset(new CancelRemainOnChannelMessage()); break;
+    case ConnectMessage::kCommand:
+      message.reset(new ConnectMessage()); break;
+    case DeauthenticateMessage::kCommand:
+      message.reset(new DeauthenticateMessage()); break;
+    case DeleteStationMessage::kCommand:
+      message.reset(new DeleteStationMessage()); break;
+    case DisassociateMessage::kCommand:
+      message.reset(new DisassociateMessage()); break;
+    case DisconnectMessage::kCommand:
+      message.reset(new DisconnectMessage()); break;
+    case FrameTxStatusMessage::kCommand:
+      message.reset(new FrameTxStatusMessage()); break;
+    case JoinIbssMessage::kCommand:
+      message.reset(new JoinIbssMessage()); break;
+    case MichaelMicFailureMessage::kCommand:
+      message.reset(new MichaelMicFailureMessage()); break;
+    case NewScanResultsMessage::kCommand:
+      message.reset(new NewScanResultsMessage()); break;
+    case NewStationMessage::kCommand:
+      message.reset(new NewStationMessage()); break;
+    case NewWifiMessage::kCommand:
+      message.reset(new NewWifiMessage()); break;
+    case NotifyCqmMessage::kCommand:
+      message.reset(new NotifyCqmMessage()); break;
+    case PmksaCandidateMessage::kCommand:
+      message.reset(new PmksaCandidateMessage()); break;
+    case RegBeaconHintMessage::kCommand:
+      message.reset(new RegBeaconHintMessage()); break;
+    case RegChangeMessage::kCommand:
+      message.reset(new RegChangeMessage()); break;
+    case RemainOnChannelMessage::kCommand:
+      message.reset(new RemainOnChannelMessage()); break;
+    case RoamMessage::kCommand:
+      message.reset(new RoamMessage()); break;
+    case ScanAbortedMessage::kCommand:
+      message.reset(new ScanAbortedMessage()); break;
+    case TriggerScanMessage::kCommand:
+      message.reset(new TriggerScanMessage()); break;
+    case UnprotDeauthenticateMessage::kCommand:
+      message.reset(new UnprotDeauthenticateMessage()); break;
+    case UnprotDisassociateMessage::kCommand:
+      message.reset(new UnprotDisassociateMessage()); break;
+
+    default:
+      message.reset(new UnknownMessage(gnlh->cmd)); break;
+      break;
+  }
+
+  nlattr *tb[NL80211_ATTR_MAX + 1];
+
+  // Parse the attributes from the nl message payload (which starts at the
+  // header) into the 'tb' array.
+  nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
+            genlmsg_attrlen(gnlh, 0), NULL);
+
+  if (!message->Init(tb, msg)) {
+    LOG(ERROR) << "Message did not initialize properly";
+    return NULL;
+  }
+
+  return message.release();
+}
+
+Nl80211MessageDataCollector *
+    Nl80211MessageDataCollector::GetInstance() {
+  return g_datacollector.Pointer();
+}
+
+Nl80211MessageDataCollector::Nl80211MessageDataCollector() {
+  need_to_print[NL80211_ATTR_PMKSA_CANDIDATE] = true;
+  need_to_print[NL80211_CMD_CANCEL_REMAIN_ON_CHANNEL] = true;
+  need_to_print[NL80211_CMD_DEL_STATION] = true;
+  need_to_print[NL80211_CMD_FRAME_TX_STATUS] = true;
+  need_to_print[NL80211_CMD_JOIN_IBSS] = true;
+  need_to_print[NL80211_CMD_MICHAEL_MIC_FAILURE] = true;
+  need_to_print[NL80211_CMD_NEW_WIPHY] = true;
+  need_to_print[NL80211_CMD_REG_BEACON_HINT] = true;
+  need_to_print[NL80211_CMD_REG_CHANGE] = true;
+  need_to_print[NL80211_CMD_REMAIN_ON_CHANNEL] = true;
+  need_to_print[NL80211_CMD_ROAM] = true;
+  need_to_print[NL80211_CMD_SCAN_ABORTED] = true;
+  need_to_print[NL80211_CMD_UNPROT_DEAUTHENTICATE] = true;
+  need_to_print[NL80211_CMD_UNPROT_DISASSOCIATE] = true;
+}
+
+void Nl80211MessageDataCollector::CollectDebugData(
+    const Nl80211Message &message, nlmsghdr *msg) {
+  if (!msg) {
+    LOG(ERROR) << "NULL |msg| parameter";
+    return;
+  }
+
+  bool doit = false;
+
+  map<uint8_t, bool>::const_iterator node;
+  node = need_to_print.find(message.message_type());
+  if (node != need_to_print.end())
+    doit = node->second;
+
+  if (doit) {
+    LOG(INFO) << "@@const unsigned char "
+               << "k" << message.message_type_string()
+               << "[] = {";
+
+    int payload_bytes = nlmsg_datalen(msg);
+
+    size_t bytes = nlmsg_total_size(payload_bytes);
+    unsigned char *rawdata = reinterpret_cast<unsigned char *>(msg);
+    for (size_t i = 0; i < bytes; ++i) {
+      LOG(INFO) << "  0x"
+                 << std::hex << std::setfill('0') << std::setw(2)
+                 << + rawdata[i] << ",";
+    }
+    LOG(INFO) << "};";
+    need_to_print[message.message_type()] = false;
+  }
+}
+
+}  // namespace shill.