Implementing DNS query

This commit is contained in:
Rodrigo Alfonso 2024-08-18 07:55:25 -03:00
parent f74504bb3e
commit f994270ee0
5 changed files with 240 additions and 76 deletions

View File

@ -437,6 +437,8 @@ You can also change these compile-time constants:
- On fatal errors, the library will transition to a `NEEDS_RESET` state. In that case, you can call `getError()` to know more details on what happened, and then `activate()` to restart.
- When calling `deactivate()`, the adapter automatically turns itself off after `3` seconds of inactivity. However, to gracefully turn it off, it's recommended to call `shutdown()` first, wait until the state is `SHUTDOWN`, and then `deactivate()`.
// TODO: Add ISP methods
Name | Return type | Description
--- | --- | ---
`isActive()` | **bool** | Returns whether the library is active or not.
@ -444,7 +446,9 @@ 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`).
`transfer(dataToSend, receivedData)` | **bool** | Requests a data transfer (up to `254` bytes) within a P2P connection and responds the received data. The `receivedData` is a pointer to a `LinkWireless::DataTransfer` struct that will be filled with data. It can point to `dataToSend`. When the transfer is completed, the `completed` field will be `true`.
`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).
`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.
`transfer(dataToSend, result)` | **bool** | Requests a data transfer (up to `254` bytes) within a P2P connection and responds the received data. 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 transfer is completed, the `completed` field will be `true`.
`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`).

View File

@ -13,6 +13,12 @@ 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,
@ -38,6 +44,7 @@ bool a = false, b = false, l = false, r = false;
bool start = false, select = false;
std::string selectedNumber = "";
std::string selectedPassword = "";
std::string selectedDomain = "";
LinkMobile* linkMobile = NULL;
@ -73,6 +80,8 @@ start:
bool isConnected = false;
LinkMobile::DataTransfer dataTransfer;
LinkMobile::DataTransfer lastCompletedTransfer;
LinkMobile::DNSQuery dnsQuery;
bool waitingDNS = false;
std::string outgoingData = "";
u32 counter = 0;
u32 frameCounter = 0;
@ -83,7 +92,6 @@ start:
u16 keys = ~REG_KEYS & KEY_ANY;
// Menu
std::string output = "";
bool shouldWaitForA = false;
output += "State = " + getStateString(linkMobile->getState()) + "\n";
@ -97,12 +105,13 @@ start:
output += "\nL = Read Configuration";
output += "\nR = Call someone";
output += "\nSTART = Call the ISP";
output += "\n (A = ok)\n (SELECT = stop)";
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 (LEFT = DNS query)";
output += "\n (L = hang up)";
}
output += "\n (SELECT = stop)";
@ -116,25 +125,26 @@ start:
: "receiver!!!";
transfer(dataTransfer, outgoingData);
}
if (dataTransfer.completed) {
if (dataTransfer.size > 0)
lastCompletedTransfer = dataTransfer;
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);
}
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++)
@ -143,11 +153,28 @@ start:
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 ? "Success!" : "Failure!"));
waitForA();
}
output += waitingDNS ? "\n\nWaiting DNS..." : "";
} else {
if (isConnected) {
isConnected = false;
dataTransfer = {};
lastCompletedTransfer = {};
dnsQuery = {};
waitingDNS = false;
counter = 0;
frameCounter = 0;
outgoingData = "";
@ -198,13 +225,30 @@ start:
if (didPress(KEY_START, start)) {
std::string password = getPasswordInput();
if (password != "") {
// (7) Connect to the internet
linkMobile->callISP(password.c_str());
}
}
break;
}
case LinkMobile::State::CALL_ESTABLISHED:
case LinkMobile::State::CALL_ESTABLISHED: {
// L = hang up
if (didPress(KEY_L, l)) {
// (6) Hang up
linkMobile->hangUp();
}
break;
}
case LinkMobile::State::ISP_ACTIVE: {
// LEFT = DNS query
if (didPress(KEY_LEFT, left)) {
std::string domain = getDomainInput();
if (domain != "") {
// (8) Run DNS queries
linkMobile->dnsQuery(domain.c_str(), &dnsQuery);
waitingDNS = true;
}
}
// L = hang up
if (didPress(KEY_L, l)) {
// (6) Hang up
@ -272,6 +316,20 @@ std::string getNumberInput() {
}
std::string getPasswordInput() {
return getTextInput(selectedPassword, LINK_MOBILE_MAX_PASSWORD_LENGTH,
"your password", "pass123", "pass123");
}
std::string getDomainInput() {
return getTextInput(selectedDomain, LINK_MOBILE_MAX_DOMAIN_NAME_LENGTH,
"a domain name", "something.com", "something.com");
}
std::string getTextInput(std::string& field,
u32 maxChars,
std::string inputName,
std::string defaultValue,
std::string defaultValueName) {
std::vector<std::vector<std::string>> rows;
rows.push_back({"a", "b", "c", "d", "e"});
rows.push_back({"f", "g", "h", "i", "j"});
@ -280,7 +338,7 @@ std::string getPasswordInput() {
rows.push_back({"u", "v", "w", "x", "y"});
rows.push_back({"z", "1", "2", "3", "4"});
rows.push_back({"5", "6", "7", "8", "9"});
rows.push_back({"0", "!", "?", "#", "*"});
rows.push_back({"0", ".", "#", "/", "?"});
std::vector<std::vector<std::string>> altRows;
altRows.push_back({"A", "B", "C", "D", "E"});
@ -290,11 +348,10 @@ std::string getPasswordInput() {
altRows.push_back({"U", "V", "W", "X", "Y"});
altRows.push_back({"Z", "1", "2", "3", "4"});
altRows.push_back({"5", "6", "7", "8", "9"});
altRows.push_back({"0", "!", "?", "#", "*"});
altRows.push_back({"0", ".", "#", "/", "?"});
return getInput(selectedPassword, LINK_MOBILE_MAX_PASSWORD_LENGTH,
"your password", rows, altRows, "pass123", "pass123",
"caps lock");
return getInput(field, maxChars, inputName, rows, altRows, defaultValue,
defaultValueName, "caps lock");
}
std::string getInput(std::string& field,
@ -429,7 +486,7 @@ std::string getErrorString(LinkMobile::Error error) {
toHex(error.cmdId) +
"\n CmdResult: " + getResultString(error.cmdResult) +
"\n CmdErrorCode: " + std::to_string(error.cmdErrorCode) +
"\n ReqType: " + std::to_string(error.reqType) + "\n\n";
"\n ReqType: " + std::to_string(error.reqType) + "\n";
}
std::string getErrorTypeString(LinkMobile::Error::Type errorType) {

View File

@ -34,7 +34,7 @@
# define EXPORT_API __declspec(dllexport)
# else
# define EXPORT_API __attribute__((visibility("default")))
// TODO: Is this one below needed in MinGW?
// Is this one below needed in MinGW?
//# define EXPORT_API __attribute__((dllexport))
# endif
#endif

View File

@ -28,9 +28,15 @@
// // (use `dataTransfer` as the received data)
// - 6) Hang up:
// linkMobile->hangUp();
// - 7) Connect to the internet:
// linkMobile->callISP("REON password");
// - 8) Run DNS queries:
// LinkMobile::DNSQuery dnsQuery;
// linkMobile->dnsQuery("something.com", &dnsQuery);
// // (do something until `dnsQuery.completed` is `true`)
// // (use `dnsQuery.success` and `dnsQuery.ipv4`)
// - 7) Turn off the adapter:
// linkMobile->shutdown();
// TODO: DOCUMENT ISP METHODS
// --------------------------------------------------------------------------
// (*) libtonc's interrupt handler sometimes ignores interrupts due to a bug.
// That causes packet loss. You REALLY want to use libugba's instead.
@ -58,6 +64,7 @@ static volatile char LINK_MOBILE_VERSION[] = "LinkMobile/v7.0.0";
#define LINK_MOBILE_MAX_PHONE_NUMBER_LENGTH 32
#define LINK_MOBILE_MAX_LOGIN_ID_LENGTH 32
#define LINK_MOBILE_MAX_PASSWORD_LENGTH 32
#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)
@ -214,6 +221,12 @@ class LinkMobile {
bool completed = false;
};
struct DNSQuery {
u8 ipv4[4] = {};
bool completed = false;
bool success = false;
};
/**
* @brief Constructs a new LinkMobile object.
* @param timeout Number of *frames* without completing a request to reset a
@ -304,9 +317,9 @@ 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 `password` and `loginId`. If `loginId`
* is empty and the adapter has been configured, it will use the one stored in
* the configuration.
* 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.
* @param password The password, as a null-terminated string (max `32`
* characters).
* @param loginId The login ID, as a null-terminated string (max `32`
@ -333,24 +346,58 @@ class LinkMobile {
return true;
}
/**
* @brief Looks up the IPv4 address for a domain name.
* @param domain A null-terminated string for the domain name (max `253`
* characters).
* @param result 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.
* \warning Non-blocking. Returns `true` immediately, or `false` if there's no
* active ISP session or available request slots.
*/
bool dnsQuery(const char* domainName, DNSQuery* result) {
if (state != ISP_ACTIVE || userRequests.isFull())
return false;
result->completed = false;
result->success = false;
u32 size = std::strlen(domainName);
if (size > LINK_MOBILE_MAX_DOMAIN_NAME_LENGTH)
size = LINK_MOBILE_MAX_DOMAIN_NAME_LENGTH;
auto request = UserRequest{.type = UserRequest::Type::DNS_QUERY,
.send = {.data = {}},
.dns = result,
.commandSent = false};
for (u32 i = 0; i < size; i++)
request.send.data[i] = domainName[i];
request.send.size = size;
pushRequest(request);
return true;
}
/**
* @brief Requests a data transfer within a P2P connection and responds the
* received data.
* @param dataToSend The data to send, up to 254 bytes.
* @param receivedData A pointer to a `LinkWireless::DataTransfer` struct that
* will be filled with data. It can point to `dataToSend`. When the transfer
* is completed, the `completed` field will be `true`.
* @param result 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 transfer is completed, the `completed` field
* will be `true`.
* \warning Non-blocking. Returns `true` immediately, or `false` if there's no
* active call or available request slots.
*/
bool transfer(DataTransfer dataToSend, DataTransfer* receivedData) {
bool transfer(DataTransfer dataToSend, DataTransfer* result) {
if (state != CALL_ESTABLISHED || userRequests.isFull())
return false;
receivedData->completed = false;
result->completed = false;
auto request = UserRequest{.type = UserRequest::Type::TRANSFER,
.send = {.data = {}, .size = dataToSend.size},
.receive = receivedData,
.receive = result,
.commandSent = false};
for (u32 i = 0; i < dataToSend.size; i++)
request.send.data[i] = dataToSend.data[i];
@ -544,12 +591,13 @@ class LinkMobile {
enum AdapterType { BLUE, YELLOW, GREEN, RED, UNKNOWN };
struct UserRequest {
enum Type { CALL, ISP_LOGIN, TRANSFER, HANG_UP, SHUTDOWN };
enum Type { CALL, ISP_LOGIN, DNS_QUERY, TRANSFER, HANG_UP, SHUTDOWN };
Type type;
char phoneNumber[LINK_MOBILE_MAX_PHONE_NUMBER_LENGTH + 1];
DataTransfer send;
DataTransfer* receive;
DNSQuery* dns;
char loginId[LINK_MOBILE_MAX_LOGIN_ID_LENGTH + 1];
char password[LINK_MOBILE_MAX_PASSWORD_LENGTH + 1];
bool commandSent;
@ -750,6 +798,18 @@ class LinkMobile {
}
break;
}
case UserRequest::Type::DNS_QUERY: {
if (state != ISP_ACTIVE) {
userRequests.pop();
return;
}
if (!asyncCommand.isActive && !request.commandSent) {
cmdDNSQuery(request.send.data, request.send.size);
request.commandSent = true;
}
break;
}
case UserRequest::Type::TRANSFER: {
if (state != CALL_ESTABLISHED) {
userRequests.pop();
@ -918,13 +978,14 @@ class LinkMobile {
break;
}
case SESSION_ACTIVE: {
if (asyncCommand.respondsTo(COMMAND_WAIT_FOR_TELEPHONE_CALL)) {
if (asyncCommand.result == CommandResult::SUCCESS) {
setState(CALL_ESTABLISHED);
role = Role::RECEIVER;
} else {
// (no call received)
}
if (!asyncCommand.respondsTo(COMMAND_WAIT_FOR_TELEPHONE_CALL))
return;
if (asyncCommand.result == CommandResult::SUCCESS) {
setState(CALL_ESTABLISHED);
role = Role::RECEIVER;
} else {
// (no call received)
}
break;
}
@ -936,7 +997,7 @@ class LinkMobile {
setState(CALL_ESTABLISHED);
role = Role::CALLER;
} else {
// (call terminated)
// (call failed)
setState(SESSION_ACTIVE);
}
break;
@ -948,45 +1009,39 @@ 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);
auto request = userRequests.peek();
if (asyncCommand.result == CommandResult::SUCCESS) {
if (asyncCommand.cmd.header.size == 0)
return abort(Error::Type::WEIRD_RESPONSE);
if (request.type == UserRequest::TRANSFER) {
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;
} else
return abort(Error::Type::WTF);
} else {
setState(SESSION_ACTIVE);
}
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;
}
case ISP_CALLING: {
if (!asyncCommand.respondsTo(COMMAND_DIAL_TELEPHONE))
return;
if (userRequests.isEmpty())
return abort(Error::Type::WTF);
auto request = userRequests.peek();
if (request.type == UserRequest::ISP_LOGIN) {
if (asyncCommand.result == CommandResult::SUCCESS) {
setState(ISP_LOGIN);
cmdISPLogin(request.loginId, request.password);
}
request.finished = true;
} else
auto request = userRequests.peekRef();
if (request->type != UserRequest::ISP_LOGIN)
return abort(Error::Type::WTF);
if (asyncCommand.result == CommandResult::SUCCESS) {
setState(ISP_LOGIN);
cmdISPLogin(request->loginId, request->password);
} else {
// (ISP call failed)
setState(SESSION_ACTIVE);
}
request->finished = true;
break;
}
case ISP_LOGIN: {
@ -1003,8 +1058,43 @@ class LinkMobile {
setState(SESSION_ACTIVE);
return;
}
if (userRequests.isEmpty())
return abort(Error::Type::WTF);
// TODO: IMPLEMENT
auto request = userRequests.peekRef();
if (asyncCommand.respondsTo(COMMAND_DNS_QUERY)) {
if (request->type != UserRequest::DNS_QUERY)
return abort(Error::Type::WTF);
if (asyncCommand.result == CommandResult::SUCCESS) {
if (asyncCommand.cmd.header.size != 4)
return abort(Error::Type::WEIRD_RESPONSE);
for (u32 i = 0; i < 4; i++)
request->dns->ipv4[i] = asyncCommand.cmd.data.bytes[i];
request->dns->success = true;
} else {
request->dns->success = false;
}
request->dns->completed = true;
request->finished = true;
break;
} 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;
}
break;
}
case ENDING_SESSION: {
@ -1063,7 +1153,7 @@ class LinkMobile {
}
void cmdTelephoneStatus() {
sendCommandAsync(buildCommand(COMMAND_TELEPHONE_STATUS, true));
sendCommandAsync(buildCommand(COMMAND_TELEPHONE_STATUS));
}
void cmdSIO32(bool enabled) {
@ -1097,6 +1187,12 @@ class LinkMobile {
sendCommandAsync(buildCommand(COMMAND_ISP_LOGIN, true));
}
void cmdDNSQuery(const u8* data, u8 size) {
for (int i = 0; i < size; i++)
addData(data[i], i == 0);
sendCommandAsync(buildCommand(COMMAND_DNS_QUERY, true));
}
void setISPNumber() {
static const char BCD[16] = "0123456789#*cde";
@ -1123,7 +1219,8 @@ class LinkMobile {
u8 commandId = asyncCommand.relatedCommandId();
return asyncCommand.direction == AsyncCommand::Direction::SENDING ||
(commandId != COMMAND_WAIT_FOR_TELEPHONE_CALL &&
commandId != COMMAND_DIAL_TELEPHONE);
commandId != COMMAND_DIAL_TELEPHONE &&
commandId != COMMAND_DNS_QUERY);
}
void addData(u8 value, bool start = false) {

View File

@ -171,6 +171,12 @@ class Queue {
return arr[front];
}
T* peekRef() {
if (isEmpty())
return nullptr;
return &arr[front];
}
template <typename F>
void forEach(F action) {
vs32 currentFront = front;