diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 61a0b1cc3..ee03567e0 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -140,6 +140,7 @@ set(SRCS hle/service/nwm/nwm_soc.cpp hle/service/nwm/nwm_tst.cpp hle/service/nwm/nwm_uds.cpp + hle/service/nwm/uds_beacon.cpp hle/service/pm_app.cpp hle/service/ptm/ptm.cpp hle/service/ptm/ptm_gets.cpp @@ -328,6 +329,7 @@ set(HEADERS hle/service/nwm/nwm_soc.h hle/service/nwm/nwm_tst.h hle/service/nwm/nwm_uds.h + hle/service/nwm/uds_beacon.h hle/service/pm_app.h hle/service/ptm/ptm.h 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..f016550b5 100644 --- a/src/core/hle/service/nwm/nwm_uds.cpp +++ b/src/core/hle/service/nwm/nwm_uds.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include #include #include @@ -12,6 +13,7 @@ #include "core/hle/kernel/shared_memory.h" #include "core/hle/result.h" #include "core/hle/service/nwm/nwm_uds.h" +#include "core/hle/service/nwm/uds_beacon.h" #include "core/memory.h" namespace Service { @@ -27,10 +29,12 @@ static Kernel::SharedPtr recv_buffer_memory; // Connection status of this 3DS. static ConnectionStatus connection_status{}; -// Node information about the current 3DS. -// TODO(Subv): Keep an array of all nodes connected to the network, -// that data has to be retransmitted in every beacon frame. -static NodeInfo node_info; +/* 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. + */ +static NodeList node_info(1); // Mapping of bind node ids to their respective events. static std::unordered_map> bind_node_events; @@ -127,7 +131,7 @@ static void InitializeWithVersion(Interface* self) { u32 sharedmem_size = rp.Pop(); // Update the node information with the data the game gave us. - rp.PopRaw(node_info); + rp.PopRaw(node_info[0]); u16 version; rp.PopRaw(version); @@ -251,13 +255,25 @@ static void BeginHostingNetwork(Interface* self) { ASSERT_MSG(network_info.max_nodes > 1, "Trying to host a network of only one member."); connection_status.status = static_cast(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(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.network_node_id = 1; + node_info[0].network_node_id = 1; // Set the bit 0 in the nodes bitmask to indicate that node 1 is already taken. connection_status.node_bitmask |= 1; @@ -373,7 +389,8 @@ static void BeaconBroadcastCallback(u64 userdata, int cycles_late) { if (connection_status.status != static_cast(NetworkStatus::ConnectedAsHost)) return; - // TODO(Subv): Actually generate the beacon and send it. + // TODO(Subv): Actually send the beacon. + std::vector frame = GenerateBeaconFrame(network_info, node_info); // Start broadcasting the network, send a beacon frame every 102.4ms. 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 @@ #include #include +#include #include "common/common_types.h" #include "common/swap.h" #include "core/hle/service/service.h" @@ -33,6 +34,8 @@ struct NodeInfo { static_assert(sizeof(NodeInfo) == 40, "NodeInfo has incorrect size."); +using NodeList = std::vector; + enum class NetworkStatus { NotConnected = 3, ConnectedAsHost = 6, @@ -75,6 +78,8 @@ struct NetworkInfo { std::array application_data; }; +static_assert(offsetof(NetworkInfo, oui_value) == 0xC, "oui_value is at the wrong offset."); +static_assert(offsetof(NetworkInfo, wlan_comm_id) == 0x10, "wlancommid is at the wrong offset."); static_assert(sizeof(NetworkInfo) == 0x108, "NetworkInfo has incorrect size."); 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 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include "core/hle/service/nwm/nwm_uds.h" +#include "core/hle/service/nwm/uds_beacon.h" + +#include +#include +#include +#include + +namespace Service { +namespace NWM { + +// 802.11 broadcast MAC address +constexpr MacAddress BroadcastMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + +constexpr u64 DefaultNetworkUptime = 900000000; // 15 minutes in microseconds. + +// Note: These values were taken from a packet capture of an o3DS XL +// broadcasting a Super Smash Bros. 4 lobby. +constexpr u16 DefaultExtraCapabilities = 0x0431; + +// Size of the SSID broadcast by an UDS beacon frame. +constexpr u8 UDSBeaconSSIDSize = 8; + +// The maximum size of the data stored in the EncryptedData0 tag (24). +constexpr u32 EncryptedDataSizeCutoff = 0xFA; + +/** + * NWM Beacon data encryption key, taken from the NWM module code. + * We stub this with an all-zeros key as that is enough for Citra's purpose. + * The real key can be used here to generate beacons that will be accepted by + * a real 3ds. + */ +constexpr std::array nwm_beacon_key = {}; + +/** + * Generates a buffer with the fixed parameters of an 802.11 Beacon frame + * using dummy values. + * @returns A buffer with the fixed parameters of the beacon frame. + */ +std::vector GenerateFixedParameters() { + std::vector buffer(sizeof(BeaconFrameHeader)); + + BeaconFrameHeader header{}; + // Use a fixed default time for now. + // TODO(Subv): Perhaps use the difference between now and the time the network was started? + header.timestamp = DefaultNetworkUptime; + header.beacon_interval = DefaultBeaconInterval; + header.capabilities = DefaultExtraCapabilities; + + std::memcpy(buffer.data(), &header, sizeof(header)); + + return buffer; +} + +/** + * Generates an SSID tag of an 802.11 Beacon frame with an 8-byte all-zero SSID value. + * @returns A buffer with the SSID tag. + */ +std::vector GenerateSSIDTag() { + std::vector buffer(sizeof(TagHeader) + UDSBeaconSSIDSize); + + TagHeader tag_header{}; + tag_header.tag_id = static_cast(TagId::SSID); + tag_header.length = UDSBeaconSSIDSize; + + std::memcpy(buffer.data(), &tag_header, sizeof(TagHeader)); + + // The rest of the buffer is already filled with zeros. + + return buffer; +} + +/** + * Generates a buffer with the basic tagged parameters of an 802.11 Beacon frame + * such as SSID, Rate Information, Country Information, etc. + * @returns A buffer with the tagged parameters of the beacon frame. + */ +std::vector GenerateBasicTaggedParameters() { + // Append the SSID tag + std::vector buffer = GenerateSSIDTag(); + + // TODO(Subv): Add the SupportedRates tag. + // TODO(Subv): Add the DSParameterSet tag. + // TODO(Subv): Add the TrafficIndicationMap tag. + // TODO(Subv): Add the CountryInformation tag. + // TODO(Subv): Add the ERPInformation tag. + + return buffer; +} + +/** + * Generates a buffer with the Dummy Nintendo tag. + * It is currently unknown what this tag does. + * TODO(Subv): Figure out if this is needed and what it does. + * @returns A buffer with the Nintendo tagged parameters of the beacon frame. + */ +std::vector GenerateNintendoDummyTag() { + // Note: These values were taken from a packet capture of an o3DS XL + // broadcasting a Super Smash Bros. 4 lobby. + constexpr std::array dummy_data = {0x0A, 0x00, 0x00}; + + DummyTag tag{}; + tag.header.tag_id = static_cast(TagId::VendorSpecific); + tag.header.length = sizeof(DummyTag) - sizeof(TagHeader); + tag.oui_type = static_cast(NintendoTagId::Dummy); + tag.oui = NintendoOUI; + tag.data = dummy_data; + + std::vector buffer(sizeof(DummyTag)); + std::memcpy(buffer.data(), &tag, sizeof(DummyTag)); + + return buffer; +} + +/** + * Generates a buffer with the Network Info Nintendo tag. + * This tag contains the network information of the network that is being broadcast. + * It also contains the application data provided by the application that opened the network. + * @returns A buffer with the Nintendo network info parameter of the beacon frame. + */ +std::vector GenerateNintendoNetworkInfoTag(const NetworkInfo& network_info) { + NetworkInfoTag tag{}; + tag.header.tag_id = static_cast(TagId::VendorSpecific); + tag.header.length = + sizeof(NetworkInfoTag) - sizeof(TagHeader) + network_info.application_data_size; + tag.appdata_size = network_info.application_data_size; + // Set the hash to zero initially, it will be updated once we calculate it. + tag.sha_hash = {}; + + // Ensure the network structure has the correct OUI and OUI type. + ASSERT(network_info.oui_type == static_cast(NintendoTagId::NetworkInfo)); + ASSERT(network_info.oui_value == NintendoOUI); + + // Ensure the application data size is less than the maximum value. + ASSERT_MSG(network_info.application_data_size <= ApplicationDataSize, "Data size is too big."); + + // This tag contains the network info structure starting at the OUI. + std::memcpy(tag.network_info.data(), &network_info.oui_value, tag.network_info.size()); + + // Copy the tag and the data so we can calculate the SHA1 over it. + std::vector buffer(sizeof(tag) + network_info.application_data_size); + std::memcpy(buffer.data(), &tag, sizeof(tag)); + std::memcpy(buffer.data() + sizeof(tag), network_info.application_data.data(), + network_info.application_data_size); + + // Calculate the SHA1 of the contents of the tag. + std::array hash; + CryptoPP::SHA1().CalculateDigest(hash.data(), + buffer.data() + offsetof(NetworkInfoTag, network_info), + buffer.size() - sizeof(TagHeader)); + + // Copy it directly into the buffer, overwriting the zeros that we had previously placed there. + std::memcpy(buffer.data() + offsetof(NetworkInfoTag, sha_hash), hash.data(), hash.size()); + + return buffer; +} + +/* + * Calculates the CTR used for the AES-CTR encryption of the data stored in the + * EncryptedDataTags. + * @returns The CTR used for beacon crypto. + */ +std::array GetBeaconCryptoCTR(const NetworkInfo& network_info) { + BeaconDataCryptoCTR data{}; + + data.host_mac = network_info.host_mac_address; + data.wlan_comm_id = network_info.wlan_comm_id; + data.id = network_info.id; + data.network_id = network_info.network_id; + + std::array hash; + std::memcpy(hash.data(), &data, sizeof(data)); + + return hash; +} + +/* + * Serializes the node information into the format needed for network transfer, + * and then encrypts it with the NWM key. + * @returns The serialized and encrypted node information. + */ +std::vector GeneratedEncryptedData(const NetworkInfo& network_info, const NodeList& nodes) { + std::vector buffer(sizeof(BeaconData)); + + BeaconData data{}; + std::memcpy(buffer.data(), &data, sizeof(BeaconData)); + + for (const NodeInfo& node : nodes) { + // Serialize each node and convert the data from + // host byte-order to Big Endian. + BeaconNodeInfo info{}; + info.friend_code_seed = node.friend_code_seed; + info.network_node_id = node.network_node_id; + for (int i = 0; i < info.username.size(); ++i) + info.username[i] = node.username[i]; + + buffer.insert(buffer.end(), reinterpret_cast(&info), + reinterpret_cast(&info) + sizeof(info)); + } + + // Calculate the MD5 hash of the data in the buffer, not including the hash field. + std::array hash; + CryptoPP::MD5().CalculateDigest(hash.data(), buffer.data() + offsetof(BeaconData, bitmask), + buffer.size() - sizeof(data.md5_hash)); + + // Copy the hash into the buffer. + std::memcpy(buffer.data(), hash.data(), hash.size()); + + // Encrypt the data using AES-CTR and the NWM beacon key. + using CryptoPP::AES; + std::array counter = GetBeaconCryptoCTR(network_info); + CryptoPP::CTR_Mode::Encryption aes; + aes.SetKeyWithIV(nwm_beacon_key.data(), AES::BLOCKSIZE, counter.data()); + aes.ProcessData(buffer.data(), buffer.data(), buffer.size()); + + return buffer; +} + +void DecryptBeaconData(const NetworkInfo& network_info, std::vector& buffer) { + // Decrypt the data using AES-CTR and the NWM beacon key. + using CryptoPP::AES; + std::array counter = GetBeaconCryptoCTR(network_info); + CryptoPP::CTR_Mode::Decryption aes; + aes.SetKeyWithIV(nwm_beacon_key.data(), AES::BLOCKSIZE, counter.data()); + aes.ProcessData(buffer.data(), buffer.data(), buffer.size()); +} + +/** + * Generates a buffer with the Network Info Nintendo tag. + * This tag contains the first portion of the encrypted payload in the 802.11 beacon frame. + * The encrypted payload contains information about the nodes currently connected to the network. + * @returns A buffer with the first Nintendo encrypted data parameters of the beacon frame. + */ +std::vector GenerateNintendoFirstEncryptedDataTag(const NetworkInfo& network_info, + const NodeList& nodes) { + const size_t payload_size = + std::min(EncryptedDataSizeCutoff, nodes.size() * sizeof(NodeInfo)); + + EncryptedDataTag tag{}; + tag.header.tag_id = static_cast(TagId::VendorSpecific); + tag.header.length = sizeof(tag) - sizeof(TagHeader) + payload_size; + tag.oui_type = static_cast(NintendoTagId::EncryptedData0); + tag.oui = NintendoOUI; + + std::vector buffer(sizeof(tag) + payload_size); + std::memcpy(buffer.data(), &tag, sizeof(tag)); + + std::vector encrypted_data = GeneratedEncryptedData(network_info, nodes); + std::memcpy(buffer.data() + sizeof(tag), encrypted_data.data(), payload_size); + + return buffer; +} + +/** + * Generates a buffer with the Network Info Nintendo tag. + * This tag contains the second portion of the encrypted payload in the 802.11 beacon frame. + * The encrypted payload contains information about the nodes currently connected to the network. + * This tag is only present if the payload size is greater than EncryptedDataSizeCutoff (0xFA) + * bytes. + * @returns A buffer with the second Nintendo encrypted data parameters of the beacon frame. + */ +std::vector GenerateNintendoSecondEncryptedDataTag(const NetworkInfo& network_info, + const NodeList& nodes) { + // This tag is only present if the payload is larger than EncryptedDataSizeCutoff (0xFA). + if (nodes.size() * sizeof(NodeInfo) <= EncryptedDataSizeCutoff) + return {}; + + const size_t payload_size = nodes.size() * sizeof(NodeInfo) - EncryptedDataSizeCutoff; + + const size_t tag_length = sizeof(EncryptedDataTag) - sizeof(TagHeader) + payload_size; + + // TODO(Subv): What does the 3DS do when a game has too much data to fit into the tag? + ASSERT_MSG(tag_length <= 255, "Data is too big."); + + EncryptedDataTag tag{}; + tag.header.tag_id = static_cast(TagId::VendorSpecific); + tag.header.length = tag_length; + tag.oui_type = static_cast(NintendoTagId::EncryptedData1); + tag.oui = NintendoOUI; + + std::vector buffer(sizeof(tag) + payload_size); + std::memcpy(buffer.data(), &tag, sizeof(tag)); + + std::vector encrypted_data = GeneratedEncryptedData(network_info, nodes); + std::memcpy(buffer.data() + sizeof(tag), encrypted_data.data() + EncryptedDataSizeCutoff, + payload_size); + + return buffer; +} + +/** + * Generates a buffer with the Nintendo tagged parameters of an 802.11 Beacon frame + * for UDS communication. + * @returns A buffer with the Nintendo tagged parameters of the beacon frame. + */ +std::vector GenerateNintendoTaggedParameters(const NetworkInfo& network_info, + const NodeList& nodes) { + ASSERT_MSG(network_info.max_nodes == nodes.size(), "Inconsistent network state."); + + std::vector buffer = GenerateNintendoDummyTag(); + std::vector network_info_tag = GenerateNintendoNetworkInfoTag(network_info); + std::vector first_data_tag = GenerateNintendoFirstEncryptedDataTag(network_info, nodes); + std::vector second_data_tag = GenerateNintendoSecondEncryptedDataTag(network_info, nodes); + + buffer.insert(buffer.end(), network_info_tag.begin(), network_info_tag.end()); + buffer.insert(buffer.end(), first_data_tag.begin(), first_data_tag.end()); + buffer.insert(buffer.end(), second_data_tag.begin(), second_data_tag.end()); + + return buffer; +} + +std::vector GenerateBeaconFrame(const NetworkInfo& network_info, const NodeList& nodes) { + std::vector buffer = GenerateFixedParameters(); + std::vector basic_tags = GenerateBasicTaggedParameters(); + std::vector nintendo_tags = GenerateNintendoTaggedParameters(network_info, nodes); + + buffer.insert(buffer.end(), basic_tags.begin(), basic_tags.end()); + buffer.insert(buffer.end(), nintendo_tags.begin(), nintendo_tags.end()); + + return buffer; +} + +std::deque 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 new file mode 100644 index 000000000..6df4c4f47 --- /dev/null +++ b/src/core/hle/service/nwm/uds_beacon.h @@ -0,0 +1,173 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include "common/common_types.h" +#include "common/swap.h" +#include "core/hle/service/service.h" + +namespace Service { +namespace NWM { + +using MacAddress = std::array; + +/// The maximum number of nodes that can exist in an UDS session. +constexpr u32 UDSMaxNodes = 16; +constexpr std::array 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. + */ +enum class NintendoTagId : u8 { + Dummy = 20, + NetworkInfo = 21, + EncryptedData0 = 24, + EncryptedData1 = 25, +}; + +struct BeaconEntryHeader { + u32_le total_size; + INSERT_PADDING_BYTES(1); + u8 wifi_channel; + INSERT_PADDING_BYTES(2); + MacAddress mac_address; + INSERT_PADDING_BYTES(6); + u32_le unk_size; + u32_le header_size; +}; + +static_assert(sizeof(BeaconEntryHeader) == 0x1C, "BeaconEntryHeader has incorrect size."); + +struct BeaconDataReplyHeader { + u32_le max_output_size; + u32_le total_size; + u32_le total_entries; +}; + +static_assert(sizeof(BeaconDataReplyHeader) == 12, "BeaconDataReplyHeader has incorrect size."); + +#pragma pack(push, 1) +struct BeaconFrameHeader { + // Number of microseconds the AP has been active. + u64_le timestamp; + // Interval between beacon transmissions, expressed in TU. + u16_le beacon_interval; + // Indicates the presence of optional capabilities. + u16_le capabilities; +}; +#pragma pack(pop) + +static_assert(sizeof(BeaconFrameHeader) == 12, "BeaconFrameHeader has incorrect size."); + +struct TagHeader { + u8 tag_id; + u8 length; +}; + +static_assert(sizeof(TagHeader) == 2, "TagHeader has incorrect size."); + +struct DummyTag { + TagHeader header; + std::array oui; + u8 oui_type; + std::array data; +}; + +static_assert(sizeof(DummyTag) == 9, "DummyTag has incorrect size."); + +struct NetworkInfoTag { + TagHeader header; + std::array network_info; + std::array sha_hash; + u8 appdata_size; +}; + +static_assert(sizeof(NetworkInfoTag) == 54, "NetworkInfoTag has incorrect size."); + +struct EncryptedDataTag { + TagHeader header; + std::array oui; + u8 oui_type; +}; + +static_assert(sizeof(EncryptedDataTag) == 6, "EncryptedDataTag has incorrect size."); + +#pragma pack(push, 1) +// The raw bytes of this structure are the CTR used in the encryption (AES-CTR) +// of the beacon data stored in the EncryptedDataTags. +struct BeaconDataCryptoCTR { + MacAddress host_mac; + u32_le wlan_comm_id; + u8 id; + INSERT_PADDING_BYTES(1); + u32_le network_id; +}; + +static_assert(sizeof(BeaconDataCryptoCTR) == 0x10, "BeaconDataCryptoCTR has incorrect size."); + +struct BeaconNodeInfo { + u64_be friend_code_seed; + std::array username; + u16_be network_node_id; +}; + +static_assert(sizeof(BeaconNodeInfo) == 0x1E, "BeaconNodeInfo has incorrect size."); + +struct BeaconData { + std::array md5_hash; + u16_be bitmask; +}; +#pragma pack(pop) + +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 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`. + */ +void DecryptBeaconData(const NetworkInfo& network_info, std::vector& buffer); + +/** + * Generates an 802.11 beacon frame starting at the management frame header. + * This frame contains information about the network and its connected clients. + * @returns The generated frame. + */ +std::vector 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 GetReceivedPackets(WifiPacket::PacketType type, const MacAddress& sender); +} // namespace NWM +} // namespace Service