Browse Source
Merge pull request #2661 from Subv/uds5
Merge pull request #2661 from Subv/uds5
Services/UDS: Generate 802.11 beacon frames when a network is open.pull/15/merge
committed by
GitHub
5 changed files with 602 additions and 33 deletions
-
2src/core/CMakeLists.txt
-
122src/core/hle/service/nwm/nwm_uds.cpp
-
5src/core/hle/service/nwm/nwm_uds.h
-
333src/core/hle/service/nwm/uds_beacon.cpp
-
173src/core/hle/service/nwm/uds_beacon.h
@ -0,0 +1,333 @@ |
|||
// Copyright 2017 Citra Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <cstring>
|
|||
|
|||
#include "core/hle/service/nwm/nwm_uds.h"
|
|||
#include "core/hle/service/nwm/uds_beacon.h"
|
|||
|
|||
#include <cryptopp/aes.h>
|
|||
#include <cryptopp/md5.h>
|
|||
#include <cryptopp/modes.h>
|
|||
#include <cryptopp/sha.h>
|
|||
|
|||
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<u8, CryptoPP::AES::BLOCKSIZE> 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<u8> GenerateFixedParameters() { |
|||
std::vector<u8> 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<u8> GenerateSSIDTag() { |
|||
std::vector<u8> buffer(sizeof(TagHeader) + UDSBeaconSSIDSize); |
|||
|
|||
TagHeader tag_header{}; |
|||
tag_header.tag_id = static_cast<u8>(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<u8> GenerateBasicTaggedParameters() { |
|||
// Append the SSID tag
|
|||
std::vector<u8> 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<u8> GenerateNintendoDummyTag() { |
|||
// Note: These values were taken from a packet capture of an o3DS XL
|
|||
// broadcasting a Super Smash Bros. 4 lobby.
|
|||
constexpr std::array<u8, 3> dummy_data = {0x0A, 0x00, 0x00}; |
|||
|
|||
DummyTag tag{}; |
|||
tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific); |
|||
tag.header.length = sizeof(DummyTag) - sizeof(TagHeader); |
|||
tag.oui_type = static_cast<u8>(NintendoTagId::Dummy); |
|||
tag.oui = NintendoOUI; |
|||
tag.data = dummy_data; |
|||
|
|||
std::vector<u8> 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<u8> GenerateNintendoNetworkInfoTag(const NetworkInfo& network_info) { |
|||
NetworkInfoTag tag{}; |
|||
tag.header.tag_id = static_cast<u8>(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<u8>(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<u8> 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<u8, CryptoPP::SHA1::DIGESTSIZE> 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<u8, CryptoPP::AES::BLOCKSIZE> 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<u8, CryptoPP::AES::BLOCKSIZE> 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<u8> GeneratedEncryptedData(const NetworkInfo& network_info, const NodeList& nodes) { |
|||
std::vector<u8> 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<u8*>(&info), |
|||
reinterpret_cast<u8*>(&info) + sizeof(info)); |
|||
} |
|||
|
|||
// Calculate the MD5 hash of the data in the buffer, not including the hash field.
|
|||
std::array<u8, CryptoPP::MD5::DIGESTSIZE> 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<u8, AES::BLOCKSIZE> counter = GetBeaconCryptoCTR(network_info); |
|||
CryptoPP::CTR_Mode<AES>::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<u8>& buffer) { |
|||
// Decrypt the data using AES-CTR and the NWM beacon key.
|
|||
using CryptoPP::AES; |
|||
std::array<u8, AES::BLOCKSIZE> counter = GetBeaconCryptoCTR(network_info); |
|||
CryptoPP::CTR_Mode<AES>::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<u8> GenerateNintendoFirstEncryptedDataTag(const NetworkInfo& network_info, |
|||
const NodeList& nodes) { |
|||
const size_t payload_size = |
|||
std::min<size_t>(EncryptedDataSizeCutoff, nodes.size() * sizeof(NodeInfo)); |
|||
|
|||
EncryptedDataTag tag{}; |
|||
tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific); |
|||
tag.header.length = sizeof(tag) - sizeof(TagHeader) + payload_size; |
|||
tag.oui_type = static_cast<u8>(NintendoTagId::EncryptedData0); |
|||
tag.oui = NintendoOUI; |
|||
|
|||
std::vector<u8> buffer(sizeof(tag) + payload_size); |
|||
std::memcpy(buffer.data(), &tag, sizeof(tag)); |
|||
|
|||
std::vector<u8> 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<u8> 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<u8>(TagId::VendorSpecific); |
|||
tag.header.length = tag_length; |
|||
tag.oui_type = static_cast<u8>(NintendoTagId::EncryptedData1); |
|||
tag.oui = NintendoOUI; |
|||
|
|||
std::vector<u8> buffer(sizeof(tag) + payload_size); |
|||
std::memcpy(buffer.data(), &tag, sizeof(tag)); |
|||
|
|||
std::vector<u8> 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<u8> GenerateNintendoTaggedParameters(const NetworkInfo& network_info, |
|||
const NodeList& nodes) { |
|||
ASSERT_MSG(network_info.max_nodes == nodes.size(), "Inconsistent network state."); |
|||
|
|||
std::vector<u8> buffer = GenerateNintendoDummyTag(); |
|||
std::vector<u8> network_info_tag = GenerateNintendoNetworkInfoTag(network_info); |
|||
std::vector<u8> first_data_tag = GenerateNintendoFirstEncryptedDataTag(network_info, nodes); |
|||
std::vector<u8> 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<u8> GenerateBeaconFrame(const NetworkInfo& network_info, const NodeList& nodes) { |
|||
std::vector<u8> buffer = GenerateFixedParameters(); |
|||
std::vector<u8> basic_tags = GenerateBasicTaggedParameters(); |
|||
std::vector<u8> 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<WifiPacket> GetReceivedPackets(WifiPacket::PacketType type, const MacAddress& sender) { |
|||
return {}; |
|||
} |
|||
} // namespace NWM
|
|||
} // namespace Service
|
|||
@ -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 <array> |
|||
#include <deque> |
|||
#include <vector> |
|||
#include "common/common_types.h" |
|||
#include "common/swap.h" |
|||
#include "core/hle/service/service.h" |
|||
|
|||
namespace Service { |
|||
namespace NWM { |
|||
|
|||
using MacAddress = std::array<u8, 6>; |
|||
|
|||
/// The maximum number of nodes that can exist in an UDS session. |
|||
constexpr u32 UDSMaxNodes = 16; |
|||
constexpr std::array<u8, 3> 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<u8, 3> oui; |
|||
u8 oui_type; |
|||
std::array<u8, 3> data; |
|||
}; |
|||
|
|||
static_assert(sizeof(DummyTag) == 9, "DummyTag has incorrect size."); |
|||
|
|||
struct NetworkInfoTag { |
|||
TagHeader header; |
|||
std::array<u8, 0x1F> network_info; |
|||
std::array<u8, 0x14> sha_hash; |
|||
u8 appdata_size; |
|||
}; |
|||
|
|||
static_assert(sizeof(NetworkInfoTag) == 54, "NetworkInfoTag has incorrect size."); |
|||
|
|||
struct EncryptedDataTag { |
|||
TagHeader header; |
|||
std::array<u8, 3> 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<u16_be, 10> username; |
|||
u16_be network_node_id; |
|||
}; |
|||
|
|||
static_assert(sizeof(BeaconNodeInfo) == 0x1E, "BeaconNodeInfo has incorrect size."); |
|||
|
|||
struct BeaconData { |
|||
std::array<u8, 0x10> 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<u8> 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<u8>& 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<u8> 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<WifiPacket> GetReceivedPackets(WifiPacket::PacketType type, const MacAddress& sender); |
|||
} // namespace NWM |
|||
} // namespace Service |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue