diff --git a/README.md b/README.md index 30cd957..9f0f1cf 100644 --- a/README.md +++ b/README.md @@ -448,19 +448,19 @@ Name | Return type | Description `deactivate()` | - | Deactivates the library, resetting the serial mode to GPIO. Calling `shutdown()` first is recommended, but the adapter will put itself in sleep mode after 3 seconds anyway. `shutdown()` | **bool** | Gracefully shuts down the adapter, closing all connections. After some time, the state will be changed to `SHUTDOWN`, and only then it's safe to call `deactivate()`. `call(phoneNumber)` | **bool** | Initiates a P2P connection with a `phoneNumber`. After some time, the state will be `CALL_ESTABLISHED` (or `ACTIVE_SESSION` if the connection fails or ends). In REON/libmobile the phone number can be a number assigned by the relay server, or a 12-digit IPv4 address (for example, `"127000000001"` would be `127.0.0.1`). -`callISP(password, loginId)` | **bool** | Calls the ISP number registered in the adapter configuration, or a default number if the adapter hasn't been configured. Then, performs a login operation using the provided `password` and `loginId`. After some time, the state will be `ISP_ACTIVE`. If `loginId` is empty and the adapter has been configured, it will use the one stored in the configuration. Both parameters are null-terminated strings (max `32` characters). +`callISP(password, loginId)` | **bool** | Calls the ISP number registered in the adapter configuration, or a default number if the adapter hasn't been configured. Then, performs a login operation using the provided `password` and `loginId`. After some time, the state will be `PPP_ACTIVE`. If `loginId` is empty and the adapter has been configured, it will use the one stored in the configuration. Both parameters are null-terminated strings (max `32` characters). `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`. If not, you can assume that the connection was closed. +`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 a PPP 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. `waitFor(asyncRequest)` | **bool** | Waits for `asyncRequest` to be completed. Returns `true` if the request was completed && successful, and the adapter session is still alive. Otherwise, it returns `false`. The `asyncRequest` is a pointer to a `LinkMobile::DNSQuery`, `LinkMobile::OpenConn`, `LinkMobile::CloseConn`, or `LinkMobile::DataTransfer`. -`hangUp()` | **bool** | Hangs up the current P2P or ISP call. Closes all connections. +`hangUp()` | **bool** | Hangs up the current P2P or PPP 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`). +`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::PPP_LOGIN`, `LinkMobile::State::PPP_ACTIVE`, `LinkMobile::State::SHUTDOWN_REQUESTED`, `LinkMobile::State::ENDING_SESSION`, `LinkMobile::State::WAITING_8BIT_SWITCH`, or `LinkMobile::State::SHUTDOWN`). `getRole()` | **LinkMobile::Role** | Returns the current role in the P2P connection (one of `LinkMobile::Role::NO_P2P_CONNECTION`, `LinkMobile::Role::CALLER`, or `LinkMobile::Role::RECEIVER`). `isConfigurationValid()` | **int** | Returns whether the adapter has been configured or not. Returns `1` = yes, `0` = no, `-1` = unknown (no session active). `isConnectedP2P()` | **bool** | Returns `true` if a P2P call is established (the state is `CALL_ESTABLISHED`). -`isConnectedISP()` | **bool** | Returns `true` if an ISP call is active (the state is `ISP_ACTIVE`). +`isConnectedPPP()` | **bool** | Returns `true` if a PPP session is active (the state is `PPP_ACTIVE`). `isSessionActive()` | **bool** | Returns `true` if the session is active. `canShutdown()` | **bool** | Returns `true` if there's an active session and there's no previous shutdown requests. `getDataSize()` | **LinkSPI::DataSize** | Returns the current operation mode (`LinkSPI::DataSize`). diff --git a/examples/LinkMobile_demo/src/main.cpp b/examples/LinkMobile_demo/src/main.cpp index cf9bbfb..8c2c1a4 100644 --- a/examples/LinkMobile_demo/src/main.cpp +++ b/examples/LinkMobile_demo/src/main.cpp @@ -3,42 +3,27 @@ // (0) Include the header #include "../../../lib/LinkMobile.hpp" +#include "main.h" + #include #include -#include -#include #include "../../_lib/interrupt.h" -void transfer(LinkMobile::DataTransfer& dataTransfer, std::string text); -std::string readConfiguration(); -std::string getNumberInput(); -std::string getPasswordInput(); -std::string getDomainInput(); -std::string getTextInput(std::string& field, - u32 maxChars, - std::string inputName, - std::string defaultValue, - std::string defaultValueName); -std::string getInput(std::string& field, - u32 maxChars, - std::string inputName, - std::vector> rows, - std::vector> altRows, - std::string defaultValue, - std::string defaultValueName, - std::string altName); -std::string getStateString(LinkMobile::State state); -std::string getErrorString(LinkMobile::Error error); -std::string getErrorTypeString(LinkMobile::Error::Type errorType); -std::string getResultString(LinkMobile::CommandResult cmdResult); -void log(std::string text); -std::string toStr(char* chars, int size); -void wait(u32 verticalLines); -bool didPress(u16 key, bool& pressed); -void waitForA(); +// One transfer for every N frames +constexpr static int TRANSFER_FREQUENCY = 30; + +bool isConnected = false; +LinkMobile::DataTransfer dataTransfer; +LinkMobile::DataTransfer lastCompletedTransfer; +LinkMobile::DNSQuery dnsQuery; +bool waitingDNS = false; +std::string outgoingData = ""; +u32 counter = 0; +u32 frameCounter = 0; +std::string output = ""; +bool hasError = false; +u16 keys = 0; -template -[[nodiscard]] std::string toHex(I w, size_t hex_len = sizeof(I) << 1); bool left = false, right = false, up = false, down = false; bool a = false, b = false, l = false, r = false; bool start = false, select = false; @@ -57,12 +42,10 @@ int main() { init(); start: - // Options - log("LinkMobile_demo (v7.0.0)\n\n" - "Press A to start"); + log("LinkMobile_demo (v7.0.0)\n\nPress A to start"); waitForA(); - // (1) Create a LinkWireless instance + // (1) Create a LinkMobile instance linkMobile = new LinkMobile(); // (2) Add the required interrupt service routines @@ -77,178 +60,25 @@ start: // (3) Initialize the library linkMobile->activate(); - bool isConnected = false; - LinkMobile::DataTransfer dataTransfer; - LinkMobile::DataTransfer lastCompletedTransfer; - LinkMobile::DNSQuery dnsQuery; - bool waitingDNS = false; - std::string outgoingData = ""; - u32 counter = 0; - u32 frameCounter = 0; - while (true) { - // (one transfer for every N frames) - constexpr static int TRANSFER_FREQUENCY = 30; + keys = ~REG_KEYS & KEY_ANY; + hasError = linkMobile->getError().type != LinkMobile::Error::Type::NONE; + output = "State = " + getStateString(linkMobile->getState()) + "\n"; - u16 keys = ~REG_KEYS & KEY_ANY; - - std::string output = ""; - bool shouldWaitForA = false; - output += "State = " + getStateString(linkMobile->getState()) + "\n"; - - auto error = linkMobile->getError(); - bool hasError = error.type != LinkMobile::Error::NONE; - if (hasError) { - output += getErrorString(error); - output += "\n (SELECT = stop)"; - } else if (linkMobile->getState() == LinkMobile::State::SESSION_ACTIVE) { - output += "\nL = Read configuration"; - output += "\nR = Call someone"; - output += "\nSTART = Call the ISP"; - output += "\n\n (A = ok)\n (SELECT = stop)"; - } else { - if (linkMobile->isConnectedP2P()) { - output += "\n (A = send)"; - output += "\n (L = hang up)"; - } else if (linkMobile->isConnectedISP()) { - output += "\n (A = DNS query)"; - output += "\n (L = hang up)"; - } - output += "\n (SELECT = stop)"; - } + printMenu(); if (linkMobile->isConnectedP2P()) { - if (!isConnected) { - isConnected = true; - outgoingData = linkMobile->getRole() == LinkMobile::Role::CALLER - ? "caller!!!" - : "receiver!!!"; - transfer(dataTransfer, outgoingData); - } - if (dataTransfer.completed) { - if (dataTransfer.size > 0) - lastCompletedTransfer = dataTransfer; - dataTransfer.completed = false; - } - - if (keys & KEY_A) { - counter++; - outgoingData = - (linkMobile->getRole() == LinkMobile::Role::CALLER ? "caller: " - : "receiver: ") + - std::to_string(counter); - } - - frameCounter++; - if (frameCounter >= TRANSFER_FREQUENCY) { - frameCounter = 0; - transfer(dataTransfer, outgoingData); - } - - if (lastCompletedTransfer.completed) { - char received[LINK_MOBILE_MAX_USER_TRANSFER_LENGTH]; - for (u32 i = 0; i < lastCompletedTransfer.size; i++) - received[i] = lastCompletedTransfer.data[i]; - received[lastCompletedTransfer.size] = '\0'; - output += "\n\n>> " + std::string(outgoingData); - output += "\n<< " + std::string(received); - } - } else if (linkMobile->isConnectedISP()) { - if (!isConnected) - isConnected = true; - - if (waitingDNS && dnsQuery.completed) { - waitingDNS = false; - log("DNS Response:\n " + std::to_string(dnsQuery.ipv4[0]) + "." + - std::to_string(dnsQuery.ipv4[1]) + "." + - std::to_string(dnsQuery.ipv4[2]) + "." + - std::to_string(dnsQuery.ipv4[3]) + "\n\n" + - (dnsQuery.success ? "OK!\nLet's connect to it on TCP 80!" - : "DNS query failed!")); - waitForA(); - - // (9) Open connections - log("Connecting..."); - LinkMobile::OpenConn openConn; - linkMobile->openConnection(dnsQuery.ipv4, 80, - LinkMobile::ConnectionType::TCP, &openConn); - - while (linkMobile->isConnectedISP() && !openConn.completed) - VBlankIntrWait(); - if (!linkMobile->isConnectedISP()) - continue; - - if (openConn.success) { - LinkMobile::DataTransfer http; - std::string request = std::string("GET / HTTP/1.1\r\nHost: ") + - selectedDomain + "\r\n\r\n"; - for (u32 i = 0; i < request.size(); i++) - http.data[i] = request[i]; - http.size = request.size(); - linkMobile->transfer(http, &http, openConn.connectionId); - log("Connected! (" + std::to_string(openConn.connectionId) + - ") Requesting /"); - - while (linkMobile->isConnectedISP() && !http.completed) - VBlankIntrWait(); - if (!linkMobile->isConnectedISP()) - continue; - - if (http.success) { - u32 retry = 1; - while (http.size == 0) { - log("Re-transfering... " + std::to_string(retry)); - LinkMobile::DataTransfer retryy = - LinkMobile::DataTransfer{.data = {}, .size = 0}; - for (u32 i = 0; i < request.size(); i++) - retryy.data[i] = request[i]; - retryy.size = request.size(); - linkMobile->transfer(retryy, &http, openConn.connectionId); - while (linkMobile->isConnectedISP() && !http.completed) - VBlankIntrWait(); - if (!linkMobile->isConnectedISP()) - break; - if (!http.success) - break; - retry++; - } - if (!linkMobile->isConnectedISP()) - continue; - if (!http.success) { - log("HTTP failed!"); - waitForA(); - continue; - } - - log("Internet on GBA! yay\n\n" + std::string((char*)http.data)); - waitForA(); - } else { - log("HTTP request failed!"); - waitForA(); - } - } else { - log("Connection to \"" + selectedDomain + "\" failed!"); - waitForA(); - } - } - - output += waitingDNS ? "\n\nWaiting DNS..." : ""; - } else { - if (isConnected) { - isConnected = false; - dataTransfer = {}; - lastCompletedTransfer = {}; - dnsQuery = {}; - waitingDNS = false; - counter = 0; - frameCounter = 0; - outgoingData = ""; - } + handleP2P(); + } else if (linkMobile->isConnectedPPP()) { + handlePPP(); + } else if (isConnected) { + cleanup(); } // SELECT = stop if (didPress(KEY_SELECT, select)) { bool didShutdown = linkMobile->getState() == LinkMobile::State::SHUTDOWN; + if (hasError || didShutdown) { linkMobile->deactivate(); interrupt_disable(INTR_VBLANK); @@ -264,7 +94,7 @@ start: goto start; } else if (linkMobile->canShutdown()) { - // (11) Turn off the adapter + // (12) Turn off the adapter linkMobile->shutdown(); } } @@ -273,8 +103,8 @@ start: case LinkMobile::State::SESSION_ACTIVE: { // L = Read Configuration if (didPress(KEY_L, l)) { - output = readConfiguration(); - shouldWaitForA = true; + readConfiguration(); + waitForA(); } // R = Call someone @@ -304,7 +134,7 @@ start: } break; } - case LinkMobile::State::ISP_ACTIVE: { + case LinkMobile::State::PPP_ACTIVE: { // A = DNS query if (didPress(KEY_A, a) && !waitingDNS) { std::string domain = getDomainInput(); @@ -327,29 +157,140 @@ start: VBlankIntrWait(); log(output); - if (shouldWaitForA) - waitForA(); } return 0; } -void transfer(LinkMobile::DataTransfer& dataTransfer, std::string text) { - // (5) Send/receive data - for (u32 i = 0; i < text.size(); i++) - dataTransfer.data[i] = text[i]; - dataTransfer.data[text.size()] = '\0'; - dataTransfer.size = text.size() + 1; - linkMobile->transfer(dataTransfer, &dataTransfer); +void handleP2P() { + if (!isConnected) { + // First transfer + isConnected = true; + outgoingData = linkMobile->getRole() == LinkMobile::Role::CALLER + ? "caller!!!" + : "receiver!!!"; + transfer(dataTransfer, outgoingData, 0xff, true); + } + + if (dataTransfer.completed) { + // Save a copy of last received data + if (dataTransfer.size > 0) + lastCompletedTransfer = dataTransfer; + dataTransfer.completed = false; + } + + if (keys & KEY_A) { + // `A` increments the counter + counter++; + outgoingData = + (linkMobile->getRole() == LinkMobile::Role::CALLER ? "caller: " + : "receiver: ") + + std::to_string(counter); + } + + frameCounter++; + if (frameCounter >= TRANSFER_FREQUENCY) { + // Transfer every N frames + frameCounter = 0; + transfer(dataTransfer, outgoingData, 0xff, true); + } + + if (lastCompletedTransfer.completed) { + // Show received data + output += "\n\n>> " + std::string(outgoingData); + output += "\n<< " + std::string((char*)lastCompletedTransfer.data); + // (LinkMobile zero-pads an extra byte, so this is safe) + } } -std::string readConfiguration() { +void handlePPP() { + if (!isConnected) + isConnected = true; + + if (waitingDNS && dnsQuery.completed) { + waitingDNS = false; + log("DNS Response:\n " + std::to_string(dnsQuery.ipv4[0]) + "." + + std::to_string(dnsQuery.ipv4[1]) + "." + + std::to_string(dnsQuery.ipv4[2]) + "." + + std::to_string(dnsQuery.ipv4[3]) + "\n\n" + + (dnsQuery.success ? "OK!\nLet's connect to it on TCP 80!" + : "DNS query failed!")); + waitForA(); + if (!dnsQuery.success) + return; + + // (9) Open connections + log("Connecting..."); + LinkMobile::OpenConn openConn; + linkMobile->openConnection(dnsQuery.ipv4, 80, + LinkMobile::ConnectionType::TCP, &openConn); + if (!linkMobile->waitFor(&openConn)) { + log("Connection failed!"); + waitForA(); + return; + } + + // HTTP request + + LinkMobile::DataTransfer http; + std::string request = + std::string("GET / HTTP/1.1\r\nHost: ") + selectedDomain + "\r\n\r\n"; + std::string output = ""; + u32 chunk = 1; + u32 retry = 1; + do { + log("Downloading... (" + std::to_string(chunk) + ", " + + std::to_string(retry) + ")\n (hold START = close conn)\n\n" + output); + + if (didPress(KEY_START, start)) { + log("Closing..."); + LinkMobile::CloseConn closeConn; + linkMobile->closeConnection( + openConn.connectionId, LinkMobile::ConnectionType::TCP, &closeConn); + linkMobile->waitFor(&closeConn); + return; + } + + transfer(http, request, openConn.connectionId); + if (!linkMobile->waitFor(&http)) { + log("Connection closed:\n " + std::to_string(chunk) + " packets!\n\n" + + output); + waitForA(); + return; + } + + if (http.size > 0) { + chunk++; + output += std::string((char*)http.data); + // (LinkMobile zero-pads an extra byte, so this is safe) + } + + http = {}; + request = ""; + retry++; + } while (true); + } + + output += waitingDNS ? "\n\nWaiting DNS..." : ""; +} + +void cleanup() { + isConnected = false; + dataTransfer = {}; + lastCompletedTransfer = {}; + dnsQuery = {}; + waitingDNS = false; + counter = 0; + frameCounter = 0; + outgoingData = ""; +} + +void readConfiguration() { LinkMobile::ConfigurationData data; if (!linkMobile->readConfiguration(data)) - return "Read failed :("; + log("Read failed :("); - return ( - "Magic:\n " + toStr(data.magic, 2) + ", $" + + log("Magic:\n " + toStr(data.magic, 2) + ", $" + toHex(data.registrationState) + "\nPrimary DNS:\n " + std::to_string(data.primaryDNS[0]) + "." + std::to_string(data.primaryDNS[1]) + "." + @@ -368,6 +309,42 @@ std::string readConfiguration() { : "SIO8")); } +void printMenu() { + auto error = linkMobile->getError(); + + if (hasError) { + output += getErrorString(error); + output += "\n (SELECT = stop)"; + } else if (linkMobile->getState() == LinkMobile::State::SESSION_ACTIVE) { + output += "\nL = Read configuration"; + output += "\nR = Call someone"; + output += "\nSTART = Call the ISP"; + output += "\n\n (A = ok)\n (SELECT = stop)"; + } else { + if (linkMobile->isConnectedP2P()) { + output += "\n (A = send)"; + output += "\n (L = hang up)"; + } else if (linkMobile->isConnectedPPP()) { + output += "\n (A = DNS query)"; + output += "\n (L = hang up)"; + } + output += "\n (SELECT = stop)"; + } +} + +void transfer(LinkMobile::DataTransfer& dataTransfer, + std::string text, + u8 connectionId, + bool addNullTerminator) { + // (5) Send/receive data + for (u32 i = 0; i < text.size(); i++) + dataTransfer.data[i] = text[i]; + if (addNullTerminator) + dataTransfer.data[text.size()] = '\0'; + dataTransfer.size = text.size() + addNullTerminator; + linkMobile->transfer(dataTransfer, &dataTransfer, connectionId); +} + std::string getNumberInput() { std::vector> rows; rows.push_back({"1", "2", "3"}); @@ -377,24 +354,25 @@ std::string getNumberInput() { std::vector> altRows; return getInput(selectedNumber, LINK_MOBILE_MAX_PHONE_NUMBER_LENGTH, - "a number", rows, altRows, "127000000001", "localhost", ""); + "a number", rows, altRows, {{"localhost", "127000000001"}}, + ""); } std::string getPasswordInput() { return getTextInput(selectedPassword, LINK_MOBILE_MAX_PASSWORD_LENGTH, - "your password", "pass123", "pass123"); + "your password", {{"pass123", "pass123"}}); } std::string getDomainInput() { - return getTextInput(selectedDomain, LINK_MOBILE_MAX_DOMAIN_NAME_LENGTH, - "a domain name", "something.com", "something.com"); + return getTextInput( + selectedDomain, LINK_MOBILE_MAX_DOMAIN_NAME_LENGTH, "a domain name", + {{"something.com", "something.com"}, {"localhost", "localhost"}}); } std::string getTextInput(std::string& field, u32 maxChars, std::string inputName, - std::string defaultValue, - std::string defaultValueName) { + std::vector defaultValues) { std::vector> rows; rows.push_back({"a", "b", "c", "d", "e"}); rows.push_back({"f", "g", "h", "i", "j"}); @@ -415,8 +393,8 @@ std::string getTextInput(std::string& field, altRows.push_back({"5", "6", "7", "8", "9"}); altRows.push_back({"0", ".", "#", "/", "?"}); - return getInput(field, maxChars, inputName, rows, altRows, defaultValue, - defaultValueName, "caps lock"); + return getInput(field, maxChars, inputName, rows, altRows, defaultValues, + "caps lock"); } std::string getInput(std::string& field, @@ -424,13 +402,13 @@ std::string getInput(std::string& field, std::string inputName, std::vector> rows, std::vector> altRows, - std::string defaultValue, - std::string defaultValueName, + std::vector defaultValues, std::string altName) { VBlankIntrWait(); int selectedX = 0; int selectedY = 0; + int selectedDefaultValue = 0; bool altActive = false; while (true) { @@ -469,8 +447,10 @@ std::string getInput(std::string& field, if (field.size() < maxChars) field += renderRows[selectedY][selectedX]; } - if (didPress(KEY_SELECT, select)) - field = defaultValue; + if (didPress(KEY_SELECT, select)) { + field = defaultValues[selectedDefaultValue].value; + selectedDefaultValue = (selectedDefaultValue + 1) % defaultValues.size(); + } if (didPress(KEY_START, start)) return field; if (altName != "" && didPress(KEY_L, l)) @@ -486,7 +466,8 @@ std::string getInput(std::string& field, output += "\n"; } - output += "\n (B = back)\n (A = select)\n (SELECT = " + defaultValueName + + output += "\n (B = back)\n (A = select)\n (SELECT = " + + defaultValues[selectedDefaultValue].name + ")\n (START = confirm)"; if (altName != "") @@ -525,10 +506,10 @@ std::string getStateString(LinkMobile::State state) { return "ISP_CALL_REQUESTED"; case LinkMobile::State::ISP_CALLING: return "ISP_CALLING"; - case LinkMobile::State::ISP_LOGIN: - return "ISP_LOGIN"; - case LinkMobile::State::ISP_ACTIVE: - return "ISP_ACTIVE"; + case LinkMobile::State::PPP_LOGIN: + return "PPP_LOGIN"; + case LinkMobile::State::PPP_ACTIVE: + return "PPP_ACTIVE"; case LinkMobile::State::SHUTDOWN_REQUESTED: return "SHUTDOWN_REQUESTED"; case LinkMobile::State::ENDING_SESSION: @@ -558,8 +539,8 @@ std::string getErrorTypeString(LinkMobile::Error::Type errorType) { switch (errorType) { case LinkMobile::Error::Type::ADAPTER_NOT_CONNECTED: return "ADAPTER_NOT_CONNECTED"; - case LinkMobile::Error::Type::ISP_LOGIN_FAILED: - return "ISP_LOGIN_FAILED"; + case LinkMobile::Error::Type::PPP_LOGIN_FAILED: + return "PPP_LOGIN_FAILED"; case LinkMobile::Error::Type::COMMAND_FAILED: return "COMMAND_FAILED"; case LinkMobile::Error::Type::WEIRD_RESPONSE: @@ -604,6 +585,8 @@ std::string lastLoggedText = ""; void log(std::string text) { if (text == lastLoggedText) return; + if (linkMobile != nullptr) + VBlankIntrWait(); tte_erase_screen(); tte_write("#{P:0,0}"); tte_write(text.c_str()); @@ -655,5 +638,3 @@ template rc[i] = digits[(w >> j) & 0x0f]; return rc; } - -// TODO: Implement TCP/UDP sockets test functions diff --git a/examples/LinkMobile_demo/src/main.h b/examples/LinkMobile_demo/src/main.h new file mode 100644 index 0000000..911515c --- /dev/null +++ b/examples/LinkMobile_demo/src/main.h @@ -0,0 +1,54 @@ +#ifndef MAIN_H +#define MAIN_H + +#include "../../../lib/LinkMobile.hpp" + +#include +#include + +struct DefaultValue { + std::string name; + std::string value; +}; + +void handleP2P(); +void handlePPP(); +void cleanup(); +void readConfiguration(); + +void printMenu(); +void transfer(LinkMobile::DataTransfer& dataTransfer, + std::string text, + unsigned char connectionId, + bool addNullTerminator = false); + +std::string getNumberInput(); +std::string getPasswordInput(); +std::string getDomainInput(); +std::string getTextInput(std::string& field, + unsigned int maxChars, + std::string inputName, + std::vector defaultValues); +std::string getInput(std::string& field, + unsigned int maxChars, + std::string inputName, + std::vector> rows, + std::vector> altRows, + std::vector defaultValues, + std::string altName); + +std::string getStateString(LinkMobile::State state); +std::string getErrorString(LinkMobile::Error error); +std::string getErrorTypeString(LinkMobile::Error::Type errorType); +std::string getResultString(LinkMobile::CommandResult cmdResult); + +void log(std::string text); +std::string toStr(char* chars, int size); +void wait(unsigned int verticalLines); +bool didPress(unsigned short key, bool& pressed); +void waitForA(); + +template +[[nodiscard]] std::string toHex(I w, size_t hex_len = sizeof(I) << 1); + +#endif // MAIN_H diff --git a/lib/LinkMobile.hpp b/lib/LinkMobile.hpp index d6343a8..dce297d 100644 --- a/lib/LinkMobile.hpp +++ b/lib/LinkMobile.hpp @@ -38,15 +38,16 @@ // - 9) Open connections: // auto type = LinkMobile::ConnectionType::TCP; // LinkMobile::OpenConn openConn; -// linkMobile->openConnection(dnsQuery.ipv4, type, openConn); +// linkMobile->openConnection(dnsQuery.ipv4, connType, &openConn); // // (do something until `openConn.completed` is `true`) // // (use `openConn.connectionId` as last argument of `transfer(...)`) // - 10) Close connections: -// auto type = LinkMobile::ConnectionType::TCP; // LinkMobile::CloseConn closeConn; -// linkMobile->closeConnection(openConn.connectionId, type, closeConn); -// // (do something until `openConn.completed` is `true`) -// - 11) Turn off the adapter: +// linkMobile->closeConnection(openConn.connectionId, type, &closeConn); +// // (do something until `closeConn.completed` is `true`) +// - 11) Synchronously wait for an action to be completed: +// linkMobile->waitFor(&dnsQuery); +// - 12) Turn off the adapter: // linkMobile->shutdown(); // -------------------------------------------------------------------------- // (*) libtonc's interrupt handler sometimes ignores interrupts due to a bug. @@ -149,25 +150,25 @@ class LinkMobile { public: enum State { - NEEDS_RESET, - PINGING, - WAITING_TO_START, - STARTING_SESSION, - ACTIVATING_SIO32, - WAITING_32BIT_SWITCH, - READING_CONFIGURATION, - SESSION_ACTIVE, - CALL_REQUESTED, - CALLING, - CALL_ESTABLISHED, - ISP_CALL_REQUESTED, - ISP_CALLING, - ISP_LOGIN, - ISP_ACTIVE, - SHUTDOWN_REQUESTED, - ENDING_SESSION, - WAITING_8BIT_SWITCH, - SHUTDOWN + NEEDS_RESET = 0, + PINGING = 1, + WAITING_TO_START = 2, + STARTING_SESSION = 3, + ACTIVATING_SIO32 = 4, + WAITING_32BIT_SWITCH = 5, + READING_CONFIGURATION = 6, + SESSION_ACTIVE = 7, + CALL_REQUESTED = 8, + CALLING = 9, + CALL_ESTABLISHED = 10, + ISP_CALL_REQUESTED = 11, + ISP_CALLING = 12, + PPP_LOGIN = 13, + PPP_ACTIVE = 14, + SHUTDOWN_REQUESTED = 15, + ENDING_SESSION = 16, + WAITING_8BIT_SWITCH = 17, + SHUTDOWN = 18 }; enum Role { NO_P2P_CONNECTION, CALLER, RECEIVER }; @@ -200,27 +201,20 @@ class LinkMobile { }; struct DNSQuery : public AsyncRequest { - volatile bool completed = false; - bool success = false; u8 ipv4[4] = {}; }; enum ConnectionType { TCP, UDP }; struct OpenConn : public AsyncRequest { - volatile bool completed = false; - bool success = false; u8 connectionId = 0; }; struct CloseConn : public AsyncRequest { - volatile bool completed = false; bool success = false; }; struct DataTransfer : public AsyncRequest { - volatile bool completed = false; - bool success = false; u8 data[LINK_MOBILE_MAX_USER_TRANSFER_LENGTH] = {}; u8 size = 0; }; @@ -242,7 +236,7 @@ class LinkMobile { enum Type { NONE, ADAPTER_NOT_CONNECTED, - ISP_LOGIN_FAILED, + PPP_LOGIN_FAILED, COMMAND_FAILED, WEIRD_RESPONSE, TIMEOUT, @@ -349,7 +343,7 @@ class LinkMobile { * @brief Calls the ISP number registered in the adapter configuration, or a * default number if the adapter hasn't been configured. Then, performs a * login operation using the provided REON `password` and `loginId`. After - * some time, the state will be `ISP_ACTIVE`. If `loginId` is empty and the + * some time, the state will be `PPP_ACTIVE`. If `loginId` is empty and the * adapter has been configured, it will use the one stored in the * configuration. * @param password The password, as a null-terminated string (max `32` @@ -363,7 +357,7 @@ class LinkMobile { if (state != SESSION_ACTIVE || userRequests.isFull()) return false; - auto request = UserRequest{.type = UserRequest::Type::ISP_LOGIN}; + auto request = UserRequest{.type = UserRequest::Type::PPP_LOGIN}; copyString(request.password, password, LINK_MOBILE_MAX_PASSWORD_LENGTH); if (std::strlen(loginId) > 0) @@ -387,11 +381,14 @@ class LinkMobile { * `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. * \warning Non-blocking. Returns `true` immediately, or `false` if there's no - * active ISP session or available request slots. + * active PPP session or available request slots. */ bool dnsQuery(const char* domainName, DNSQuery* result) { - if (state != ISP_ACTIVE || userRequests.isFull()) + if (state != PPP_ACTIVE || userRequests.isFull()) { + result->success = false; + result->completed = true; return false; + } result->completed = false; result->success = false; @@ -425,14 +422,17 @@ class LinkMobile { * 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. + * active PPP session, no available request slots. */ bool openConnection(const u8* ip, u16 port, ConnectionType type, OpenConn* result) { - if (state != ISP_ACTIVE || userRequests.isFull()) + if (state != PPP_ACTIVE || userRequests.isFull()) { + result->success = false; + result->completed = true; return false; + } result->completed = false; result->success = false; @@ -458,13 +458,16 @@ class LinkMobile { * `completed` field will be `true`. If the connection was closed correctly, * the `success` field will be `true`. * \warning Non-blocking. Returns `true` immediately, or `false` if there's no - * active ISP session, no available request slots. + * active PPP session, no available request slots. */ bool closeConnection(u8 connectionId, ConnectionType type, CloseConn* result) { - if (state != ISP_ACTIVE || userRequests.isFull()) + if (state != PPP_ACTIVE || userRequests.isFull()) { + result->success = false; + result->completed = true; return false; + } result->completed = false; result->success = false; @@ -482,7 +485,7 @@ class LinkMobile { /** * @brief Requests a data transfer 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 + * open TCP/UDP connection if a PPP session is active. In the case of a * TCP/UDP connection, the `connectionId` must be provided. * @param dataToSend The data to send, up to 254 bytes. * @param result A pointer to a `LinkMobile::DataTransfer` struct that @@ -496,9 +499,12 @@ class LinkMobile { bool transfer(DataTransfer dataToSend, DataTransfer* result, u8 connectionId = 0xff) { - if ((state != CALL_ESTABLISHED && state != ISP_ACTIVE) || - userRequests.isFull()) + if ((state != CALL_ESTABLISHED && state != PPP_ACTIVE) || + userRequests.isFull()) { + result->success = false; + result->completed = true; return false; + } result->completed = false; result->success = false; @@ -521,20 +527,21 @@ class LinkMobile { * `LinkMobile::OpenConn`, `LinkMobile::CloseConn`, or * `LinkMobile::DataTransfer`. */ - bool waitForCompletion(AsyncRequest* asyncRequest) { + bool waitFor(AsyncRequest* asyncRequest) { while (isSessionActive() && !asyncRequest->completed) Link::_IntrWait(1, Link::_IRQ_SERIAL | Link::_IRQ_VBLANK); + return isSessionActive() && asyncRequest->completed && asyncRequest->success; } /** - * @brief Hangs up the current P2P or ISP call. Closes all connections. + * @brief Hangs up the current P2P or PPP call. Closes all connections. * \warning Non-blocking. Returns `true` immediately, or `false` if there's no * active call or available request slots. */ bool hangUp() { - if ((state != CALL_ESTABLISHED && state != ISP_ACTIVE) || + if ((state != CALL_ESTABLISHED && state != PPP_ACTIVE) || userRequests.isFull()) return false; @@ -588,10 +595,10 @@ class LinkMobile { [[nodiscard]] bool isConnectedP2P() { return state == CALL_ESTABLISHED; } /** - * @brief Returns `true` if an ISP call is active (the state is - * `ISP_ACTIVE`). + * @brief Returns `true` if a PPP session is active (the state is + * `PPP_ACTIVE`). */ - [[nodiscard]] bool isConnectedISP() { return state == ISP_ACTIVE; } + [[nodiscard]] bool isConnectedPPP() { return state == PPP_ACTIVE; } /** * @brief Returns `true` if the session is active. @@ -716,7 +723,7 @@ class LinkMobile { struct UserRequest { enum Type { CALL, - ISP_LOGIN, + PPP_LOGIN, DNS_QUERY, OPEN_CONNECTION, CLOSE_CONNECTION, @@ -933,7 +940,7 @@ class LinkMobile { } break; } - case UserRequest::Type::ISP_LOGIN: { + case UserRequest::Type::PPP_LOGIN: { if (state != SESSION_ACTIVE && state != ISP_CALL_REQUESTED && state != ISP_CALLING) { popRequest(); @@ -951,7 +958,7 @@ class LinkMobile { break; } case UserRequest::Type::DNS_QUERY: { - if (state != ISP_ACTIVE) { + if (state != PPP_ACTIVE) { popRequest(); return; } @@ -963,7 +970,7 @@ class LinkMobile { break; } case UserRequest::Type::OPEN_CONNECTION: { - if (state != ISP_ACTIVE) { + if (state != PPP_ACTIVE) { popRequest(); return; } @@ -978,7 +985,7 @@ class LinkMobile { break; } case UserRequest::Type::CLOSE_CONNECTION: { - if (state != ISP_ACTIVE) { + if (state != PPP_ACTIVE) { popRequest(); return; } @@ -993,7 +1000,7 @@ class LinkMobile { break; } case UserRequest::Type::TRANSFER: { - if (state != CALL_ESTABLISHED && state != ISP_ACTIVE) { + if (state != CALL_ESTABLISHED && state != PPP_ACTIVE) { popRequest(); return; } @@ -1006,7 +1013,7 @@ class LinkMobile { break; } case UserRequest::Type::HANG_UP: { - if (state != CALL_ESTABLISHED && state != ISP_ACTIVE) { + if (state != CALL_ESTABLISHED && state != PPP_ACTIVE) { popRequest(); return; } @@ -1207,11 +1214,11 @@ class LinkMobile { if (userRequests.isEmpty()) return abort(Error::Type::WTF); auto request = userRequests.peekRef(); - if (request->type != UserRequest::ISP_LOGIN) + if (request->type != UserRequest::PPP_LOGIN) return abort(Error::Type::WTF); if (asyncCommand.result == CommandResult::SUCCESS) { - setState(ISP_LOGIN); + setState(PPP_LOGIN); cmdISPLogin(request->loginId, request->password); } else { // (ISP call failed) @@ -1221,16 +1228,16 @@ class LinkMobile { break; } - case ISP_LOGIN: { + case PPP_LOGIN: { if (!asyncCommand.respondsTo(COMMAND_ISP_LOGIN)) return; if (asyncCommand.result != CommandResult::SUCCESS) - return abort(Error::Type::ISP_LOGIN_FAILED); + return abort(Error::Type::PPP_LOGIN_FAILED); - setState(ISP_ACTIVE); + setState(PPP_ACTIVE); break; } - case ISP_ACTIVE: { + case PPP_ACTIVE: { if (asyncCommand.respondsTo(COMMAND_HANG_UP_TELEPHONE)) { setState(SESSION_ACTIVE); return; @@ -1477,13 +1484,16 @@ class LinkMobile { u8 commandId = asyncCommand.relatedCommandId(); return asyncCommand.direction == AsyncCommand::Direction::SENDING || (commandId != COMMAND_WAIT_FOR_TELEPHONE_CALL && - commandId != COMMAND_DIAL_TELEPHONE && - 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); + commandId != COMMAND_DIAL_TELEPHONE && !isAsyncRequest(commandId)); + } + + bool isAsyncRequest(u8 commandId) { + return 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) {