Services/UDS: Handle the rest of the connection sequence. (#2963)
Services/UDS: Handle the rest of the connection sequence.
This commit is contained in:
		| @@ -15,6 +15,7 @@ | ||||
| #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" | ||||
| @@ -100,6 +101,20 @@ 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) { | ||||
| @@ -143,18 +158,88 @@ void HandleAssociationResponseFrame(const Network::WifiPacket& packet) { | ||||
|     SendPacket(eapol_start); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * 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; | ||||
| 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; | ||||
|         } | ||||
|  | ||||
|     // Any connection attempts to an already full network should have been refused. | ||||
|     ASSERT_MSG(false, "No available connection slots in the network"); | ||||
|         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(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* | ||||
| @@ -238,6 +323,17 @@ void HandleAuthenticationFrame(const Network::WifiPacket& packet) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| 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) { | ||||
| @@ -250,6 +346,9 @@ void OnWifiPacketReceived(const Network::WifiPacket& packet) { | ||||
|     case Network::WifiPacket::PacketType::AssociationResponse: | ||||
|         HandleAssociationResponseFrame(packet); | ||||
|         break; | ||||
|     case Network::WifiPacket::PacketType::Data: | ||||
|         HandleDataFrame(packet); | ||||
|         break; | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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> | ||||
| @@ -277,10 +278,10 @@ std::vector<u8> GenerateDataPayload(const std::vector<u8>& data, u8 channel, u16 | ||||
| std::vector<u8> GenerateEAPoLStartFrame(u16 association_id, const NodeInfo& node_info) { | ||||
|     EAPoLStartPacket eapol_start{}; | ||||
|     eapol_start.association_id = association_id; | ||||
|     eapol_start.friend_code_seed = node_info.friend_code_seed; | ||||
|     eapol_start.node.friend_code_seed = node_info.friend_code_seed; | ||||
|  | ||||
|     for (int i = 0; i < node_info.username.size(); ++i) | ||||
|         eapol_start.username[i] = node_info.username[i]; | ||||
|     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 | ||||
| @@ -295,5 +296,78 @@ std::vector<u8> GenerateEAPoLStartFrame(u16 association_id, const NodeInfo& node | ||||
|     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 | ||||
|   | ||||
| @@ -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,16 @@ 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; | ||||
|  | ||||
| /* | ||||
| @@ -78,16 +89,28 @@ struct EAPoLStartPacket { | ||||
|     // This value is hardcoded to 1 in the NWM module. | ||||
|     u16_be unknown = 1; | ||||
|     INSERT_PADDING_BYTES(2); | ||||
|  | ||||
|     u64_be friend_code_seed; | ||||
|     std::array<u16_be, 10> username; | ||||
|     INSERT_PADDING_BYTES(4); | ||||
|     u16_be network_node_id; | ||||
|     INSERT_PADDING_BYTES(6); | ||||
|     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. | ||||
| @@ -102,5 +125,40 @@ std::vector<u8> GenerateDataPayload(const std::vector<u8>& data, u8 channel, u16 | ||||
|  */ | ||||
| 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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user