summaryrefslogtreecommitdiffstats
path: root/src/core/hle/service/nwm
diff options
context:
space:
mode:
authorbunnei <bunneidev@gmail.com>2017-10-10 05:56:20 +0200
committerbunnei <bunneidev@gmail.com>2017-10-10 05:56:20 +0200
commitb1d5db1cf60344b6b081c9d03cb6ccc3264326cd (patch)
treefde377c4ba3c0f92c032e6f5ec8627aae37270ef /src/core/hle/service/nwm
parentloader: Various improvements for NSO/NRO loaders. (diff)
parentMerge pull request #2996 from MerryMage/split-travis (diff)
downloadyuzu-b1d5db1cf60344b6b081c9d03cb6ccc3264326cd.tar
yuzu-b1d5db1cf60344b6b081c9d03cb6ccc3264326cd.tar.gz
yuzu-b1d5db1cf60344b6b081c9d03cb6ccc3264326cd.tar.bz2
yuzu-b1d5db1cf60344b6b081c9d03cb6ccc3264326cd.tar.lz
yuzu-b1d5db1cf60344b6b081c9d03cb6ccc3264326cd.tar.xz
yuzu-b1d5db1cf60344b6b081c9d03cb6ccc3264326cd.tar.zst
yuzu-b1d5db1cf60344b6b081c9d03cb6ccc3264326cd.zip
Diffstat (limited to 'src/core/hle/service/nwm')
-rw-r--r--src/core/hle/service/nwm/nwm_uds.cpp485
-rw-r--r--src/core/hle/service/nwm/nwm_uds.h12
-rw-r--r--src/core/hle/service/nwm/uds_beacon.cpp7
-rw-r--r--src/core/hle/service/nwm/uds_beacon.h30
-rw-r--r--src/core/hle/service/nwm/uds_connection.cpp88
-rw-r--r--src/core/hle/service/nwm/uds_connection.h56
-rw-r--r--src/core/hle/service/nwm/uds_data.cpp103
-rw-r--r--src/core/hle/service/nwm/uds_data.h86
8 files changed, 747 insertions, 120 deletions
diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp
index 6dbdff044..87a6b0eca 100644
--- a/src/core/hle/service/nwm/nwm_uds.cpp
+++ b/src/core/hle/service/nwm/nwm_uds.cpp
@@ -2,8 +2,11 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <array>
#include <cstring>
+#include <list>
+#include <mutex>
#include <unordered_map>
#include <vector>
#include "common/common_types.h"
@@ -12,11 +15,14 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/shared_memory.h"
+#include "core/hle/lock.h"
#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 {
@@ -34,9 +40,12 @@ static ConnectionStatus connection_status{};
/* Node information about the current network.
* The amount of elements in this vector is always the maximum number
* of nodes specified in the network configuration.
- * The first node is always the host, so this always contains at least 1 entry.
+ * The first node is always the host.
*/
-static NodeList node_info(1);
+static NodeList node_info;
+
+// Node information about our own system.
+static NodeInfo current_node;
// Mapping of bind node ids to their respective events.
static std::unordered_map<u32, Kernel::SharedPtr<Kernel::Event>> bind_node_events;
@@ -51,6 +60,298 @@ 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 connection status between the emulation thread and the
+// network thread.
+static std::mutex connection_status_mutex;
+
+// 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::list<Network::WifiPacket> received_beacons;
+
+/**
+ * Returns a list of received 802.11 beacon frames from the specified sender since the last call.
+ */
+std::list<Network::WifiPacket> GetReceivedBeacons(const MacAddress& sender) {
+ std::lock_guard<std::mutex> lock(beacon_mutex);
+ if (sender != Network::BroadcastMac) {
+ std::list<Network::WifiPacket> filtered_list;
+ const auto beacon = std::find_if(received_beacons.begin(), received_beacons.end(),
+ [&sender](const Network::WifiPacket& packet) {
+ return packet.transmitter_address == sender;
+ });
+ if (beacon != received_beacons.end()) {
+ filtered_list.push_back(*beacon);
+ // TODO(B3N30): Check if the complete deque is cleared or just the fetched entries
+ received_beacons.erase(beacon);
+ }
+ return filtered_list;
+ }
+ return std::move(received_beacons);
+}
+
+/// Sends a WifiPacket to the room we're currently connected to.
+void SendPacket(Network::WifiPacket& packet) {
+ // TODO(Subv): Implement.
+}
+
+/*
+ * Returns an available index in the nodes array for the
+ * currently-hosted UDS network.
+ */
+static u16 GetNextAvailableNodeId() {
+ 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");
+}
+
+// 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);
+ const auto unique_beacon =
+ std::find_if(received_beacons.begin(), received_beacons.end(),
+ [&packet](const Network::WifiPacket& new_packet) {
+ return new_packet.transmitter_address == packet.transmitter_address;
+ });
+ if (unique_beacon != received_beacons.end()) {
+ // We already have a beacon from the same mac in the deque, remove the old one;
+ received_beacons.erase(unique_beacon);
+ }
+
+ received_beacons.emplace_back(packet);
+
+ // Discard old beacons if the buffer is full.
+ if (received_beacons.size() > MaxBeaconFrames)
+ received_beacons.pop_front();
+}
+
+void HandleAssociationResponseFrame(const Network::WifiPacket& packet) {
+ auto assoc_result = GetAssociationResult(packet.data);
+
+ ASSERT_MSG(std::get<AssocStatus>(assoc_result) == AssocStatus::Successful,
+ "Could not join network");
+ {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
+ ASSERT(connection_status.status == static_cast<u32>(NetworkStatus::Connecting));
+ }
+
+ // Send the EAPoL-Start packet to the server.
+ using Network::WifiPacket;
+ WifiPacket eapol_start;
+ eapol_start.channel = network_channel;
+ eapol_start.data = GenerateEAPoLStartFrame(std::get<u16>(assoc_result), current_node);
+ // TODO(B3N30): Encrypt the packet.
+ eapol_start.destination_address = packet.transmitter_address;
+ eapol_start.type = WifiPacket::PacketType::Data;
+
+ SendPacket(eapol_start);
+}
+
+static void HandleEAPoLPacket(const Network::WifiPacket& packet) {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
+
+ if (GetEAPoLFrameType(packet.data) == EAPoLStartMagic) {
+ if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) {
+ LOG_DEBUG(Service_NWM, "Connection sequence aborted, because connection status is %u",
+ connection_status.status);
+ return;
+ }
+
+ auto node = DeserializeNodeInfoFromFrame(packet.data);
+
+ if (connection_status.max_nodes == connection_status.total_nodes) {
+ // Reject connection attempt
+ LOG_ERROR(Service_NWM, "Reached maximum nodes, but reject packet wasn't sent.");
+ // TODO(B3N30): Figure out what packet is sent here
+ return;
+ }
+
+ // Get an unused network node id
+ u16 node_id = GetNextAvailableNodeId();
+ node.network_node_id = node_id + 1;
+
+ connection_status.node_bitmask |= 1 << node_id;
+ connection_status.changed_nodes |= 1 << node_id;
+ connection_status.nodes[node_id] = node.network_node_id;
+ connection_status.total_nodes++;
+
+ u8 current_nodes = network_info.total_nodes;
+ node_info[current_nodes] = node;
+
+ network_info.total_nodes++;
+
+ // Send the EAPoL-Logoff packet.
+ using Network::WifiPacket;
+ WifiPacket eapol_logoff;
+ eapol_logoff.channel = network_channel;
+ eapol_logoff.data =
+ GenerateEAPoLLogoffFrame(packet.transmitter_address, node.network_node_id, node_info,
+ network_info.max_nodes, network_info.total_nodes);
+ // TODO(Subv): Encrypt the packet.
+ eapol_logoff.destination_address = packet.transmitter_address;
+ eapol_logoff.type = WifiPacket::PacketType::Data;
+
+ SendPacket(eapol_logoff);
+ // TODO(B3N30): Broadcast updated node list
+ // The 3ds does this presumably to support spectators.
+ std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ connection_status_event->Signal();
+ } else {
+ if (connection_status.status != static_cast<u32>(NetworkStatus::NotConnected)) {
+ LOG_DEBUG(Service_NWM, "Connection sequence aborted, because connection status is %u",
+ connection_status.status);
+ return;
+ }
+ auto logoff = ParseEAPoLLogoffFrame(packet.data);
+
+ network_info.total_nodes = logoff.connected_nodes;
+ network_info.max_nodes = logoff.max_nodes;
+
+ connection_status.network_node_id = logoff.assigned_node_id;
+ connection_status.total_nodes = logoff.connected_nodes;
+ connection_status.max_nodes = logoff.max_nodes;
+
+ node_info.clear();
+ node_info.reserve(network_info.max_nodes);
+ for (size_t index = 0; index < logoff.connected_nodes; ++index) {
+ connection_status.node_bitmask |= 1 << index;
+ connection_status.changed_nodes |= 1 << index;
+ connection_status.nodes[index] = logoff.nodes[index].network_node_id;
+
+ node_info.emplace_back(DeserializeNodeInfo(logoff.nodes[index]));
+ }
+
+ // We're now connected, signal the application
+ connection_status.status = static_cast<u32>(NetworkStatus::ConnectedAsClient);
+ // Some games require ConnectToNetwork to block, for now it doesn't
+ // If blocking is implemented this lock needs to be changed,
+ // otherwise it might cause deadlocks
+ std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ connection_status_event->Signal();
+ }
+}
+
+/*
+ * 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) {
+ using Network::WifiPacket;
+ WifiPacket auth_request;
+ {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
+ ASSERT(connection_status.status == static_cast<u32>(NetworkStatus::NotConnected));
+
+ // TODO(Subv): Handle timeout.
+
+ // Send an authentication frame with SEQ1
+ 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) {
+ using Network::WifiPacket;
+ WifiPacket assoc_response;
+
+ {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
+ if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) {
+ LOG_ERROR(Service_NWM, "Connection sequence aborted, because connection status is %u",
+ connection_status.status);
+ return;
+ }
+
+ 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) {
+ using Network::WifiPacket;
+ WifiPacket auth_request;
+ {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
+ if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) {
+ LOG_ERROR(Service_NWM,
+ "Connection sequence aborted, because connection status is %u",
+ connection_status.status);
+ return;
+ }
+
+ // Respond with an authentication response frame with SEQ2
+ 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);
+ }
+}
+
+static void HandleDataFrame(const Network::WifiPacket& packet) {
+ switch (GetFrameEtherType(packet.data)) {
+ case EtherType::EAPoL:
+ HandleEAPoLPacket(packet);
+ break;
+ case EtherType::SecureData:
+ // TODO(B3N30): Handle SecureData packets
+ break;
+ }
+}
+
+/// 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;
+ case Network::WifiPacket::PacketType::AssociationResponse:
+ HandleAssociationResponseFrame(packet);
+ break;
+ case Network::WifiPacket::PacketType::Data:
+ HandleDataFrame(packet);
+ break;
+ }
+}
+
/**
* NWM_UDS::Shutdown service function
* Inputs:
@@ -111,11 +412,10 @@ 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();
+ data_reply_header.total_entries = static_cast<u32>(beacons.size());
data_reply_header.max_output_size = out_buffer_size;
Memory::WriteBlock(current_buffer_pos, &data_reply_header, sizeof(BeaconDataReplyHeader));
@@ -125,8 +425,8 @@ static void RecvBeaconBroadcastData(Interface* self) {
for (const auto& beacon : beacons) {
BeaconEntryHeader entry{};
// TODO(Subv): Figure out what this size is used for.
- entry.unk_size = sizeof(BeaconEntryHeader) + beacon.data.size();
- entry.total_size = sizeof(BeaconEntryHeader) + beacon.data.size();
+ entry.unk_size = static_cast<u32>(sizeof(BeaconEntryHeader) + beacon.data.size());
+ entry.total_size = static_cast<u32>(sizeof(BeaconEntryHeader) + beacon.data.size());
entry.wifi_channel = beacon.channel;
entry.header_size = sizeof(BeaconEntryHeader);
entry.mac_address = beacon.transmitter_address;
@@ -137,9 +437,9 @@ static void RecvBeaconBroadcastData(Interface* self) {
current_buffer_pos += sizeof(BeaconEntryHeader);
Memory::WriteBlock(current_buffer_pos, beacon.data.data(), beacon.data.size());
- current_buffer_pos += beacon.data.size();
+ current_buffer_pos += static_cast<VAddr>(beacon.data.size());
- total_size += sizeof(BeaconEntryHeader) + beacon.data.size();
+ total_size += static_cast<u32>(sizeof(BeaconEntryHeader) + beacon.data.size());
}
// Update the total size in the structure and write it to the buffer again.
@@ -174,7 +474,7 @@ static void InitializeWithVersion(Interface* self) {
u32 sharedmem_size = rp.Pop<u32>();
// Update the node information with the data the game gave us.
- rp.PopRaw(node_info[0]);
+ rp.PopRaw(current_node);
u16 version = rp.Pop<u16>();
@@ -184,15 +484,22 @@ static void InitializeWithVersion(Interface* self) {
ASSERT_MSG(recv_buffer_memory->size == sharedmem_size, "Invalid shared memory size.");
- // Reset the connection status, it contains all zeros after initialization,
- // except for the actual status value.
- connection_status = {};
- connection_status.status = static_cast<u32>(NetworkStatus::NotConnected);
+ {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
+
+ // Reset the connection status, it contains all zeros after initialization,
+ // except for the actual status value.
+ connection_status = {};
+ connection_status.status = static_cast<u32>(NetworkStatus::NotConnected);
+ }
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
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);
}
@@ -214,12 +521,16 @@ static void GetConnectionStatus(Interface* self) {
IPC::RequestBuilder rb = rp.MakeBuilder(13, 0);
rb.Push(RESULT_SUCCESS);
- rb.PushRaw(connection_status);
-
- // Reset the bitmask of changed nodes after each call to this
- // function to prevent falsely informing games of outstanding
- // changes in subsequent calls.
- connection_status.changed_nodes = 0;
+ {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
+ rb.PushRaw(connection_status);
+
+ // Reset the bitmask of changed nodes after each call to this
+ // function to prevent falsely informing games of outstanding
+ // changes in subsequent calls.
+ // TODO(Subv): Find exactly where the NWM module resets this value.
+ connection_status.changed_nodes = 0;
+ }
LOG_DEBUG(Service_NWM, "called");
}
@@ -300,31 +611,36 @@ static void BeginHostingNetwork(Interface* self) {
// The real UDS module throws a fatal error if this assert fails.
ASSERT_MSG(network_info.max_nodes > 1, "Trying to host a network of only one member.");
- connection_status.status = static_cast<u32>(NetworkStatus::ConnectedAsHost);
-
- // Ensure the application data size is less than the maximum value.
- ASSERT_MSG(network_info.application_data_size <= ApplicationDataSize, "Data size is too big.");
-
- // Set up basic information for this network.
- network_info.oui_value = NintendoOUI;
- network_info.oui_type = static_cast<u8>(NintendoTagId::NetworkInfo);
-
- connection_status.max_nodes = network_info.max_nodes;
-
- // Resize the nodes list to hold max_nodes.
- node_info.resize(network_info.max_nodes);
-
- // There's currently only one node in the network (the host).
- connection_status.total_nodes = 1;
- network_info.total_nodes = 1;
- // The host is always the first node
- connection_status.network_node_id = 1;
- node_info[0].network_node_id = 1;
- connection_status.nodes[0] = connection_status.network_node_id;
- // Set the bit 0 in the nodes bitmask to indicate that node 1 is already taken.
- connection_status.node_bitmask |= 1;
- // Notify the application that the first node was set.
- connection_status.changed_nodes |= 1;
+ {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
+ connection_status.status = static_cast<u32>(NetworkStatus::ConnectedAsHost);
+
+ // Ensure the application data size is less than the maximum value.
+ ASSERT_MSG(network_info.application_data_size <= ApplicationDataSize,
+ "Data size is too big.");
+
+ // Set up basic information for this network.
+ network_info.oui_value = NintendoOUI;
+ network_info.oui_type = static_cast<u8>(NintendoTagId::NetworkInfo);
+
+ connection_status.max_nodes = network_info.max_nodes;
+
+ // Resize the nodes list to hold max_nodes.
+ node_info.resize(network_info.max_nodes);
+
+ // There's currently only one node in the network (the host).
+ connection_status.total_nodes = 1;
+ network_info.total_nodes = 1;
+ // The host is always the first node
+ connection_status.network_node_id = 1;
+ current_node.network_node_id = 1;
+ connection_status.nodes[0] = connection_status.network_node_id;
+ // Set the bit 0 in the nodes bitmask to indicate that node 1 is already taken.
+ connection_status.node_bitmask |= 1;
+ // Notify the application that the first node was set.
+ connection_status.changed_nodes |= 1;
+ node_info[0] = current_node;
+ }
// If the game has a preferred channel, use that instead.
if (network_info.channel != 0)
@@ -361,9 +677,13 @@ static void DestroyNetwork(Interface* self) {
// Unschedule the beacon broadcast event.
CoreTiming::UnscheduleEvent(beacon_broadcast_event, 0);
- // TODO(Subv): Check if connection_status is indeed reset after this call.
- connection_status = {};
- connection_status.status = static_cast<u8>(NetworkStatus::NotConnected);
+ {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
+
+ // TODO(Subv): Check if connection_status is indeed reset after this call.
+ connection_status = {};
+ connection_status.status = static_cast<u8>(NetworkStatus::NotConnected);
+ }
connection_status_event->Signal();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
@@ -406,17 +726,24 @@ static void SendTo(Interface* self) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
- if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsClient) &&
- connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) {
- rb.Push(ResultCode(ErrorDescription::NotAuthorized, ErrorModule::UDS,
- ErrorSummary::InvalidState, ErrorLevel::Status));
- return;
- }
+ u16 network_node_id;
- if (dest_node_id == connection_status.network_node_id) {
- rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::UDS,
- ErrorSummary::WrongArgument, ErrorLevel::Status));
- return;
+ {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
+ if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsClient) &&
+ connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) {
+ rb.Push(ResultCode(ErrorDescription::NotAuthorized, ErrorModule::UDS,
+ ErrorSummary::InvalidState, ErrorLevel::Status));
+ return;
+ }
+
+ if (dest_node_id == connection_status.network_node_id) {
+ rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::UDS,
+ ErrorSummary::WrongArgument, ErrorLevel::Status));
+ return;
+ }
+
+ network_node_id = connection_status.network_node_id;
}
// TODO(Subv): Do something with the flags.
@@ -433,8 +760,8 @@ static void SendTo(Interface* self) {
// TODO(Subv): Increment the sequence number after each sent packet.
u16 sequence_number = 0;
- std::vector<u8> data_payload = GenerateDataPayload(
- data, data_channel, dest_node_id, connection_status.network_node_id, sequence_number);
+ std::vector<u8> data_payload =
+ GenerateDataPayload(data, data_channel, dest_node_id, network_node_id, sequence_number);
// TODO(Subv): Retrieve the MAC address of the dest_node_id and our own to encrypt
// and encapsulate the payload.
@@ -461,6 +788,7 @@ static void GetChannel(Interface* self) {
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1A, 0, 0);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
bool is_connected = connection_status.status != static_cast<u32>(NetworkStatus::NotConnected);
u8 channel = is_connected ? network_channel : 0;
@@ -610,37 +938,29 @@ 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);
}
/*
- * 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.
* @param network_node_id Network Node Id of the connecting client.
*/
void OnClientConnected(u16 network_node_id) {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost),
"Can not accept clients if we're not hosting a network");
ASSERT_MSG(connection_status.total_nodes < connection_status.max_nodes,
@@ -655,7 +975,7 @@ void OnClientConnected(u16 network_node_id) {
}
const Interface::FunctionInfo FunctionTable[] = {
- {0x00010442, nullptr, "Initialize (deprecated)"},
+ {0x000102C2, nullptr, "Initialize (deprecated)"},
{0x00020000, nullptr, "Scrap"},
{0x00030000, Shutdown, "Shutdown"},
{0x00040402, nullptr, "CreateNetwork (deprecated)"},
@@ -702,8 +1022,11 @@ NWM_UDS::~NWM_UDS() {
connection_status_event = nullptr;
recv_buffer_memory = nullptr;
- connection_status = {};
- connection_status.status = static_cast<u32>(NetworkStatus::NotConnected);
+ {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
+ connection_status = {};
+ connection_status.status = static_cast<u32>(NetworkStatus::NotConnected);
+ }
CoreTiming::UnscheduleEvent(beacon_broadcast_event, 0);
}
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.cpp b/src/core/hle/service/nwm/uds_beacon.cpp
index 6332b404c..73a80d940 100644
--- a/src/core/hle/service/nwm/uds_beacon.cpp
+++ b/src/core/hle/service/nwm/uds_beacon.cpp
@@ -243,7 +243,7 @@ std::vector<u8> GenerateNintendoFirstEncryptedDataTag(const NetworkInfo& network
EncryptedDataTag tag{};
tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific);
- tag.header.length = sizeof(tag) - sizeof(TagHeader) + payload_size;
+ tag.header.length = static_cast<u8>(sizeof(tag) - sizeof(TagHeader) + payload_size);
tag.oui_type = static_cast<u8>(NintendoTagId::EncryptedData0);
tag.oui = NintendoOUI;
@@ -279,7 +279,7 @@ std::vector<u8> GenerateNintendoSecondEncryptedDataTag(const NetworkInfo& networ
EncryptedDataTag tag{};
tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific);
- tag.header.length = tag_length;
+ tag.header.length = static_cast<u8>(tag_length);
tag.oui_type = static_cast<u8>(NintendoTagId::EncryptedData1);
tag.oui = NintendoOUI;
@@ -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 caacf4c6f..50cc76da2 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.
@@ -135,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`.
*/
@@ -161,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
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..c74f51253
--- /dev/null
+++ b/src/core/hle/service/nwm/uds_connection.cpp
@@ -0,0 +1,88 @@
+// 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;
+}
+
+std::tuple<AssocStatus, u16> GetAssociationResult(const std::vector<u8>& body) {
+ AssociationResponseFrame frame;
+ memcpy(&frame, body.data(), sizeof(frame));
+
+ constexpr u16 AssociationIdMask = 0x3FFF;
+ return std::make_tuple(static_cast<AssocStatus>(frame.status_code),
+ frame.assoc_id & AssociationIdMask);
+}
+
+} // 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..a664f8471
--- /dev/null
+++ b/src/core/hle/service/nwm/uds_connection.h
@@ -0,0 +1,56 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <tuple>
+#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);
+
+/// Returns a tuple of (association status, association id) from the body of an AssociationResponse
+/// frame.
+std::tuple<AssocStatus, u16> GetAssociationResult(const std::vector<u8>& body);
+
+} // namespace NWM
+} // namespace Service
diff --git a/src/core/hle/service/nwm/uds_data.cpp b/src/core/hle/service/nwm/uds_data.cpp
index 8c6742dba..4b389710f 100644
--- a/src/core/hle/service/nwm/uds_data.cpp
+++ b/src/core/hle/service/nwm/uds_data.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <cstring>
#include <cryptopp/aes.h>
#include <cryptopp/ccm.h>
@@ -197,7 +198,7 @@ static std::vector<u8> DecryptDataFrame(const std::vector<u8>& encrypted_payload
df.ChannelMessageEnd(CryptoPP::DEFAULT_CHANNEL);
df.SetRetrievalChannel(CryptoPP::DEFAULT_CHANNEL);
- int size = df.MaxRetrievable();
+ size_t size = df.MaxRetrievable();
std::vector<u8> pdata(size);
df.Get(pdata.data(), size);
@@ -251,7 +252,7 @@ static std::vector<u8> EncryptDataFrame(const std::vector<u8>& payload,
df.SetRetrievalChannel(CryptoPP::DEFAULT_CHANNEL);
- int size = df.MaxRetrievable();
+ size_t size = df.MaxRetrievable();
std::vector<u8> cipher(size);
df.Get(cipher.data(), size);
@@ -266,13 +267,107 @@ static std::vector<u8> EncryptDataFrame(const std::vector<u8>& payload,
std::vector<u8> GenerateDataPayload(const std::vector<u8>& data, u8 channel, u16 dest_node,
u16 src_node, u16 sequence_number) {
std::vector<u8> buffer = GenerateLLCHeader(EtherType::SecureData);
- std::vector<u8> securedata_header =
- GenerateSecureDataHeader(data.size(), channel, dest_node, src_node, sequence_number);
+ std::vector<u8> securedata_header = GenerateSecureDataHeader(
+ static_cast<u16>(data.size()), channel, dest_node, src_node, sequence_number);
buffer.insert(buffer.end(), securedata_header.begin(), securedata_header.end());
buffer.insert(buffer.end(), data.begin(), data.end());
return buffer;
}
+std::vector<u8> GenerateEAPoLStartFrame(u16 association_id, const NodeInfo& node_info) {
+ EAPoLStartPacket eapol_start{};
+ eapol_start.association_id = association_id;
+ eapol_start.node.friend_code_seed = node_info.friend_code_seed;
+
+ std::copy(node_info.username.begin(), node_info.username.end(),
+ eapol_start.node.username.begin());
+
+ // Note: The network_node_id and unknown bytes seem to be uninitialized in the NWM module.
+ // TODO(B3N30): The last 8 bytes seem to have a fixed value of 07 88 15 00 04 e9 13 00 in
+ // EAPoL-Start packets from different 3DSs to the same host during a Super Smash Bros. 4 game.
+ // Find out what that means.
+
+ std::vector<u8> eapol_buffer(sizeof(EAPoLStartPacket));
+ std::memcpy(eapol_buffer.data(), &eapol_start, sizeof(eapol_start));
+
+ std::vector<u8> buffer = GenerateLLCHeader(EtherType::EAPoL);
+ buffer.insert(buffer.end(), eapol_buffer.begin(), eapol_buffer.end());
+ return buffer;
+}
+
+EtherType GetFrameEtherType(const std::vector<u8>& frame) {
+ LLCHeader header;
+ std::memcpy(&header, frame.data(), sizeof(header));
+
+ u16 ethertype = header.protocol;
+ return static_cast<EtherType>(ethertype);
+}
+
+u16 GetEAPoLFrameType(const std::vector<u8>& frame) {
+ // Ignore the LLC header
+ u16_be eapol_type;
+ std::memcpy(&eapol_type, frame.data() + sizeof(LLCHeader), sizeof(eapol_type));
+ return eapol_type;
+}
+
+NodeInfo DeserializeNodeInfoFromFrame(const std::vector<u8>& frame) {
+ EAPoLStartPacket eapol_start;
+
+ // Skip the LLC header
+ std::memcpy(&eapol_start, frame.data() + sizeof(LLCHeader), sizeof(eapol_start));
+
+ NodeInfo node{};
+ node.friend_code_seed = eapol_start.node.friend_code_seed;
+
+ std::copy(eapol_start.node.username.begin(), eapol_start.node.username.end(),
+ node.username.begin());
+
+ return node;
+}
+
+NodeInfo DeserializeNodeInfo(const EAPoLNodeInfo& node) {
+ NodeInfo node_info{};
+ node_info.friend_code_seed = node.friend_code_seed;
+ node_info.network_node_id = node.network_node_id;
+
+ std::copy(node.username.begin(), node.username.end(), node_info.username.begin());
+
+ return node_info;
+}
+
+std::vector<u8> GenerateEAPoLLogoffFrame(const MacAddress& mac_address, u16 network_node_id,
+ const NodeList& nodes, u8 max_nodes, u8 total_nodes) {
+ EAPoLLogoffPacket eapol_logoff{};
+ eapol_logoff.assigned_node_id = network_node_id;
+ eapol_logoff.connected_nodes = total_nodes;
+ eapol_logoff.max_nodes = max_nodes;
+
+ for (size_t index = 0; index < total_nodes; ++index) {
+ const auto& node_info = nodes[index];
+ auto& node = eapol_logoff.nodes[index];
+
+ node.friend_code_seed = node_info.friend_code_seed;
+ node.network_node_id = node_info.network_node_id;
+
+ std::copy(node_info.username.begin(), node_info.username.end(), node.username.begin());
+ }
+
+ std::vector<u8> eapol_buffer(sizeof(EAPoLLogoffPacket));
+ std::memcpy(eapol_buffer.data(), &eapol_logoff, sizeof(eapol_logoff));
+
+ std::vector<u8> buffer = GenerateLLCHeader(EtherType::EAPoL);
+ buffer.insert(buffer.end(), eapol_buffer.begin(), eapol_buffer.end());
+ return buffer;
+}
+
+EAPoLLogoffPacket ParseEAPoLLogoffFrame(const std::vector<u8>& frame) {
+ EAPoLLogoffPacket eapol_logoff;
+
+ // Skip the LLC header
+ std::memcpy(&eapol_logoff, frame.data() + sizeof(LLCHeader), sizeof(eapol_logoff));
+ return eapol_logoff;
+}
+
} // namespace NWM
} // namespace Service
diff --git a/src/core/hle/service/nwm/uds_data.h b/src/core/hle/service/nwm/uds_data.h
index a23520a41..76bccb1bf 100644
--- a/src/core/hle/service/nwm/uds_data.h
+++ b/src/core/hle/service/nwm/uds_data.h
@@ -8,6 +8,7 @@
#include <vector>
#include "common/common_types.h"
#include "common/swap.h"
+#include "core/hle/service/nwm/uds_beacon.h"
#include "core/hle/service/service.h"
namespace Service {
@@ -67,6 +68,49 @@ struct DataFrameCryptoCTR {
static_assert(sizeof(DataFrameCryptoCTR) == 16, "DataFrameCryptoCTR has the wrong size");
+struct EAPoLNodeInfo {
+ u64_be friend_code_seed;
+ std::array<u16_be, 10> username;
+ INSERT_PADDING_BYTES(4);
+ u16_be network_node_id;
+ INSERT_PADDING_BYTES(6);
+};
+
+static_assert(sizeof(EAPoLNodeInfo) == 0x28, "EAPoLNodeInfo has the wrong size");
+
+constexpr u16 EAPoLStartMagic = 0x201;
+
+/*
+ * Nintendo EAPoLStartPacket, is used to initaliaze a connection between client and host
+ */
+struct EAPoLStartPacket {
+ u16_be magic = EAPoLStartMagic;
+ u16_be association_id;
+ // This value is hardcoded to 1 in the NWM module.
+ u16_be unknown = 1;
+ INSERT_PADDING_BYTES(2);
+ EAPoLNodeInfo node;
+};
+
+static_assert(sizeof(EAPoLStartPacket) == 0x30, "EAPoLStartPacket has the wrong size");
+
+constexpr u16 EAPoLLogoffMagic = 0x202;
+
+struct EAPoLLogoffPacket {
+ u16_be magic = EAPoLLogoffMagic;
+ INSERT_PADDING_BYTES(2);
+ u16_be assigned_node_id;
+ MacAddress client_mac_address;
+ INSERT_PADDING_BYTES(6);
+ u8 connected_nodes;
+ u8 max_nodes;
+ INSERT_PADDING_BYTES(4);
+
+ std::array<EAPoLNodeInfo, UDSMaxNodes> nodes;
+};
+
+static_assert(sizeof(EAPoLLogoffPacket) == 0x298, "EAPoLLogoffPacket has the wrong size");
+
/**
* Generates an unencrypted 802.11 data payload.
* @returns The generated frame payload.
@@ -74,5 +118,47 @@ static_assert(sizeof(DataFrameCryptoCTR) == 16, "DataFrameCryptoCTR has the wron
std::vector<u8> GenerateDataPayload(const std::vector<u8>& data, u8 channel, u16 dest_node,
u16 src_node, u16 sequence_number);
+/*
+ * Generates an unencrypted 802.11 data frame body with the EAPoL-Start format for UDS
+ * communication.
+ * @returns The generated frame body.
+ */
+std::vector<u8> GenerateEAPoLStartFrame(u16 association_id, const NodeInfo& node_info);
+
+/*
+ * Returns the EtherType of the specified 802.11 frame.
+ */
+EtherType GetFrameEtherType(const std::vector<u8>& frame);
+
+/*
+ * Returns the EAPoL type (Start / Logoff) of the specified 802.11 frame.
+ * Note: The frame *must* be an EAPoL frame.
+ */
+u16 GetEAPoLFrameType(const std::vector<u8>& frame);
+
+/*
+ * Returns a deserialized NodeInfo structure from the information inside an EAPoL-Start packet
+ * encapsulated in an 802.11 data frame.
+ */
+NodeInfo DeserializeNodeInfoFromFrame(const std::vector<u8>& frame);
+
+/*
+ * Returns a NodeInfo constructed from the data in the specified EAPoLNodeInfo.
+ */
+NodeInfo DeserializeNodeInfo(const EAPoLNodeInfo& node);
+
+/*
+ * Generates an unencrypted 802.11 data frame body with the EAPoL-Logoff format for UDS
+ * communication.
+ * @returns The generated frame body.
+ */
+std::vector<u8> GenerateEAPoLLogoffFrame(const MacAddress& mac_address, u16 network_node_id,
+ const NodeList& nodes, u8 max_nodes, u8 total_nodes);
+
+/*
+ * Returns a EAPoLLogoffPacket representing the specified 802.11-encapsulated data frame.
+ */
+EAPoLLogoffPacket ParseEAPoLLogoffFrame(const std::vector<u8>& frame);
+
} // namespace NWM
} // namespace Service