From 0aa2c1b5b82bb71cfc23bb99fd99d260dc56edb4 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Sun, 18 Aug 2024 11:07:22 -0300 Subject: [PATCH] Finishing TCP/UDP commands --- README.md | 4 +- examples/LinkMobile_demo/src/main.cpp | 10 +- lib/LinkMobile.hpp | 169 ++++++++++++++++++++------ 3 files changed, 142 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 9f58073..6fc54c5 100644 --- a/README.md +++ b/README.md @@ -419,7 +419,7 @@ The original accessory was sold in Japan only and doesn't work anymore since it Name | Type | Default | Description --- | --- | --- | --- -`timeout` | **u32** | `180` | Number of *frames* without completing a request to reset a connection. +`timeout` | **u32** | `600` | Number of *frames* without completing a request to reset a connection. `timerId` | **u8** *(0~3)* | `3` | GBA Timer to use for sending. You can update these values at any time without creating a new instance: @@ -448,7 +448,7 @@ Name | Return type | Description `dnsQuery(domainName, result)` | **bool** | Looks up the IPv4 address for a `domainName` (a null-terminated string, max `253` characters). The `result` is a pointer to a `LinkMobile::DNSQuery` struct that will be filled with the result. When the request is completed, the `completed` field will be `true`. If an IP address was found, the `success` field will be `true` and the `ipv4` field can be read as a 4-byte address. `openConnection(ip, port, type, result)` | **bool** | Opens a TCP/UDP (`type`) connection at the given `ip` (4-byte address) on the given `port`. The `result` is a pointer to a `LinkMobile::OpenConn` struct that will be filled with the result. When the request is completed, the `completed` field will be `true`. If the connection was successful, the `success` field will be `true` and the `connectionId` field can be used when calling the `transfer(...)` method. Only `2` connections can be opened at the same time. `closeConnection(connectionId, type, result)` | **bool** | Closes an active TCP/UDP (`type`) connection. The `result` is a pointer to a `LinkMobile::CloseConn` struct that will be filled with the result. When the request is completed, the `completed` field will be `true`. If the connection was closed correctly, the `success` field will be `true`. -`transfer(dataToSend, result, [connectionId])` | **bool** | Requests a data transfer (up to `254` bytes) and responds the received data. The transfer can be done with the other node in a P2P connection, or with any open TCP/UDP connection if an ISP session is active. In the case of a TCP/UDP connection, the `connectionId` must be provided. The `result` is a pointer to a `LinkMobile::DataTransfer` struct that will be filled with the received data. It can also point to `dataToSend` to reuse the struct. When the request is completed, the `completed` field will be `true`. If the transfer was successful, the `success` field will be `true`. +`transfer(dataToSend, result, [connectionId])` | **bool** | Requests a data transfer (up to `254` bytes) and responds the received data. The transfer can be done with the other node in a P2P connection, or with any open TCP/UDP connection if an ISP session is active. In the case of a TCP/UDP connection, the `connectionId` must be provided. The `result` is a pointer to a `LinkMobile::DataTransfer` struct that will be filled with the received data. It can also point to `dataToSend` to reuse the struct. When the request is completed, the `completed` field will be `true`. If the transfer was successful, the `success` field will be `true`. If not, you can assume that the connection was closed. `hangUp()` | **bool** | Hangs up the current P2P or ISP call. Closes all connections. `readConfiguration(configurationData)` | **bool** | Retrieves the adapter configuration, and puts it in the `configurationData` struct. If the adapter has an active session, the data is already loaded, so it's instantaneous. `getState()` | **LinkMobile::State** | Returns the current state (one of `LinkMobile::State::NEEDS_RESET`, `LinkMobile::State::PINGING`, `LinkMobile::State::WAITING_TO_START`, `LinkMobile::State::STARTING_SESSION`, `LinkMobile::State::ACTIVATING_SIO32`, `LinkMobile::State::WAITING_32BIT_SWITCH`, `LinkMobile::State::READING_CONFIGURATION`, `LinkMobile::State::SESSION_ACTIVE`, `LinkMobile::State::CALL_REQUESTED`, `LinkMobile::State::CALLING`, `LinkMobile::State::CALL_ESTABLISHED`, `LinkMobile::State::ISP_CALL_REQUESTED`, `LinkMobile::State::ISP_CALLING`, `LinkMobile::State::ISP_LOGIN`, `LinkMobile::State::ISP_ACTIVE`, `LinkMobile::State::SHUTDOWN_REQUESTED`, `LinkMobile::State::ENDING_SESSION`, `LinkMobile::State::WAITING_8BIT_SWITCH`, or `LinkMobile::State::SHUTDOWN`). diff --git a/examples/LinkMobile_demo/src/main.cpp b/examples/LinkMobile_demo/src/main.cpp index bb9d1df..3c2b5bd 100644 --- a/examples/LinkMobile_demo/src/main.cpp +++ b/examples/LinkMobile_demo/src/main.cpp @@ -102,7 +102,7 @@ start: output += getErrorString(error); output += "\n (SELECT = stop)"; } else if (linkMobile->getState() == LinkMobile::State::SESSION_ACTIVE) { - output += "\nL = Read Configuration"; + output += "\nL = Read configuration"; output += "\nR = Call someone"; output += "\nSTART = Call the ISP"; output += "\n\n (A = ok)\n (SELECT = stop)"; @@ -111,7 +111,7 @@ start: output += "\n (A = send)"; output += "\n (L = hang up)"; } else if (linkMobile->isConnectedISP()) { - output += "\n (LEFT = DNS query)"; + output += "\n (A = DNS query)"; output += "\n (L = hang up)"; } output += "\n (SELECT = stop)"; @@ -240,8 +240,8 @@ start: break; } case LinkMobile::State::ISP_ACTIVE: { - // LEFT = DNS query - if (didPress(KEY_LEFT, left)) { + // A = DNS query + if (didPress(KEY_A, a) && !waitingDNS) { std::string domain = getDomainInput(); if (domain != "") { // (8) Run DNS queries @@ -590,3 +590,5 @@ template rc[i] = digits[(w >> j) & 0x0f]; return rc; } + +// TODO: Implement TCP/UDP sockets test functions diff --git a/lib/LinkMobile.hpp b/lib/LinkMobile.hpp index ae97488..440389d 100644 --- a/lib/LinkMobile.hpp +++ b/lib/LinkMobile.hpp @@ -78,7 +78,7 @@ static volatile char LINK_MOBILE_VERSION[] = "LinkMobile/v7.0.0"; #define LINK_MOBILE_MAX_DOMAIN_NAME_LENGTH 253 #define LINK_MOBILE_COMMAND_TRANSFER_BUFFER \ (LINK_MOBILE_MAX_COMMAND_TRANSFER_LENGTH + 4) -#define LINK_MOBILE_DEFAULT_TIMEOUT (60 * 3) +#define LINK_MOBILE_DEFAULT_TIMEOUT (60 * 10) #define LINK_MOBILE_DEFAULT_TIMER_ID 3 #define LINK_MOBILE_BARRIER asm volatile("" ::: "memory") @@ -102,18 +102,18 @@ class LinkMobile { static constexpr int INIT_WAIT_FRAMES = 7; static constexpr int INIT_TIMEOUT_FRAMES = 30; static constexpr int PING_FREQUENCY_FRAMES = 60; - static constexpr int ADAPTER_WAITING = 0xD2; - static constexpr u32 ADAPTER_WAITING_32BIT = 0xD2D2D2D2; - static constexpr int GBA_WAITING = 0x4B; - static constexpr u32 GBA_WAITING_32BIT = 0x4B4B4B4B; + static constexpr int ADAPTER_WAITING = 0xd2; + static constexpr u32 ADAPTER_WAITING_32BIT = 0xd2d2d2d2; + static constexpr int GBA_WAITING = 0x4b; + static constexpr u32 GBA_WAITING_32BIT = 0x4b4b4b4b; static constexpr int OR_VALUE = 0x80; static constexpr int COMMAND_MAGIC_VALUE1 = 0x99; static constexpr int COMMAND_MAGIC_VALUE2 = 0x66; static constexpr int DEVICE_GBA = 0x1; static constexpr int DEVICE_ADAPTER_BLUE = 0x8; static constexpr int DEVICE_ADAPTER_YELLOW = 0x9; - static constexpr int DEVICE_ADAPTER_GREEN = 0xA; - static constexpr int DEVICE_ADAPTER_RED = 0xB; + static constexpr int DEVICE_ADAPTER_GREEN = 0xa; + static constexpr int DEVICE_ADAPTER_RED = 0xb; static constexpr int ACK_SENDER = 0; static constexpr int CONFIGURATION_DATA_SIZE = 192; static constexpr int CONFIGURATION_DATA_CHUNK = CONFIGURATION_DATA_SIZE / 2; @@ -135,7 +135,8 @@ class LinkMobile { static constexpr int COMMAND_OPEN_UDP_CONNECTION = 0x25; static constexpr int COMMAND_CLOSE_UDP_CONNECTION = 0x26; static constexpr int COMMAND_DNS_QUERY = 0x28; - static constexpr int COMMAND_ERROR_STATUS = 0x6E | OR_VALUE; + static constexpr int COMMAND_CONNECTION_CLOSED = 0x1f; + static constexpr int COMMAND_ERROR_STATUS = 0x6e | OR_VALUE; static constexpr u8 WAIT_TICKS[] = {4, 8}; static constexpr int LOGIN_PARTS_SIZE = 8; static constexpr u8 LOGIN_PARTS[] = {0x4e, 0x49, 0x4e, 0x54, @@ -255,7 +256,7 @@ class LinkMobile { /** * @brief Constructs a new LinkMobile object. * @param timeout Number of *frames* without completing a request to reset a - * connection. Defaults to 180 (3 seconds). + * connection. Defaults to 600 (10 seconds). * @param timerId GBA Timer to use for waiting. */ explicit LinkMobile(u32 timeout = LINK_MOBILE_DEFAULT_TIMEOUT, @@ -414,7 +415,8 @@ class LinkMobile { * will be filled with the result. When the request is completed, the * `completed` field will be `true`. If the connection was successful, the * `success` field will be `true` and the `connectionId` field can be used - * when calling the `transfer(...)` method. + * when calling the `transfer(...)` method. If not, you can assume that the + * connection was closed. * \warning Only `2` connections can be opened at the same time. * \warning Non-blocking. Returns `true` immediately, or `false` if there's no * active ISP session, no available request slots. @@ -440,7 +442,6 @@ class LinkMobile { pushRequest(request); return true; } - // TODO: IMPLEMENT SOCKET METHODS /** * @brief Closes an active TCP/UDP (`type`) connection. @@ -926,13 +927,44 @@ class LinkMobile { } break; } + case UserRequest::Type::OPEN_CONNECTION: { + if (state != ISP_ACTIVE) { + userRequests.pop(); + return; + } + + if (!asyncCommand.isActive && !request.commandSent) { + if (request.type == ConnectionType::TCP) + cmdOpenTCPConnection(request.ip, request.port); + else + cmdOpenUDPConnection(request.ip, request.port); + request.commandSent = true; + } + break; + } + case UserRequest::Type::CLOSE_CONNECTION: { + if (state != ISP_ACTIVE) { + userRequests.pop(); + return; + } + + if (!asyncCommand.isActive && !request.commandSent) { + if (request.type == ConnectionType::TCP) + cmdCloseTCPConnection(request.connectionId); + else + cmdCloseUDPConnection(request.connectionId); + request.commandSent = true; + } + break; + } case UserRequest::Type::TRANSFER: { if (state != CALL_ESTABLISHED) { userRequests.pop(); return; } if (!asyncCommand.isActive && !request.commandSent) { - cmdTransferData(0xff, request.send.data, request.send.size); + cmdTransferData(request.connectionId, request.send.data, + request.send.size); request.commandSent = true; } break; @@ -1125,21 +1157,13 @@ class LinkMobile { } if (!asyncCommand.respondsTo(COMMAND_TRANSFER_DATA)) return; - if (asyncCommand.cmd.header.size == 0) - return abort(Error::Type::WEIRD_RESPONSE); if (userRequests.isEmpty()) return abort(Error::Type::WTF); auto request = userRequests.peekRef(); - if (request->type != UserRequest::TRANSFER) - return abort(Error::Type::WTF); - u32 size = asyncCommand.cmd.header.size - 1; - for (u32 i = 0; i < size; i++) - request->receive->data[i] = asyncCommand.cmd.data.bytes[1 + i]; - request->receive->size = size; - request->receive->completed = true; - request->receive->success = true; - request->finished = true; + handleTransferDataResponse(request); + + break; } case ISP_CALLING: { if (!asyncCommand.respondsTo(COMMAND_DIAL_TELEPHONE)) @@ -1196,21 +1220,44 @@ class LinkMobile { request->dns->completed = true; request->finished = true; - break; + } else if (asyncCommand.respondsTo(COMMAND_OPEN_TCP_CONNECTION) || + asyncCommand.respondsTo(COMMAND_OPEN_UDP_CONNECTION)) { + if (request->type != UserRequest::OPEN_CONNECTION) + return abort(Error::Type::WTF); + + if (asyncCommand.result == CommandResult::SUCCESS) { + if (asyncCommand.cmd.header.size != 1) + return abort(Error::Type::WEIRD_RESPONSE); + request->open->connectionId = asyncCommand.cmd.data.bytes[0]; + request->open->success = true; + } else { + request->open->success = false; + } + + request->close->completed = true; + request->finished = true; + } else if (asyncCommand.respondsTo(COMMAND_CLOSE_TCP_CONNECTION) || + asyncCommand.respondsTo(COMMAND_CLOSE_UDP_CONNECTION)) { + if (request->type != UserRequest::CLOSE_CONNECTION) + return abort(Error::Type::WTF); + + request->close->success = + asyncCommand.result == CommandResult::SUCCESS; + request->close->completed = true; + request->finished = true; } else if (asyncCommand.respondsTo(COMMAND_TRANSFER_DATA)) { if (request->type != UserRequest::TRANSFER) return abort(Error::Type::WTF); - // TODO: HANDLE TRANSFER DATA - // if (asyncCommand.cmd.header.size == 0) - // return abort(Error::Type::WEIRD_RESPONSE); - // TODO: IMPLEMENT - // u32 size = asyncCommand.cmd.header.size - 1; - // for (u32 i = 0; i < size; i++) - // request.receive->data[i] = asyncCommand.cmd.data.bytes[1 + i]; - // request.receive->size = size; - // request.receive->completed = true; - // request.finished = true; + handleTransferDataResponse(request); + } else if (asyncCommand.respondsTo(COMMAND_CONNECTION_CLOSED)) { + if (request->type != UserRequest::TRANSFER) + return abort(Error::Type::WTF); + + // (connection closed) + request->receive->success = false; + request->receive->completed = true; + request->finished = true; } break; } @@ -1227,6 +1274,27 @@ class LinkMobile { } } + void handleTransferDataResponse(UserRequest* request) { + if (request->type != UserRequest::TRANSFER) + return abort(Error::Type::WTF); + + if (asyncCommand.result == CommandResult::SUCCESS) { + if (asyncCommand.cmd.header.size == 0) + return abort(Error::Type::WEIRD_RESPONSE); + + u32 size = asyncCommand.cmd.header.size - 1; + for (u32 i = 0; i < size; i++) + request->receive->data[i] = asyncCommand.cmd.data.bytes[1 + i]; + request->receive->size = size; + request->receive->success = true; + } else { + request->receive->success = false; + } + + request->receive->completed = true; + request->finished = true; + } + void processLoosePacket(u32 newData) { switch (state) { case PINGING: { @@ -1304,6 +1372,32 @@ class LinkMobile { sendCommandAsync(buildCommand(COMMAND_ISP_LOGIN, true)); } + void cmdOpenTCPConnection(const u8* ip, u16 port) { + for (u32 i = 0; i < 4; i++) + addData(ip[i], i == 0); + addData(msB16(port)); + addData(lsB16(port)); + sendCommandAsync(buildCommand(COMMAND_OPEN_TCP_CONNECTION, true)); + } + + void cmdCloseTCPConnection(u8 connectionId) { + addData(connectionId); + sendCommandAsync(buildCommand(COMMAND_CLOSE_TCP_CONNECTION, true)); + } + + void cmdOpenUDPConnection(const u8* ip, u16 port) { + for (u32 i = 0; i < 4; i++) + addData(ip[i], i == 0); + addData(msB16(port)); + addData(lsB16(port)); + sendCommandAsync(buildCommand(COMMAND_OPEN_UDP_CONNECTION, true)); + } + + void cmdCloseUDPConnection(u8 connectionId) { + addData(connectionId); + sendCommandAsync(buildCommand(COMMAND_CLOSE_UDP_CONNECTION, true)); + } + void cmdDNSQuery(const u8* data, u8 size) { for (int i = 0; i < size; i++) addData(data[i], i == 0); @@ -1337,7 +1431,12 @@ class LinkMobile { return asyncCommand.direction == AsyncCommand::Direction::SENDING || (commandId != COMMAND_WAIT_FOR_TELEPHONE_CALL && commandId != COMMAND_DIAL_TELEPHONE && - commandId != COMMAND_DNS_QUERY); + commandId != COMMAND_DNS_QUERY && + commandId != COMMAND_OPEN_TCP_CONNECTION && + commandId != COMMAND_CLOSE_TCP_CONNECTION && + commandId != COMMAND_OPEN_UDP_CONNECTION && + commandId != COMMAND_CLOSE_UDP_CONNECTION && + commandId != COMMAND_TRANSFER_DATA); } void addData(u8 value, bool start = false) {