[wlan][mesh] Parse MPM Confirm actions and forward to SME

WLAN-844

Change-Id: I6a6fdf94280f17fe8b7c8ff4618efad7d4cc8736
diff --git a/garnet/lib/wlan/mlme/include/wlan/mlme/mesh/mesh_mlme.h b/garnet/lib/wlan/mlme/include/wlan/mlme/mesh/mesh_mlme.h
index 3bada20..43f8f0f 100644
--- a/garnet/lib/wlan/mlme/include/wlan/mlme/mesh/mesh_mlme.h
+++ b/garnet/lib/wlan/mlme/include/wlan/mlme/mesh/mesh_mlme.h
@@ -46,8 +46,9 @@
     zx_status_t HandleAnyWlanFrame(fbl::unique_ptr<Packet> pkt);
     zx_status_t HandleAnyMgmtFrame(MgmtFrame<>&& frame);
     zx_status_t HandleActionFrame(const MgmtFrameHeader& mgmt, BufferReader* r);
-    zx_status_t HandleSelfProtectedAction(common::MacAddr src_addr, BufferReader* r);
-    zx_status_t HandleMpmOpenAction(common::MacAddr src_addr, BufferReader* r);
+    zx_status_t HandleSelfProtectedAction(const common::MacAddr& src_addr, BufferReader* r);
+    zx_status_t HandleMpmOpenAction(const common::MacAddr& src_addr, BufferReader* r);
+    zx_status_t HandleMpmConfirmAction(const common::MacAddr& src_addr, BufferReader* r);
     void HandleMeshAction(const MgmtFrameHeader& mgmt, BufferReader* r);
 
     const MeshPath* QueryPathTable(const common::MacAddr& mesh_dest);
diff --git a/garnet/lib/wlan/mlme/include/wlan/mlme/mesh/parse_mp_action.h b/garnet/lib/wlan/mlme/include/wlan/mlme/mesh/parse_mp_action.h
index d2945eb..bcc78e6 100644
--- a/garnet/lib/wlan/mlme/include/wlan/mlme/mesh/parse_mp_action.h
+++ b/garnet/lib/wlan/mlme/include/wlan/mlme/mesh/parse_mp_action.h
@@ -12,6 +12,8 @@
 
 bool ParseMpOpenAction(BufferReader* r, ::fuchsia::wlan::mlme::MeshPeeringOpenAction* out);
 
+bool ParseMpConfirmAction(BufferReader* r, ::fuchsia::wlan::mlme::MeshPeeringConfirmAction* out);
+
 }  // namespace wlan
 
 #endif  // GARNET_LIB_WLAN_MLME_INCLUDE_WLAN_MLME_MESH_PARSE_MP_ACTION_H_
diff --git a/garnet/lib/wlan/mlme/mesh/mesh_mlme.cpp b/garnet/lib/wlan/mlme/mesh/mesh_mlme.cpp
index 55e8587..b33bbf7 100644
--- a/garnet/lib/wlan/mlme/mesh/mesh_mlme.cpp
+++ b/garnet/lib/wlan/mlme/mesh/mesh_mlme.cpp
@@ -347,13 +347,15 @@
     }
 }
 
-zx_status_t MeshMlme::HandleSelfProtectedAction(common::MacAddr src_addr, BufferReader* r) {
+zx_status_t MeshMlme::HandleSelfProtectedAction(const common::MacAddr& src_addr, BufferReader* r) {
     auto self_prot_header = r->Read<SelfProtectedActionHeader>();
     if (self_prot_header == nullptr) { return ZX_OK; }
 
     switch (self_prot_header->self_prot_action) {
     case action::kMeshPeeringOpen:
         return HandleMpmOpenAction(src_addr, r);
+    case action::kMeshPeeringConfirm:
+        return HandleMpmConfirmAction(src_addr, r);
     default:
         return ZX_OK;
     }
@@ -381,7 +383,7 @@
     }
 }
 
-zx_status_t MeshMlme::HandleMpmOpenAction(common::MacAddr src_addr, BufferReader* r) {
+zx_status_t MeshMlme::HandleMpmOpenAction(const common::MacAddr& src_addr, BufferReader* r) {
     wlan_mlme::MeshPeeringOpenAction action;
     if (!ParseMpOpenAction(r, &action)) { return ZX_OK; }
 
@@ -389,6 +391,14 @@
     return SendServiceMsg(device_, &action, fuchsia_wlan_mlme_MLMEIncomingMpOpenActionOrdinal);
 }
 
+zx_status_t MeshMlme::HandleMpmConfirmAction(const common::MacAddr& src_addr, BufferReader* r) {
+    wlan_mlme::MeshPeeringConfirmAction action;
+    if (!ParseMpConfirmAction(r, &action)) { return ZX_OK; }
+
+    src_addr.CopyTo(action.common.peer_sta_address.data());
+    return SendServiceMsg(device_, &action, fuchsia_wlan_mlme_MLMEIncomingMpConfirmActionOrdinal);
+}
+
 const MeshPath* MeshMlme::QueryPathTable(const common::MacAddr& mesh_dest) {
     ZX_ASSERT(state_);
 
diff --git a/garnet/lib/wlan/mlme/mesh/parse_mp_action.cpp b/garnet/lib/wlan/mlme/mesh/parse_mp_action.cpp
index 22e659e..1ecc0f9 100644
--- a/garnet/lib/wlan/mlme/mesh/parse_mp_action.cpp
+++ b/garnet/lib/wlan/mlme/mesh/parse_mp_action.cpp
@@ -75,6 +75,11 @@
     }
 }
 
+static void ConvertMpmHeader(const MpmHeader& header, wlan_mlme::MeshPeeringCommon* out) {
+    out->protocol_id = header.protocol;
+    out->local_link_id = header.local_link_id;
+}
+
 // IEEE Std 802.11-2016, 9.6.16.2.2
 bool ParseMpOpenAction(BufferReader* r, wlan_mlme::MeshPeeringOpenAction* out) {
     auto cap_info = r->Read<CapabilityInfo>();
@@ -86,8 +91,7 @@
             // Handle the MPM element separately since there is no way to handle
             // it in a generic fashion
             if (auto mpm_open = common::ParseMpmOpen(raw_body)) {
-                out->common.protocol_id = mpm_open->header.protocol;
-                out->common.local_link_id = mpm_open->header.local_link_id;
+                ConvertMpmHeader(mpm_open->header, &out->common);
                 required_ies.have_mpm = true;
             }
         } else {
@@ -97,4 +101,30 @@
     return required_ies.have_all();
 }
 
+// IEEE Std 802.11-2016, 9.6.16.3.2
+bool ParseMpConfirmAction(BufferReader* r, wlan_mlme::MeshPeeringConfirmAction* out) {
+    auto cap_info = r->Read<CapabilityInfo>();
+    if (cap_info == nullptr) { return false; }
+
+    auto aid = r->Read<uint16_t>();
+    if (aid == nullptr) { return false; }
+    out->aid = *aid;
+
+    RequiredIes required_ies;
+    for (auto [id, raw_body] : common::ElementSplitter(r->ReadRemaining())) {
+        if (id == element_id::kMeshPeeringManagement) {
+            // Handle the MPM element separately since there is no way to handle
+            // it in a generic fashion
+            if (auto mpm_confirm = common::ParseMpmConfirm(raw_body)) {
+                ConvertMpmHeader(mpm_confirm->header, &out->common);
+                required_ies.have_mpm = true;
+                out->peer_link_id = mpm_confirm->peer_link_id;
+            }
+        } else {
+            HandleCommonMpElement(id, raw_body, &out->common, &required_ies);
+        }
+    }
+    return required_ies.have_all();
+}
+
 }  // namespace wlan
diff --git a/garnet/lib/wlan/mlme/tests/mesh_mlme_unittest.cpp b/garnet/lib/wlan/mlme/tests/mesh_mlme_unittest.cpp
index 391d43a..a4f36f5 100644
--- a/garnet/lib/wlan/mlme/tests/mesh_mlme_unittest.cpp
+++ b/garnet/lib/wlan/mlme/tests/mesh_mlme_unittest.cpp
@@ -140,6 +140,46 @@
     }
 }
 
+TEST_F(MeshMlmeTest, HandleMpmConfirm) {
+    EXPECT_EQ(JoinMesh(), wlan_mlme::StartResultCodes::SUCCESS);
+
+    // clang-format off
+    const uint8_t frame[] = {
+        // Mgmt header
+        0xd0, 0x00, 0x00, 0x00,              // fc, duration
+        0x01, 0x01, 0x01, 0x01, 0x01, 0x01,  // addr1
+        0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,  // addr2
+        0x03, 0x03, 0x03, 0x03, 0x03, 0x03,  // addr3
+        0x00, 0x00,                          // seq ctl
+        // Action
+        15,  // category (self-protected)
+        2,   // action = Mesh Peering Confirm
+        // Body
+        0xaa, 0xbb,                                        // capability info
+        0xcc, 0xdd,                                        // aid
+        1, 1, 0x81,                                        // supported rates
+        114, 3, 'f', 'o', 'o',                             // mesh id
+        113, 7, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,  // mesh config
+        117, 6, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,        // MPM
+    };
+    // clang-format on
+
+    ASSERT_EQ(mlme.HandleFramePacket(MakeWlanPacket(frame)), ZX_OK);
+
+    auto msgs = device.GetServiceMsgs<wlan_mlme::MeshPeeringConfirmAction>();
+    ASSERT_EQ(msgs.size(), 1ULL);
+
+    {
+        const uint8_t expected[] = {'f', 'o', 'o'};
+        EXPECT_RANGES_EQ(msgs[0].body()->common.mesh_id, expected);
+    }
+
+    {
+        const uint8_t expected[] = {0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
+        EXPECT_RANGES_EQ(msgs[0].body()->common.peer_sta_address, expected);
+    }
+}
+
 TEST_F(MeshMlmeTest, GetPathTable) {
     EXPECT_EQ(JoinMesh(), wlan_mlme::StartResultCodes::SUCCESS);
     auto path_table_msgs = GetPathTable();
diff --git a/garnet/lib/wlan/mlme/tests/parse_mp_action_unittest.cpp b/garnet/lib/wlan/mlme/tests/parse_mp_action_unittest.cpp
index 55ac4ca..daf6366 100644
--- a/garnet/lib/wlan/mlme/tests/parse_mp_action_unittest.cpp
+++ b/garnet/lib/wlan/mlme/tests/parse_mp_action_unittest.cpp
@@ -187,4 +187,133 @@
     ASSERT_FALSE(ParseMpOpenAction(&reader, &action));
 }
 
+TEST(ParseMpConfirm, Full) {
+    // clang-format off
+    const uint8_t data[] = {
+        0xaa, 0xbb, // capability info
+        0x12, 0x34, // aid
+        1, 8, 0x81, 0x82, 0x83, 0x84, 0x05, 0x06, 0x07, 0x08, // supported rates
+        50, 1, 0x09, // ext supported rates
+        114, 3, 'f', 'o', 'o', // mesh id
+        113, 7, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, // mesh config
+        117, 6, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, // MPM
+        45, 26, // ht capabilities
+            0xaa, 0xbb, // ht cap info
+            0x55, // ampdu params
+            0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
+            0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, // mcs
+            0xdd, 0xee, // ext caps
+            0x11, 0x22, 0x33, 0x44, // beamforming
+            0x77, // asel
+        61, 22, // ht operation
+            36, 0x11, 0x22, 0x33, 0x44, 0x55,
+            0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+            0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
+        191, 12, // vht capabilities
+            0xaa, 0xbb, 0xcc, 0xdd,
+            0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
+        192, 5, // vht operation
+            0xd0, 0xd1, 0xd2, 0xd3, 0xd4
+    };
+    // clang-format on
+
+    wlan_mlme::MeshPeeringConfirmAction action;
+    BufferReader reader { data };
+    ASSERT_TRUE(ParseMpConfirmAction(&reader, &action));
+
+    {
+        // Rates are expected to be a concatenation of Supp Rates and Ext Supp Rates
+        const uint8_t expected[9] = { 0x81, 0x82, 0x83, 0x84, 0x05, 0x06, 0x07, 0x08, 0x09 };
+        EXPECT_RANGES_EQ(action.common.rates, expected);
+    }
+
+    {
+        const uint8_t expected[3] = { 'f', 'o', 'o' };
+        EXPECT_RANGES_EQ(action.common.mesh_id, expected);
+    }
+
+    EXPECT_EQ(action.peer_link_id, 0xb6b5);
+    EXPECT_EQ(action.aid, 0x3412u);
+
+    EXPECT_EQ(action.common.mesh_config.active_path_sel_proto_id, 0xa1u);
+    EXPECT_EQ(action.common.protocol_id, 0xb2b1u);
+    EXPECT_EQ(action.common.local_link_id, 0xb4b3);
+
+    ASSERT_NE(action.common.ht_cap, nullptr);
+    EXPECT_EQ(action.common.ht_cap->mcs_set.rx_mcs_set, 0x0706050403020100ul);
+
+    ASSERT_NE(action.common.ht_op, nullptr);
+    EXPECT_EQ(action.common.ht_op->basic_mcs_set.rx_mcs_set, 0xc7c6c5c4c3c2c1c0ul);
+
+    ASSERT_NE(action.common.vht_cap, nullptr);
+    EXPECT_EQ(action.common.vht_cap->vht_mcs_nss.rx_max_data_rate, 0x0433);
+
+    ASSERT_NE(action.common.vht_op, nullptr);
+    ASSERT_EQ(action.common.vht_op->vht_cbw, 0xd0);
+}
+
+TEST(ParseMpConfirm, Minimal) {
+    // clang-format off
+    const uint8_t data[] = {
+        0xaa, 0xbb, // capability info
+        0x12, 0x34, // AID
+        1, 1, 0x81, // supported rates
+        114, 3, 'f', 'o', 'o', // mesh id
+        113, 7, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, // mesh config
+        117, 6, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, // MPM
+    };
+    // clang-format on
+
+    wlan_mlme::MeshPeeringConfirmAction action;
+    BufferReader reader { data };
+    ASSERT_TRUE(ParseMpConfirmAction(&reader, &action));
+
+    {
+        // Rates are expected to be a concatenation of Supp Rates and Ext Supp Rates
+        const uint8_t expected[1] = { 0x81 };
+        EXPECT_RANGES_EQ(action.common.rates, expected);
+    }
+
+    {
+        const uint8_t expected[3] = { 'f', 'o', 'o' };
+        EXPECT_RANGES_EQ(action.common.mesh_id, expected);
+    }
+
+    EXPECT_EQ(action.aid, 0x3412u);
+    EXPECT_EQ(action.peer_link_id, 0xb6b5);
+
+    EXPECT_EQ(action.common.mesh_config.active_path_sel_proto_id, 0xa1u);
+    EXPECT_EQ(action.common.protocol_id, 0xb2b1u);
+}
+
+TEST(ParseMpConfirm, TooShortForCapabilityInfo) {
+    const uint8_t data[] = { 0xaa }; // too short to hold a CapabilityInfo
+    BufferReader reader { data };
+    wlan_mlme::MeshPeeringConfirmAction action;
+    ASSERT_FALSE(ParseMpConfirmAction(&reader, &action));
+}
+
+TEST(ParseMpConfirm, TooShortForAid) {
+    const uint8_t data[] = { 0xaa, 0xbb, 0xcc }; // too short to hold a CapabilityInfo + AID
+    BufferReader reader { data };
+    wlan_mlme::MeshPeeringConfirmAction action;
+    ASSERT_FALSE(ParseMpConfirmAction(&reader, &action));
+}
+
+TEST(ParseMpConfirm, MissingMpm) {
+    // clang-format off
+    const uint8_t data[] = {
+        0xaa, 0xbb, // capability info
+        0x12, 0x34, // AID
+        1, 1, 0x81, // supported rates
+        114, 3, 'f', 'o', 'o', // mesh id
+        113, 7, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, // mesh config
+    };
+    // clang-format on
+
+    wlan_mlme::MeshPeeringConfirmAction action;
+    BufferReader reader { data };
+    ASSERT_FALSE(ParseMpConfirmAction(&reader, &action));
+}
+
 } // namespace wlan
diff --git a/sdk/fidl/fuchsia.wlan.mlme/wlan_mlme.fidl b/sdk/fidl/fuchsia.wlan.mlme/wlan_mlme.fidl
index 272cc065..1ea78b1 100644
--- a/sdk/fidl/fuchsia.wlan.mlme/wlan_mlme.fidl
+++ b/sdk/fidl/fuchsia.wlan.mlme/wlan_mlme.fidl
@@ -932,9 +932,10 @@
 
     // The following are extensions to the 802.11 MLME SAP interface.
 
-    // ==== 90xxxx: Mesh ===
+    // ==== Mesh ===
     -> IncomingMpOpenAction(MeshPeeringOpenAction action);
     SendMpOpenAction(MeshPeeringOpenAction action);
+    -> IncomingMpConfirmAction(MeshPeeringConfirmAction action);
     SendMpConfirmAction(MeshPeeringConfirmAction action);
 
     MeshPeeringEstablished(MeshPeeringParams peering);