Refactoring library and example. Renaming ISP => PPP

This commit is contained in:
Rodrigo Alfonso 2024-08-19 00:41:49 -03:00
parent b7f5288d4c
commit c41d99562a
4 changed files with 355 additions and 310 deletions

View File

@ -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`).

View File

@ -3,42 +3,27 @@
// (0) Include the header
#include "../../../lib/LinkMobile.hpp"
#include "main.h"
#include <tonc.h>
#include <functional>
#include <string>
#include <vector>
#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<std::vector<std::string>> rows,
std::vector<std::vector<std::string>> 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 <typename I>
[[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<std::vector<std::string>> rows;
rows.push_back({"1", "2", "3"});
@ -377,24 +354,25 @@ std::string getNumberInput() {
std::vector<std::vector<std::string>> 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<DefaultValue> defaultValues) {
std::vector<std::vector<std::string>> 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<std::vector<std::string>> rows,
std::vector<std::vector<std::string>> altRows,
std::string defaultValue,
std::string defaultValueName,
std::vector<DefaultValue> 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 <typename I>
rc[i] = digits[(w >> j) & 0x0f];
return rc;
}
// TODO: Implement TCP/UDP sockets test functions

View File

@ -0,0 +1,54 @@
#ifndef MAIN_H
#define MAIN_H
#include "../../../lib/LinkMobile.hpp"
#include <string>
#include <vector>
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<DefaultValue> defaultValues);
std::string getInput(std::string& field,
unsigned int maxChars,
std::string inputName,
std::vector<std::vector<std::string>> rows,
std::vector<std::vector<std::string>> altRows,
std::vector<DefaultValue> 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 <typename I>
[[nodiscard]] std::string toHex(I w, size_t hex_len = sizeof(I) << 1);
#endif // MAIN_H

View File

@ -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) {