summaryrefslogtreecommitdiffstats
path: root/src/core/hle/service/nwm/uds_beacon.cpp
blob: 6332b404cceb43f6e0b6d38c0e30cd60940797e9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
// 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/md5.h>
#include <cryptopp/modes.h>
#include <cryptopp/sha.h>
#include "common/assert.h"
#include "core/hle/service/nwm/nwm_uds.h"
#include "core/hle/service/nwm/uds_beacon.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