diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/core/hle/service/nwm/nwm_uds.cpp | 122 | ||||
| -rw-r--r-- | src/core/hle/service/nwm/nwm_uds.h | 5 | ||||
| -rw-r--r-- | src/core/hle/service/nwm/uds_beacon.cpp | 333 | ||||
| -rw-r--r-- | src/core/hle/service/nwm/uds_beacon.h | 173 |
5 files changed, 602 insertions, 33 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index e404063f0..b894564b6 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt | |||
| @@ -139,6 +139,7 @@ set(SRCS | |||
| 139 | hle/service/nwm/nwm_soc.cpp | 139 | hle/service/nwm/nwm_soc.cpp |
| 140 | hle/service/nwm/nwm_tst.cpp | 140 | hle/service/nwm/nwm_tst.cpp |
| 141 | hle/service/nwm/nwm_uds.cpp | 141 | hle/service/nwm/nwm_uds.cpp |
| 142 | hle/service/nwm/uds_beacon.cpp | ||
| 142 | hle/service/pm_app.cpp | 143 | hle/service/pm_app.cpp |
| 143 | hle/service/ptm/ptm.cpp | 144 | hle/service/ptm/ptm.cpp |
| 144 | hle/service/ptm/ptm_gets.cpp | 145 | hle/service/ptm/ptm_gets.cpp |
| @@ -326,6 +327,7 @@ set(HEADERS | |||
| 326 | hle/service/nwm/nwm_soc.h | 327 | hle/service/nwm/nwm_soc.h |
| 327 | hle/service/nwm/nwm_tst.h | 328 | hle/service/nwm/nwm_tst.h |
| 328 | hle/service/nwm/nwm_uds.h | 329 | hle/service/nwm/nwm_uds.h |
| 330 | hle/service/nwm/uds_beacon.h | ||
| 329 | hle/service/pm_app.h | 331 | hle/service/pm_app.h |
| 330 | hle/service/ptm/ptm.h | 332 | hle/service/ptm/ptm.h |
| 331 | hle/service/ptm/ptm_gets.h | 333 | hle/service/ptm/ptm_gets.h |
diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp index ef6c5ebe3..581816e81 100644 --- a/src/core/hle/service/nwm/nwm_uds.cpp +++ b/src/core/hle/service/nwm/nwm_uds.cpp | |||
| @@ -2,6 +2,7 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <array> | ||
| 5 | #include <cstring> | 6 | #include <cstring> |
| 6 | #include <unordered_map> | 7 | #include <unordered_map> |
| 7 | #include <vector> | 8 | #include <vector> |
| @@ -12,6 +13,7 @@ | |||
| 12 | #include "core/hle/kernel/shared_memory.h" | 13 | #include "core/hle/kernel/shared_memory.h" |
| 13 | #include "core/hle/result.h" | 14 | #include "core/hle/result.h" |
| 14 | #include "core/hle/service/nwm/nwm_uds.h" | 15 | #include "core/hle/service/nwm/nwm_uds.h" |
| 16 | #include "core/hle/service/nwm/uds_beacon.h" | ||
| 15 | #include "core/memory.h" | 17 | #include "core/memory.h" |
| 16 | 18 | ||
| 17 | namespace Service { | 19 | namespace Service { |
| @@ -27,10 +29,12 @@ static Kernel::SharedPtr<Kernel::SharedMemory> recv_buffer_memory; | |||
| 27 | // Connection status of this 3DS. | 29 | // Connection status of this 3DS. |
| 28 | static ConnectionStatus connection_status{}; | 30 | static ConnectionStatus connection_status{}; |
| 29 | 31 | ||
| 30 | // Node information about the current 3DS. | 32 | /* Node information about the current network. |
| 31 | // TODO(Subv): Keep an array of all nodes connected to the network, | 33 | * The amount of elements in this vector is always the maximum number |
| 32 | // that data has to be retransmitted in every beacon frame. | 34 | * of nodes specified in the network configuration. |
| 33 | static NodeInfo node_info; | 35 | * The first node is always the host, so this always contains at least 1 entry. |
| 36 | */ | ||
| 37 | static NodeList node_info(1); | ||
| 34 | 38 | ||
| 35 | // Mapping of bind node ids to their respective events. | 39 | // Mapping of bind node ids to their respective events. |
| 36 | static std::unordered_map<u32, Kernel::SharedPtr<Kernel::Event>> bind_node_events; | 40 | static std::unordered_map<u32, Kernel::SharedPtr<Kernel::Event>> bind_node_events; |
| @@ -82,29 +86,70 @@ static void Shutdown(Interface* self) { | |||
| 82 | * 1 : Result of function, 0 on success, otherwise error code | 86 | * 1 : Result of function, 0 on success, otherwise error code |
| 83 | */ | 87 | */ |
| 84 | static void RecvBeaconBroadcastData(Interface* self) { | 88 | static void RecvBeaconBroadcastData(Interface* self) { |
| 85 | u32* cmd_buff = Kernel::GetCommandBuffer(); | 89 | IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0F, 16, 4); |
| 86 | u32 out_buffer_size = cmd_buff[1]; | ||
| 87 | u32 unk1 = cmd_buff[2]; | ||
| 88 | u32 unk2 = cmd_buff[3]; | ||
| 89 | u32 mac_address = cmd_buff[4]; | ||
| 90 | 90 | ||
| 91 | u32 unk3 = cmd_buff[6]; | 91 | u32 out_buffer_size = rp.Pop<u32>(); |
| 92 | u32 unk1 = rp.Pop<u32>(); | ||
| 93 | u32 unk2 = rp.Pop<u32>(); | ||
| 92 | 94 | ||
| 93 | u32 wlan_comm_id = cmd_buff[15]; | 95 | MacAddress mac_address; |
| 94 | u32 ctr_gen_id = cmd_buff[16]; | 96 | rp.PopRaw(mac_address); |
| 95 | u32 value = cmd_buff[17]; | ||
| 96 | u32 input_handle = cmd_buff[18]; | ||
| 97 | u32 new_buffer_size = cmd_buff[19]; | ||
| 98 | u32 out_buffer_ptr = cmd_buff[20]; | ||
| 99 | 97 | ||
| 100 | cmd_buff[1] = RESULT_SUCCESS.raw; | 98 | rp.Skip(9, false); |
| 101 | 99 | ||
| 102 | LOG_WARNING(Service_NWM, | 100 | u32 wlan_comm_id = rp.Pop<u32>(); |
| 103 | "(STUBBED) called out_buffer_size=0x%08X, unk1=0x%08X, unk2=0x%08X," | 101 | u32 id = rp.Pop<u32>(); |
| 104 | "mac_address=0x%08X, unk3=0x%08X, wlan_comm_id=0x%08X, ctr_gen_id=0x%08X," | 102 | Kernel::Handle input_handle = rp.PopHandle(); |
| 105 | "value=%u, input_handle=0x%08X, new_buffer_size=0x%08X, out_buffer_ptr=0x%08X", | 103 | |
| 106 | out_buffer_size, unk1, unk2, mac_address, unk3, wlan_comm_id, ctr_gen_id, value, | 104 | size_t desc_size; |
| 107 | input_handle, new_buffer_size, out_buffer_ptr); | 105 | const VAddr out_buffer_ptr = rp.PopMappedBuffer(&desc_size); |
| 106 | ASSERT(desc_size == out_buffer_size); | ||
| 107 | |||
| 108 | VAddr current_buffer_pos = out_buffer_ptr; | ||
| 109 | u32 total_size = sizeof(BeaconDataReplyHeader); | ||
| 110 | |||
| 111 | // Retrieve all beacon frames that were received from the desired mac address. | ||
| 112 | std::deque<WifiPacket> beacons = | ||
| 113 | GetReceivedPackets(WifiPacket::PacketType::Beacon, mac_address); | ||
| 114 | |||
| 115 | BeaconDataReplyHeader data_reply_header{}; | ||
| 116 | data_reply_header.total_entries = beacons.size(); | ||
| 117 | data_reply_header.max_output_size = out_buffer_size; | ||
| 118 | |||
| 119 | Memory::WriteBlock(current_buffer_pos, &data_reply_header, sizeof(BeaconDataReplyHeader)); | ||
| 120 | current_buffer_pos += sizeof(BeaconDataReplyHeader); | ||
| 121 | |||
| 122 | // Write each of the received beacons into the buffer | ||
| 123 | for (const auto& beacon : beacons) { | ||
| 124 | BeaconEntryHeader entry{}; | ||
| 125 | // TODO(Subv): Figure out what this size is used for. | ||
| 126 | entry.unk_size = sizeof(BeaconEntryHeader) + beacon.data.size(); | ||
| 127 | entry.total_size = sizeof(BeaconEntryHeader) + beacon.data.size(); | ||
| 128 | entry.wifi_channel = beacon.channel; | ||
| 129 | entry.header_size = sizeof(BeaconEntryHeader); | ||
| 130 | entry.mac_address = beacon.transmitter_address; | ||
| 131 | |||
| 132 | ASSERT(current_buffer_pos < out_buffer_ptr + out_buffer_size); | ||
| 133 | |||
| 134 | Memory::WriteBlock(current_buffer_pos, &entry, sizeof(BeaconEntryHeader)); | ||
| 135 | current_buffer_pos += sizeof(BeaconEntryHeader); | ||
| 136 | |||
| 137 | Memory::WriteBlock(current_buffer_pos, beacon.data.data(), beacon.data.size()); | ||
| 138 | current_buffer_pos += beacon.data.size(); | ||
| 139 | |||
| 140 | total_size += sizeof(BeaconEntryHeader) + beacon.data.size(); | ||
| 141 | } | ||
| 142 | |||
| 143 | // Update the total size in the structure and write it to the buffer again. | ||
| 144 | data_reply_header.total_size = total_size; | ||
| 145 | Memory::WriteBlock(out_buffer_ptr, &data_reply_header, sizeof(BeaconDataReplyHeader)); | ||
| 146 | |||
| 147 | IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||
| 148 | rb.Push(RESULT_SUCCESS); | ||
| 149 | |||
| 150 | LOG_DEBUG(Service_NWM, "called out_buffer_size=0x%08X, wlan_comm_id=0x%08X, id=0x%08X," | ||
| 151 | "input_handle=0x%08X, out_buffer_ptr=0x%08X, unk1=0x%08X, unk2=0x%08X", | ||
| 152 | out_buffer_size, wlan_comm_id, id, input_handle, out_buffer_ptr, unk1, unk2); | ||
| 108 | } | 153 | } |
| 109 | 154 | ||
| 110 | /** | 155 | /** |
| @@ -127,10 +172,10 @@ static void InitializeWithVersion(Interface* self) { | |||
| 127 | u32 sharedmem_size = rp.Pop<u32>(); | 172 | u32 sharedmem_size = rp.Pop<u32>(); |
| 128 | 173 | ||
| 129 | // Update the node information with the data the game gave us. | 174 | // Update the node information with the data the game gave us. |
| 130 | rp.PopRaw(node_info); | 175 | rp.PopRaw(node_info[0]); |
| 176 | |||
| 177 | u16 version = rp.Pop<u16>(); | ||
| 131 | 178 | ||
| 132 | u16 version; | ||
| 133 | rp.PopRaw(version); | ||
| 134 | Kernel::Handle sharedmem_handle = rp.PopHandle(); | 179 | Kernel::Handle sharedmem_handle = rp.PopHandle(); |
| 135 | 180 | ||
| 136 | recv_buffer_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(sharedmem_handle); | 181 | recv_buffer_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(sharedmem_handle); |
| @@ -191,10 +236,8 @@ static void Bind(Interface* self) { | |||
| 191 | 236 | ||
| 192 | u32 bind_node_id = rp.Pop<u32>(); | 237 | u32 bind_node_id = rp.Pop<u32>(); |
| 193 | u32 recv_buffer_size = rp.Pop<u32>(); | 238 | u32 recv_buffer_size = rp.Pop<u32>(); |
| 194 | u8 data_channel; | 239 | u8 data_channel = rp.Pop<u8>(); |
| 195 | rp.PopRaw(data_channel); | 240 | u16 network_node_id = rp.Pop<u16>(); |
| 196 | u16 network_node_id; | ||
| 197 | rp.PopRaw(network_node_id); | ||
| 198 | 241 | ||
| 199 | // TODO(Subv): Store the data channel and verify it when receiving data frames. | 242 | // TODO(Subv): Store the data channel and verify it when receiving data frames. |
| 200 | 243 | ||
| @@ -251,13 +294,25 @@ static void BeginHostingNetwork(Interface* self) { | |||
| 251 | ASSERT_MSG(network_info.max_nodes > 1, "Trying to host a network of only one member."); | 294 | ASSERT_MSG(network_info.max_nodes > 1, "Trying to host a network of only one member."); |
| 252 | 295 | ||
| 253 | connection_status.status = static_cast<u32>(NetworkStatus::ConnectedAsHost); | 296 | connection_status.status = static_cast<u32>(NetworkStatus::ConnectedAsHost); |
| 297 | |||
| 298 | // Ensure the application data size is less than the maximum value. | ||
| 299 | ASSERT_MSG(network_info.application_data_size <= ApplicationDataSize, "Data size is too big."); | ||
| 300 | |||
| 301 | // Set up basic information for this network. | ||
| 302 | network_info.oui_value = NintendoOUI; | ||
| 303 | network_info.oui_type = static_cast<u8>(NintendoTagId::NetworkInfo); | ||
| 304 | |||
| 254 | connection_status.max_nodes = network_info.max_nodes; | 305 | connection_status.max_nodes = network_info.max_nodes; |
| 255 | 306 | ||
| 307 | // Resize the nodes list to hold max_nodes. | ||
| 308 | node_info.resize(network_info.max_nodes); | ||
| 309 | |||
| 256 | // There's currently only one node in the network (the host). | 310 | // There's currently only one node in the network (the host). |
| 257 | connection_status.total_nodes = 1; | 311 | connection_status.total_nodes = 1; |
| 312 | network_info.total_nodes = 1; | ||
| 258 | // The host is always the first node | 313 | // The host is always the first node |
| 259 | connection_status.network_node_id = 1; | 314 | connection_status.network_node_id = 1; |
| 260 | node_info.network_node_id = 1; | 315 | node_info[0].network_node_id = 1; |
| 261 | // Set the bit 0 in the nodes bitmask to indicate that node 1 is already taken. | 316 | // Set the bit 0 in the nodes bitmask to indicate that node 1 is already taken. |
| 262 | connection_status.node_bitmask |= 1; | 317 | connection_status.node_bitmask |= 1; |
| 263 | 318 | ||
| @@ -325,7 +380,7 @@ static void GetChannel(Interface* self) { | |||
| 325 | u8 channel = is_connected ? network_channel : 0; | 380 | u8 channel = is_connected ? network_channel : 0; |
| 326 | 381 | ||
| 327 | rb.Push(RESULT_SUCCESS); | 382 | rb.Push(RESULT_SUCCESS); |
| 328 | rb.PushRaw(channel); | 383 | rb.Push(channel); |
| 329 | 384 | ||
| 330 | LOG_DEBUG(Service_NWM, "called"); | 385 | LOG_DEBUG(Service_NWM, "called"); |
| 331 | } | 386 | } |
| @@ -373,7 +428,8 @@ static void BeaconBroadcastCallback(u64 userdata, int cycles_late) { | |||
| 373 | if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) | 428 | if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) |
| 374 | return; | 429 | return; |
| 375 | 430 | ||
| 376 | // TODO(Subv): Actually generate the beacon and send it. | 431 | // TODO(Subv): Actually send the beacon. |
| 432 | std::vector<u8> frame = GenerateBeaconFrame(network_info, node_info); | ||
| 377 | 433 | ||
| 378 | // Start broadcasting the network, send a beacon frame every 102.4ms. | 434 | // Start broadcasting the network, send a beacon frame every 102.4ms. |
| 379 | CoreTiming::ScheduleEvent(msToCycles(DefaultBeaconInterval * MillisecondsPerTU) - cycles_late, | 435 | CoreTiming::ScheduleEvent(msToCycles(DefaultBeaconInterval * MillisecondsPerTU) - cycles_late, |
diff --git a/src/core/hle/service/nwm/nwm_uds.h b/src/core/hle/service/nwm/nwm_uds.h index 65349f9fd..29b146569 100644 --- a/src/core/hle/service/nwm/nwm_uds.h +++ b/src/core/hle/service/nwm/nwm_uds.h | |||
| @@ -6,6 +6,7 @@ | |||
| 6 | 6 | ||
| 7 | #include <array> | 7 | #include <array> |
| 8 | #include <cstddef> | 8 | #include <cstddef> |
| 9 | #include <vector> | ||
| 9 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 10 | #include "common/swap.h" | 11 | #include "common/swap.h" |
| 11 | #include "core/hle/service/service.h" | 12 | #include "core/hle/service/service.h" |
| @@ -33,6 +34,8 @@ struct NodeInfo { | |||
| 33 | 34 | ||
| 34 | static_assert(sizeof(NodeInfo) == 40, "NodeInfo has incorrect size."); | 35 | static_assert(sizeof(NodeInfo) == 40, "NodeInfo has incorrect size."); |
| 35 | 36 | ||
| 37 | using NodeList = std::vector<NodeInfo>; | ||
| 38 | |||
| 36 | enum class NetworkStatus { | 39 | enum class NetworkStatus { |
| 37 | NotConnected = 3, | 40 | NotConnected = 3, |
| 38 | ConnectedAsHost = 6, | 41 | ConnectedAsHost = 6, |
| @@ -75,6 +78,8 @@ struct NetworkInfo { | |||
| 75 | std::array<u8, ApplicationDataSize> application_data; | 78 | std::array<u8, ApplicationDataSize> application_data; |
| 76 | }; | 79 | }; |
| 77 | 80 | ||
| 81 | static_assert(offsetof(NetworkInfo, oui_value) == 0xC, "oui_value is at the wrong offset."); | ||
| 82 | static_assert(offsetof(NetworkInfo, wlan_comm_id) == 0x10, "wlancommid is at the wrong offset."); | ||
| 78 | static_assert(sizeof(NetworkInfo) == 0x108, "NetworkInfo has incorrect size."); | 83 | static_assert(sizeof(NetworkInfo) == 0x108, "NetworkInfo has incorrect size."); |
| 79 | 84 | ||
| 80 | class NWM_UDS final : public Interface { | 85 | class NWM_UDS final : public Interface { |
diff --git a/src/core/hle/service/nwm/uds_beacon.cpp b/src/core/hle/service/nwm/uds_beacon.cpp new file mode 100644 index 000000000..c6e5bc5f1 --- /dev/null +++ b/src/core/hle/service/nwm/uds_beacon.cpp | |||
| @@ -0,0 +1,333 @@ | |||
| 1 | // Copyright 2017 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <cstring> | ||
| 6 | |||
| 7 | #include "core/hle/service/nwm/nwm_uds.h" | ||
| 8 | #include "core/hle/service/nwm/uds_beacon.h" | ||
| 9 | |||
| 10 | #include <cryptopp/aes.h> | ||
| 11 | #include <cryptopp/md5.h> | ||
| 12 | #include <cryptopp/modes.h> | ||
| 13 | #include <cryptopp/sha.h> | ||
| 14 | |||
| 15 | namespace Service { | ||
| 16 | namespace NWM { | ||
| 17 | |||
| 18 | // 802.11 broadcast MAC address | ||
| 19 | constexpr MacAddress BroadcastMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; | ||
| 20 | |||
| 21 | constexpr u64 DefaultNetworkUptime = 900000000; // 15 minutes in microseconds. | ||
| 22 | |||
| 23 | // Note: These values were taken from a packet capture of an o3DS XL | ||
| 24 | // broadcasting a Super Smash Bros. 4 lobby. | ||
| 25 | constexpr u16 DefaultExtraCapabilities = 0x0431; | ||
| 26 | |||
| 27 | // Size of the SSID broadcast by an UDS beacon frame. | ||
| 28 | constexpr u8 UDSBeaconSSIDSize = 8; | ||
| 29 | |||
| 30 | // The maximum size of the data stored in the EncryptedData0 tag (24). | ||
| 31 | constexpr u32 EncryptedDataSizeCutoff = 0xFA; | ||
| 32 | |||
| 33 | /** | ||
| 34 | * NWM Beacon data encryption key, taken from the NWM module code. | ||
| 35 | * We stub this with an all-zeros key as that is enough for Citra's purpose. | ||
| 36 | * The real key can be used here to generate beacons that will be accepted by | ||
| 37 | * a real 3ds. | ||
| 38 | */ | ||
| 39 | constexpr std::array<u8, CryptoPP::AES::BLOCKSIZE> nwm_beacon_key = {}; | ||
| 40 | |||
| 41 | /** | ||
| 42 | * Generates a buffer with the fixed parameters of an 802.11 Beacon frame | ||
| 43 | * using dummy values. | ||
| 44 | * @returns A buffer with the fixed parameters of the beacon frame. | ||
| 45 | */ | ||
| 46 | std::vector<u8> GenerateFixedParameters() { | ||
| 47 | std::vector<u8> buffer(sizeof(BeaconFrameHeader)); | ||
| 48 | |||
| 49 | BeaconFrameHeader header{}; | ||
| 50 | // Use a fixed default time for now. | ||
| 51 | // TODO(Subv): Perhaps use the difference between now and the time the network was started? | ||
| 52 | header.timestamp = DefaultNetworkUptime; | ||
| 53 | header.beacon_interval = DefaultBeaconInterval; | ||
| 54 | header.capabilities = DefaultExtraCapabilities; | ||
| 55 | |||
| 56 | std::memcpy(buffer.data(), &header, sizeof(header)); | ||
| 57 | |||
| 58 | return buffer; | ||
| 59 | } | ||
| 60 | |||
| 61 | /** | ||
| 62 | * Generates an SSID tag of an 802.11 Beacon frame with an 8-byte all-zero SSID value. | ||
| 63 | * @returns A buffer with the SSID tag. | ||
| 64 | */ | ||
| 65 | std::vector<u8> GenerateSSIDTag() { | ||
| 66 | std::vector<u8> buffer(sizeof(TagHeader) + UDSBeaconSSIDSize); | ||
| 67 | |||
| 68 | TagHeader tag_header{}; | ||
| 69 | tag_header.tag_id = static_cast<u8>(TagId::SSID); | ||
| 70 | tag_header.length = UDSBeaconSSIDSize; | ||
| 71 | |||
| 72 | std::memcpy(buffer.data(), &tag_header, sizeof(TagHeader)); | ||
| 73 | |||
| 74 | // The rest of the buffer is already filled with zeros. | ||
| 75 | |||
| 76 | return buffer; | ||
| 77 | } | ||
| 78 | |||
| 79 | /** | ||
| 80 | * Generates a buffer with the basic tagged parameters of an 802.11 Beacon frame | ||
| 81 | * such as SSID, Rate Information, Country Information, etc. | ||
| 82 | * @returns A buffer with the tagged parameters of the beacon frame. | ||
| 83 | */ | ||
| 84 | std::vector<u8> GenerateBasicTaggedParameters() { | ||
| 85 | // Append the SSID tag | ||
| 86 | std::vector<u8> buffer = GenerateSSIDTag(); | ||
| 87 | |||
| 88 | // TODO(Subv): Add the SupportedRates tag. | ||
| 89 | // TODO(Subv): Add the DSParameterSet tag. | ||
| 90 | // TODO(Subv): Add the TrafficIndicationMap tag. | ||
| 91 | // TODO(Subv): Add the CountryInformation tag. | ||
| 92 | // TODO(Subv): Add the ERPInformation tag. | ||
| 93 | |||
| 94 | return buffer; | ||
| 95 | } | ||
| 96 | |||
| 97 | /** | ||
| 98 | * Generates a buffer with the Dummy Nintendo tag. | ||
| 99 | * It is currently unknown what this tag does. | ||
| 100 | * TODO(Subv): Figure out if this is needed and what it does. | ||
| 101 | * @returns A buffer with the Nintendo tagged parameters of the beacon frame. | ||
| 102 | */ | ||
| 103 | std::vector<u8> GenerateNintendoDummyTag() { | ||
| 104 | // Note: These values were taken from a packet capture of an o3DS XL | ||
| 105 | // broadcasting a Super Smash Bros. 4 lobby. | ||
| 106 | constexpr std::array<u8, 3> dummy_data = {0x0A, 0x00, 0x00}; | ||
| 107 | |||
| 108 | DummyTag tag{}; | ||
| 109 | tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific); | ||
| 110 | tag.header.length = sizeof(DummyTag) - sizeof(TagHeader); | ||
| 111 | tag.oui_type = static_cast<u8>(NintendoTagId::Dummy); | ||
| 112 | tag.oui = NintendoOUI; | ||
| 113 | tag.data = dummy_data; | ||
| 114 | |||
| 115 | std::vector<u8> buffer(sizeof(DummyTag)); | ||
| 116 | std::memcpy(buffer.data(), &tag, sizeof(DummyTag)); | ||
| 117 | |||
| 118 | return buffer; | ||
| 119 | } | ||
| 120 | |||
| 121 | /** | ||
| 122 | * Generates a buffer with the Network Info Nintendo tag. | ||
| 123 | * This tag contains the network information of the network that is being broadcast. | ||
| 124 | * It also contains the application data provided by the application that opened the network. | ||
| 125 | * @returns A buffer with the Nintendo network info parameter of the beacon frame. | ||
| 126 | */ | ||
| 127 | std::vector<u8> GenerateNintendoNetworkInfoTag(const NetworkInfo& network_info) { | ||
| 128 | NetworkInfoTag tag{}; | ||
| 129 | tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific); | ||
| 130 | tag.header.length = | ||
| 131 | sizeof(NetworkInfoTag) - sizeof(TagHeader) + network_info.application_data_size; | ||
| 132 | tag.appdata_size = network_info.application_data_size; | ||
| 133 | // Set the hash to zero initially, it will be updated once we calculate it. | ||
| 134 | tag.sha_hash = {}; | ||
| 135 | |||
| 136 | // Ensure the network structure has the correct OUI and OUI type. | ||
| 137 | ASSERT(network_info.oui_type == static_cast<u8>(NintendoTagId::NetworkInfo)); | ||
| 138 | ASSERT(network_info.oui_value == NintendoOUI); | ||
| 139 | |||
| 140 | // Ensure the application data size is less than the maximum value. | ||
| 141 | ASSERT_MSG(network_info.application_data_size <= ApplicationDataSize, "Data size is too big."); | ||
| 142 | |||
| 143 | // This tag contains the network info structure starting at the OUI. | ||
| 144 | std::memcpy(tag.network_info.data(), &network_info.oui_value, tag.network_info.size()); | ||
| 145 | |||
| 146 | // Copy the tag and the data so we can calculate the SHA1 over it. | ||
| 147 | std::vector<u8> buffer(sizeof(tag) + network_info.application_data_size); | ||
| 148 | std::memcpy(buffer.data(), &tag, sizeof(tag)); | ||
| 149 | std::memcpy(buffer.data() + sizeof(tag), network_info.application_data.data(), | ||
| 150 | network_info.application_data_size); | ||
| 151 | |||
| 152 | // Calculate the SHA1 of the contents of the tag. | ||
| 153 | std::array<u8, CryptoPP::SHA1::DIGESTSIZE> hash; | ||
| 154 | CryptoPP::SHA1().CalculateDigest(hash.data(), | ||
| 155 | buffer.data() + offsetof(NetworkInfoTag, network_info), | ||
| 156 | buffer.size() - sizeof(TagHeader)); | ||
| 157 | |||
| 158 | // Copy it directly into the buffer, overwriting the zeros that we had previously placed there. | ||
| 159 | std::memcpy(buffer.data() + offsetof(NetworkInfoTag, sha_hash), hash.data(), hash.size()); | ||
| 160 | |||
| 161 | return buffer; | ||
| 162 | } | ||
| 163 | |||
| 164 | /* | ||
| 165 | * Calculates the CTR used for the AES-CTR encryption of the data stored in the | ||
| 166 | * EncryptedDataTags. | ||
| 167 | * @returns The CTR used for beacon crypto. | ||
| 168 | */ | ||
| 169 | std::array<u8, CryptoPP::AES::BLOCKSIZE> GetBeaconCryptoCTR(const NetworkInfo& network_info) { | ||
| 170 | BeaconDataCryptoCTR data{}; | ||
| 171 | |||
| 172 | data.host_mac = network_info.host_mac_address; | ||
| 173 | data.wlan_comm_id = network_info.wlan_comm_id; | ||
| 174 | data.id = network_info.id; | ||
| 175 | data.network_id = network_info.network_id; | ||
| 176 | |||
| 177 | std::array<u8, CryptoPP::AES::BLOCKSIZE> hash; | ||
| 178 | std::memcpy(hash.data(), &data, sizeof(data)); | ||
| 179 | |||
| 180 | return hash; | ||
| 181 | } | ||
| 182 | |||
| 183 | /* | ||
| 184 | * Serializes the node information into the format needed for network transfer, | ||
| 185 | * and then encrypts it with the NWM key. | ||
| 186 | * @returns The serialized and encrypted node information. | ||
| 187 | */ | ||
| 188 | std::vector<u8> GeneratedEncryptedData(const NetworkInfo& network_info, const NodeList& nodes) { | ||
| 189 | std::vector<u8> buffer(sizeof(BeaconData)); | ||
| 190 | |||
| 191 | BeaconData data{}; | ||
| 192 | std::memcpy(buffer.data(), &data, sizeof(BeaconData)); | ||
| 193 | |||
| 194 | for (const NodeInfo& node : nodes) { | ||
| 195 | // Serialize each node and convert the data from | ||
| 196 | // host byte-order to Big Endian. | ||
| 197 | BeaconNodeInfo info{}; | ||
| 198 | info.friend_code_seed = node.friend_code_seed; | ||
| 199 | info.network_node_id = node.network_node_id; | ||
| 200 | for (int i = 0; i < info.username.size(); ++i) | ||
| 201 | info.username[i] = node.username[i]; | ||
| 202 | |||
| 203 | buffer.insert(buffer.end(), reinterpret_cast<u8*>(&info), | ||
| 204 | reinterpret_cast<u8*>(&info) + sizeof(info)); | ||
| 205 | } | ||
| 206 | |||
| 207 | // Calculate the MD5 hash of the data in the buffer, not including the hash field. | ||
| 208 | std::array<u8, CryptoPP::MD5::DIGESTSIZE> hash; | ||
| 209 | CryptoPP::MD5().CalculateDigest(hash.data(), buffer.data() + offsetof(BeaconData, bitmask), | ||
| 210 | buffer.size() - sizeof(data.md5_hash)); | ||
| 211 | |||
| 212 | // Copy the hash into the buffer. | ||
| 213 | std::memcpy(buffer.data(), hash.data(), hash.size()); | ||
| 214 | |||
| 215 | // Encrypt the data using AES-CTR and the NWM beacon key. | ||
| 216 | using CryptoPP::AES; | ||
| 217 | std::array<u8, AES::BLOCKSIZE> counter = GetBeaconCryptoCTR(network_info); | ||
| 218 | CryptoPP::CTR_Mode<AES>::Encryption aes; | ||
| 219 | aes.SetKeyWithIV(nwm_beacon_key.data(), AES::BLOCKSIZE, counter.data()); | ||
| 220 | aes.ProcessData(buffer.data(), buffer.data(), buffer.size()); | ||
| 221 | |||
| 222 | return buffer; | ||
| 223 | } | ||
| 224 | |||
| 225 | void DecryptBeaconData(const NetworkInfo& network_info, std::vector<u8>& buffer) { | ||
| 226 | // Decrypt the data using AES-CTR and the NWM beacon key. | ||
| 227 | using CryptoPP::AES; | ||
| 228 | std::array<u8, AES::BLOCKSIZE> counter = GetBeaconCryptoCTR(network_info); | ||
| 229 | CryptoPP::CTR_Mode<AES>::Decryption aes; | ||
| 230 | aes.SetKeyWithIV(nwm_beacon_key.data(), AES::BLOCKSIZE, counter.data()); | ||
| 231 | aes.ProcessData(buffer.data(), buffer.data(), buffer.size()); | ||
| 232 | } | ||
| 233 | |||
| 234 | /** | ||
| 235 | * Generates a buffer with the Network Info Nintendo tag. | ||
| 236 | * This tag contains the first portion of the encrypted payload in the 802.11 beacon frame. | ||
| 237 | * The encrypted payload contains information about the nodes currently connected to the network. | ||
| 238 | * @returns A buffer with the first Nintendo encrypted data parameters of the beacon frame. | ||
| 239 | */ | ||
| 240 | std::vector<u8> GenerateNintendoFirstEncryptedDataTag(const NetworkInfo& network_info, | ||
| 241 | const NodeList& nodes) { | ||
| 242 | const size_t payload_size = | ||
| 243 | std::min<size_t>(EncryptedDataSizeCutoff, nodes.size() * sizeof(NodeInfo)); | ||
| 244 | |||
| 245 | EncryptedDataTag tag{}; | ||
| 246 | tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific); | ||
| 247 | tag.header.length = sizeof(tag) - sizeof(TagHeader) + payload_size; | ||
| 248 | tag.oui_type = static_cast<u8>(NintendoTagId::EncryptedData0); | ||
| 249 | tag.oui = NintendoOUI; | ||
| 250 | |||
| 251 | std::vector<u8> buffer(sizeof(tag) + payload_size); | ||
| 252 | std::memcpy(buffer.data(), &tag, sizeof(tag)); | ||
| 253 | |||
| 254 | std::vector<u8> encrypted_data = GeneratedEncryptedData(network_info, nodes); | ||
| 255 | std::memcpy(buffer.data() + sizeof(tag), encrypted_data.data(), payload_size); | ||
| 256 | |||
| 257 | return buffer; | ||
| 258 | } | ||
| 259 | |||
| 260 | /** | ||
| 261 | * Generates a buffer with the Network Info Nintendo tag. | ||
| 262 | * This tag contains the second portion of the encrypted payload in the 802.11 beacon frame. | ||
| 263 | * The encrypted payload contains information about the nodes currently connected to the network. | ||
| 264 | * This tag is only present if the payload size is greater than EncryptedDataSizeCutoff (0xFA) | ||
| 265 | * bytes. | ||
| 266 | * @returns A buffer with the second Nintendo encrypted data parameters of the beacon frame. | ||
| 267 | */ | ||
| 268 | std::vector<u8> GenerateNintendoSecondEncryptedDataTag(const NetworkInfo& network_info, | ||
| 269 | const NodeList& nodes) { | ||
| 270 | // This tag is only present if the payload is larger than EncryptedDataSizeCutoff (0xFA). | ||
| 271 | if (nodes.size() * sizeof(NodeInfo) <= EncryptedDataSizeCutoff) | ||
| 272 | return {}; | ||
| 273 | |||
| 274 | const size_t payload_size = nodes.size() * sizeof(NodeInfo) - EncryptedDataSizeCutoff; | ||
| 275 | |||
| 276 | const size_t tag_length = sizeof(EncryptedDataTag) - sizeof(TagHeader) + payload_size; | ||
| 277 | |||
| 278 | // TODO(Subv): What does the 3DS do when a game has too much data to fit into the tag? | ||
| 279 | ASSERT_MSG(tag_length <= 255, "Data is too big."); | ||
| 280 | |||
| 281 | EncryptedDataTag tag{}; | ||
| 282 | tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific); | ||
| 283 | tag.header.length = tag_length; | ||
| 284 | tag.oui_type = static_cast<u8>(NintendoTagId::EncryptedData1); | ||
| 285 | tag.oui = NintendoOUI; | ||
| 286 | |||
| 287 | std::vector<u8> buffer(sizeof(tag) + payload_size); | ||
| 288 | std::memcpy(buffer.data(), &tag, sizeof(tag)); | ||
| 289 | |||
| 290 | std::vector<u8> encrypted_data = GeneratedEncryptedData(network_info, nodes); | ||
| 291 | std::memcpy(buffer.data() + sizeof(tag), encrypted_data.data() + EncryptedDataSizeCutoff, | ||
| 292 | payload_size); | ||
| 293 | |||
| 294 | return buffer; | ||
| 295 | } | ||
| 296 | |||
| 297 | /** | ||
| 298 | * Generates a buffer with the Nintendo tagged parameters of an 802.11 Beacon frame | ||
| 299 | * for UDS communication. | ||
| 300 | * @returns A buffer with the Nintendo tagged parameters of the beacon frame. | ||
| 301 | */ | ||
| 302 | std::vector<u8> GenerateNintendoTaggedParameters(const NetworkInfo& network_info, | ||
| 303 | const NodeList& nodes) { | ||
| 304 | ASSERT_MSG(network_info.max_nodes == nodes.size(), "Inconsistent network state."); | ||
| 305 | |||
| 306 | std::vector<u8> buffer = GenerateNintendoDummyTag(); | ||
| 307 | std::vector<u8> network_info_tag = GenerateNintendoNetworkInfoTag(network_info); | ||
| 308 | std::vector<u8> first_data_tag = GenerateNintendoFirstEncryptedDataTag(network_info, nodes); | ||
| 309 | std::vector<u8> second_data_tag = GenerateNintendoSecondEncryptedDataTag(network_info, nodes); | ||
| 310 | |||
| 311 | buffer.insert(buffer.end(), network_info_tag.begin(), network_info_tag.end()); | ||
| 312 | buffer.insert(buffer.end(), first_data_tag.begin(), first_data_tag.end()); | ||
| 313 | buffer.insert(buffer.end(), second_data_tag.begin(), second_data_tag.end()); | ||
| 314 | |||
| 315 | return buffer; | ||
| 316 | } | ||
| 317 | |||
| 318 | std::vector<u8> GenerateBeaconFrame(const NetworkInfo& network_info, const NodeList& nodes) { | ||
| 319 | std::vector<u8> buffer = GenerateFixedParameters(); | ||
| 320 | std::vector<u8> basic_tags = GenerateBasicTaggedParameters(); | ||
| 321 | std::vector<u8> nintendo_tags = GenerateNintendoTaggedParameters(network_info, nodes); | ||
| 322 | |||
| 323 | buffer.insert(buffer.end(), basic_tags.begin(), basic_tags.end()); | ||
| 324 | buffer.insert(buffer.end(), nintendo_tags.begin(), nintendo_tags.end()); | ||
| 325 | |||
| 326 | return buffer; | ||
| 327 | } | ||
| 328 | |||
| 329 | std::deque<WifiPacket> GetReceivedPackets(WifiPacket::PacketType type, const MacAddress& sender) { | ||
| 330 | return {}; | ||
| 331 | } | ||
| 332 | } // namespace NWM | ||
| 333 | } // namespace Service | ||
diff --git a/src/core/hle/service/nwm/uds_beacon.h b/src/core/hle/service/nwm/uds_beacon.h new file mode 100644 index 000000000..6df4c4f47 --- /dev/null +++ b/src/core/hle/service/nwm/uds_beacon.h | |||
| @@ -0,0 +1,173 @@ | |||
| 1 | // Copyright 2017 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <array> | ||
| 8 | #include <deque> | ||
| 9 | #include <vector> | ||
| 10 | #include "common/common_types.h" | ||
| 11 | #include "common/swap.h" | ||
| 12 | #include "core/hle/service/service.h" | ||
| 13 | |||
| 14 | namespace Service { | ||
| 15 | namespace NWM { | ||
| 16 | |||
| 17 | using MacAddress = std::array<u8, 6>; | ||
| 18 | |||
| 19 | /// The maximum number of nodes that can exist in an UDS session. | ||
| 20 | constexpr u32 UDSMaxNodes = 16; | ||
| 21 | constexpr std::array<u8, 3> NintendoOUI = {0x00, 0x1F, 0x32}; | ||
| 22 | |||
| 23 | /// Additional block tag ids in the Beacon frames | ||
| 24 | enum class TagId : u8 { | ||
| 25 | SSID = 0, | ||
| 26 | SupportedRates = 1, | ||
| 27 | DSParameterSet = 2, | ||
| 28 | TrafficIndicationMap = 5, | ||
| 29 | CountryInformation = 7, | ||
| 30 | ERPInformation = 42, | ||
| 31 | VendorSpecific = 221 | ||
| 32 | }; | ||
| 33 | |||
| 34 | /** | ||
| 35 | * Internal vendor-specific tag ids as stored inside | ||
| 36 | * VendorSpecific blocks in the Beacon frames. | ||
| 37 | */ | ||
| 38 | enum class NintendoTagId : u8 { | ||
| 39 | Dummy = 20, | ||
| 40 | NetworkInfo = 21, | ||
| 41 | EncryptedData0 = 24, | ||
| 42 | EncryptedData1 = 25, | ||
| 43 | }; | ||
| 44 | |||
| 45 | struct BeaconEntryHeader { | ||
| 46 | u32_le total_size; | ||
| 47 | INSERT_PADDING_BYTES(1); | ||
| 48 | u8 wifi_channel; | ||
| 49 | INSERT_PADDING_BYTES(2); | ||
| 50 | MacAddress mac_address; | ||
| 51 | INSERT_PADDING_BYTES(6); | ||
| 52 | u32_le unk_size; | ||
| 53 | u32_le header_size; | ||
| 54 | }; | ||
| 55 | |||
| 56 | static_assert(sizeof(BeaconEntryHeader) == 0x1C, "BeaconEntryHeader has incorrect size."); | ||
| 57 | |||
| 58 | struct BeaconDataReplyHeader { | ||
| 59 | u32_le max_output_size; | ||
| 60 | u32_le total_size; | ||
| 61 | u32_le total_entries; | ||
| 62 | }; | ||
| 63 | |||
| 64 | static_assert(sizeof(BeaconDataReplyHeader) == 12, "BeaconDataReplyHeader has incorrect size."); | ||
| 65 | |||
| 66 | #pragma pack(push, 1) | ||
| 67 | struct BeaconFrameHeader { | ||
| 68 | // Number of microseconds the AP has been active. | ||
| 69 | u64_le timestamp; | ||
| 70 | // Interval between beacon transmissions, expressed in TU. | ||
| 71 | u16_le beacon_interval; | ||
| 72 | // Indicates the presence of optional capabilities. | ||
| 73 | u16_le capabilities; | ||
| 74 | }; | ||
| 75 | #pragma pack(pop) | ||
| 76 | |||
| 77 | static_assert(sizeof(BeaconFrameHeader) == 12, "BeaconFrameHeader has incorrect size."); | ||
| 78 | |||
| 79 | struct TagHeader { | ||
| 80 | u8 tag_id; | ||
| 81 | u8 length; | ||
| 82 | }; | ||
| 83 | |||
| 84 | static_assert(sizeof(TagHeader) == 2, "TagHeader has incorrect size."); | ||
| 85 | |||
| 86 | struct DummyTag { | ||
| 87 | TagHeader header; | ||
| 88 | std::array<u8, 3> oui; | ||
| 89 | u8 oui_type; | ||
| 90 | std::array<u8, 3> data; | ||
| 91 | }; | ||
| 92 | |||
| 93 | static_assert(sizeof(DummyTag) == 9, "DummyTag has incorrect size."); | ||
| 94 | |||
| 95 | struct NetworkInfoTag { | ||
| 96 | TagHeader header; | ||
| 97 | std::array<u8, 0x1F> network_info; | ||
| 98 | std::array<u8, 0x14> sha_hash; | ||
| 99 | u8 appdata_size; | ||
| 100 | }; | ||
| 101 | |||
| 102 | static_assert(sizeof(NetworkInfoTag) == 54, "NetworkInfoTag has incorrect size."); | ||
| 103 | |||
| 104 | struct EncryptedDataTag { | ||
| 105 | TagHeader header; | ||
| 106 | std::array<u8, 3> oui; | ||
| 107 | u8 oui_type; | ||
| 108 | }; | ||
| 109 | |||
| 110 | static_assert(sizeof(EncryptedDataTag) == 6, "EncryptedDataTag has incorrect size."); | ||
| 111 | |||
| 112 | #pragma pack(push, 1) | ||
| 113 | // The raw bytes of this structure are the CTR used in the encryption (AES-CTR) | ||
| 114 | // of the beacon data stored in the EncryptedDataTags. | ||
| 115 | struct BeaconDataCryptoCTR { | ||
| 116 | MacAddress host_mac; | ||
| 117 | u32_le wlan_comm_id; | ||
| 118 | u8 id; | ||
| 119 | INSERT_PADDING_BYTES(1); | ||
| 120 | u32_le network_id; | ||
| 121 | }; | ||
| 122 | |||
| 123 | static_assert(sizeof(BeaconDataCryptoCTR) == 0x10, "BeaconDataCryptoCTR has incorrect size."); | ||
| 124 | |||
| 125 | struct BeaconNodeInfo { | ||
| 126 | u64_be friend_code_seed; | ||
| 127 | std::array<u16_be, 10> username; | ||
| 128 | u16_be network_node_id; | ||
| 129 | }; | ||
| 130 | |||
| 131 | static_assert(sizeof(BeaconNodeInfo) == 0x1E, "BeaconNodeInfo has incorrect size."); | ||
| 132 | |||
| 133 | struct BeaconData { | ||
| 134 | std::array<u8, 0x10> md5_hash; | ||
| 135 | u16_be bitmask; | ||
| 136 | }; | ||
| 137 | #pragma pack(pop) | ||
| 138 | |||
| 139 | static_assert(sizeof(BeaconData) == 0x12, "BeaconData has incorrect size."); | ||
| 140 | |||
| 141 | /// Information about a received WiFi packet. | ||
| 142 | /// Acts as our own 802.11 header. | ||
| 143 | struct WifiPacket { | ||
| 144 | enum class PacketType { Beacon, Data }; | ||
| 145 | |||
| 146 | PacketType type; ///< The type of 802.11 frame, Beacon / Data. | ||
| 147 | |||
| 148 | /// Raw 802.11 frame data, starting at the management frame header for management frames. | ||
| 149 | std::vector<u8> data; | ||
| 150 | MacAddress transmitter_address; ///< Mac address of the transmitter. | ||
| 151 | MacAddress destination_address; ///< Mac address of the receiver. | ||
| 152 | u8 channel; ///< WiFi channel where this frame was transmitted. | ||
| 153 | }; | ||
| 154 | |||
| 155 | /** | ||
| 156 | * Decrypts the beacon data buffer for the network described by `network_info`. | ||
| 157 | */ | ||
| 158 | void DecryptBeaconData(const NetworkInfo& network_info, std::vector<u8>& buffer); | ||
| 159 | |||
| 160 | /** | ||
| 161 | * Generates an 802.11 beacon frame starting at the management frame header. | ||
| 162 | * This frame contains information about the network and its connected clients. | ||
| 163 | * @returns The generated frame. | ||
| 164 | */ | ||
| 165 | std::vector<u8> GenerateBeaconFrame(const NetworkInfo& network_info, const NodeList& nodes); | ||
| 166 | |||
| 167 | /** | ||
| 168 | * Returns a list of received 802.11 frames from the specified sender | ||
| 169 | * matching the type since the last call. | ||
| 170 | */ | ||
| 171 | std::deque<WifiPacket> GetReceivedPackets(WifiPacket::PacketType type, const MacAddress& sender); | ||
| 172 | } // namespace NWM | ||
| 173 | } // namespace Service | ||