From 54411bef4eb16af0822820205a923690ea7e822a Mon Sep 17 00:00:00 2001
From: Subv <subv2112@gmail.com>
Date: Mon, 17 Jul 2017 09:25:58 -0500
Subject: [PATCH 1/4] Services/UDS: Add functions to generate 802.11 auth and
 assoc response frames.

---
 src/core/CMakeLists.txt                     |  2 +
 src/core/hle/service/nwm/nwm_uds.h          | 12 ++++
 src/core/hle/service/nwm/uds_beacon.h       | 11 ---
 src/core/hle/service/nwm/uds_connection.cpp | 79 +++++++++++++++++++++
 src/core/hle/service/nwm/uds_connection.h   | 51 +++++++++++++
 5 files changed, 144 insertions(+), 11 deletions(-)
 create mode 100644 src/core/hle/service/nwm/uds_connection.cpp
 create mode 100644 src/core/hle/service/nwm/uds_connection.h

diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index ea09819e5..0719138af 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -144,6 +144,7 @@ set(SRCS
             hle/service/nwm/nwm_tst.cpp
             hle/service/nwm/nwm_uds.cpp
             hle/service/nwm/uds_beacon.cpp
+            hle/service/nwm/uds_connection.cpp
             hle/service/nwm/uds_data.cpp
             hle/service/pm_app.cpp
             hle/service/ptm/ptm.cpp
@@ -342,6 +343,7 @@ set(HEADERS
             hle/service/nwm/nwm_tst.h
             hle/service/nwm/nwm_uds.h
             hle/service/nwm/uds_beacon.h
+            hle/service/nwm/uds_connection.h
             hle/service/nwm/uds_data.h
             hle/service/pm_app.h
             hle/service/ptm/ptm.h
diff --git a/src/core/hle/service/nwm/nwm_uds.h b/src/core/hle/service/nwm/nwm_uds.h
index 141f49f9c..f1caaf974 100644
--- a/src/core/hle/service/nwm/nwm_uds.h
+++ b/src/core/hle/service/nwm/nwm_uds.h
@@ -42,6 +42,7 @@ using NodeList = std::vector<NodeInfo>;
 enum class NetworkStatus {
     NotConnected = 3,
     ConnectedAsHost = 6,
+    Connecting = 7,
     ConnectedAsClient = 9,
     ConnectedAsSpectator = 10,
 };
@@ -85,6 +86,17 @@ static_assert(offsetof(NetworkInfo, oui_value) == 0xC, "oui_value is at the wron
 static_assert(offsetof(NetworkInfo, wlan_comm_id) == 0x10, "wlancommid is at the wrong offset.");
 static_assert(sizeof(NetworkInfo) == 0x108, "NetworkInfo has incorrect size.");
 
+/// Additional block tag ids in the Beacon and Association Response frames
+enum class TagId : u8 {
+    SSID = 0,
+    SupportedRates = 1,
+    DSParameterSet = 2,
+    TrafficIndicationMap = 5,
+    CountryInformation = 7,
+    ERPInformation = 42,
+    VendorSpecific = 221
+};
+
 class NWM_UDS final : public Interface {
 public:
     NWM_UDS();
diff --git a/src/core/hle/service/nwm/uds_beacon.h b/src/core/hle/service/nwm/uds_beacon.h
index caacf4c6f..c726b04d9 100644
--- a/src/core/hle/service/nwm/uds_beacon.h
+++ b/src/core/hle/service/nwm/uds_beacon.h
@@ -17,17 +17,6 @@ namespace NWM {
 using MacAddress = std::array<u8, 6>;
 constexpr std::array<u8, 3> NintendoOUI = {0x00, 0x1F, 0x32};
 
-/// Additional block tag ids in the Beacon frames
-enum class TagId : u8 {
-    SSID = 0,
-    SupportedRates = 1,
-    DSParameterSet = 2,
-    TrafficIndicationMap = 5,
-    CountryInformation = 7,
-    ERPInformation = 42,
-    VendorSpecific = 221
-};
-
 /**
  * Internal vendor-specific tag ids as stored inside
  * VendorSpecific blocks in the Beacon frames.
diff --git a/src/core/hle/service/nwm/uds_connection.cpp b/src/core/hle/service/nwm/uds_connection.cpp
new file mode 100644
index 000000000..c8a76ec2a
--- /dev/null
+++ b/src/core/hle/service/nwm/uds_connection.cpp
@@ -0,0 +1,79 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/nwm/nwm_uds.h"
+#include "core/hle/service/nwm/uds_connection.h"
+#include "fmt/format.h"
+
+namespace Service {
+namespace NWM {
+
+// Note: These values were taken from a packet capture of an o3DS XL
+// broadcasting a Super Smash Bros. 4 lobby.
+constexpr u16 DefaultExtraCapabilities = 0x0431;
+
+std::vector<u8> GenerateAuthenticationFrame(AuthenticationSeq seq) {
+    AuthenticationFrame frame{};
+    frame.auth_seq = static_cast<u16>(seq);
+
+    std::vector<u8> data(sizeof(frame));
+    std::memcpy(data.data(), &frame, sizeof(frame));
+
+    return data;
+}
+
+AuthenticationSeq GetAuthenticationSeqNumber(const std::vector<u8>& body) {
+    AuthenticationFrame frame;
+    std::memcpy(&frame, body.data(), sizeof(frame));
+
+    return static_cast<AuthenticationSeq>(frame.auth_seq);
+}
+
+/**
+ * Generates an SSID tag of an 802.11 Beacon frame with an 8-byte character representation of the
+ * specified network id as the SSID value.
+ * @param network_id The network id to use.
+ * @returns A buffer with the SSID tag.
+ */
+static std::vector<u8> GenerateSSIDTag(u32 network_id) {
+    constexpr u8 SSIDSize = 8;
+
+    struct {
+        u8 id = static_cast<u8>(TagId::SSID);
+        u8 size = SSIDSize;
+    } tag_header;
+
+    std::vector<u8> buffer(sizeof(tag_header) + SSIDSize);
+
+    std::memcpy(buffer.data(), &tag_header, sizeof(tag_header));
+
+    std::string network_name = fmt::format("{0:08X}", network_id);
+
+    std::memcpy(buffer.data() + sizeof(tag_header), network_name.c_str(), SSIDSize);
+
+    return buffer;
+}
+
+std::vector<u8> GenerateAssocResponseFrame(AssocStatus status, u16 association_id, u32 network_id) {
+    AssociationResponseFrame frame{};
+    frame.capabilities = DefaultExtraCapabilities;
+    frame.status_code = static_cast<u16>(status);
+    // The association id is ORed with this magic value (0xC000)
+    constexpr u16 AssociationIdMagic = 0xC000;
+    frame.assoc_id = association_id | AssociationIdMagic;
+
+    std::vector<u8> data(sizeof(frame));
+    std::memcpy(data.data(), &frame, sizeof(frame));
+
+    auto ssid_tag = GenerateSSIDTag(network_id);
+    data.insert(data.end(), ssid_tag.begin(), ssid_tag.end());
+
+    // TODO(Subv): Add the SupportedRates tag.
+    // TODO(Subv): Add the DSParameterSet tag.
+    // TODO(Subv): Add the ERPInformation tag.
+    return data;
+}
+
+} // namespace NWM
+} // namespace Service
diff --git a/src/core/hle/service/nwm/uds_connection.h b/src/core/hle/service/nwm/uds_connection.h
new file mode 100644
index 000000000..73f55a4fd
--- /dev/null
+++ b/src/core/hle/service/nwm/uds_connection.h
@@ -0,0 +1,51 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace NWM {
+
+/// Sequence number of the 802.11 authentication frames.
+enum class AuthenticationSeq : u16 { SEQ1 = 1, SEQ2 = 2 };
+
+enum class AuthAlgorithm : u16 { OpenSystem = 0 };
+
+enum class AuthStatus : u16 { Successful = 0 };
+
+enum class AssocStatus : u16 { Successful = 0 };
+
+struct AuthenticationFrame {
+    u16_le auth_algorithm = static_cast<u16>(AuthAlgorithm::OpenSystem);
+    u16_le auth_seq;
+    u16_le status_code = static_cast<u16>(AuthStatus::Successful);
+};
+
+static_assert(sizeof(AuthenticationFrame) == 6, "AuthenticationFrame has wrong size");
+
+struct AssociationResponseFrame {
+    u16_le capabilities;
+    u16_le status_code;
+    u16_le assoc_id;
+};
+
+static_assert(sizeof(AssociationResponseFrame) == 6, "AssociationResponseFrame has wrong size");
+
+/// Generates an 802.11 authentication frame, starting at the frame body.
+std::vector<u8> GenerateAuthenticationFrame(AuthenticationSeq seq);
+
+/// Returns the sequence number from the body of an Authentication frame.
+AuthenticationSeq GetAuthenticationSeqNumber(const std::vector<u8>& body);
+
+/// Generates an 802.11 association response frame with the specified status, association id and
+/// network id, starting at the frame body.
+std::vector<u8> GenerateAssocResponseFrame(AssocStatus status, u16 association_id, u32 network_id);
+
+} // namespace NWM
+} // namespace Service

From 2e9f544ecc9a01ff59859b43d65c61a2838e7c34 Mon Sep 17 00:00:00 2001
From: Subv <subv2112@gmail.com>
Date: Mon, 17 Jul 2017 09:39:12 -0500
Subject: [PATCH 2/4] Services/UDS: Store the received beacon frames until
 RecvBeaconBroadcastData is called, up to 15 beacons at the same time,
 removing any older beacon frames when the limit is exceeded.

---
 src/core/hle/service/nwm/nwm_uds.cpp | 65 ++++++++++++++++++++++++++--
 1 file changed, 62 insertions(+), 3 deletions(-)

diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp
index 6dbdff044..8fdf160ff 100644
--- a/src/core/hle/service/nwm/nwm_uds.cpp
+++ b/src/core/hle/service/nwm/nwm_uds.cpp
@@ -4,6 +4,7 @@
 
 #include <array>
 #include <cstring>
+#include <mutex>
 #include <unordered_map>
 #include <vector>
 #include "common/common_types.h"
@@ -15,8 +16,10 @@
 #include "core/hle/result.h"
 #include "core/hle/service/nwm/nwm_uds.h"
 #include "core/hle/service/nwm/uds_beacon.h"
+#include "core/hle/service/nwm/uds_connection.h"
 #include "core/hle/service/nwm/uds_data.h"
 #include "core/memory.h"
+#include "network/network.h"
 
 namespace Service {
 namespace NWM {
@@ -51,6 +54,52 @@ static NetworkInfo network_info;
 // Event that will generate and send the 802.11 beacon frames.
 static int beacon_broadcast_event;
 
+// Mutex to synchronize access to the list of received beacons between the emulation thread and the
+// network thread.
+static std::mutex beacon_mutex;
+
+// Number of beacons to store before we start dropping the old ones.
+// TODO(Subv): Find a more accurate value for this limit.
+constexpr size_t MaxBeaconFrames = 15;
+
+// List of the last <MaxBeaconFrames> beacons received from the network.
+static std::deque<Network::WifiPacket> received_beacons;
+
+/**
+ * Returns a list of received 802.11 beacon frames from the specified sender since the last call.
+ */
+std::deque<Network::WifiPacket> GetReceivedBeacons(const MacAddress& sender) {
+    std::lock_guard<std::mutex> lock(beacon_mutex);
+    // TODO(Subv): Filter by sender.
+    return std::move(received_beacons);
+}
+
+/// Sends a WifiPacket to the room we're currently connected to.
+void SendPacket(Network::WifiPacket& packet) {
+    // TODO(Subv): Implement.
+}
+
+// Inserts the received beacon frame in the beacon queue and removes any older beacons if the size
+// limit is exceeded.
+void HandleBeaconFrame(const Network::WifiPacket& packet) {
+    std::lock_guard<std::mutex> lock(beacon_mutex);
+
+    received_beacons.emplace_back(packet);
+
+    // Discard old beacons if the buffer is full.
+    if (received_beacons.size() > MaxBeaconFrames)
+        received_beacons.pop_front();
+}
+
+/// Callback to parse and handle a received wifi packet.
+void OnWifiPacketReceived(const Network::WifiPacket& packet) {
+    switch (packet.type) {
+    case Network::WifiPacket::PacketType::Beacon:
+        HandleBeaconFrame(packet);
+        break;
+    }
+}
+
 /**
  * NWM_UDS::Shutdown service function
  *  Inputs:
@@ -111,8 +160,7 @@ static void RecvBeaconBroadcastData(Interface* self) {
     u32 total_size = sizeof(BeaconDataReplyHeader);
 
     // Retrieve all beacon frames that were received from the desired mac address.
-    std::deque<WifiPacket> beacons =
-        GetReceivedPackets(WifiPacket::PacketType::Beacon, mac_address);
+    auto beacons = GetReceivedBeacons(mac_address);
 
     BeaconDataReplyHeader data_reply_header{};
     data_reply_header.total_entries = beacons.size();
@@ -193,6 +241,9 @@ static void InitializeWithVersion(Interface* self) {
     rb.Push(RESULT_SUCCESS);
     rb.PushCopyHandles(Kernel::g_handle_table.Create(connection_status_event).Unwrap());
 
+    // TODO(Subv): Connect the OnWifiPacketReceived function to the wifi packet received callback of
+    // the room we're currently in.
+
     LOG_DEBUG(Service_NWM, "called sharedmem_size=0x%08X, version=0x%08X, sharedmem_handle=0x%08X",
               sharedmem_size, version, sharedmem_handle);
 }
@@ -610,9 +661,17 @@ static void BeaconBroadcastCallback(u64 userdata, int cycles_late) {
     if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost))
         return;
 
-    // TODO(Subv): Actually send the beacon.
     std::vector<u8> frame = GenerateBeaconFrame(network_info, node_info);
 
+    using Network::WifiPacket;
+    WifiPacket packet;
+    packet.type = WifiPacket::PacketType::Beacon;
+    packet.data = std::move(frame);
+    packet.destination_address = Network::BroadcastMac;
+    packet.channel = network_channel;
+
+    SendPacket(packet);
+
     // Start broadcasting the network, send a beacon frame every 102.4ms.
     CoreTiming::ScheduleEvent(msToCycles(DefaultBeaconInterval * MillisecondsPerTU) - cycles_late,
                               beacon_broadcast_event, 0);

From d088dbfbe1064bb5212e83c50e71e4b2ea5b00cd Mon Sep 17 00:00:00 2001
From: Subv <subv2112@gmail.com>
Date: Mon, 17 Jul 2017 09:51:03 -0500
Subject: [PATCH 3/4] Services/UDS: Handle the connection sequence packets.

There is currently no stage tracking, a client is considered "Connected" when it receives the EAPoL Logoff packet from the server, this is not yet implemented.
---
 src/core/hle/service/nwm/nwm_uds.cpp | 100 ++++++++++++++++++++++-----
 1 file changed, 83 insertions(+), 17 deletions(-)

diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp
index 8fdf160ff..893bbb1e7 100644
--- a/src/core/hle/service/nwm/nwm_uds.cpp
+++ b/src/core/hle/service/nwm/nwm_uds.cpp
@@ -91,12 +91,95 @@ void HandleBeaconFrame(const Network::WifiPacket& packet) {
         received_beacons.pop_front();
 }
 
+/*
+ * Returns an available index in the nodes array for the
+ * currently-hosted UDS network.
+ */
+static u16 GetNextAvailableNodeId() {
+    ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost),
+               "Can not accept clients if we're not hosting a network");
+
+    for (u16 index = 0; index < connection_status.max_nodes; ++index) {
+        if ((connection_status.node_bitmask & (1 << index)) == 0)
+            return index;
+    }
+
+    // Any connection attempts to an already full network should have been refused.
+    ASSERT_MSG(false, "No available connection slots in the network");
+}
+
+/*
+ * Start a connection sequence with an UDS server. The sequence starts by sending an 802.11
+ * authentication frame with SEQ1.
+ */
+void StartConnectionSequence(const MacAddress& server) {
+    ASSERT(connection_status.status == static_cast<u32>(NetworkStatus::NotConnected));
+
+    // TODO(Subv): Handle timeout.
+
+    // Send an authentication frame with SEQ1
+    using Network::WifiPacket;
+    WifiPacket auth_request;
+    auth_request.channel = network_channel;
+    auth_request.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ1);
+    auth_request.destination_address = server;
+    auth_request.type = WifiPacket::PacketType::Authentication;
+
+    SendPacket(auth_request);
+}
+
+/// Sends an Association Response frame to the specified mac address
+void SendAssociationResponseFrame(const MacAddress& address) {
+    ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost));
+
+    using Network::WifiPacket;
+    WifiPacket assoc_response;
+    assoc_response.channel = network_channel;
+    // TODO(Subv): This will cause multiple clients to end up with the same association id, but
+    // we're not using that for anything.
+    u16 association_id = 1;
+    assoc_response.data = GenerateAssocResponseFrame(AssocStatus::Successful, association_id,
+                                                     network_info.network_id);
+    assoc_response.destination_address = address;
+    assoc_response.type = WifiPacket::PacketType::AssociationResponse;
+
+    SendPacket(assoc_response);
+}
+
+/*
+ * Handles the authentication request frame and sends the authentication response and association
+ * response frames. Once an Authentication frame with SEQ1 is received by the server, it responds
+ * with an Authentication frame containing SEQ2, and immediately sends an Association response frame
+ * containing the details of the access point and the assigned association id for the new client.
+ */
+void HandleAuthenticationFrame(const Network::WifiPacket& packet) {
+    // Only the SEQ1 auth frame is handled here, the SEQ2 frame doesn't need any special behavior
+    if (GetAuthenticationSeqNumber(packet.data) == AuthenticationSeq::SEQ1) {
+        ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost));
+
+        // Respond with an authentication response frame with SEQ2
+        using Network::WifiPacket;
+        WifiPacket auth_request;
+        auth_request.channel = network_channel;
+        auth_request.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ2);
+        auth_request.destination_address = packet.transmitter_address;
+        auth_request.type = WifiPacket::PacketType::Authentication;
+
+        SendPacket(auth_request);
+
+        SendAssociationResponseFrame(packet.transmitter_address);
+    }
+}
+
 /// Callback to parse and handle a received wifi packet.
 void OnWifiPacketReceived(const Network::WifiPacket& packet) {
     switch (packet.type) {
     case Network::WifiPacket::PacketType::Beacon:
         HandleBeaconFrame(packet);
         break;
+    case Network::WifiPacket::PacketType::Authentication:
+        HandleAuthenticationFrame(packet);
+        break;
     }
 }
 
@@ -677,23 +760,6 @@ static void BeaconBroadcastCallback(u64 userdata, int cycles_late) {
                               beacon_broadcast_event, 0);
 }
 
-/*
- * Returns an available index in the nodes array for the
- * currently-hosted UDS network.
- */
-static u32 GetNextAvailableNodeId() {
-    ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost),
-               "Can not accept clients if we're not hosting a network");
-
-    for (unsigned index = 0; index < connection_status.max_nodes; ++index) {
-        if ((connection_status.node_bitmask & (1 << index)) == 0)
-            return index;
-    }
-
-    // Any connection attempts to an already full network should have been refused.
-    ASSERT_MSG(false, "No available connection slots in the network");
-}
-
 /*
  * Called when a client connects to an UDS network we're hosting,
  * updates the connection status and signals the update event.

From f64cd87604b7a760e2832c76938d83ec6a284b22 Mon Sep 17 00:00:00 2001
From: Subv <subv2112@gmail.com>
Date: Mon, 17 Jul 2017 09:51:40 -0500
Subject: [PATCH 4/4] Services/UDS: Remove an old duplicated declaration of
 WifiPacket.

---
 src/core/hle/service/nwm/uds_beacon.cpp |  3 ---
 src/core/hle/service/nwm/uds_beacon.h   | 19 -------------------
 2 files changed, 22 deletions(-)

diff --git a/src/core/hle/service/nwm/uds_beacon.cpp b/src/core/hle/service/nwm/uds_beacon.cpp
index 6332b404c..552eaf65e 100644
--- a/src/core/hle/service/nwm/uds_beacon.cpp
+++ b/src/core/hle/service/nwm/uds_beacon.cpp
@@ -325,8 +325,5 @@ std::vector<u8> GenerateBeaconFrame(const NetworkInfo& network_info, const NodeL
     return buffer;
 }
 
-std::deque<WifiPacket> GetReceivedPackets(WifiPacket::PacketType type, const MacAddress& sender) {
-    return {};
-}
 } // namespace NWM
 } // namespace Service
diff --git a/src/core/hle/service/nwm/uds_beacon.h b/src/core/hle/service/nwm/uds_beacon.h
index c726b04d9..50cc76da2 100644
--- a/src/core/hle/service/nwm/uds_beacon.h
+++ b/src/core/hle/service/nwm/uds_beacon.h
@@ -124,20 +124,6 @@ struct BeaconData {
 
 static_assert(sizeof(BeaconData) == 0x12, "BeaconData has incorrect size.");
 
-/// Information about a received WiFi packet.
-/// Acts as our own 802.11 header.
-struct WifiPacket {
-    enum class PacketType { Beacon, Data };
-
-    PacketType type; ///< The type of 802.11 frame, Beacon / Data.
-
-    /// Raw 802.11 frame data, starting at the management frame header for management frames.
-    std::vector<u8> data;
-    MacAddress transmitter_address; ///< Mac address of the transmitter.
-    MacAddress destination_address; ///< Mac address of the receiver.
-    u8 channel;                     ///< WiFi channel where this frame was transmitted.
-};
-
 /**
  * Decrypts the beacon data buffer for the network described by `network_info`.
  */
@@ -150,10 +136,5 @@ void DecryptBeaconData(const NetworkInfo& network_info, std::vector<u8>& buffer)
  */
 std::vector<u8> GenerateBeaconFrame(const NetworkInfo& network_info, const NodeList& nodes);
 
-/**
- * Returns a list of received 802.11 frames from the specified sender
- * matching the type since the last call.
- */
-std::deque<WifiPacket> GetReceivedPackets(WifiPacket::PacketType type, const MacAddress& sender);
 } // namespace NWM
 } // namespace Service