From 0d68dc8a54e811e0f59acb904cafc34446d210b0 Mon Sep 17 00:00:00 2001 From: mrhappyasthma Date: Mon, 8 Nov 2021 02:04:45 -0800 Subject: [PATCH] Rewrite PKSM Bridge protocol to support dynamic file size. This commit introduces the PKSM Bridge Protocol (v1), which is a simple protocol on top of the TCP connection to allow for future changes through versioning and transmitting dynamic file sizes. NOTE: This commit adds new strings. I just put them all in english for now. I'll need assistance from the community to translate them all into the respective languages. --- 3ds/include/utils/pksmbridge_api.h | 167 ++++++++++++ 3ds/include/utils/pksmbridge_tcp.h | 91 +++++++ 3ds/source/utils/pksmbridge.cpp | 193 ++++++------- 3ds/source/utils/pksmbridge_api.c | 55 ++++ 3ds/source/utils/pksmbridge_tcp.c | 312 ++++++++++++++++++++++ assets/gui_strings/chs/communication.json | 4 + assets/gui_strings/cht/communication.json | 4 + assets/gui_strings/eng/communication.json | 4 + assets/gui_strings/fre/communication.json | 4 + assets/gui_strings/ger/communication.json | 4 + assets/gui_strings/ita/communication.json | 4 + assets/gui_strings/jpn/communication.json | 4 + assets/gui_strings/kor/communication.json | 4 + assets/gui_strings/nl/communication.json | 4 + assets/gui_strings/pt/communication.json | 4 + assets/gui_strings/ro/communication.json | 4 + assets/gui_strings/spa/communication.json | 4 + 17 files changed, 753 insertions(+), 113 deletions(-) create mode 100644 3ds/include/utils/pksmbridge_api.h create mode 100644 3ds/include/utils/pksmbridge_tcp.h create mode 100644 3ds/source/utils/pksmbridge_api.c create mode 100644 3ds/source/utils/pksmbridge_tcp.c diff --git a/3ds/include/utils/pksmbridge_api.h b/3ds/include/utils/pksmbridge_api.h new file mode 100644 index 00000000..b1ce7c0e --- /dev/null +++ b/3ds/include/utils/pksmbridge_api.h @@ -0,0 +1,167 @@ +/* + * This file is part of PKSM + * Copyright (C) 2016-2021 mrhappyasthma, Flagbrew + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Additional Terms 7.b and 7.c of GPLv3 apply to this file: + * * Requiring preservation of specified reasonable legal notices or + * author attributions in that material or in the Appropriate Legal + * Notices displayed by works containing it. + * * Prohibiting misrepresentation of the origin of that material, + * or requiring that modified versions of such material be marked in + * reasonable ways as different from the original version. + */ + +/** + * This file defines the PKSM Bridge protocol API. + * + * Example data flow: + * + * Client -> Send `pksmBridgeRequest` to server and await acknowledgement of supported version + * + * Server: Checks the protocol version from the request to see if it is supported. + * + * <- Server send `pksmBridgeResponse`. If version is supported, the value matches + * one sent from the client. If unsupported, then the value is + * PKSM_BRIDGE_UNSUPPORTED_PROTOCOL_VERSION. + * + * Client: Parses the `pksmBridgeResponse` to see if the server acknowledged and will send a + * file payload. + * + * (if the version requested by the client is supported) + * <- Server sends the `pksmBridgeFile` + * + * Client: Conditionally receives the file payload, if the version was supported. + */ +#ifndef PKSMBRIDGE_API_H +#define PKSMBRIDGE_API_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** A string literal used to identify PKSM Bridge messages. */ +static const char PKSM_BRIDGE_PROTOCOL_NAME[] = "PKSMBRIDGE"; + +/** The latest protocol version. */ +static const int PKSM_BRIDGE_LATEST_PROTOCOL_VERSION = 1; + +/** A sentinel value indicating an unsupported version number. */ +extern const int PKSM_BRIDGE_UNSUPPORTED_PROTOCOL_VERSION; + +struct pksmBridgeRequest +{ + /** + * Used as a sentinel value to identify the request type. + * Contains `PKSM_BRIDGE_PROTOCOL_NAME`, without the null terminator. + */ + char protocol_name[10]; + + /** The requested protocol version to use. */ + int8_t protocol_version; +}; + +struct pksmBridgeResponse +{ + /** + * Used as a sentinel value to identify the request type. + * Contains `PKSM_BRIDGE_PROTOCOL_NAME`, without the null terminator. + */ + char protocol_name[10]; + + /** + * If the requested protocol_version is supported by the server, this value will + * contain the same value to confirm support. If the requested version is not + * supported, it will contain `PKSM_BRIDGE_UNSUPPORTED_PROTOCOL_VERSION`. + */ + int8_t protocol_version; +}; + +struct pksmBridgeFile +{ + /** The size of the checksum. */ + uint32_t checksumSize; + + /** + * The checksum of `contents`. Used to verify data integrity after the transmission. + * The v1 protocol uses SHA256. + */ + uint8_t* checksum; + + /** The size of the file in bytes. */ + uint32_t size; + + /** A pointer to the file contents. The size of these contents is specified in `size`. */ + uint8_t* contents; +}; + +enum pksmBridgeError +{ + /** Indicates that no error occurred (i.e. success). */ + PKSM_BRIDGE_ERROR_NONE = 0, + /** The requested PKSM Bridge protocol version is not supported. */ + PKSM_BRIDGE_ERROR_UNSUPPORTED_PROTCOL_VERSION = -1, + /** A connection error occurred and the PKSM Bridge could not be established. */ + PKSM_BRIDGE_ERROR_CONNECTION_ERROR = -2, + /** The protocol encountered an error while reading data. */ + PKSM_BRIDGE_DATA_READ_FAILURE = -3, + /** The protocol encountered an error while writing data. */ + PKSM_BRIDGE_DATA_WRITE_FAILURE = -4, + /** + * The file received from the PKSM Bridge contains unexpected bytes. + * This likely indicates that a checksum failed. + */ + PKSM_BRIDGE_DATA_FILE_CORRUPTED = -5, + /** + * A generic error indicating that an unexpected message was received that is not part + * of the PKSM Bridge protocol. + */ + PKSM_BRIDGE_ERROR_UNEXPECTED_MESSAGE = -99999, +}; + +/** + * Create a PKSMBridge request with the specified protocol version. + * + * @param protocol_version The protocol version to specify in the request. + * + * @return The created bridge request. + */ +struct pksmBridgeRequest createPKSMBridgeRequest(int protocol_version); + +/** + * Create a PKSMBridge response corresponding to the given request. + * + * @param request The request that was received over the bridge. + * @param supportedProtocolVersionsFunc A pointer to a function that validates the + * support protocol versions by the client + * implementing the PKSM Bridge protocol. It + * takes one argument `version`, which is the + * requested version number from the request. + * It s a boolean indicating whether or not + * the `version` is supported by this client. + * + * @return The created bridge response. + */ +struct pksmBridgeResponse createPKSMBridgeResponseForRequest( + struct pksmBridgeRequest request, bool (*supportedProtocolVersionsFunc)(int version)); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // PKSMBRIDGE_API_H \ No newline at end of file diff --git a/3ds/include/utils/pksmbridge_tcp.h b/3ds/include/utils/pksmbridge_tcp.h new file mode 100644 index 00000000..bb623b17 --- /dev/null +++ b/3ds/include/utils/pksmbridge_tcp.h @@ -0,0 +1,91 @@ +/* + * This file is part of PKSM + * Copyright (C) 2016-2021 mrhappyasthma, Flagbrew + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Additional Terms 7.b and 7.c of GPLv3 apply to this file: + * * Requiring preservation of specified reasonable legal notices or + * author attributions in that material or in the Appropriate Legal + * Notices displayed by works containing it. + * * Prohibiting misrepresentation of the origin of that material, + * or requiring that modified versions of such material be marked in + * reasonable ways as different from the original version. + */ + +#ifndef PKSMBRIDGE_TCP_H +#define PKSMBRIDGE_TCP_H + +#include "pksmbridge_api.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * Returns a boolean indicating whether or not the `version` is supported by this PKSM Bridge + * TCP implementation. + */ +bool checkSupportedPKSMBridgeProtocolVersionForTCP(int version); + +/** + * Send a file over the PKSM Bridge using TCP. + * + * @warning This is a blocking operation. + * + * @param port The TCP port to use for the PKSM Bridge connection. + * @param address The IP address to use for the PKSM Bridge connection. + * @param file The file to send over the PKSM Bridge connection. + * + * @return If the file was sent successfully, returns `PKSM_BRIDGE_ERROR_NONE`. If an error + * occurred, returns the corresponding `enum pksmBridgeError` error value. + */ +enum pksmBridgeError sendFileOverPKSMBridgeViaTCP( + uint16_t port, struct in_addr address, struct pksmBridgeFile file); + +/** + * Receive a file over the PKSM Bridge using TCP. + * + * @warning This is a blocking operation. + * + * @note Callers should check for an error in the return value before attempting to access + * any of the out parameters. The out parameters are not assigned unless the operation + * succeeds. + * + * @param port The TCP port to use for the PKSM Bridge connection. + * @param outAddress (out) Returns the IP address that was used for the PKSM Bridge connection. + * @param outFile (out) The file that was received over the PKSM Bridge connection. The client + * is responsible for calling `free()` on the data returned via `outFile`. + * @param outFileSize (out) The size of the `outFile` in bytes. + * @param validateChecksumFunc A function pointer to the function that should be used to evaluate + * the file's checksum. It takes one argument `file`, which is the + * received file from the PKSM Bridge. It is expected to return a + * boolean indicating whether or not the checksum in + * `file.checksum` matches one computed from the `file.contents`. + * + * @return If the file was received successfully, returns `PKSM_BRIDGE_ERROR_NONE`. If an error + * occurred, returns the corresponding `enum pksmBridgeError` error value. + */ +enum pksmBridgeError receiveFileOverPKSMBridgeViaTCP(uint16_t port, struct in_addr* outAddress, + uint8_t** outFile, uint32_t* outFileSize, + bool (*validateChecksumFunc)(struct pksmBridgeFile file)); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // PKSMBRIDGE_TCP_H \ No newline at end of file diff --git a/3ds/source/utils/pksmbridge.cpp b/3ds/source/utils/pksmbridge.cpp index 38a87f6e..33d8b51e 100644 --- a/3ds/source/utils/pksmbridge.cpp +++ b/3ds/source/utils/pksmbridge.cpp @@ -33,8 +33,10 @@ #include "gui.hpp" #include "i18n_ext.hpp" #include "loader.hpp" +#include "pksmbridge_api.h" +#include "pksmbridge_tcp.h" +#include "utils/crypto.hpp" #include -#include namespace { @@ -47,6 +49,18 @@ namespace addr.sin_addr.s_addr = gethostid(); return inet_ntoa(addr.sin_addr); } + + bool verifyPKSMBridgeFileSHA256Checksum(struct pksmBridgeFile file) + { + uint32_t expectedSHA256ChecksumSize = 32; + if (file.checksumSize != expectedSHA256ChecksumSize) + { + return false; + } + std::array checksum = pksm::crypto::sha256(file.contents, file.size); + int result = memcmp(checksum.data(), file.checksum, file.checksumSize); + return (result == 0) ? true : false; + } } bool isLoadedSaveFromBridge(void) @@ -64,133 +78,86 @@ bool receiveSaveFromBridge(void) fmt::format(i18n::localize("WIRELESS_IP"), getHostId()))) { return false; - } - - int fd; - struct sockaddr_in servaddr; - if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP)) < 0) - { - Gui::error(i18n::localize("SOCKET_CREATE_FAIL"), errno); - return false; - } - - memset(&servaddr, 0, sizeof(servaddr)); - servaddr.sin_family = AF_INET; - servaddr.sin_port = htons(PKSM_PORT); - servaddr.sin_addr.s_addr = INADDR_ANY; - - if (bind(fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) - { - Gui::error(i18n::localize("SOCKET_BIND_FAIL"), errno); - close(fd); - return false; - } - - if (listen(fd, 5) < 0) - { - Gui::error(i18n::localize("SOCKET_LISTEN_FAIL"), errno); - close(fd); - return false; - } + }; + uint8_t* file = nullptr; + uint32_t fileSize; + enum pksmBridgeError error = receiveFileOverPKSMBridgeViaTCP( + PKSM_PORT, &lastIPAddr, &file, &fileSize, &verifyPKSMBridgeFileSHA256Checksum); - int fdconn; - int addrlen = sizeof(servaddr); - if ((fdconn = accept(fd, (struct sockaddr*)&servaddr, (socklen_t*)&addrlen)) < 0) + switch (error) { - Gui::error(i18n::localize("SOCKET_ACCEPT_FAIL"), errno); - close(fd); - return false; - } - - lastIPAddr = servaddr.sin_addr; - - size_t size = 0x180B19; - std::shared_ptr data = std::shared_ptr(new u8[size]); - - size_t total = 0; - size_t chunk = 1024; - int n; - while (total < size) - { - size_t torecv = size - total > chunk ? chunk : size - total; - n = recv(fdconn, &data[total], torecv, 0); - total += n; - if (n <= 0) - { + case PKSM_BRIDGE_ERROR_NONE: break; - } - fmt::print(stderr, "Recv {:d} bytes, {:d} still missing\n", total, size - total); + case PKSM_BRIDGE_ERROR_UNSUPPORTED_PROTCOL_VERSION: + Gui::error(i18n::localize("BRIDGE_ERROR_UNSUPPORTED_PROTOCOL_VERISON"), errno); + return false; + case PKSM_BRIDGE_ERROR_CONNECTION_ERROR: + Gui::error(i18n::localize("SOCKET_CONNECTION_FAIL"), errno); + return false; + case PKSM_BRIDGE_DATA_READ_FAILURE: + Gui::error(i18n::localize("DATA_RECEIVE_FAIL"), errno); + return false; + case PKSM_BRIDGE_DATA_WRITE_FAILURE: + Gui::error(i18n::localize("DATA_SEND_FAIL"), errno); + return false; + case PKSM_BRIDGE_DATA_FILE_CORRUPTED: + Gui::error(i18n::localize("BRIDGE_ERROR_FILE_DATA_CORRUPTED"), errno); + return false; + case PKSM_BRIDGE_ERROR_UNEXPECTED_MESSAGE: + Gui::error(i18n::localize("BRIDGE_ERROR_UNEXPECTED_MESSAGE"), errno); + return false; + default: + Gui::error(i18n::localize("BRIDGE_ERROR_UNHANDLED"), errno); + return false; } - close(fdconn); - close(fd); - - if (n == 0 || total == size) - { - if (TitleLoader::load(data, total)) - { - saveFromBridge = true; - Gui::setScreen(std::make_unique()); - } - } - else + std::shared_ptr data = std::shared_ptr(file, free); + if (!TitleLoader::load(data, fileSize)) { - Gui::error(i18n::localize("DATA_RECEIVE_FAIL"), errno); + Gui::error(i18n::localize("BRIDGE_ERROR_FILE_DATA_CORRUPTED"), 2); + return false; } + saveFromBridge = true; + Gui::setScreen(std::make_unique()); return true; } bool sendSaveToBridge(void) { - bool result = false; - // send via TCP - int fd; - struct sockaddr_in servaddr; - if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) - { - Gui::error(i18n::localize("SOCKET_CREATE_FAIL"), errno); - return result; - } - memset(&servaddr, 0, sizeof(servaddr)); - servaddr.sin_family = AF_INET; - servaddr.sin_port = htons(PKSM_PORT); - servaddr.sin_addr = lastIPAddr; - - if (connect(fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) + struct pksmBridgeFile file; + file.size = TitleLoader::save->getLength(); + file.contents = TitleLoader::save->rawData().get(); + std::array checksum = pksm::crypto::sha256(file.contents, file.size); + file.checksum = checksum.data(); + file.checksumSize = checksum.size(); + enum pksmBridgeError error = sendFileOverPKSMBridgeViaTCP(PKSM_PORT, lastIPAddr, file); + switch (error) { - Gui::error(i18n::localize("SOCKET_CONNECTION_FAIL"), errno); - close(fd); - return result; + case PKSM_BRIDGE_ERROR_NONE: + return true; + case PKSM_BRIDGE_ERROR_UNSUPPORTED_PROTCOL_VERSION: + Gui::error(i18n::localize("BRIDGE_ERROR_UNSUPPORTED_PROTOCOL_VERISON"), errno); + return false; + case PKSM_BRIDGE_ERROR_CONNECTION_ERROR: + Gui::error(i18n::localize("SOCKET_CONNECTION_FAIL"), errno); + return false; + case PKSM_BRIDGE_DATA_READ_FAILURE: + Gui::error(i18n::localize("DATA_RECEIVE_FAIL"), errno); + return false; + case PKSM_BRIDGE_DATA_WRITE_FAILURE: + Gui::error(i18n::localize("DATA_SEND_FAIL"), errno); + return false; + case PKSM_BRIDGE_DATA_FILE_CORRUPTED: + Gui::error(i18n::localize("BRIDGE_ERROR_FILE_DATA_CORRUPTED"), errno); + return false; + case PKSM_BRIDGE_ERROR_UNEXPECTED_MESSAGE: + Gui::error(i18n::localize("BRIDGE_ERROR_UNEXPECTED_MESSAGE"), errno); + return false; + default: + Gui::error(i18n::localize("BRIDGE_ERROR_UNHANDLED"), errno); + return false; } - - size_t size = TitleLoader::save->getLength(); - size_t total = 0; - size_t chunk = 1024; - int n; - while (total < size) - { - size_t tosend = size - total > chunk ? chunk : size - total; - n = send(fd, TitleLoader::save->rawData().get() + total, tosend, 0); - if (n == -1) - { - break; - } - total += n; - fmt::print(stderr, "Recv {:d} bytes, {:d} still missing\n", total, size - total); - } - if (total == size) - { - // Gui::createInfo("Success!", "Data sent back correctly."); - result = true; - } - else - { - Gui::error(i18n::localize("DATA_SEND_FAIL"), errno); - } - - close(fd); - return result; } void backupBridgeChanges() diff --git a/3ds/source/utils/pksmbridge_api.c b/3ds/source/utils/pksmbridge_api.c new file mode 100644 index 00000000..fdb01935 --- /dev/null +++ b/3ds/source/utils/pksmbridge_api.c @@ -0,0 +1,55 @@ +/* + * This file is part of PKSM + * Copyright (C) 2016-2021 mrhappyasthma, Flagbrew + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Additional Terms 7.b and 7.c of GPLv3 apply to this file: + * * Requiring preservation of specified reasonable legal notices or + * author attributions in that material or in the Appropriate Legal + * Notices displayed by works containing it. + * * Prohibiting misrepresentation of the origin of that material, + * or requiring that modified versions of such material be marked in + * reasonable ways as different from the original version. + */ + +#include "pksmbridge_api.h" +#include +#include +#include + +const int PKSM_BRIDGE_UNSUPPORTED_PROTOCOL_VERSION = -1; + +struct pksmBridgeRequest createPKSMBridgeRequest(int protocol_version) +{ + struct pksmBridgeRequest request; + memcpy(request.protocol_name, PKSM_BRIDGE_PROTOCOL_NAME, + sizeof(request.protocol_name) / sizeof(request.protocol_name[0])); + request.protocol_version = protocol_version; + return request; +} + +struct pksmBridgeResponse createPKSMBridgeResponseForRequest( + struct pksmBridgeRequest request, bool (*supportedProtocolVersionsFunc)(int version)) +{ + struct pksmBridgeResponse response; + memcpy(response.protocol_name, PKSM_BRIDGE_PROTOCOL_NAME, + sizeof(response.protocol_name) / sizeof(response.protocol_name[0])); + response.protocol_version = request.protocol_version; + if (!supportedProtocolVersionsFunc(request.protocol_version)) + { + response.protocol_version = PKSM_BRIDGE_UNSUPPORTED_PROTOCOL_VERSION; + } + return response; +} \ No newline at end of file diff --git a/3ds/source/utils/pksmbridge_tcp.c b/3ds/source/utils/pksmbridge_tcp.c new file mode 100644 index 00000000..ca1beddb --- /dev/null +++ b/3ds/source/utils/pksmbridge_tcp.c @@ -0,0 +1,312 @@ +/* + * This file is part of PKSM + * Copyright (C) 2016-2021 mrhappyasthma, Flagbrew + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Additional Terms 7.b and 7.c of GPLv3 apply to this file: + * * Requiring preservation of specified reasonable legal notices or + * author attributions in that material or in the Appropriate Legal + * Notices displayed by works containing it. + * * Prohibiting misrepresentation of the origin of that material, + * or requiring that modified versions of such material be marked in + * reasonable ways as different from the original version. + */ + +#include "pksmbridge_tcp.h" +#include "pksmbridge_api.h" +#include +#include +#include +#include +#include + +/** Creates a socket and returns the file descriptor. If an error occurs, returns -1. */ +static int createSocket(void) +{ + return socket(AF_INET, SOCK_STREAM, IPPROTO_IP); +} + +/** Creates a socket address with the provided port and address. */ +static struct sockaddr_in createSocketAddress(uint16_t port, in_addr_t address) +{ + struct sockaddr_in servaddr; + memset(&servaddr, 0, sizeof(servaddr)); + servaddr.sin_family = AF_INET; + servaddr.sin_port = htons(port); + servaddr.sin_addr.s_addr = address; + return servaddr; +} + +/** Sends chunks of `data` from a buffer to a socket. Returns the number of sent bytes. */ +static int sendDataFromBufferToSocket(int sockfd, void* buffer, size_t size) +{ + size_t total = 0; + size_t chunk = 1024; + while (total < size) + { + size_t tosend = size - total > chunk ? chunk : size - total; + int n = send(sockfd, ((char*)buffer) + total, tosend, 0); + if (n == -1) + { + break; + } + total += n; + } + return total; +} + +/** + * Reads chunks of data from a socket into the provided `buffer`. Returns the + * number of read bytes. + */ +static int receiveDataFromSocketIntoBuffer(int sockfd, void* buffer, size_t size) +{ + size_t total = 0; + size_t chunk = 1024; + while (total < size) + { + size_t torecv = size - total > chunk ? chunk : size - total; + int n = recv(sockfd, ((char*)buffer) + total, torecv, 0); + if (n <= 0) + { + break; + } + total += n; + } + return total; +} + +/** Expects the `protocol_name` field from either pksmBridgeRequest or pksmBridgeResponse. */ +static bool verifyPKSMBridgeHeader(char protocol_name[10]) +{ + int result = strncmp(protocol_name, PKSM_BRIDGE_PROTOCOL_NAME, 10); + return (result == 0) ? true : false; +} + +/** A helper function to send a file over the PKSM Bridge piece-by-piece to avoid additional memory + * allocations. */ +static bool sendPKSMBridgeFileToSocket(int sockfd, struct pksmBridgeFile file) +{ + uint32_t sentBytes = + sendDataFromBufferToSocket(sockfd, &file.checksumSize, sizeof(file.checksumSize)); + if (sentBytes != sizeof(file.checksumSize)) + { + return false; + } + sentBytes = sendDataFromBufferToSocket(sockfd, file.checksum, file.checksumSize); + if (sentBytes != file.checksumSize) + { + return false; + } + sentBytes = sendDataFromBufferToSocket(sockfd, &file.size, sizeof(file.size)); + if (sentBytes != sizeof(file.size)) + { + return false; + } + sentBytes = sendDataFromBufferToSocket(sockfd, file.contents, file.size); + if (sentBytes != file.size) + { + return false; + } + return true; +} + +/** A helper function to receive file bytes over the PKSM Bridge. Callers are expected to `free()` + * the `outFile`. */ +static uint32_t receiveFileOverPKSMBridgeFromSocket( + int sockfd, uint8_t** outFile, bool (*validateChecksumFunc)(struct pksmBridgeFile file)) +{ + uint32_t checksumSize; + uint32_t bytesRead = + receiveDataFromSocketIntoBuffer(sockfd, &checksumSize, sizeof(checksumSize)); + if (bytesRead != sizeof(checksumSize)) + { + return 0; + } + uint8_t* checksumBuffer = (uint8_t*)malloc(checksumSize); + bytesRead = receiveDataFromSocketIntoBuffer(sockfd, checksumBuffer, checksumSize); + if (bytesRead != checksumSize) + { + free(checksumBuffer); + return 0; + } + uint32_t fileSize; + bytesRead = receiveDataFromSocketIntoBuffer(sockfd, &fileSize, sizeof(fileSize)); + if (bytesRead != sizeof(fileSize)) + { + free(checksumBuffer); + return 0; + } + uint8_t* fileBuffer = (uint8_t*)malloc(fileSize); + bytesRead = receiveDataFromSocketIntoBuffer(sockfd, fileBuffer, fileSize); + if (bytesRead != fileSize) + { + free(fileBuffer); + free(checksumBuffer); + return 0; + } + + // Construct a temporary `struct pksmBridgeFile` pointing to the corresponding buffers to pass + // to the validation function. + struct pksmBridgeFile file = {.checksumSize = checksumSize, + .checksum = checksumBuffer, + .size = fileSize, + .contents = fileBuffer}; + if (!validateChecksumFunc(file)) + { + return 0; + } + free(checksumBuffer); + + *outFile = fileBuffer; + return fileSize; +} + +bool checkSupportedPKSMBridgeProtocolVersionForTCP(int version) +{ + // This logic should be updated if newer protocol versions are introduced. + // For now, there is only a single protocol version. + return (version == 1); +} + +enum pksmBridgeError sendFileOverPKSMBridgeViaTCP( + uint16_t port, struct in_addr address, struct pksmBridgeFile file) +{ + int fd = createSocket(); + if (fd < 0) + { + return PKSM_BRIDGE_ERROR_CONNECTION_ERROR; + } + + struct sockaddr_in servaddr = createSocketAddress(port, address.s_addr); + if (connect(fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) + { + close(fd); + return PKSM_BRIDGE_ERROR_CONNECTION_ERROR; + } + + // Expect a pksmBridgeRequest, which specifies the protocol version. + struct pksmBridgeRequest request; + int bytesRead = receiveDataFromSocketIntoBuffer(fd, &request, sizeof(request)); + if (bytesRead != sizeof(request)) + { + close(fd); + return PKSM_BRIDGE_DATA_READ_FAILURE; + } + if (!verifyPKSMBridgeHeader(request.protocol_name)) + { + close(fd); + return PKSM_BRIDGE_ERROR_UNEXPECTED_MESSAGE; + } + + // Construct and send the pksmBridgeResponse. + struct pksmBridgeResponse response = + createPKSMBridgeResponseForRequest(request, &checkSupportedPKSMBridgeProtocolVersionForTCP); + int sentBytes = sendDataFromBufferToSocket(fd, &response, sizeof(response)); + if (sentBytes != sizeof(response)) + { + close(fd); + return PKSM_BRIDGE_DATA_WRITE_FAILURE; + } + if (response.protocol_version == PKSM_BRIDGE_UNSUPPORTED_PROTOCOL_VERSION) + { + close(fd); + return PKSM_BRIDGE_ERROR_UNSUPPORTED_PROTCOL_VERSION; + } + + // Send the pksmBridgeFile, since the protocol version was confirmed. + bool success = sendPKSMBridgeFileToSocket(fd, file); + close(fd); + return success ? PKSM_BRIDGE_ERROR_NONE : PKSM_BRIDGE_DATA_WRITE_FAILURE; +} + +enum pksmBridgeError receiveFileOverPKSMBridgeViaTCP(uint16_t port, struct in_addr* outAddress, + uint8_t** outFile, uint32_t* outFileSize, + bool (*validateChecksumFunc)(struct pksmBridgeFile file)) +{ + int fd = createSocket(); + if (fd < 0) + { + return PKSM_BRIDGE_ERROR_CONNECTION_ERROR; + } + + struct sockaddr_in servaddr = createSocketAddress(port, INADDR_ANY); + if (bind(fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) + { + close(fd); + return PKSM_BRIDGE_ERROR_CONNECTION_ERROR; + } + if (listen(fd, /*backlog=*/1) < 0) + { + close(fd); + return PKSM_BRIDGE_ERROR_CONNECTION_ERROR; + } + + int fdconn; + int addrlen = sizeof(servaddr); + if ((fdconn = accept(fd, (struct sockaddr*)&servaddr, (socklen_t*)&addrlen)) < 0) + { + close(fd); + return PKSM_BRIDGE_ERROR_CONNECTION_ERROR; + } + close(fd); + + // Send a pksmBridgeRequest, requesting a specific protocol version. + struct pksmBridgeRequest request = createPKSMBridgeRequest(PKSM_BRIDGE_LATEST_PROTOCOL_VERSION); + uint32_t sentBytes = sendDataFromBufferToSocket(fdconn, &request, sizeof(request)); + if (sentBytes != sizeof(request)) + { + close(fdconn); + return PKSM_BRIDGE_DATA_WRITE_FAILURE; + } + + // Expect a pksmBridgeResponse and see if the protocol version was supported. + struct pksmBridgeResponse response; + uint32_t readBytes = receiveDataFromSocketIntoBuffer(fdconn, &response, sizeof(response)); + if (readBytes != sizeof(response)) + { + close(fdconn); + return PKSM_BRIDGE_DATA_READ_FAILURE; + } + if (!verifyPKSMBridgeHeader(response.protocol_name)) + { + close(fdconn); + return PKSM_BRIDGE_ERROR_UNEXPECTED_MESSAGE; + } + if (response.protocol_version == PKSM_BRIDGE_UNSUPPORTED_PROTOCOL_VERSION) + { + close(fdconn); + return PKSM_BRIDGE_ERROR_UNSUPPORTED_PROTCOL_VERSION; + } + + // Expect a pksmBridgeFile since the protocol version was confirmed. + uint8_t* fileBuffer = NULL; + uint32_t fileSize = + receiveFileOverPKSMBridgeFromSocket(fdconn, &fileBuffer, validateChecksumFunc); + close(fdconn); + + if (fileSize == 0 || fileBuffer == NULL) + { + return PKSM_BRIDGE_DATA_FILE_CORRUPTED; + } + + if (outAddress != NULL) + { + *outAddress = servaddr.sin_addr; + } + *outFileSize = fileSize; + *outFile = fileBuffer; + return PKSM_BRIDGE_ERROR_NONE; +} \ No newline at end of file diff --git a/assets/gui_strings/chs/communication.json b/assets/gui_strings/chs/communication.json index 5f6bd97a..c931126d 100644 --- a/assets/gui_strings/chs/communication.json +++ b/assets/gui_strings/chs/communication.json @@ -1,4 +1,8 @@ { + "BRIDGE_ERROR_FILE_DATA_CORRUPTED": "Transfer failed. File data corrupted.", + "BRIDGE_ERROR_UNEXPECTED_MESSAGE" : "Unsupported PKSM Bridge protocol version.", + "BRIDGE_ERROR_UNHANDLED" : "Unhandled PKSM Bridge error occurred: {:d}.", + "BRIDGE_ERROR_UNSUPPORTED_PROTOCOL_VERISON" : "Unexpected message received over PKSM Bridge.", "BRIDGE_SHOULD_SEND_1": "您想要发送更改的文件", "BRIDGE_SHOULD_SEND_2": "到原始客户端吗?", "CLOUD_BOX": "Cloud {:d}", diff --git a/assets/gui_strings/cht/communication.json b/assets/gui_strings/cht/communication.json index 5f6bd97a..c931126d 100644 --- a/assets/gui_strings/cht/communication.json +++ b/assets/gui_strings/cht/communication.json @@ -1,4 +1,8 @@ { + "BRIDGE_ERROR_FILE_DATA_CORRUPTED": "Transfer failed. File data corrupted.", + "BRIDGE_ERROR_UNEXPECTED_MESSAGE" : "Unsupported PKSM Bridge protocol version.", + "BRIDGE_ERROR_UNHANDLED" : "Unhandled PKSM Bridge error occurred: {:d}.", + "BRIDGE_ERROR_UNSUPPORTED_PROTOCOL_VERISON" : "Unexpected message received over PKSM Bridge.", "BRIDGE_SHOULD_SEND_1": "您想要发送更改的文件", "BRIDGE_SHOULD_SEND_2": "到原始客户端吗?", "CLOUD_BOX": "Cloud {:d}", diff --git a/assets/gui_strings/eng/communication.json b/assets/gui_strings/eng/communication.json index 246d0a54..43fcf0a1 100644 --- a/assets/gui_strings/eng/communication.json +++ b/assets/gui_strings/eng/communication.json @@ -1,4 +1,8 @@ { + "BRIDGE_ERROR_FILE_DATA_CORRUPTED": "Transfer failed. File data corrupted.", + "BRIDGE_ERROR_UNEXPECTED_MESSAGE" : "Unsupported PKSM Bridge protocol version.", + "BRIDGE_ERROR_UNHANDLED" : "Unhandled PKSM Bridge error occurred: {:d}.", + "BRIDGE_ERROR_UNSUPPORTED_PROTOCOL_VERISON" : "Unexpected message received over PKSM Bridge.", "BRIDGE_SHOULD_SEND_1": "Would you like to send the changed file", "BRIDGE_SHOULD_SEND_2": "to the original client?", "CLOUD_BOX": "Cloud {:d}", diff --git a/assets/gui_strings/fre/communication.json b/assets/gui_strings/fre/communication.json index 9f38de21..b2e9d023 100644 --- a/assets/gui_strings/fre/communication.json +++ b/assets/gui_strings/fre/communication.json @@ -1,4 +1,8 @@ { + "BRIDGE_ERROR_FILE_DATA_CORRUPTED": "Transfer failed. File data corrupted.", + "BRIDGE_ERROR_UNEXPECTED_MESSAGE" : "Unsupported PKSM Bridge protocol version.", + "BRIDGE_ERROR_UNHANDLED" : "Unhandled PKSM Bridge error occurred: {:d}.", + "BRIDGE_ERROR_UNSUPPORTED_PROTOCOL_VERISON" : "Unexpected message received over PKSM Bridge.", "BRIDGE_SHOULD_SEND_1": "Voulez-vous envoyer le nouveau fichier", "BRIDGE_SHOULD_SEND_2": "vers votre Switch?", "CLOUD_BOX": "Nuage {:d}", diff --git a/assets/gui_strings/ger/communication.json b/assets/gui_strings/ger/communication.json index 6be63c64..e64af830 100644 --- a/assets/gui_strings/ger/communication.json +++ b/assets/gui_strings/ger/communication.json @@ -1,4 +1,8 @@ { + "BRIDGE_ERROR_FILE_DATA_CORRUPTED": "Transfer failed. File data corrupted.", + "BRIDGE_ERROR_UNEXPECTED_MESSAGE" : "Unsupported PKSM Bridge protocol version.", + "BRIDGE_ERROR_UNHANDLED" : "Unhandled PKSM Bridge error occurred: {:d}.", + "BRIDGE_ERROR_UNSUPPORTED_PROTOCOL_VERISON" : "Unexpected message received over PKSM Bridge.", "BRIDGE_SHOULD_SEND_1": "Möchtest Du die veränderte Datei", "BRIDGE_SHOULD_SEND_2": "zum origin. Client senden?", "CLOUD_BOX": "Cloud {:d}", diff --git a/assets/gui_strings/ita/communication.json b/assets/gui_strings/ita/communication.json index 9b6ac49e..a5244003 100644 --- a/assets/gui_strings/ita/communication.json +++ b/assets/gui_strings/ita/communication.json @@ -1,4 +1,8 @@ { + "BRIDGE_ERROR_FILE_DATA_CORRUPTED": "Transfer failed. File data corrupted.", + "BRIDGE_ERROR_UNEXPECTED_MESSAGE" : "Unsupported PKSM Bridge protocol version.", + "BRIDGE_ERROR_UNHANDLED" : "Unhandled PKSM Bridge error occurred: {:d}.", + "BRIDGE_ERROR_UNSUPPORTED_PROTOCOL_VERISON" : "Unexpected message received over PKSM Bridge.", "BRIDGE_SHOULD_SEND_1": "Vuoi mandare il salvataggio", "BRIDGE_SHOULD_SEND_2": "al client originale?", "CLOUD_BOX": "Cloud {:d}", diff --git a/assets/gui_strings/jpn/communication.json b/assets/gui_strings/jpn/communication.json index c4bbb2cb..1fb35904 100644 --- a/assets/gui_strings/jpn/communication.json +++ b/assets/gui_strings/jpn/communication.json @@ -1,4 +1,8 @@ { + "BRIDGE_ERROR_FILE_DATA_CORRUPTED": "Transfer failed. File data corrupted.", + "BRIDGE_ERROR_UNEXPECTED_MESSAGE" : "Unsupported PKSM Bridge protocol version.", + "BRIDGE_ERROR_UNHANDLED" : "Unhandled PKSM Bridge error occurred: {:d}.", + "BRIDGE_ERROR_UNSUPPORTED_PROTOCOL_VERISON" : "Unexpected message received over PKSM Bridge.", "BRIDGE_SHOULD_SEND_1": "変更したファイルを", "BRIDGE_SHOULD_SEND_2": "元のクライアントに送りますか?", "CLOUD_BOX": "クラウド {:d}", diff --git a/assets/gui_strings/kor/communication.json b/assets/gui_strings/kor/communication.json index 27cd45e9..6fc6bdb7 100644 --- a/assets/gui_strings/kor/communication.json +++ b/assets/gui_strings/kor/communication.json @@ -1,4 +1,8 @@ { + "BRIDGE_ERROR_FILE_DATA_CORRUPTED": "Transfer failed. File data corrupted.", + "BRIDGE_ERROR_UNEXPECTED_MESSAGE" : "Unsupported PKSM Bridge protocol version.", + "BRIDGE_ERROR_UNHANDLED" : "Unhandled PKSM Bridge error occurred: {:d}.", + "BRIDGE_ERROR_UNSUPPORTED_PROTOCOL_VERISON" : "Unexpected message received over PKSM Bridge.", "BRIDGE_SHOULD_SEND_1": "변경된 파일을 기준 클라이언트로", "BRIDGE_SHOULD_SEND_2": "보내겠습니까?", "CLOUD_BOX": "Cloud {:d}", diff --git a/assets/gui_strings/nl/communication.json b/assets/gui_strings/nl/communication.json index bcb8ca01..063ad2b5 100644 --- a/assets/gui_strings/nl/communication.json +++ b/assets/gui_strings/nl/communication.json @@ -1,4 +1,8 @@ { + "BRIDGE_ERROR_FILE_DATA_CORRUPTED": "Transfer failed. File data corrupted.", + "BRIDGE_ERROR_UNEXPECTED_MESSAGE" : "Unsupported PKSM Bridge protocol version.", + "BRIDGE_ERROR_UNHANDLED" : "Unhandled PKSM Bridge error occurred: {:d}.", + "BRIDGE_ERROR_UNSUPPORTED_PROTOCOL_VERISON" : "Unexpected message received over PKSM Bridge.", "BRIDGE_SHOULD_SEND_1": "Wilt u het veranderde bestand naar", "BRIDGE_SHOULD_SEND_2": "de originele client sturen?", "CLOUD_BOX": "Cloud {:d}", diff --git a/assets/gui_strings/pt/communication.json b/assets/gui_strings/pt/communication.json index ee72373b..5256ace8 100644 --- a/assets/gui_strings/pt/communication.json +++ b/assets/gui_strings/pt/communication.json @@ -1,4 +1,8 @@ { + "BRIDGE_ERROR_FILE_DATA_CORRUPTED": "Transfer failed. File data corrupted.", + "BRIDGE_ERROR_UNEXPECTED_MESSAGE" : "Unsupported PKSM Bridge protocol version.", + "BRIDGE_ERROR_UNHANDLED" : "Unhandled PKSM Bridge error occurred: {:d}.", + "BRIDGE_ERROR_UNSUPPORTED_PROTOCOL_VERISON" : "Unexpected message received over PKSM Bridge.", "BRIDGE_SHOULD_SEND_1": "Você gostaria de enviar o arquivo modificado", "BRIDGE_SHOULD_SEND_2": "ao Client original?", "CLOUD_BOX": "Cloud {:d}", diff --git a/assets/gui_strings/ro/communication.json b/assets/gui_strings/ro/communication.json index f8f5bfec..fc3dd2f7 100644 --- a/assets/gui_strings/ro/communication.json +++ b/assets/gui_strings/ro/communication.json @@ -1,4 +1,8 @@ { + "BRIDGE_ERROR_FILE_DATA_CORRUPTED": "Transfer failed. File data corrupted.", + "BRIDGE_ERROR_UNEXPECTED_MESSAGE" : "Unsupported PKSM Bridge protocol version.", + "BRIDGE_ERROR_UNHANDLED" : "Unhandled PKSM Bridge error occurred: {:d}.", + "BRIDGE_ERROR_UNSUPPORTED_PROTOCOL_VERISON" : "Unexpected message received over PKSM Bridge.", "BRIDGE_SHOULD_SEND_1": "Vrei să trimiți fişierul schimbat?", "BRIDGE_SHOULD_SEND_2": "clientului original?", "CLOUD_BOX": "Cloud {:d}", diff --git a/assets/gui_strings/spa/communication.json b/assets/gui_strings/spa/communication.json index 56851cff..736adf89 100644 --- a/assets/gui_strings/spa/communication.json +++ b/assets/gui_strings/spa/communication.json @@ -1,4 +1,8 @@ { + "BRIDGE_ERROR_FILE_DATA_CORRUPTED": "Transfer failed. File data corrupted.", + "BRIDGE_ERROR_UNEXPECTED_MESSAGE" : "Unsupported PKSM Bridge protocol version.", + "BRIDGE_ERROR_UNHANDLED" : "Unhandled PKSM Bridge error occurred: {:d}.", + "BRIDGE_ERROR_UNSUPPORTED_PROTOCOL_VERISON" : "Unexpected message received over PKSM Bridge.", "BRIDGE_SHOULD_SEND_1": "¿Te gustaría enviar el archivo modificado", "BRIDGE_SHOULD_SEND_2": "al cliente original?", "CLOUD_BOX": "Nube {:d}",