diff options
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | externals/cryptopp/CMakeLists.txt | 5 | ||||
-rw-r--r-- | src/citra/citra.cpp | 3 | ||||
-rw-r--r-- | src/citra/config.cpp | 6 | ||||
-rw-r--r-- | src/citra_qt/configuration/config.cpp | 6 | ||||
-rw-r--r-- | src/citra_qt/configuration/configure_debug.ui | 7 | ||||
-rw-r--r-- | src/core/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/core/hle/function_wrappers.h | 15 | ||||
-rw-r--r-- | src/core/hle/kernel/errors.h | 5 | ||||
-rw-r--r-- | src/core/hle/kernel/hle_ipc.cpp | 19 | ||||
-rw-r--r-- | src/core/hle/kernel/server_port.cpp | 12 | ||||
-rw-r--r-- | src/core/hle/kernel/server_port.h | 11 | ||||
-rw-r--r-- | src/core/hle/service/nwm/nwm_uds.cpp | 77 | ||||
-rw-r--r-- | src/core/hle/service/nwm/uds_data.cpp | 278 | ||||
-rw-r--r-- | src/core/hle/service/nwm/uds_data.h | 78 | ||||
-rw-r--r-- | src/core/hle/svc.cpp | 45 | ||||
-rw-r--r-- | src/core/hw/aes/key.h | 2 | ||||
-rw-r--r-- | src/tests/core/hle/kernel/hle_ipc.cpp | 25 |
19 files changed, 575 insertions, 27 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index a61dee6e0..4668d4bea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,6 +79,8 @@ else() add_definitions(/D_CRT_SECURE_NO_WARNINGS /D_CRT_NONSTDC_NO_DEPRECATE /D_SCL_SECURE_NO_WARNINGS) # Avoid windows.h junk add_definitions(/DNOMINMAX) + # Avoid windows.h from including some usually unused libs like winsocks.h, since this might cause some redefinition errors. + add_definitions(/DWIN32_LEAN_AND_MEAN) # set up output paths for executable binaries (.exe-files, and .dll-files on DLL-capable platforms) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) @@ -5,7 +5,7 @@ Citra Emulator [![Travis CI Build Status](https://travis-ci.org/citra-emu/citra.svg?branch=master)](https://travis-ci.org/citra-emu/citra) [![AppVeyor CI Build Status](https://ci.appveyor.com/api/projects/status/sdf1o4kh3g1e68m9?svg=true)](https://ci.appveyor.com/project/bunnei/citra) -Citra is an experimental open-source Nintendo 3DS emulator/debugger written in C++. It is written with portability in mind, with builds actively maintained for Windows, Linux and macOS. Citra only emulates a subset of 3DS hardware, and therefore is generally only useful for running/debugging homebrew applications. At this time, Citra is even able to boot several commercial games! Most of these do not run to a playable state, but we are working every day to advance the project forward. +Citra is an experimental open-source Nintendo 3DS emulator/debugger written in C++. It is written with portability in mind, with builds actively maintained for Windows, Linux and macOS. Citra only emulates a subset of 3DS hardware and therefore is generally only useful for running/debugging homebrew applications. At this time, Citra is even able to boot several commercial games! Most of these do not run to a playable state, but we are working every day to advance the project forward. Citra is licensed under the GPLv2 (or any later version). Refer to the license.txt file included. Please read the [FAQ](https://citra-emu.org/wiki/faq/) before getting started with the project. @@ -27,7 +27,7 @@ If you want to contribute please take a look at the [Contributor's Guide](CONTRI ### Support -We happily accept monetary donations, or donated games and hardware. Please see our [donations page](https://citra-emu.org/donate/) for more information on how you can contribute to Citra. Any donations received will go towards things like: +We happily accept monetary donations or donated games and hardware. Please see our [donations page](https://citra-emu.org/donate/) for more information on how you can contribute to Citra. Any donations received will go towards things like: * 3DS consoles for developers to explore the hardware * 3DS games for testing * Any equipment required for homebrew diff --git a/externals/cryptopp/CMakeLists.txt b/externals/cryptopp/CMakeLists.txt index 864de18bb..8a626e44a 100644 --- a/externals/cryptopp/CMakeLists.txt +++ b/externals/cryptopp/CMakeLists.txt @@ -44,6 +44,11 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Intel") add_definitions(-wd68 -wd186 -wd279 -wd327 -wd161 -wd3180) endif() +if(MSVC) + # Disable C4390: empty controlled statement found: is this the intent? + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4390") +endif() + # Endianness TEST_BIG_ENDIAN(IS_BIG_ENDIAN) if(IS_BIG_ENDIAN) diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp index dd357ff72..14574e56c 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp @@ -18,7 +18,10 @@ #endif #ifdef _WIN32 +// windows.h needs to be included before shellapi.h #include <windows.h> + +#include <shellapi.h> #endif #include "citra/config.h" diff --git a/src/citra/config.cpp b/src/citra/config.cpp index f08b4069c..957d8dc86 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -88,9 +88,9 @@ void Config::ReadValues() { Settings::values.toggle_framelimit = sdl2_config->GetBoolean("Renderer", "toggle_framelimit", true); - Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 1.0); - Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 1.0); - Settings::values.bg_blue = (float)sdl2_config->GetReal("Renderer", "bg_blue", 1.0); + Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 0.0); + Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 0.0); + Settings::values.bg_blue = (float)sdl2_config->GetReal("Renderer", "bg_blue", 0.0); // Layout Settings::values.layout_option = diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 2b99447ec..64ffc9152 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -70,9 +70,9 @@ void Config::ReadValues() { Settings::values.use_vsync = qt_config->value("use_vsync", false).toBool(); Settings::values.toggle_framelimit = qt_config->value("toggle_framelimit", true).toBool(); - Settings::values.bg_red = qt_config->value("bg_red", 1.0).toFloat(); - Settings::values.bg_green = qt_config->value("bg_green", 1.0).toFloat(); - Settings::values.bg_blue = qt_config->value("bg_blue", 1.0).toFloat(); + Settings::values.bg_red = qt_config->value("bg_red", 0.0).toFloat(); + Settings::values.bg_green = qt_config->value("bg_green", 0.0).toFloat(); + Settings::values.bg_blue = qt_config->value("bg_blue", 0.0).toFloat(); qt_config->endGroup(); qt_config->beginGroup("Layout"); diff --git a/src/citra_qt/configuration/configure_debug.ui b/src/citra_qt/configuration/configure_debug.ui index bbbb0e3f4..96638ebdb 100644 --- a/src/citra_qt/configuration/configure_debug.ui +++ b/src/citra_qt/configuration/configure_debug.ui @@ -23,6 +23,13 @@ </property> <layout class="QVBoxLayout" name="verticalLayout_2"> <item> + <widget class="QLabel"> + <property name="text"> + <string>The GDB Stub only works correctly when the CPU JIT is off.</string> + </property> + </widget> + </item> + <item> <layout class="QHBoxLayout" name="horizontalLayout_3"> <item> <widget class="QCheckBox" name="toggle_gdbstub"> diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index b16a89990..ea09819e5 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -144,6 +144,7 @@ set(SRCS hle/service/nwm/nwm_tst.cpp hle/service/nwm/nwm_uds.cpp hle/service/nwm/uds_beacon.cpp + hle/service/nwm/uds_data.cpp hle/service/pm_app.cpp hle/service/ptm/ptm.cpp hle/service/ptm/ptm_gets.cpp @@ -341,6 +342,7 @@ set(HEADERS hle/service/nwm/nwm_tst.h hle/service/nwm/nwm_uds.h hle/service/nwm/uds_beacon.h + hle/service/nwm/uds_data.h hle/service/pm_app.h hle/service/ptm/ptm.h hle/service/ptm/ptm_gets.h diff --git a/src/core/hle/function_wrappers.h b/src/core/hle/function_wrappers.h index 18b6e7017..410bb87ea 100644 --- a/src/core/hle/function_wrappers.h +++ b/src/core/hle/function_wrappers.h @@ -226,9 +226,18 @@ void Wrap() { u32 retval = func(¶m_1, ¶m_2, reinterpret_cast<const char*>(Memory::GetPointer(PARAM(2))), PARAM(3)) .raw; - // The first out parameter is moved into R2 and the second is moved into R1. - Core::CPU().SetReg(1, param_2); - Core::CPU().SetReg(2, param_1); + Core::CPU().SetReg(1, param_1); + Core::CPU().SetReg(2, param_2); + FuncReturn(retval); +} + +template <ResultCode func(Kernel::Handle*, Kernel::Handle*)> +void Wrap() { + Kernel::Handle param_1 = 0; + Kernel::Handle param_2 = 0; + u32 retval = func(¶m_1, ¶m_2).raw; + Core::CPU().SetReg(1, param_1); + Core::CPU().SetReg(2, param_2); FuncReturn(retval); } diff --git a/src/core/hle/kernel/errors.h b/src/core/hle/kernel/errors.h index b3b60e7df..64aa61460 100644 --- a/src/core/hle/kernel/errors.h +++ b/src/core/hle/kernel/errors.h @@ -13,6 +13,7 @@ enum { OutOfHandles = 19, SessionClosedByRemote = 26, PortNameTooLong = 30, + NoPendingSessions = 35, WrongPermission = 46, InvalidBufferDescriptor = 48, MaxConnectionsReached = 52, @@ -94,5 +95,9 @@ constexpr ResultCode ERR_OUT_OF_RANGE_KERNEL(ErrorDescription::OutOfRange, Error ErrorLevel::Permanent); // 0xD8E007FD constexpr ResultCode RESULT_TIMEOUT(ErrorDescription::Timeout, ErrorModule::OS, ErrorSummary::StatusChanged, ErrorLevel::Info); +/// Returned when Accept() is called on a port with no sessions to be accepted. +constexpr ResultCode ERR_NO_PENDING_SESSIONS(ErrCodes::NoPendingSessions, ErrorModule::OS, + ErrorSummary::WouldBlock, + ErrorLevel::Permanent); // 0xD8401823 } // namespace Kernel diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index 1cac1d0c9..5ebe2eca4 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -67,10 +67,13 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* sr ASSERT(i + num_handles <= command_size); // TODO(yuriks): Return error for (u32 j = 0; j < num_handles; ++j) { Handle handle = src_cmdbuf[i]; - SharedPtr<Object> object = src_table.GetGeneric(handle); - ASSERT(object != nullptr); // TODO(yuriks): Return error - if (descriptor == IPC::DescriptorType::MoveHandle) { - src_table.Close(handle); + SharedPtr<Object> object = nullptr; + if (handle != 0) { + object = src_table.GetGeneric(handle); + ASSERT(object != nullptr); // TODO(yuriks): Return error + if (descriptor == IPC::DescriptorType::MoveHandle) { + src_table.Close(handle); + } } cmd_buf[i++] = AddOutgoingHandle(std::move(object)); @@ -112,9 +115,11 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, P ASSERT(i + num_handles <= command_size); for (u32 j = 0; j < num_handles; ++j) { SharedPtr<Object> object = GetIncomingHandle(cmd_buf[i]); - - // TODO(yuriks): Figure out the proper error handling for if this fails - Handle handle = dst_table.Create(object).Unwrap(); + Handle handle = 0; + if (object != nullptr) { + // TODO(yuriks): Figure out the proper error handling for if this fails + handle = dst_table.Create(object).Unwrap(); + } dst_cmdbuf[i++] = handle; } break; diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp index 4d20c39a1..49a9cdfa3 100644 --- a/src/core/hle/kernel/server_port.cpp +++ b/src/core/hle/kernel/server_port.cpp @@ -5,8 +5,10 @@ #include <tuple> #include "common/assert.h" #include "core/hle/kernel/client_port.h" +#include "core/hle/kernel/errors.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/server_port.h" +#include "core/hle/kernel/server_session.h" #include "core/hle/kernel/thread.h" namespace Kernel { @@ -14,6 +16,16 @@ namespace Kernel { ServerPort::ServerPort() {} ServerPort::~ServerPort() {} +ResultVal<SharedPtr<ServerSession>> ServerPort::Accept() { + if (pending_sessions.empty()) { + return ERR_NO_PENDING_SESSIONS; + } + + auto session = std::move(pending_sessions.back()); + pending_sessions.pop_back(); + return MakeResult(std::move(session)); +} + bool ServerPort::ShouldWait(Thread* thread) const { // If there are no pending sessions, we wait until a new one is added. return pending_sessions.size() == 0; diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h index f1419cd46..6fe7c7f2f 100644 --- a/src/core/hle/kernel/server_port.h +++ b/src/core/hle/kernel/server_port.h @@ -14,6 +14,7 @@ namespace Kernel { class ClientPort; +class ServerSession; class SessionRequestHandler; class ServerPort final : public WaitObject { @@ -41,6 +42,12 @@ public: } /** + * Accepts a pending incoming connection on this port. If there are no pending sessions, will + * return ERR_NO_PENDING_SESSIONS. + */ + ResultVal<SharedPtr<ServerSession>> Accept(); + + /** * Sets the HLE handler template for the port. ServerSessions crated by connecting to this port * will inherit a reference to this handler. */ @@ -50,8 +57,8 @@ public: std::string name; ///< Name of port (optional) - std::vector<SharedPtr<WaitObject>> - pending_sessions; ///< ServerSessions waiting to be accepted by the port + /// ServerSessions waiting to be accepted by the port + std::vector<SharedPtr<ServerSession>> pending_sessions; /// This session's HLE request handler template (optional) /// ServerSessions created from this port inherit a reference to this handler. diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp index a7149c9e8..6dbdff044 100644 --- a/src/core/hle/service/nwm/nwm_uds.cpp +++ b/src/core/hle/service/nwm/nwm_uds.cpp @@ -15,6 +15,7 @@ #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_data.h" #include "core/memory.h" namespace Service { @@ -373,6 +374,80 @@ static void DestroyNetwork(Interface* self) { } /** + * NWM_UDS::SendTo service function. + * Sends a data frame to the UDS network we're connected to. + * Inputs: + * 0 : Command header. + * 1 : Unknown. + * 2 : u16 Destination network node id. + * 3 : u8 Data channel. + * 4 : Buffer size >> 2 + * 5 : Data size + * 6 : Flags + * 7 : Input buffer descriptor + * 8 : Input buffer address + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + */ +static void SendTo(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x17, 6, 2); + + rp.Skip(1, false); + u16 dest_node_id = rp.Pop<u16>(); + u8 data_channel = rp.Pop<u8>(); + rp.Skip(1, false); + u32 data_size = rp.Pop<u32>(); + u32 flags = rp.Pop<u32>(); + + size_t desc_size; + const VAddr input_address = rp.PopStaticBuffer(&desc_size, false); + ASSERT(desc_size == data_size); + + 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; + } + + if (dest_node_id == connection_status.network_node_id) { + rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::UDS, + ErrorSummary::WrongArgument, ErrorLevel::Status)); + return; + } + + // TODO(Subv): Do something with the flags. + + constexpr size_t MaxSize = 0x5C6; + if (data_size > MaxSize) { + rb.Push(ResultCode(ErrorDescription::TooLarge, ErrorModule::UDS, + ErrorSummary::WrongArgument, ErrorLevel::Usage)); + return; + } + + std::vector<u8> data(data_size); + Memory::ReadBlock(input_address, data.data(), data.size()); + + // 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); + + // TODO(Subv): Retrieve the MAC address of the dest_node_id and our own to encrypt + // and encapsulate the payload. + + // TODO(Subv): Send the frame. + + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_NWM, "(STUB) called dest_node_id=%u size=%u flags=%u channel=%u", + static_cast<u32>(dest_node_id), data_size, flags, static_cast<u32>(data_channel)); +} + +/** * NWM_UDS::GetChannel service function. * Returns the WiFi channel in which the network we're connected to is transmitting. * Inputs: @@ -600,7 +675,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00130040, nullptr, "Unbind"}, {0x001400C0, nullptr, "PullPacket"}, {0x00150080, nullptr, "SetMaxSendDelay"}, - {0x00170182, nullptr, "SendTo"}, + {0x00170182, SendTo, "SendTo"}, {0x001A0000, GetChannel, "GetChannel"}, {0x001B0302, InitializeWithVersion, "InitializeWithVersion"}, {0x001D0044, BeginHostingNetwork, "BeginHostingNetwork"}, diff --git a/src/core/hle/service/nwm/uds_data.cpp b/src/core/hle/service/nwm/uds_data.cpp new file mode 100644 index 000000000..8c6742dba --- /dev/null +++ b/src/core/hle/service/nwm/uds_data.cpp @@ -0,0 +1,278 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include <cryptopp/aes.h> +#include <cryptopp/ccm.h> +#include <cryptopp/filters.h> +#include <cryptopp/md5.h> +#include <cryptopp/modes.h> +#include "core/hle/service/nwm/nwm_uds.h" +#include "core/hle/service/nwm/uds_data.h" +#include "core/hw/aes/key.h" + +namespace Service { +namespace NWM { + +using MacAddress = std::array<u8, 6>; + +/* + * Generates a SNAP-enabled 802.2 LLC header for the specified protocol. + * @returns a buffer with the bytes of the generated header. + */ +static std::vector<u8> GenerateLLCHeader(EtherType protocol) { + LLCHeader header{}; + header.protocol = static_cast<u16>(protocol); + + std::vector<u8> buffer(sizeof(header)); + memcpy(buffer.data(), &header, sizeof(header)); + + return buffer; +} + +/* + * Generates a Nintendo UDS SecureData header with the specified parameters. + * @returns a buffer with the bytes of the generated header. + */ +static std::vector<u8> GenerateSecureDataHeader(u16 data_size, u8 channel, u16 dest_node_id, + u16 src_node_id, u16 sequence_number) { + SecureDataHeader header{}; + header.protocol_size = data_size + sizeof(SecureDataHeader); + // Note: This size includes everything except the first 4 bytes of the structure, + // reinforcing the hypotheses that the first 4 bytes are actually the header of + // another container protocol. + header.securedata_size = data_size + sizeof(SecureDataHeader) - 4; + // Frames sent by the emulated application are never UDS management frames + header.is_management = 0; + header.data_channel = channel; + header.sequence_number = sequence_number; + header.dest_node_id = dest_node_id; + header.src_node_id = src_node_id; + + std::vector<u8> buffer(sizeof(header)); + memcpy(buffer.data(), &header, sizeof(header)); + + return buffer; +} + +/* + * Calculates the CTR used for the AES-CTR process that calculates + * the CCMP crypto key for data frames. + * @returns The CTR used for data frames crypto key generation. + */ +static std::array<u8, CryptoPP::MD5::DIGESTSIZE> GetDataCryptoCTR(const NetworkInfo& network_info) { + DataFrameCryptoCTR 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<u8, CryptoPP::MD5::DIGESTSIZE> hash; + CryptoPP::MD5().CalculateDigest(hash.data(), reinterpret_cast<u8*>(&data), sizeof(data)); + + return hash; +} + +/* + * Generates the key used for encrypting the 802.11 data frames generated by UDS. + * @returns The key used for data frames crypto. + */ +static std::array<u8, CryptoPP::AES::BLOCKSIZE> GenerateDataCCMPKey( + const std::vector<u8>& passphrase, const NetworkInfo& network_info) { + // Calculate the MD5 hash of the input passphrase. + std::array<u8, CryptoPP::MD5::DIGESTSIZE> passphrase_hash; + CryptoPP::MD5().CalculateDigest(passphrase_hash.data(), passphrase.data(), passphrase.size()); + + std::array<u8, CryptoPP::AES::BLOCKSIZE> ccmp_key; + + // The CCMP key is the result of encrypting the MD5 hash of the passphrase with AES-CTR using + // keyslot 0x2D. + using CryptoPP::AES; + std::array<u8, CryptoPP::MD5::DIGESTSIZE> counter = GetDataCryptoCTR(network_info); + std::array<u8, AES::BLOCKSIZE> key = HW::AES::GetNormalKey(HW::AES::KeySlotID::UDSDataKey); + CryptoPP::CTR_Mode<AES>::Encryption aes; + aes.SetKeyWithIV(key.data(), AES::BLOCKSIZE, counter.data()); + aes.ProcessData(ccmp_key.data(), passphrase_hash.data(), passphrase_hash.size()); + + return ccmp_key; +} + +/* + * Generates the Additional Authenticated Data (AAD) for an UDS 802.11 encrypted data frame. + * @returns a buffer with the bytes of the AAD. + */ +static std::vector<u8> GenerateCCMPAAD(const MacAddress& sender, const MacAddress& receiver, + const MacAddress& bssid, u16 frame_control) { + // Reference: IEEE 802.11-2007 + + // 8.3.3.3.2 Construct AAD (22-30 bytes) + // The AAD is constructed from the MPDU header. The AAD does not include the header Duration + // field, because the Duration field value can change due to normal IEEE 802.11 operation (e.g., + // a rate change during retransmission). For similar reasons, several subfields in the Frame + // Control field are masked to 0. + struct { + u16_be FC; // MPDU Frame Control field + MacAddress A1; + MacAddress A2; + MacAddress A3; + u16_be SC; // MPDU Sequence Control field + } aad_struct{}; + + constexpr u16 AADFrameControlMask = 0x8FC7; + aad_struct.FC = frame_control & AADFrameControlMask; + aad_struct.SC = 0; + + bool to_ds = (frame_control & (1 << 0)) != 0; + bool from_ds = (frame_control & (1 << 1)) != 0; + // In the 802.11 standard, ToDS = 1 and FromDS = 1 is a valid configuration, + // however, the 3DS doesn't seem to transmit frames with such combination. + ASSERT_MSG(to_ds != from_ds, "Invalid combination"); + + // The meaning of the address fields depends on the ToDS and FromDS fields. + if (from_ds) { + aad_struct.A1 = receiver; + aad_struct.A2 = bssid; + aad_struct.A3 = sender; + } + + if (to_ds) { + aad_struct.A1 = bssid; + aad_struct.A2 = sender; + aad_struct.A3 = receiver; + } + + std::vector<u8> aad(sizeof(aad_struct)); + std::memcpy(aad.data(), &aad_struct, sizeof(aad_struct)); + + return aad; +} + +/* + * Decrypts the payload of an encrypted 802.11 data frame using the specified key. + * @returns The decrypted payload. + */ +static std::vector<u8> DecryptDataFrame(const std::vector<u8>& encrypted_payload, + const std::array<u8, CryptoPP::AES::BLOCKSIZE>& ccmp_key, + const MacAddress& sender, const MacAddress& receiver, + const MacAddress& bssid, u16 sequence_number, + u16 frame_control) { + + // Reference: IEEE 802.11-2007 + + std::vector<u8> aad = GenerateCCMPAAD(sender, receiver, bssid, frame_control); + + std::vector<u8> packet_number{0, + 0, + 0, + 0, + static_cast<u8>((sequence_number >> 8) & 0xFF), + static_cast<u8>(sequence_number & 0xFF)}; + + // 8.3.3.3.3 Construct CCM nonce (13 bytes) + std::vector<u8> nonce; + nonce.push_back(0); // priority + nonce.insert(nonce.end(), sender.begin(), sender.end()); // Address 2 + nonce.insert(nonce.end(), packet_number.begin(), packet_number.end()); // PN + + try { + CryptoPP::CCM<CryptoPP::AES, 8>::Decryption d; + d.SetKeyWithIV(ccmp_key.data(), ccmp_key.size(), nonce.data(), nonce.size()); + d.SpecifyDataLengths(aad.size(), encrypted_payload.size() - 8, 0); + + CryptoPP::AuthenticatedDecryptionFilter df( + d, nullptr, CryptoPP::AuthenticatedDecryptionFilter::MAC_AT_END | + CryptoPP::AuthenticatedDecryptionFilter::THROW_EXCEPTION); + // put aad + df.ChannelPut(CryptoPP::AAD_CHANNEL, aad.data(), aad.size()); + + // put cipher with mac + df.ChannelPut(CryptoPP::DEFAULT_CHANNEL, encrypted_payload.data(), + encrypted_payload.size() - 8); + df.ChannelPut(CryptoPP::DEFAULT_CHANNEL, + encrypted_payload.data() + encrypted_payload.size() - 8, 8); + + df.ChannelMessageEnd(CryptoPP::AAD_CHANNEL); + df.ChannelMessageEnd(CryptoPP::DEFAULT_CHANNEL); + df.SetRetrievalChannel(CryptoPP::DEFAULT_CHANNEL); + + int size = df.MaxRetrievable(); + + std::vector<u8> pdata(size); + df.Get(pdata.data(), size); + return pdata; + } catch (CryptoPP::Exception&) { + LOG_ERROR(Service_NWM, "failed to decrypt"); + } + + return {}; +} + +/* + * Encrypts the payload of an 802.11 data frame using the specified key. + * @returns The encrypted payload. + */ +static std::vector<u8> EncryptDataFrame(const std::vector<u8>& payload, + const std::array<u8, CryptoPP::AES::BLOCKSIZE>& ccmp_key, + const MacAddress& sender, const MacAddress& receiver, + const MacAddress& bssid, u16 sequence_number, + u16 frame_control) { + // Reference: IEEE 802.11-2007 + + std::vector<u8> aad = GenerateCCMPAAD(sender, receiver, bssid, frame_control); + + std::vector<u8> packet_number{0, + 0, + 0, + 0, + static_cast<u8>((sequence_number >> 8) & 0xFF), + static_cast<u8>(sequence_number & 0xFF)}; + + // 8.3.3.3.3 Construct CCM nonce (13 bytes) + std::vector<u8> nonce; + nonce.push_back(0); // priority + nonce.insert(nonce.end(), sender.begin(), sender.end()); // Address 2 + nonce.insert(nonce.end(), packet_number.begin(), packet_number.end()); // PN + + try { + CryptoPP::CCM<CryptoPP::AES, 8>::Encryption d; + d.SetKeyWithIV(ccmp_key.data(), ccmp_key.size(), nonce.data(), nonce.size()); + d.SpecifyDataLengths(aad.size(), payload.size(), 0); + + CryptoPP::AuthenticatedEncryptionFilter df(d); + // put aad + df.ChannelPut(CryptoPP::AAD_CHANNEL, aad.data(), aad.size()); + df.ChannelMessageEnd(CryptoPP::AAD_CHANNEL); + + // put plaintext + df.ChannelPut(CryptoPP::DEFAULT_CHANNEL, payload.data(), payload.size()); + df.ChannelMessageEnd(CryptoPP::DEFAULT_CHANNEL); + + df.SetRetrievalChannel(CryptoPP::DEFAULT_CHANNEL); + + int size = df.MaxRetrievable(); + + std::vector<u8> cipher(size); + df.Get(cipher.data(), size); + return cipher; + } catch (CryptoPP::Exception&) { + LOG_ERROR(Service_NWM, "failed to encrypt"); + } + + return {}; +} + +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); + + buffer.insert(buffer.end(), securedata_header.begin(), securedata_header.end()); + buffer.insert(buffer.end(), data.begin(), data.end()); + return buffer; +} + +} // namespace NWM +} // namespace Service diff --git a/src/core/hle/service/nwm/uds_data.h b/src/core/hle/service/nwm/uds_data.h new file mode 100644 index 000000000..a23520a41 --- /dev/null +++ b/src/core/hle/service/nwm/uds_data.h @@ -0,0 +1,78 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <vector> +#include "common/common_types.h" +#include "common/swap.h" +#include "core/hle/service/service.h" + +namespace Service { +namespace NWM { + +enum class SAP : u8 { SNAPExtensionUsed = 0xAA }; + +enum class PDUControl : u8 { UnnumberedInformation = 3 }; + +enum class EtherType : u16 { SecureData = 0x876D, EAPoL = 0x888E }; + +/* + * 802.2 header, UDS packets always use SNAP for these headers, + * which means the dsap and ssap are always SNAPExtensionUsed (0xAA) + * and the OUI is always 0. + */ +struct LLCHeader { + u8 dsap = static_cast<u8>(SAP::SNAPExtensionUsed); + u8 ssap = static_cast<u8>(SAP::SNAPExtensionUsed); + u8 control = static_cast<u8>(PDUControl::UnnumberedInformation); + std::array<u8, 3> OUI = {}; + u16_be protocol; +}; + +static_assert(sizeof(LLCHeader) == 8, "LLCHeader has the wrong size"); + +/* + * Nintendo SecureData header, every UDS packet contains one, + * it is used to store metadata about the transmission such as + * the source and destination network node ids. + */ +struct SecureDataHeader { + // TODO(Subv): It is likely that the first 4 bytes of this header are + // actually part of another container protocol. + u16_be protocol_size; + INSERT_PADDING_BYTES(2); + u16_be securedata_size; + u8 is_management; + u8 data_channel; + u16_be sequence_number; + u16_be dest_node_id; + u16_be src_node_id; +}; + +static_assert(sizeof(SecureDataHeader) == 14, "SecureDataHeader has the wrong size"); + +/* + * The raw bytes of this structure are the CTR used in the encryption (AES-CTR) + * process used to generate the CCMP key for data frame encryption. + */ +struct DataFrameCryptoCTR { + u32_le wlan_comm_id; + u32_le network_id; + std::array<u8, 6> host_mac; + u16_le id; +}; + +static_assert(sizeof(DataFrameCryptoCTR) == 16, "DataFrameCryptoCTR has the wrong size"); + +/** + * Generates an unencrypted 802.11 data payload. + * @returns The generated frame payload. + */ +std::vector<u8> GenerateDataPayload(const std::vector<u8>& data, u8 channel, u16 dest_node, + u16 src_node, u16 sequence_number); + +} // namespace NWM +} // namespace Service diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index e68b9f16a..c05401143 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -36,8 +36,9 @@ //////////////////////////////////////////////////////////////////////////////////////////////////// // Namespace SVC -using Kernel::SharedPtr; using Kernel::ERR_INVALID_HANDLE; +using Kernel::Handle; +using Kernel::SharedPtr; namespace SVC { @@ -933,7 +934,6 @@ static ResultCode CreatePort(Kernel::Handle* server_port, Kernel::Handle* client using Kernel::ServerPort; using Kernel::ClientPort; - using Kernel::SharedPtr; auto ports = ServerPort::CreatePortPair(max_sessions); CASCADE_RESULT(*client_port, Kernel::g_handle_table.Create( @@ -947,6 +947,41 @@ static ResultCode CreatePort(Kernel::Handle* server_port, Kernel::Handle* client return RESULT_SUCCESS; } +static ResultCode CreateSessionToPort(Handle* out_client_session, Handle client_port_handle) { + using Kernel::ClientPort; + SharedPtr<ClientPort> client_port = Kernel::g_handle_table.Get<ClientPort>(client_port_handle); + if (client_port == nullptr) + return ERR_INVALID_HANDLE; + + CASCADE_RESULT(auto session, client_port->Connect()); + CASCADE_RESULT(*out_client_session, Kernel::g_handle_table.Create(std::move(session))); + return RESULT_SUCCESS; +} + +static ResultCode CreateSession(Handle* server_session, Handle* client_session) { + auto sessions = Kernel::ServerSession::CreateSessionPair(); + + auto& server = std::get<SharedPtr<Kernel::ServerSession>>(sessions); + CASCADE_RESULT(*server_session, Kernel::g_handle_table.Create(std::move(server))); + + auto& client = std::get<SharedPtr<Kernel::ClientSession>>(sessions); + CASCADE_RESULT(*client_session, Kernel::g_handle_table.Create(std::move(client))); + + LOG_TRACE(Kernel_SVC, "called"); + return RESULT_SUCCESS; +} + +static ResultCode AcceptSession(Handle* out_server_session, Handle server_port_handle) { + using Kernel::ServerPort; + SharedPtr<ServerPort> server_port = Kernel::g_handle_table.Get<ServerPort>(server_port_handle); + if (server_port == nullptr) + return ERR_INVALID_HANDLE; + + CASCADE_RESULT(auto session, server_port->Accept()); + CASCADE_RESULT(*out_server_session, Kernel::g_handle_table.Create(std::move(session))); + return RESULT_SUCCESS; +} + static ResultCode GetSystemInfo(s64* out, u32 type, s32 param) { using Kernel::MemoryRegion; @@ -1121,9 +1156,9 @@ static const FunctionDef SVC_Table[] = { {0x45, nullptr, "Unknown"}, {0x46, nullptr, "Unknown"}, {0x47, HLE::Wrap<CreatePort>, "CreatePort"}, - {0x48, nullptr, "CreateSessionToPort"}, - {0x49, nullptr, "CreateSession"}, - {0x4A, nullptr, "AcceptSession"}, + {0x48, HLE::Wrap<CreateSessionToPort>, "CreateSessionToPort"}, + {0x49, HLE::Wrap<CreateSession>, "CreateSession"}, + {0x4A, HLE::Wrap<AcceptSession>, "AcceptSession"}, {0x4B, nullptr, "ReplyAndReceive1"}, {0x4C, nullptr, "ReplyAndReceive2"}, {0x4D, nullptr, "ReplyAndReceive3"}, diff --git a/src/core/hw/aes/key.h b/src/core/hw/aes/key.h index b01d04f13..c9f1342f4 100644 --- a/src/core/hw/aes/key.h +++ b/src/core/hw/aes/key.h @@ -12,6 +12,8 @@ namespace HW { namespace AES { enum KeySlotID : size_t { + // AES Keyslot used to generate the UDS data frame CCMP key. + UDSDataKey = 0x2D, APTWrap = 0x31, MaxKeySlotID = 0x40, diff --git a/src/tests/core/hle/kernel/hle_ipc.cpp b/src/tests/core/hle/kernel/hle_ipc.cpp index e07a28c5b..52336d027 100644 --- a/src/tests/core/hle/kernel/hle_ipc.cpp +++ b/src/tests/core/hle/kernel/hle_ipc.cpp @@ -18,7 +18,7 @@ static SharedPtr<Object> MakeObject() { return Event::Create(ResetType::OneShot); } -TEST_CASE("HLERequestContext::PopoulateFromIncomingCommandBuffer", "[core][kernel]") { +TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel]") { auto session = std::get<SharedPtr<ServerSession>>(ServerSession::CreateSessionPair()); HLERequestContext context(std::move(session)); @@ -94,6 +94,18 @@ TEST_CASE("HLERequestContext::PopoulateFromIncomingCommandBuffer", "[core][kerne REQUIRE(context.GetIncomingHandle(output[5]) == c); } + SECTION("translates null handles") { + const u32_le input[]{ + IPC::MakeHeader(0, 0, 2), IPC::MoveHandleDesc(1), 0, + }; + + auto result = context.PopulateFromIncomingCommandBuffer(input, *process, handle_table); + + REQUIRE(result == RESULT_SUCCESS); + auto* output = context.CommandBuffer(); + REQUIRE(context.GetIncomingHandle(output[2]) == nullptr); + } + SECTION("translates CallingPid descriptors") { const u32_le input[]{ IPC::MakeHeader(0, 0, 2), IPC::CallingPidDesc(), 0x98989898, @@ -171,6 +183,17 @@ TEST_CASE("HLERequestContext::WriteToOutgoingCommandBuffer", "[core][kernel]") { REQUIRE(handle_table.GetGeneric(output[4]) == b); } + SECTION("translates null handles") { + input[0] = IPC::MakeHeader(0, 0, 2); + input[1] = IPC::MoveHandleDesc(1); + input[2] = context.AddOutgoingHandle(nullptr); + + auto result = context.WriteToOutgoingCommandBuffer(output, *process, handle_table); + + REQUIRE(result == RESULT_SUCCESS); + REQUIRE(output[2] == 0); + } + SECTION("translates multi-handle descriptors") { auto a = MakeObject(); auto b = MakeObject(); |