mirror of
https://github.com/citra-emu/citra.git
synced 2024-12-23 22:00:04 +00:00
UDS: Add coonnect and disconnect
This commit is contained in:
parent
3c03da12c8
commit
f29a9ac3c1
@ -7,6 +7,7 @@
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@ -75,13 +76,22 @@ static u8 network_channel = DefaultNetworkChannel;
|
||||
// Information about the network that we're currently connected to.
|
||||
static NetworkInfo network_info;
|
||||
|
||||
// Mapping of mac addresses to their respective node_ids.
|
||||
static std::map<MacAddress, u32> node_map;
|
||||
|
||||
// Event that will generate and send the 802.11 beacon frames.
|
||||
static CoreTiming::EventType* beacon_broadcast_event;
|
||||
|
||||
// Callback identifier for the OnWifiPacketReceived event.
|
||||
static Network::RoomMember::CallbackHandle<Network::WifiPacket> wifi_packet_received;
|
||||
|
||||
// Mutex to synchronize access to the connection status between the emulation thread and the
|
||||
// network thread.
|
||||
static std::mutex connection_status_mutex;
|
||||
|
||||
// token for the blocking ConnectToNetwork
|
||||
static ThreadContinuationToken connection_token;
|
||||
|
||||
// Mutex to synchronize access to the list of received beacons between the emulation thread and the
|
||||
// network thread.
|
||||
static std::mutex beacon_mutex;
|
||||
@ -119,7 +129,12 @@ std::list<Network::WifiPacket> GetReceivedBeacons(const MacAddress& sender) {
|
||||
|
||||
/// Sends a WifiPacket to the room we're currently connected to.
|
||||
void SendPacket(Network::WifiPacket& packet) {
|
||||
// TODO(Subv): Implement.
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
if (room_member->GetState() == Network::RoomMember::State::Joined) {
|
||||
packet.transmitter_address = room_member->GetMacAddress();
|
||||
room_member->SendWifiPacket(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -214,6 +229,8 @@ static void HandleEAPoLPacket(const Network::WifiPacket& packet) {
|
||||
|
||||
network_info.total_nodes++;
|
||||
|
||||
node_map[packet.transmitter_address] = node_id;
|
||||
|
||||
// Send the EAPoL-Logoff packet.
|
||||
using Network::WifiPacket;
|
||||
WifiPacket eapol_logoff;
|
||||
@ -237,6 +254,7 @@ static void HandleEAPoLPacket(const Network::WifiPacket& packet) {
|
||||
}
|
||||
auto logoff = ParseEAPoLLogoffFrame(packet.data);
|
||||
|
||||
network_info.host_mac_address = packet.transmitter_address;
|
||||
network_info.total_nodes = logoff.connected_nodes;
|
||||
network_info.max_nodes = logoff.max_nodes;
|
||||
|
||||
@ -260,6 +278,9 @@ static void HandleEAPoLPacket(const Network::WifiPacket& packet) {
|
||||
// If blocking is implemented this lock needs to be changed,
|
||||
// otherwise it might cause deadlocks
|
||||
connection_status_event->Signal();
|
||||
if (connection_token.IsValid()) {
|
||||
ContinueClientThread(connection_token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -397,6 +418,36 @@ void HandleAuthenticationFrame(const Network::WifiPacket& packet) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles the deauthentication frames sent from clients to hosts, when they leave a session
|
||||
void HandleDeauthenticationFrame(const Network::WifiPacket& packet) {
|
||||
LOG_DEBUG(Service_NWM, "called");
|
||||
std::unique_lock<std::recursive_mutex> hle_lock(HLE::g_hle_lock, std::defer_lock);
|
||||
std::unique_lock<std::mutex> lock(connection_status_mutex, std::defer_lock);
|
||||
std::lock(hle_lock, lock);
|
||||
if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) {
|
||||
LOG_ERROR(Service_NWM, "Got deauthentication frame but we are not the host");
|
||||
return;
|
||||
}
|
||||
if (node_map.find(packet.transmitter_address) == node_map.end()) {
|
||||
LOG_ERROR(Service_NWM, "Got deauthentication frame from unknown node");
|
||||
return;
|
||||
}
|
||||
|
||||
u16 node_id = node_map[packet.transmitter_address];
|
||||
auto node = std::find_if(node_info.begin(), node_info.end(), [&node_id](const NodeInfo& info) {
|
||||
return info.network_node_id == node_id + 1;
|
||||
});
|
||||
ASSERT(node != node_info.end());
|
||||
|
||||
connection_status.node_bitmask &= ~(1 << node_id);
|
||||
connection_status.changed_nodes |= 1 << node_id;
|
||||
connection_status.total_nodes--;
|
||||
|
||||
network_info.total_nodes--;
|
||||
node_info.erase(node);
|
||||
connection_status_event->Signal();
|
||||
}
|
||||
|
||||
static void HandleDataFrame(const Network::WifiPacket& packet) {
|
||||
switch (GetFrameEtherType(packet.data)) {
|
||||
case EtherType::EAPoL:
|
||||
@ -423,6 +474,9 @@ void OnWifiPacketReceived(const Network::WifiPacket& packet) {
|
||||
case Network::WifiPacket::PacketType::Data:
|
||||
HandleDataFrame(packet);
|
||||
break;
|
||||
case Network::WifiPacket::PacketType::Deauthentication:
|
||||
HandleDeauthenticationFrame(packet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -435,13 +489,22 @@ void OnWifiPacketReceived(const Network::WifiPacket& packet) {
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
*/
|
||||
static void Shutdown(Interface* self) {
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x03, 0, 0);
|
||||
|
||||
// TODO(purpasmart): Verify return header on HW
|
||||
if (auto room_member = Network::GetRoomMember().lock())
|
||||
room_member->Unbind(wifi_packet_received);
|
||||
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||
// TODO(B3N30): Check on HW if Shutdown signals those events
|
||||
for (auto bind_node : channel_data) {
|
||||
bind_node.second.event->Signal();
|
||||
}
|
||||
channel_data.clear();
|
||||
|
||||
LOG_WARNING(Service_NWM, "(STUBBED) called");
|
||||
recv_buffer_memory.reset();
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_DEBUG(Service_NWM, "called");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -494,7 +557,6 @@ static void RecvBeaconBroadcastData(Interface* self) {
|
||||
|
||||
Memory::WriteBlock(current_buffer_pos, &data_reply_header, sizeof(BeaconDataReplyHeader));
|
||||
current_buffer_pos += sizeof(BeaconDataReplyHeader);
|
||||
|
||||
// Write each of the received beacons into the buffer
|
||||
for (const auto& beacon : beacons) {
|
||||
BeaconEntryHeader entry{};
|
||||
@ -560,6 +622,16 @@ static void InitializeWithVersion(Interface* self) {
|
||||
|
||||
ASSERT_MSG(recv_buffer_memory->size == sharedmem_size, "Invalid shared memory size.");
|
||||
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
wifi_packet_received = room_member->BindOnWifiPacketReceived(OnWifiPacketReceived);
|
||||
} else {
|
||||
LOG_ERROR(Service_NWM, "Network isn't initalized");
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
// TODO(B3N30): Find the correct error code and return it;
|
||||
rb.Push<u32>(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(connection_status_mutex);
|
||||
|
||||
@ -567,15 +639,14 @@ static void InitializeWithVersion(Interface* self) {
|
||||
// except for the actual status value.
|
||||
connection_status = {};
|
||||
connection_status.status = static_cast<u32>(NetworkStatus::NotConnected);
|
||||
node_info.clear();
|
||||
node_info.push_back(NodeInfo{});
|
||||
}
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushCopyHandles(Kernel::g_handle_table.Create(connection_status_event).Unwrap());
|
||||
|
||||
// TODO(Subv): Connect the OnWifiPacketReceived function to the wifi packet received callback of
|
||||
// the room we're currently in.
|
||||
|
||||
LOG_DEBUG(Service_NWM, "called sharedmem_size=0x%08X, version=0x%08X, sharedmem_handle=0x%08X",
|
||||
sharedmem_size, version, sharedmem_handle);
|
||||
}
|
||||
@ -857,26 +928,79 @@ static void BeginHostingNetwork(Interface* self) {
|
||||
static void DestroyNetwork(Interface* self) {
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x08, 0, 0);
|
||||
|
||||
// TODO(Subv): Find out what happens if this is called while
|
||||
// no network is being hosted.
|
||||
|
||||
// Unschedule the beacon broadcast event.
|
||||
CoreTiming::UnscheduleEvent(beacon_broadcast_event, 0);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(connection_status_mutex);
|
||||
|
||||
// TODO(Subv): Check if connection_status is indeed reset after this call.
|
||||
connection_status = {};
|
||||
connection_status.status = static_cast<u8>(NetworkStatus::NotConnected);
|
||||
// Only a host can destroy
|
||||
std::lock_guard<std::mutex> lock(connection_status_mutex);
|
||||
if (connection_status.status != static_cast<u8>(NetworkStatus::ConnectedAsHost)) {
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_NWM, "called with status %u", connection_status.status);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(B3N30): Send 3 Deauth packets
|
||||
|
||||
u16_le tmp_node_id = connection_status.network_node_id;
|
||||
connection_status = {};
|
||||
connection_status.status = static_cast<u32>(NetworkStatus::NotConnected);
|
||||
connection_status.network_node_id = tmp_node_id;
|
||||
connection_status_event->Signal();
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
|
||||
// TODO(B3N30): HW test if events get signaled here.
|
||||
for (auto bind_node : channel_data) {
|
||||
bind_node.second.event->Signal();
|
||||
}
|
||||
channel_data.clear();
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_WARNING(Service_NWM, "called");
|
||||
LOG_DEBUG(Service_NWM, "called");
|
||||
}
|
||||
|
||||
static void DisconnectNetwork(Interface* self) {
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xA, 0, 0);
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
using Network::WifiPacket;
|
||||
WifiPacket deauth;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(connection_status_mutex);
|
||||
if (connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost)) {
|
||||
// A real 3ds makes strange things here. We do the same
|
||||
u16_le tmp_node_id = connection_status.network_node_id;
|
||||
connection_status = {};
|
||||
connection_status.status = static_cast<u32>(NetworkStatus::ConnectedAsHost);
|
||||
connection_status.network_node_id = tmp_node_id;
|
||||
LOG_DEBUG(Service_NWM, "called as a host");
|
||||
return;
|
||||
}
|
||||
u16_le tmp_node_id = connection_status.network_node_id;
|
||||
connection_status = {};
|
||||
connection_status.status = static_cast<u32>(NetworkStatus::NotConnected);
|
||||
connection_status.network_node_id = tmp_node_id;
|
||||
connection_status_event->Signal();
|
||||
|
||||
deauth.channel = network_channel;
|
||||
// TODO(B3N30): Add disconnect reason
|
||||
deauth.data = {};
|
||||
deauth.destination_address = network_info.host_mac_address;
|
||||
deauth.type = WifiPacket::PacketType::Deauthentication;
|
||||
}
|
||||
|
||||
SendPacket(deauth);
|
||||
|
||||
// TODO(B3N30): Check on HW if Shutdown signals those events
|
||||
for (auto bind_node : channel_data) {
|
||||
bind_node.second.event->Signal();
|
||||
}
|
||||
channel_data.clear();
|
||||
|
||||
LOG_DEBUG(Service_NWM, "called");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1075,6 +1199,55 @@ static void GetChannel(Interface* self) {
|
||||
LOG_DEBUG(Service_NWM, "called");
|
||||
}
|
||||
|
||||
/**
|
||||
* NWM_UDS::ConnectToNetwork service function.
|
||||
* This connects to the specified network
|
||||
* Inputs:
|
||||
* 0 : Command header
|
||||
* 1 : Connection type: 0x1 = Client, 0x2 = Spectator.
|
||||
* 2 : Passphrase buffer size
|
||||
* 3 : (NetworkStructSize<<12) | 0x402
|
||||
* 4 : Network struct buffer ptr
|
||||
* 5 : (PassphraseSize<<12) | 2
|
||||
* 6 : Input passphrase buffer ptr
|
||||
* Outputs:
|
||||
* 0 : Return header
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
*/
|
||||
static void ConnectToNetwork(Interface* self) {
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1E, 2, 4);
|
||||
|
||||
u8 connection_type = rp.Pop<u8>();
|
||||
u32 passphrase_size = rp.Pop<u32>();
|
||||
|
||||
size_t desc_size;
|
||||
const VAddr network_struct_addr = rp.PopStaticBuffer(&desc_size);
|
||||
ASSERT(desc_size == sizeof(NetworkInfo));
|
||||
|
||||
size_t passphrase_desc_size;
|
||||
const VAddr passphrase_addr = rp.PopStaticBuffer(&passphrase_desc_size);
|
||||
|
||||
Memory::ReadBlock(network_struct_addr, &network_info, sizeof(network_info));
|
||||
|
||||
// Start the connection sequence
|
||||
StartConnectionSequence(network_info.host_mac_address);
|
||||
|
||||
connection_token =
|
||||
SleepClientThread("uds::ConnectToNetwork", [](Kernel::SharedPtr<Kernel::Thread> thread) {
|
||||
VAddr address = thread->GetCommandBufferAddress();
|
||||
std::array<u32, IPC::COMMAND_BUFFER_LENGTH> buffer;
|
||||
IPC::RequestBuilder rb(buffer.data(), 0x1E, 1, 0);
|
||||
// TODO(B3N30): Add error handling for host full and timeout
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
Memory::WriteBlock(address, &*thread->owner_process, *buffer.data());
|
||||
|
||||
LOG_DEBUG(Service_NWM, "connection sequence finished");
|
||||
});
|
||||
|
||||
// TODO(B3N30): Add a timout for the connection sequence
|
||||
LOG_DEBUG(Service_NWM, "called");
|
||||
}
|
||||
|
||||
/**
|
||||
* NWM_UDS::SetApplicationData service function.
|
||||
* Updates the application data that is being broadcast in the beacon frames
|
||||
@ -1088,7 +1261,7 @@ static void GetChannel(Interface* self) {
|
||||
* 2 : Channel of the current WiFi network connection.
|
||||
*/
|
||||
static void SetApplicationData(Interface* self) {
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1A, 1, 2);
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x10, 1, 2);
|
||||
|
||||
u32 size = rp.Pop<u32>();
|
||||
|
||||
@ -1147,8 +1320,8 @@ static void DecryptBeaconData(Interface* self) {
|
||||
// This size is hardcoded in the 3DS UDS code.
|
||||
ASSERT(output_buffer_size == sizeof(NodeInfo) * UDSMaxNodes);
|
||||
|
||||
LOG_WARNING(Service_NWM, "called in0=%08X in1=%08X out=%08X", encrypted_data0_addr,
|
||||
encrypted_data1_addr, output_buffer_addr);
|
||||
LOG_DEBUG(Service_NWM, "called in0=%08X in1=%08X out=%08X", encrypted_data0_addr,
|
||||
encrypted_data1_addr, output_buffer_addr);
|
||||
|
||||
NetworkInfo net_info;
|
||||
Memory::ReadBlock(network_struct_addr, &net_info, sizeof(net_info));
|
||||
@ -1158,15 +1331,10 @@ static void DecryptBeaconData(Interface* self) {
|
||||
std::array<u8, 3> oui;
|
||||
Memory::ReadBlock(encrypted_data0_addr, oui.data(), oui.size());
|
||||
ASSERT_MSG(oui == NintendoOUI, "Unexpected OUI");
|
||||
Memory::ReadBlock(encrypted_data1_addr, oui.data(), oui.size());
|
||||
ASSERT_MSG(oui == NintendoOUI, "Unexpected OUI");
|
||||
|
||||
ASSERT_MSG(Memory::Read8(encrypted_data0_addr + 3) ==
|
||||
static_cast<u8>(NintendoTagId::EncryptedData0),
|
||||
"Unexpected tag id");
|
||||
ASSERT_MSG(Memory::Read8(encrypted_data1_addr + 3) ==
|
||||
static_cast<u8>(NintendoTagId::EncryptedData1),
|
||||
"Unexpected tag id");
|
||||
|
||||
std::vector<u8> beacon_data(data0_size + data1_size);
|
||||
Memory::ReadBlock(encrypted_data0_addr + 4, beacon_data.data(), data0_size);
|
||||
@ -1230,26 +1398,6 @@ static void BeaconBroadcastCallback(u64 userdata, int cycles_late) {
|
||||
beacon_broadcast_event, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when a client connects to an UDS network we're hosting,
|
||||
* updates the connection status and signals the update event.
|
||||
* @param network_node_id Network Node Id of the connecting client.
|
||||
*/
|
||||
void OnClientConnected(u16 network_node_id) {
|
||||
std::lock_guard<std::mutex> lock(connection_status_mutex);
|
||||
ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost),
|
||||
"Can not accept clients if we're not hosting a network");
|
||||
ASSERT_MSG(connection_status.total_nodes < connection_status.max_nodes,
|
||||
"Can not accept connections on a full network");
|
||||
|
||||
u32 node_id = GetNextAvailableNodeId();
|
||||
connection_status.node_bitmask |= 1 << node_id;
|
||||
connection_status.changed_nodes |= 1 << node_id;
|
||||
connection_status.nodes[node_id] = network_node_id;
|
||||
connection_status.total_nodes++;
|
||||
connection_status_event->Signal();
|
||||
}
|
||||
|
||||
const Interface::FunctionInfo FunctionTable[] = {
|
||||
{0x000102C2, nullptr, "Initialize (deprecated)"},
|
||||
{0x00020000, nullptr, "Scrap"},
|
||||
@ -1260,7 +1408,7 @@ const Interface::FunctionInfo FunctionTable[] = {
|
||||
{0x00070080, nullptr, "UpdateNetworkAttribute"},
|
||||
{0x00080000, DestroyNetwork, "DestroyNetwork"},
|
||||
{0x00090442, nullptr, "ConnectNetwork (deprecated)"},
|
||||
{0x000A0000, nullptr, "DisconnectNetwork"},
|
||||
{0x000A0000, DisconnectNetwork, "DisconnectNetwork"},
|
||||
{0x000B0000, GetConnectionStatus, "GetConnectionStatus"},
|
||||
{0x000D0040, GetNodeInformation, "GetNodeInformation"},
|
||||
{0x000E0006, nullptr, "DecryptBeaconData (deprecated)"},
|
||||
@ -1275,7 +1423,7 @@ const Interface::FunctionInfo FunctionTable[] = {
|
||||
{0x001A0000, GetChannel, "GetChannel"},
|
||||
{0x001B0302, InitializeWithVersion, "InitializeWithVersion"},
|
||||
{0x001D0044, BeginHostingNetwork, "BeginHostingNetwork"},
|
||||
{0x001E0084, nullptr, "ConnectToNetwork"},
|
||||
{0x001E0084, ConnectToNetwork, "ConnectToNetwork"},
|
||||
{0x001F0006, DecryptBeaconData, "DecryptBeaconData"},
|
||||
{0x00200040, nullptr, "Flush"},
|
||||
{0x00210080, nullptr, "SetProbeResponseParam"},
|
||||
@ -1305,6 +1453,9 @@ NWM_UDS::~NWM_UDS() {
|
||||
connection_status.status = static_cast<u32>(NetworkStatus::NotConnected);
|
||||
}
|
||||
|
||||
if (auto room_member = Network::GetRoomMember().lock())
|
||||
room_member->Unbind(wifi_packet_received);
|
||||
|
||||
CoreTiming::UnscheduleEvent(beacon_broadcast_event, 0);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user