@ -2,8 +2,10 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
# include <algorithm>
# include <array>
# include <cstring>
# include <list>
# include <mutex>
# include <unordered_map>
# include <vector>
@ -37,9 +39,12 @@ static ConnectionStatus connection_status{};
/* 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 .
* The first node is always the host .
*/
static NodeList node_info ( 1 ) ;
static NodeList node_info ;
// Node information about our own system.
static NodeInfo current_node ;
// Mapping of bind node ids to their respective events.
static std : : unordered_map < u32 , Kernel : : SharedPtr < Kernel : : Event > > bind_node_events ;
@ -54,6 +59,10 @@ static NetworkInfo network_info;
// Event that will generate and send the 802.11 beacon frames.
static int beacon_broadcast_event ;
// Mutex to synchronize access to the connection status between the emulation thread and the
// network thread.
static std : : mutex connection_status_mutex ;
// Mutex to synchronize access to the list of received beacons between the emulation thread and the
// network thread.
static std : : mutex beacon_mutex ;
@ -63,14 +72,26 @@ static std::mutex beacon_mutex;
constexpr size_t MaxBeaconFrames = 15 ;
// List of the last <MaxBeaconFrames> beacons received from the network.
static std : : deque < Network : : WifiPacket > received_beacons ;
static std : : list < Network : : WifiPacket > received_beacons ;
/**
* Returns a list of received 802.11 beacon frames from the specified sender since the last call .
*/
std : : deque < Network : : WifiPacket > GetReceivedBeacons ( const MacAddress & sender ) {
std : : list < Network : : WifiPacket > GetReceivedBeacons ( const MacAddress & sender ) {
std : : lock_guard < std : : mutex > lock ( beacon_mutex ) ;
// TODO(Subv): Filter by sender.
if ( sender ! = Network : : BroadcastMac ) {
std : : list < Network : : WifiPacket > filtered_list ;
const auto beacon = std : : find_if ( received_beacons . begin ( ) , received_beacons . end ( ) ,
[ & sender ] ( const Network : : WifiPacket & packet ) {
return packet . transmitter_address = = sender ;
} ) ;
if ( beacon ! = received_beacons . end ( ) ) {
filtered_list . push_back ( * beacon ) ;
// TODO(B3N30): Check if the complete deque is cleared or just the fetched entries
received_beacons . erase ( beacon ) ;
}
return filtered_list ;
}
return std : : move ( received_beacons ) ;
}
@ -83,6 +104,15 @@ void SendPacket(Network::WifiPacket& packet) {
// limit is exceeded.
void HandleBeaconFrame ( const Network : : WifiPacket & packet ) {
std : : lock_guard < std : : mutex > lock ( beacon_mutex ) ;
const auto unique_beacon =
std : : find_if ( received_beacons . begin ( ) , received_beacons . end ( ) ,
[ & packet ] ( const Network : : WifiPacket & new_packet ) {
return new_packet . transmitter_address = = packet . transmitter_address ;
} ) ;
if ( unique_beacon ! = received_beacons . end ( ) ) {
// We already have a beacon from the same mac in the deque, remove the old one;
received_beacons . erase ( unique_beacon ) ;
}
received_beacons . emplace_back ( packet ) ;
@ -91,14 +121,33 @@ void HandleBeaconFrame(const Network::WifiPacket& packet) {
received_beacons . pop_front ( ) ;
}
void HandleAssociationResponseFrame ( const Network : : WifiPacket & packet ) {
auto assoc_result = GetAssociationResult ( packet . data ) ;
ASSERT_MSG ( std : : get < AssocStatus > ( assoc_result ) = = AssocStatus : : Successful ,
" Could not join network " ) ;
{
std : : lock_guard < std : : mutex > lock ( connection_status_mutex ) ;
ASSERT ( connection_status . status = = static_cast < u32 > ( NetworkStatus : : Connecting ) ) ;
}
// Send the EAPoL-Start packet to the server.
using Network : : WifiPacket ;
WifiPacket eapol_start ;
eapol_start . channel = network_channel ;
eapol_start . data = GenerateEAPoLStartFrame ( std : : get < u16 > ( assoc_result ) , current_node ) ;
// TODO(B3N30): Encrypt the packet.
eapol_start . destination_address = packet . transmitter_address ;
eapol_start . type = WifiPacket : : PacketType : : Data ;
SendPacket ( eapol_start ) ;
}
/*
* Returns an available index in the nodes array for the
* currently - hosted UDS network .
*/
static u16 GetNextAvailableNodeId ( ) {
ASSERT_MSG ( connection_status . status = = static_cast < u32 > ( NetworkStatus : : ConnectedAsHost ) ,
" Can not accept clients if we're not hosting a network " ) ;
for ( u16 index = 0 ; index < connection_status . max_nodes ; + + index ) {
if ( ( connection_status . node_bitmask & ( 1 < < index ) ) = = 0 )
return index ;
@ -113,35 +162,46 @@ static u16 GetNextAvailableNodeId() {
* authentication frame with SEQ1 .
*/
void StartConnectionSequence ( const MacAddress & server ) {
ASSERT ( connection_status . status = = static_cast < u32 > ( NetworkStatus : : NotConnected ) ) ;
// TODO(Subv): Handle timeout.
// Send an authentication frame with SEQ1
using Network : : WifiPacket ;
WifiPacket auth_request ;
auth_request . channel = network_channel ;
auth_request . data = GenerateAuthenticationFrame ( AuthenticationSeq : : SEQ1 ) ;
auth_request . destination_address = server ;
auth_request . type = WifiPacket : : PacketType : : Authentication ;
{
std : : lock_guard < std : : mutex > lock ( connection_status_mutex ) ;
ASSERT ( connection_status . status = = static_cast < u32 > ( NetworkStatus : : NotConnected ) ) ;
// TODO(Subv): Handle timeout.
// Send an authentication frame with SEQ1
auth_request . channel = network_channel ;
auth_request . data = GenerateAuthenticationFrame ( AuthenticationSeq : : SEQ1 ) ;
auth_request . destination_address = server ;
auth_request . type = WifiPacket : : PacketType : : Authentication ;
}
SendPacket ( auth_request ) ;
}
/// Sends an Association Response frame to the specified mac address
void SendAssociationResponseFrame ( const MacAddress & address ) {
ASSERT_MSG ( connection_status . status = = static_cast < u32 > ( NetworkStatus : : ConnectedAsHost ) ) ;
using Network : : WifiPacket ;
WifiPacket assoc_response ;
assoc_response . channel = network_channel ;
// TODO(Subv): This will cause multiple clients to end up with the same association id, but
// we're not using that for anything.
u16 association_id = 1 ;
assoc_response . data = GenerateAssocResponseFrame ( AssocStatus : : Successful , association_id ,
network_info . network_id ) ;
assoc_response . destination_address = address ;
assoc_response . type = WifiPacket : : PacketType : : AssociationResponse ;
{
std : : lock_guard < std : : mutex > lock ( connection_status_mutex ) ;
if ( connection_status . status ! = static_cast < u32 > ( NetworkStatus : : ConnectedAsHost ) ) {
LOG_ERROR ( Service_NWM , " Connection sequence aborted, because connection status is %u " ,
connection_status . status ) ;
return ;
}
assoc_response . channel = network_channel ;
// TODO(Subv): This will cause multiple clients to end up with the same association id, but
// we're not using that for anything.
u16 association_id = 1 ;
assoc_response . data = GenerateAssocResponseFrame ( AssocStatus : : Successful , association_id ,
network_info . network_id ) ;
assoc_response . destination_address = address ;
assoc_response . type = WifiPacket : : PacketType : : AssociationResponse ;
}
SendPacket ( assoc_response ) ;
}
@ -155,16 +215,23 @@ void SendAssociationResponseFrame(const MacAddress& address) {
void HandleAuthenticationFrame ( const Network : : WifiPacket & packet ) {
// Only the SEQ1 auth frame is handled here, the SEQ2 frame doesn't need any special behavior
if ( GetAuthenticationSeqNumber ( packet . data ) = = AuthenticationSeq : : SEQ1 ) {
ASSERT_MSG ( connection_status . status = = static_cast < u32 > ( NetworkStatus : : ConnectedAsHost ) ) ;
// Respond with an authentication response frame with SEQ2
using Network : : WifiPacket ;
WifiPacket auth_request ;
auth_request . channel = network_channel ;
auth_request . data = GenerateAuthenticationFrame ( AuthenticationSeq : : SEQ2 ) ;
auth_request . destination_address = packet . transmitter_address ;
auth_request . type = WifiPacket : : PacketType : : Authentication ;
{
std : : lock_guard < std : : mutex > lock ( connection_status_mutex ) ;
if ( connection_status . status ! = static_cast < u32 > ( NetworkStatus : : ConnectedAsHost ) ) {
LOG_ERROR ( Service_NWM ,
" Connection sequence aborted, because connection status is %u " ,
connection_status . status ) ;
return ;
}
// Respond with an authentication response frame with SEQ2
auth_request . channel = network_channel ;
auth_request . data = GenerateAuthenticationFrame ( AuthenticationSeq : : SEQ2 ) ;
auth_request . destination_address = packet . transmitter_address ;
auth_request . type = WifiPacket : : PacketType : : Authentication ;
}
SendPacket ( auth_request ) ;
SendAssociationResponseFrame ( packet . transmitter_address ) ;
@ -180,6 +247,9 @@ void OnWifiPacketReceived(const Network::WifiPacket& packet) {
case Network : : WifiPacket : : PacketType : : Authentication :
HandleAuthenticationFrame ( packet ) ;
break ;
case Network : : WifiPacket : : PacketType : : AssociationResponse :
HandleAssociationResponseFrame ( packet ) ;
break ;
}
}
@ -305,7 +375,7 @@ static void InitializeWithVersion(Interface* self) {
u32 sharedmem_size = rp . Pop < u32 > ( ) ;
// Update the node information with the data the game gave us.
rp . PopRaw ( node_info[ 0 ] ) ;
rp . PopRaw ( current_node ) ;
u16 version = rp . Pop < u16 > ( ) ;
@ -315,10 +385,14 @@ static void InitializeWithVersion(Interface* self) {
ASSERT_MSG ( recv_buffer_memory - > size = = sharedmem_size , " Invalid shared memory size. " ) ;
// Reset the connection status, it contains all zeros after initialization,
// except for the actual status value.
connection_status = { } ;
connection_status . status = static_cast < u32 > ( NetworkStatus : : NotConnected ) ;
{
std : : lock_guard < std : : mutex > lock ( connection_status_mutex ) ;
// Reset the connection status, it contains all zeros after initialization,
// except for the actual status value.
connection_status = { } ;
connection_status . status = static_cast < u32 > ( NetworkStatus : : NotConnected ) ;
}
IPC : : RequestBuilder rb = rp . MakeBuilder ( 1 , 2 ) ;
rb . Push ( RESULT_SUCCESS ) ;
@ -348,12 +422,16 @@ static void GetConnectionStatus(Interface* self) {
IPC : : RequestBuilder rb = rp . MakeBuilder ( 13 , 0 ) ;
rb . Push ( RESULT_SUCCESS ) ;
rb . PushRaw ( connection_status ) ;
// Reset the bitmask of changed nodes after each call to this
// function to prevent falsely informing games of outstanding
// changes in subsequent calls.
connection_status . changed_nodes = 0 ;
{
std : : lock_guard < std : : mutex > lock ( connection_status_mutex ) ;
rb . PushRaw ( connection_status ) ;
// Reset the bitmask of changed nodes after each call to this
// function to prevent falsely informing games of outstanding
// changes in subsequent calls.
// TODO(Subv): Find exactly where the NWM module resets this value.
connection_status . changed_nodes = 0 ;
}
LOG_DEBUG ( Service_NWM , " called " ) ;
}
@ -434,31 +512,36 @@ static void BeginHostingNetwork(Interface* self) {
// The real UDS module throws a fatal error if this assert fails.
ASSERT_MSG ( network_info . max_nodes > 1 , " Trying to host a network of only one member. " ) ;
connection_status . status = static_cast < u32 > ( 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 < u8 > ( 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 [ 0 ] . network_node_id = 1 ;
connection_status . nodes [ 0 ] = connection_status . network_node_id ;
// Set the bit 0 in the nodes bitmask to indicate that node 1 is already taken.
connection_status . node_bitmask | = 1 ;
// Notify the application that the first node was set.
connection_status . changed_nodes | = 1 ;
{
std : : lock_guard < std : : mutex > lock ( connection_status_mutex ) ;
connection_status . status = static_cast < u32 > ( 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 < u8 > ( 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 ;
current_node . network_node_id = 1 ;
connection_status . nodes [ 0 ] = connection_status . network_node_id ;
// Set the bit 0 in the nodes bitmask to indicate that node 1 is already taken.
connection_status . node_bitmask | = 1 ;
// Notify the application that the first node was set.
connection_status . changed_nodes | = 1 ;
node_info [ 0 ] = current_node ;
}
// If the game has a preferred channel, use that instead.
if ( network_info . channel ! = 0 )
@ -495,9 +578,13 @@ static void DestroyNetwork(Interface* self) {
// Unschedule the beacon broadcast event.
CoreTiming : : UnscheduleEvent ( beacon_broadcast_event , 0 ) ;
// TODO(Subv): Check if connection_status is indeed reset after this call.
connection_status = { } ;
connection_status . status = static_cast < u8 > ( NetworkStatus : : NotConnected ) ;
{
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 ) ;
}
connection_status_event - > Signal ( ) ;
IPC : : RequestBuilder rb = rp . MakeBuilder ( 1 , 0 ) ;
@ -540,17 +627,24 @@ static void SendTo(Interface* self) {
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 ;
}
u16 network_node_id ;
if ( dest_node_id = = connection_status . network_node_id ) {
rb . Push ( ResultCode ( ErrorDescription : : NotFound , ErrorModule : : UDS ,
ErrorSummary : : WrongArgument , ErrorLevel : : Status ) ) ;
return ;
{
std : : lock_guard < std : : mutex > lock ( connection_status_mutex ) ;
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 ;
}
network_node_id = connection_status . network_node_id ;
}
// TODO(Subv): Do something with the flags.
@ -567,8 +661,8 @@ static void SendTo(Interface* self) {
// 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 ) ;
std : : vector < u8 > data_payload =
GenerateDataPayload( data, data_channel , dest_node_id , 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.
@ -595,6 +689,7 @@ static void GetChannel(Interface* self) {
IPC : : RequestParser rp ( Kernel : : GetCommandBuffer ( ) , 0x1A , 0 , 0 ) ;
IPC : : RequestBuilder rb = rp . MakeBuilder ( 2 , 0 ) ;
std : : lock_guard < std : : mutex > lock ( connection_status_mutex ) ;
bool is_connected = connection_status . status ! = static_cast < u32 > ( NetworkStatus : : NotConnected ) ;
u8 channel = is_connected ? network_channel : 0 ;
@ -766,6 +861,7 @@ static void BeaconBroadcastCallback(u64 userdata, int cycles_late) {
* @ 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 ,
@ -827,8 +923,11 @@ NWM_UDS::~NWM_UDS() {
connection_status_event = nullptr ;
recv_buffer_memory = nullptr ;
connection_status = { } ;
connection_status . status = static_cast < u32 > ( NetworkStatus : : NotConnected ) ;
{
std : : lock_guard < std : : mutex > lock ( connection_status_mutex ) ;
connection_status = { } ;
connection_status . status = static_cast < u32 > ( NetworkStatus : : NotConnected ) ;
}
CoreTiming : : UnscheduleEvent ( beacon_broadcast_event , 0 ) ;
}