From 0201651a60b57310ddb03b94de4cc018d896a1b2 Mon Sep 17 00:00:00 2001 From: Amy Nagle Date: Mon, 23 Mar 2026 21:28:05 -0400 Subject: [PATCH] Add REON adapter config protocol --- CMakeLists.txt | 2 + Makefile.am | 2 + callback.c | 99 ++++++ callback.h | 26 ++ commands.c | 28 +- commands.h | 6 +- debug.c | 70 ++++ meson.build | 2 + mobile.h | 192 +++++++++++ reon.c | 849 +++++++++++++++++++++++++++++++++++++++++++++++++ reon.h | 96 ++++++ serial.c | 2 +- 12 files changed, 1367 insertions(+), 7 deletions(-) create mode 100644 reon.c create mode 100644 reon.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 52ea6be..df5e1c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,6 +67,8 @@ set(sources mobile_data.h relay.c relay.h + reon.c + reon.h serial.c serial.h util.c diff --git a/Makefile.am b/Makefile.am index 38a5dcb..08303dd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -31,6 +31,8 @@ libmobile_la_SOURCES = \ mobile_data.h \ relay.c \ relay.h \ + reon.c \ + reon.h \ serial.c \ serial.h \ util.c \ diff --git a/callback.c b/callback.c index 03528e9..de89d48 100644 --- a/callback.c +++ b/callback.c @@ -5,6 +5,7 @@ #include "mobile_data.h" #include "compat.h" +#include "reon.h" // Optional implementations of the callback functions. // Library internals will always call `mobile_impl_` functions, which will be @@ -100,6 +101,78 @@ IMPL void mobile_impl_update_number(A_UNUSED void *user, A_UNUSED enum mobile_nu { return; } + +IMPL const char *mobile_impl_reon_impl_name(A_UNUSED void *user) +{ + return NULL; +} + +IMPL const char *mobile_impl_reon_get_number(A_UNUSED void *user) +{ + return NULL; +} + +IMPL bool mobile_impl_reon_get_current_ip(A_UNUSED void *user, A_UNUSED struct mobile_addr *addr) +{ + return false; +} + +IMPL bool mobile_impl_reon_get_baud_rate(A_UNUSED void *user, A_UNUSED unsigned *baud_rate, A_UNUSED bool *writable) +{ + return false; +} + +IMPL bool mobile_impl_reon_set_baud_rate(A_UNUSED void *user, A_UNUSED unsigned baud_rate) +{ + return false; +} + +IMPL bool mobile_impl_reon_wifi_ap_count(A_UNUSED void *user, A_UNUSED unsigned *count) +{ + return false; +} + +IMPL bool mobile_impl_reon_wifi_ap_get(A_UNUSED void *user, A_UNUSED unsigned index, + A_UNUSED char *ssid, A_UNUSED signed char *rssi, A_UNUSED unsigned char *security) +{ + return false; +} + +IMPL bool mobile_impl_reon_bt_device_count(A_UNUSED void *user, A_UNUSED unsigned *count) +{ + return false; +} + +IMPL bool mobile_impl_reon_bt_device_get(A_UNUSED void *user, A_UNUSED unsigned index, + A_UNUSED unsigned char *mac, A_UNUSED char *name) +{ + return false; +} + +IMPL bool mobile_impl_reon_custom_count(A_UNUSED void *user, A_UNUSED unsigned *count) +{ + return false; +} + +IMPL bool mobile_impl_reon_custom_get_desc(A_UNUSED void *user, A_UNUSED unsigned index, + A_UNUSED unsigned char *id, A_UNUSED unsigned char *type, + A_UNUSED unsigned char *flags, A_UNUSED char *name) +{ + return false; +} + +IMPL int mobile_impl_reon_custom_get_value(A_UNUSED void *user, A_UNUSED unsigned char id, + A_UNUSED unsigned char *buffer, A_UNUSED size_t buffer_size) +{ + return 0; +} + +IMPL int mobile_impl_reon_custom_set_value(A_UNUSED void *user, A_UNUSED unsigned char id, + A_UNUSED unsigned char type, A_UNUSED const unsigned char *value, + A_UNUSED unsigned char value_len) +{ + return MOBILE_REON_ERROR_INVALID_PARAM; +} #endif void mobile_callback_init(struct mobile_adapter *adapter) @@ -121,6 +194,19 @@ void mobile_callback_init(struct mobile_adapter *adapter) adapter->callback.sock_send = mobile_impl_sock_send; adapter->callback.sock_recv = mobile_impl_sock_recv; adapter->callback.update_number = mobile_impl_update_number; + adapter->callback.reon_impl_name = mobile_impl_reon_impl_name; + adapter->callback.reon_get_number = mobile_impl_reon_get_number; + adapter->callback.reon_get_current_ip = mobile_impl_reon_get_current_ip; + adapter->callback.reon_get_baud_rate = mobile_impl_reon_get_baud_rate; + adapter->callback.reon_set_baud_rate = mobile_impl_reon_set_baud_rate; + adapter->callback.reon_wifi_ap_count = mobile_impl_reon_wifi_ap_count; + adapter->callback.reon_wifi_ap_get = mobile_impl_reon_wifi_ap_get; + adapter->callback.reon_bt_device_count = mobile_impl_reon_bt_device_count; + adapter->callback.reon_bt_device_get = mobile_impl_reon_bt_device_get; + adapter->callback.reon_custom_count = mobile_impl_reon_custom_count; + adapter->callback.reon_custom_get_desc = mobile_impl_reon_custom_get_desc; + adapter->callback.reon_custom_get_value = mobile_impl_reon_custom_get_value; + adapter->callback.reon_custom_set_value = mobile_impl_reon_custom_set_value; #endif } @@ -146,4 +232,17 @@ def(sock_accept) def(sock_send) def(sock_recv) def(update_number) +def(reon_impl_name) +def(reon_get_number) +def(reon_get_current_ip) +def(reon_get_baud_rate) +def(reon_set_baud_rate) +def(reon_wifi_ap_count) +def(reon_wifi_ap_get) +def(reon_bt_device_count) +def(reon_bt_device_get) +def(reon_custom_count) +def(reon_custom_get_desc) +def(reon_custom_get_value) +def(reon_custom_set_value) #endif diff --git a/callback.h b/callback.h index 123d229..30f9a36 100644 --- a/callback.h +++ b/callback.h @@ -32,6 +32,19 @@ struct mobile_adapter_callback { mobile_func_sock_send sock_send; mobile_func_sock_recv sock_recv; mobile_func_update_number update_number; + mobile_func_reon_impl_name reon_impl_name; + mobile_func_reon_get_number reon_get_number; + mobile_func_reon_get_current_ip reon_get_current_ip; + mobile_func_reon_get_baud_rate reon_get_baud_rate; + mobile_func_reon_set_baud_rate reon_set_baud_rate; + mobile_func_reon_wifi_ap_count reon_wifi_ap_count; + mobile_func_reon_wifi_ap_get reon_wifi_ap_get; + mobile_func_reon_bt_device_count reon_bt_device_count; + mobile_func_reon_bt_device_get reon_bt_device_get; + mobile_func_reon_custom_count reon_custom_count; + mobile_func_reon_custom_get_desc reon_custom_get_desc; + mobile_func_reon_custom_get_value reon_custom_get_value; + mobile_func_reon_custom_set_value reon_custom_set_value; #endif }; void mobile_callback_init(struct mobile_adapter *adapter); @@ -63,3 +76,16 @@ void mobile_callback_init(struct mobile_adapter *adapter); #define mobile_cb_sock_send(...) _mobile_cb(sock_send, __VA_ARGS__) #define mobile_cb_sock_recv(...) _mobile_cb(sock_recv, __VA_ARGS__) #define mobile_cb_update_number(...) _mobile_cb(update_number, __VA_ARGS__) +#define mobile_cb_reon_impl_name(...) _mobile_cb(reon_impl_name, __VA_ARGS__) +#define mobile_cb_reon_get_number(...) _mobile_cb(reon_get_number, __VA_ARGS__) +#define mobile_cb_reon_get_current_ip(...) _mobile_cb(reon_get_current_ip, __VA_ARGS__) +#define mobile_cb_reon_get_baud_rate(...) _mobile_cb(reon_get_baud_rate, __VA_ARGS__) +#define mobile_cb_reon_set_baud_rate(...) _mobile_cb(reon_set_baud_rate, __VA_ARGS__) +#define mobile_cb_reon_wifi_ap_count(...) _mobile_cb(reon_wifi_ap_count, __VA_ARGS__) +#define mobile_cb_reon_wifi_ap_get(...) _mobile_cb(reon_wifi_ap_get, __VA_ARGS__) +#define mobile_cb_reon_bt_device_count(...) _mobile_cb(reon_bt_device_count, __VA_ARGS__) +#define mobile_cb_reon_bt_device_get(...) _mobile_cb(reon_bt_device_get, __VA_ARGS__) +#define mobile_cb_reon_custom_count(...) _mobile_cb(reon_custom_count, __VA_ARGS__) +#define mobile_cb_reon_custom_get_desc(...) _mobile_cb(reon_custom_get_desc, __VA_ARGS__) +#define mobile_cb_reon_custom_get_value(...) _mobile_cb(reon_custom_get_value, __VA_ARGS__) +#define mobile_cb_reon_custom_set_value(...) _mobile_cb(reon_custom_set_value, __VA_ARGS__) diff --git a/commands.c b/commands.c index 7b99118..7d08d38 100644 --- a/commands.c +++ b/commands.c @@ -7,6 +7,7 @@ #include "mobile_inet.h" #include "util.h" #include "compat.h" +#include "reon.h" #ifdef MOBILE_LIBCONF_USE #include @@ -134,11 +135,12 @@ static void do_end_session(struct mobile_adapter *adapter) s->mode_32bit = false; } -static void do_start_session(struct mobile_adapter *adapter) +static void do_start_session(struct mobile_adapter *adapter, bool reon_mode) { struct mobile_adapter_commands *s = &adapter->commands; s->session_started = true; + s->reon_mode = reon_mode; s->state = MOBILE_CONNECTION_DISCONNECTED; memset(s->connections, false, sizeof(s->connections)); @@ -162,7 +164,7 @@ static struct mobile_packet *command_start(struct mobile_adapter *adapter, struc if (s->session_started) return error_packet(packet, 1); if (packet->length == sizeof(happy) && memcmp_P(packet->data, happy, sizeof(happy)) == 0) { - do_start_session(adapter); + do_start_session(adapter, true); // REON mode enabled return packet; } if (adapter->serial.device != MOBILE_ADAPTER_RED) { @@ -175,7 +177,7 @@ static struct mobile_packet *command_start(struct mobile_adapter *adapter, struc return error_packet(packet, 2); } - do_start_session(adapter); + do_start_session(adapter, false); // Standard mode return packet; } @@ -659,9 +661,14 @@ static struct mobile_packet *command_data(struct mobile_adapter *adapter, struct // 2 - Still connected/failed to disconnect(?) static struct mobile_packet *command_reinit(struct mobile_adapter *adapter, struct mobile_packet *packet) { + struct mobile_adapter_commands *s = &adapter->commands; + + // Preserve REON mode across reinit + bool reon_mode = s->reon_mode; + // Reset everything without ending the session do_end_session(adapter); - do_start_session(adapter); + do_start_session(adapter, reon_mode); packet->length = 0; return packet; @@ -1205,6 +1212,12 @@ struct mobile_packet *mobile_commands_process(struct mobile_adapter *adapter, st return command_udp_disconnect(adapter, packet); case MOBILE_COMMAND_DNS_REQUEST: return command_dns_request(adapter, packet); + case MOBILE_COMMAND_REON_GET_OPTIONS: + return command_reon_get_options(adapter, packet); + case MOBILE_COMMAND_REON_GET_VALUE: + return command_reon_get_value(adapter, packet); + case MOBILE_COMMAND_REON_SET_VALUE: + return command_reon_set_value(adapter, packet); case MOBILE_COMMAND_TEST_MODE: return command_test_mode(adapter, packet); default: @@ -1213,7 +1226,7 @@ struct mobile_packet *mobile_commands_process(struct mobile_adapter *adapter, st } } -bool mobile_commands_exists(enum mobile_command command) +bool mobile_commands_exists(struct mobile_adapter *adapter, enum mobile_command command) { // Used by serial.c:mobile_serial_transfer() to check if a command may be used @@ -1239,6 +1252,11 @@ bool mobile_commands_exists(enum mobile_command command) case MOBILE_COMMAND_DNS_REQUEST: case MOBILE_COMMAND_TEST_MODE: return true; + // REON commands only exist when in REON mode + case MOBILE_COMMAND_REON_GET_OPTIONS: + case MOBILE_COMMAND_REON_GET_VALUE: + case MOBILE_COMMAND_REON_SET_VALUE: + return adapter->commands.reon_mode; default: return false; } diff --git a/commands.h b/commands.h index d9aaa01..bfa5c1e 100644 --- a/commands.h +++ b/commands.h @@ -27,6 +27,9 @@ enum mobile_command { MOBILE_COMMAND_UDP_CONNECT, MOBILE_COMMAND_UDP_DISCONNECT, MOBILE_COMMAND_DNS_REQUEST = 0x28, + MOBILE_COMMAND_REON_GET_OPTIONS = 0x30, + MOBILE_COMMAND_REON_GET_VALUE, + MOBILE_COMMAND_REON_SET_VALUE, MOBILE_COMMAND_TEST_MODE = 0x3F, MOBILE_COMMAND_ERROR = 0x6E }; @@ -65,6 +68,7 @@ struct mobile_adapter_commands { enum mobile_connection_state state; bool connections[MOBILE_MAX_CONNECTIONS]; bool dns2_use; + bool reon_mode; struct mobile_addr4 dns1; struct mobile_addr4 dns2; }; @@ -72,6 +76,6 @@ struct mobile_adapter_commands { void mobile_commands_init(struct mobile_adapter *adapter); void mobile_commands_reset(struct mobile_adapter *adapter); struct mobile_packet *mobile_commands_process(struct mobile_adapter *adapter, struct mobile_packet *packet); -bool mobile_commands_exists(enum mobile_command command); +bool mobile_commands_exists(struct mobile_adapter *adapter, enum mobile_command command); #undef _Atomic // "atomic.h" diff --git a/debug.c b/debug.c index 180f0cb..342e45f 100644 --- a/debug.c +++ b/debug.c @@ -6,6 +6,7 @@ #include #include "mobile_data.h" +#include "commands.h" #include "compat.h" void mobile_debug_init(struct mobile_adapter *adapter) @@ -376,6 +377,75 @@ void mobile_debug_command(struct mobile_adapter *adapter, const struct mobile_pa packet_end(adapter, packet, 2); break; + case MOBILE_COMMAND_REON_GET_OPTIONS: + debug_print("REON Get Options"); + if (!send) { + if (packet->length < 2) { + packet_end(adapter, packet, 0); + break; + } + debug_print(" (offset: %u, count: %u)", + packet->data[0], packet->data[1]); + packet_end(adapter, packet, 2); + } else { + if (packet->length < 2) { + packet_end(adapter, packet, 0); + break; + } + debug_print(" (total: %u, returned: %u)", + packet->data[0], packet->data[1]); + packet_end(adapter, packet, packet->length); + } + break; + + case MOBILE_COMMAND_REON_GET_VALUE: + debug_print("REON Get Value"); + if (!send) { + if (packet->length < 1) { + packet_end(adapter, packet, 0); + break; + } + debug_print(" (option: 0x%02X)", packet->data[0]); + packet_end(adapter, packet, packet->length); + } else { + if (packet->length < 3) { + packet_end(adapter, packet, 0); + break; + } + debug_print(" (option: 0x%02X, type: 0x%02X, len: %u)", + packet->data[0], packet->data[1], packet->data[2]); + if (packet->length > 3) { + dump_hex(adapter, packet->data + 3, packet->length - 3); + } else { + debug_endl(); + } + } + break; + + case MOBILE_COMMAND_REON_SET_VALUE: + debug_print("REON Set Value"); + if (!send) { + if (packet->length < 3) { + packet_end(adapter, packet, 0); + break; + } + debug_print(" (option: 0x%02X, type: 0x%02X, len: %u)", + packet->data[0], packet->data[1], packet->data[2]); + if (packet->length > 3) { + dump_hex(adapter, packet->data + 3, packet->length - 3); + } else { + debug_endl(); + } + } else { + if (packet->length < 1) { + packet_end(adapter, packet, 0); + break; + } + debug_print(" (option: 0x%02X) OK", packet->data[0]); + packet_end(adapter, packet, 1); + } + break; + default: debug_print("Unknown"); dump_hex(adapter, packet->data, packet->length); diff --git a/meson.build b/meson.build index f1a8cf5..b009903 100644 --- a/meson.build +++ b/meson.build @@ -64,6 +64,8 @@ sources = [ 'mobile_data.h', 'relay.c', 'relay.h', + 'reon.c', + 'reon.h', 'serial.c', 'serial.h', 'util.c', diff --git a/mobile.h b/mobile.h index 43028ab..1564361 100644 --- a/mobile.h +++ b/mobile.h @@ -416,6 +416,198 @@ typedef void (*mobile_func_update_number)(void *user, enum mobile_number type, c void mobile_impl_update_number(void *user, enum mobile_number type, const char *number); void mobile_def_update_number(struct mobile_adapter *adapter, mobile_func_update_number func); +// REON Config Protocol callbacks +// +// These callbacks provide implementation-specific information for the REON +// config protocol. They are optional but highly recommended for proper +// identification of the adapter/emulator by configuration ROMs. + +// mobile_func_reon_impl_name - Get implementation name +// +// Returns the name of the frontend/implementation (e.g., "mGBA", "PicoAdapterGB"). +// If not set, returns NULL and the option will not be available. +// +// Returns: NULL-terminated string, or NULL if not available +typedef const char *(*mobile_func_reon_impl_name)(void *user); +const char *mobile_impl_reon_impl_name(void *user); +void mobile_def_reon_impl_name(struct mobile_adapter *adapter, mobile_func_reon_impl_name func); + +// mobile_func_reon_get_number - Get user's phone number/friend code +// +// Returns the user's current phone number or relay-assigned friend code. +// This is typically set by the relay server and stored by the implementation. +// If not set, returns NULL and the option will not be available. +// +// Returns: NULL-terminated string (max MOBILE_MAX_NUMBER_SIZE chars), or NULL +typedef const char *(*mobile_func_reon_get_number)(void *user); +const char *mobile_impl_reon_get_number(void *user); +void mobile_def_reon_get_number(struct mobile_adapter *adapter, mobile_func_reon_get_number func); + +// mobile_func_reon_get_current_ip - Get adapter's current IP address +// +// Returns the adapter's current IP address for display or P2P purposes. +// The addr parameter must be filled with the current IP address. +// If not available, returns false and the option will not be available. +// +// Parameters: +// - addr: Address structure to fill with current IP +// Returns: true if address is available, false otherwise +typedef bool (*mobile_func_reon_get_current_ip)(void *user, struct mobile_addr *addr); +bool mobile_impl_reon_get_current_ip(void *user, struct mobile_addr *addr); +void mobile_def_reon_get_current_ip(struct mobile_adapter *adapter, mobile_func_reon_get_current_ip func); + +// mobile_func_reon_get_baud_rate - Get serial baud rate +// +// Returns the current serial baud rate and whether it can be changed. +// If the implementation does not support baud rate reporting, return false. +// +// Parameters: +// - baud_rate: Pointer to store the current baud rate +// - writable: Pointer to store whether the baud rate can be changed +// Returns: true if baud rate is available, false otherwise +typedef bool (*mobile_func_reon_get_baud_rate)(void *user, unsigned *baud_rate, bool *writable); +bool mobile_impl_reon_get_baud_rate(void *user, unsigned *baud_rate, bool *writable); +void mobile_def_reon_get_baud_rate(struct mobile_adapter *adapter, mobile_func_reon_get_baud_rate func); + +// mobile_func_reon_set_baud_rate - Set serial baud rate +// +// Sets the serial baud rate. Only called if reon_get_baud_rate indicated +// the baud rate is writable. The implementation should validate the rate. +// +// Parameters: +// - baud_rate: The new baud rate to set +// Returns: true on success, false if the rate is invalid or unsupported +typedef bool (*mobile_func_reon_set_baud_rate)(void *user, unsigned baud_rate); +bool mobile_impl_reon_set_baud_rate(void *user, unsigned baud_rate); +void mobile_def_reon_set_baud_rate(struct mobile_adapter *adapter, mobile_func_reon_set_baud_rate func); + +// WiFi security types for reon_wifi_ap_get +enum mobile_reon_wifi_security { + MOBILE_REON_WIFI_OPEN = 0, + MOBILE_REON_WIFI_WEP = 1, + MOBILE_REON_WIFI_WPA_PSK = 2, + MOBILE_REON_WIFI_WPA2_PSK = 3, + MOBILE_REON_WIFI_WPA_WPA2_PSK = 4, + MOBILE_REON_WIFI_WPA3_PSK = 5 +}; + +// mobile_func_reon_wifi_ap_count - Get number of WiFi access points +// +// Returns whether WiFi scanning is supported, and if so, the current count. +// If scanning is supported but no networks are visible, returns true with count=0. +// +// Parameters: +// - count: Pointer to store the number of access points +// Returns: true if WiFi scanning is supported, false otherwise +typedef bool (*mobile_func_reon_wifi_ap_count)(void *user, unsigned *count); +bool mobile_impl_reon_wifi_ap_count(void *user, unsigned *count); +void mobile_def_reon_wifi_ap_count(struct mobile_adapter *adapter, mobile_func_reon_wifi_ap_count func); + +// mobile_func_reon_wifi_ap_get - Get WiFi access point information +// +// Retrieves information about a specific WiFi access point by index. +// +// Parameters: +// - index: Index of the access point (0 to count-1) +// - ssid: Buffer for SSID (at least 33 bytes) +// - rssi: Pointer to store signal strength in dBm (typically -100 to 0) +// - security: Pointer to store security type (mobile_reon_wifi_security) +// Returns: true on success, false if index is out of range +typedef bool (*mobile_func_reon_wifi_ap_get)(void *user, unsigned index, + char *ssid, signed char *rssi, unsigned char *security); +bool mobile_impl_reon_wifi_ap_get(void *user, unsigned index, + char *ssid, signed char *rssi, unsigned char *security); +void mobile_def_reon_wifi_ap_get(struct mobile_adapter *adapter, mobile_func_reon_wifi_ap_get func); + +// mobile_func_reon_bt_device_count - Get number of Bluetooth devices +// +// Returns whether Bluetooth scanning is supported, and if so, the current count. +// If scanning is supported but no devices are visible, returns true with count=0. +// +// Parameters: +// - count: Pointer to store the number of devices +// Returns: true if Bluetooth scanning is supported, false otherwise +typedef bool (*mobile_func_reon_bt_device_count)(void *user, unsigned *count); +bool mobile_impl_reon_bt_device_count(void *user, unsigned *count); +void mobile_def_reon_bt_device_count(struct mobile_adapter *adapter, mobile_func_reon_bt_device_count func); + +// mobile_func_reon_bt_device_get - Get Bluetooth device information +// +// Retrieves information about a specific Bluetooth device by index. +// +// Parameters: +// - index: Index of the device (0 to count-1) +// - mac: Buffer for MAC address (6 bytes) +// - name: Buffer for device name (at least 249 bytes) +// Returns: true on success, false if index is out of range +typedef bool (*mobile_func_reon_bt_device_get)(void *user, unsigned index, + unsigned char *mac, char *name); +bool mobile_impl_reon_bt_device_get(void *user, unsigned index, + unsigned char *mac, char *name); +void mobile_def_reon_bt_device_get(struct mobile_adapter *adapter, mobile_func_reon_bt_device_get func); + +// mobile_func_reon_custom_count - Get number of custom options +// +// Returns whether the adapter has custom options (0x30+), and if so, how many. +// +// Parameters: +// - count: Pointer to store the number of custom options +// Returns: true if custom options are supported, false otherwise +typedef bool (*mobile_func_reon_custom_count)(void *user, unsigned *count); +bool mobile_impl_reon_custom_count(void *user, unsigned *count); +void mobile_def_reon_custom_count(struct mobile_adapter *adapter, mobile_func_reon_custom_count func); + +// mobile_func_reon_custom_get_desc - Get custom option descriptor +// +// Retrieves the descriptor for a custom option by index (not by ID). +// Option IDs should be in the range 0x30+. +// +// Parameters: +// - index: Index of the custom option (0 to count-1) +// - id: Pointer to store the option ID (should be >= 0x30) +// - type: Pointer to store the option type +// - flags: Pointer to store the option flags +// - name: Buffer for option name (at least 64 bytes recommended) +// Returns: true on success, false if index is out of range +typedef bool (*mobile_func_reon_custom_get_desc)(void *user, unsigned index, + unsigned char *id, unsigned char *type, unsigned char *flags, char *name); +bool mobile_impl_reon_custom_get_desc(void *user, unsigned index, + unsigned char *id, unsigned char *type, unsigned char *flags, char *name); +void mobile_def_reon_custom_get_desc(struct mobile_adapter *adapter, mobile_func_reon_custom_get_desc func); + +// mobile_func_reon_custom_get_value - Get custom option value +// +// Retrieves the value for a custom option. The adapter writes the value +// directly to the output buffer in wire format: [type:1] [len:1] [value:N] +// +// Parameters: +// - id: Option ID (>= 0x30) +// - buffer: Output buffer for the value (at least 255 bytes) +// - buffer_size: Size of the output buffer +// Returns: Number of bytes written (type + len + value), or 0 on error +typedef int (*mobile_func_reon_custom_get_value)(void *user, unsigned char id, + unsigned char *buffer, size_t buffer_size); +int mobile_impl_reon_custom_get_value(void *user, unsigned char id, + unsigned char *buffer, size_t buffer_size); +void mobile_def_reon_custom_get_value(struct mobile_adapter *adapter, mobile_func_reon_custom_get_value func); + +// mobile_func_reon_custom_set_value - Set custom option value +// +// Sets the value for a custom option. The adapter receives the value +// in wire format. +// +// Parameters: +// - id: Option ID (>= 0x30) +// - type: Value type from the request +// - value: Pointer to the value data +// - value_len: Length of the value data +// Returns: 0 on success, MOBILE_REON_ERROR_* on error +typedef int (*mobile_func_reon_custom_set_value)(void *user, unsigned char id, + unsigned char type, const unsigned char *value, unsigned char value_len); +int mobile_impl_reon_custom_set_value(void *user, unsigned char id, + unsigned char type, const unsigned char *value, unsigned char value_len); +void mobile_def_reon_custom_set_value(struct mobile_adapter *adapter, mobile_func_reon_custom_set_value func); + void mobile_config_set_device(struct mobile_adapter *adapter, enum mobile_adapter_device device, bool unmetered); void mobile_config_get_device(struct mobile_adapter *adapter, enum mobile_adapter_device *device, bool *unmetered); void mobile_config_set_dns(struct mobile_adapter *adapter, const struct mobile_addr *dns, enum mobile_dns num); diff --git a/reon.c b/reon.c new file mode 100644 index 0000000..549bc67 --- /dev/null +++ b/reon.c @@ -0,0 +1,849 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +#include "reon.h" + +#include + +#include "mobile_data.h" +#include "commands.h" +#include "compat.h" + +// Option descriptors - static list of available options +// Note: Names are stored in PROGMEM for AVR compatibility +static const char opt_name_impl_name[] PROGMEM = "Implementation name"; +static const char opt_name_user_number[] PROGMEM = "User number"; +static const char opt_name_current_ip[] PROGMEM = "Current IP"; +static const char opt_name_adapter_type[] PROGMEM = "Adapter type"; +static const char opt_name_unmetered[] PROGMEM = "Unmetered"; +static const char opt_name_dns1[] PROGMEM = "DNS 1"; +static const char opt_name_dns1_port[] PROGMEM = "DNS 1 Port"; +static const char opt_name_dns2[] PROGMEM = "DNS 2"; +static const char opt_name_dns2_port[] PROGMEM = "DNS 2 Port"; +static const char opt_name_relay[] PROGMEM = "Relay server"; +static const char opt_name_p2p_port[] PROGMEM = "P2P Port"; +static const char opt_name_baud_rate[] PROGMEM = "Baud rate"; +static const char opt_name_wifi_ap_list[] PROGMEM = "WiFi AP list"; +static const char opt_name_bt_device_list[] PROGMEM = "BT device list"; + +// Build the list of available options dynamically based on implementation callbacks +static int reon_build_option_list(struct mobile_adapter *adapter, + struct mobile_reon_option_desc *options, + int max_options) +{ + int count = 0; + + // Implementation name (if callback returns non-NULL) + const char *impl_name = mobile_cb_reon_impl_name(adapter); + if (impl_name && count < max_options) { + options[count].id = MOBILE_REON_OPT_IMPL_NAME; + options[count].type = MOBILE_REON_TYPE_STRING; + options[count].flags = MOBILE_REON_FLAG_READONLY; + options[count].name = opt_name_impl_name; + count++; + } + + // User number/friend code (if callback returns non-NULL) + const char *user_number = mobile_cb_reon_get_number(adapter); + if (user_number && count < max_options) { + options[count].id = MOBILE_REON_OPT_USER_NUMBER; + options[count].type = MOBILE_REON_TYPE_STRING; + options[count].flags = MOBILE_REON_FLAG_READONLY; + options[count].name = opt_name_user_number; + count++; + } + + // Current IP (if callback returns valid address) + struct mobile_addr current_ip; + if (mobile_cb_reon_get_current_ip(adapter, ¤t_ip) && count < max_options) { + options[count].id = MOBILE_REON_OPT_CURRENT_IP; + options[count].type = MOBILE_REON_TYPE_IP; + options[count].flags = MOBILE_REON_FLAG_READONLY; + options[count].name = opt_name_current_ip; + count++; + } + + // Adapter type (always available, R/W) + if (count < max_options) { + options[count].id = MOBILE_REON_OPT_ADAPTER_TYPE; + options[count].type = MOBILE_REON_TYPE_BYTE; + options[count].flags = 0; + options[count].name = opt_name_adapter_type; + count++; + } + + // Unmetered (always available, R/W) + if (count < max_options) { + options[count].id = MOBILE_REON_OPT_UNMETERED; + options[count].type = MOBILE_REON_TYPE_BYTE; + options[count].flags = 0; + options[count].name = opt_name_unmetered; + count++; + } + + // DNS 1 (always available, R/W) + if (count < max_options) { + options[count].id = MOBILE_REON_OPT_DNS1; + options[count].type = MOBILE_REON_TYPE_IP; + options[count].flags = 0; + options[count].name = opt_name_dns1; + count++; + } + + // DNS 1 Port (always available, R/W) + if (count < max_options) { + options[count].id = MOBILE_REON_OPT_DNS1_PORT; + options[count].type = MOBILE_REON_TYPE_UINT16; + options[count].flags = 0; + options[count].name = opt_name_dns1_port; + count++; + } + + // DNS 2 (always available, R/W) + if (count < max_options) { + options[count].id = MOBILE_REON_OPT_DNS2; + options[count].type = MOBILE_REON_TYPE_IP; + options[count].flags = 0; + options[count].name = opt_name_dns2; + count++; + } + + // DNS 2 Port (always available, R/W) + if (count < max_options) { + options[count].id = MOBILE_REON_OPT_DNS2_PORT; + options[count].type = MOBILE_REON_TYPE_UINT16; + options[count].flags = 0; + options[count].name = opt_name_dns2_port; + count++; + } + + // Relay server (always available, R/W) + if (count < max_options) { + options[count].id = MOBILE_REON_OPT_RELAY; + options[count].type = MOBILE_REON_TYPE_IP; + options[count].flags = 0; + options[count].name = opt_name_relay; + count++; + } + + // P2P Port (always available, R/W) + if (count < max_options) { + options[count].id = MOBILE_REON_OPT_P2P_PORT; + options[count].type = MOBILE_REON_TYPE_UINT16; + options[count].flags = 0; + options[count].name = opt_name_p2p_port; + count++; + } + + // Baud rate (if callback returns valid rate) + unsigned baud_rate; + bool baud_writable; + if (mobile_cb_reon_get_baud_rate(adapter, &baud_rate, &baud_writable) && count < max_options) { + options[count].id = MOBILE_REON_OPT_BAUD_RATE; + options[count].type = MOBILE_REON_TYPE_UINT32; + options[count].flags = baud_writable ? 0 : MOBILE_REON_FLAG_READONLY; + options[count].name = opt_name_baud_rate; + count++; + } + + // WiFi AP list (if WiFi scanning is supported) + unsigned wifi_count; + if (mobile_cb_reon_wifi_ap_count(adapter, &wifi_count) && count < max_options) { + options[count].id = MOBILE_REON_OPT_WIFI_AP_LIST; + options[count].type = MOBILE_REON_TYPE_ARRAY | MOBILE_REON_TYPE_BYTE; + options[count].flags = MOBILE_REON_FLAG_READONLY; + options[count].name = opt_name_wifi_ap_list; + count++; + } + + // BT device list (if Bluetooth scanning is supported) + unsigned bt_count; + if (mobile_cb_reon_bt_device_count(adapter, &bt_count) && count < max_options) { + options[count].id = MOBILE_REON_OPT_BT_DEVICE_LIST; + options[count].type = MOBILE_REON_TYPE_ARRAY | MOBILE_REON_TYPE_BYTE; + options[count].flags = MOBILE_REON_FLAG_READONLY; + options[count].name = opt_name_bt_device_list; + count++; + } + + // Note: Custom options (0x30+) are handled separately in GET_OPTIONS + // because they require dynamic name retrieval + + return count; +} + +// Get count of custom options from adapter +static unsigned reon_get_custom_count(struct mobile_adapter *adapter) +{ + unsigned count; + if (mobile_cb_reon_custom_count(adapter, &count)) { + return count; + } + return 0; +} + +// Find option descriptor by ID +static const struct mobile_reon_option_desc *reon_find_option( + struct mobile_adapter *adapter, + unsigned char id, + struct mobile_reon_option_desc *options, + int count) +{ + (void)adapter; + for (int i = 0; i < count; i++) { + if (options[i].id == id) return &options[i]; + } + return NULL; +} + +// GET_REON_CONFIG_OPTIONS (0x30) +// Request: [offset:1] [count:1] +// Response: [total:1] [returned:1] [options...] +struct mobile_packet *command_reon_get_options(struct mobile_adapter *adapter, + struct mobile_packet *packet) +{ + // Note: REON mode check is done at serial layer - commands don't exist when not in REON mode + + // Validate request + if (packet->length < 2) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + + unsigned char offset = packet->data[0]; + unsigned char req_count = packet->data[1]; + + if (req_count == 0 || req_count > MOBILE_REON_MAX_OPTIONS_PER_PAGE) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + + // Build core option list + struct mobile_reon_option_desc options[20]; + int core_count = reon_build_option_list(adapter, options, 20); + unsigned custom_count = reon_get_custom_count(adapter); + int total = core_count + (int)custom_count; + + // Calculate how many options to return + int start = offset; + if (start >= total) start = total; + int count = total - start; + if (count > (int)req_count) count = req_count; + + // Build response + unsigned char *out = packet->data; + *out++ = total; // Total options available + *out++ = count; // Options in this response + + int returned = 0; + for (int i = 0; i < count; i++) { + int idx = start + i; + + unsigned char id, type, flags; + const char *name; + char custom_name[64]; + bool is_custom = (idx >= core_count); + + if (is_custom) { + // Custom option - get descriptor from callback + unsigned custom_idx = idx - core_count; + if (!mobile_cb_reon_custom_get_desc(adapter, custom_idx, &id, &type, &flags, custom_name)) { + continue; + } + name = custom_name; + } else { + // Core option + const struct mobile_reon_option_desc *opt = &options[idx]; + id = opt->id; + type = opt->type; + flags = opt->flags; + name = opt->name; + } + + size_t name_len = is_custom ? strlen(name) : strlen_P(name); + if (name_len > 253) name_len = 253; + + // Check if we have enough space (max packet size is 254) + if ((out - packet->data) + 4 + name_len > MOBILE_MAX_TRANSFER_SIZE) { + // Truncate and update returned count + packet->data[1] = returned; + break; + } + + *out++ = id; + *out++ = type; + *out++ = flags; + *out++ = name_len; + if (is_custom) { + memcpy(out, name, name_len); + } else { + memcpy_P(out, name, name_len); + } + out += name_len; + returned++; + } + + // Update returned count if we completed normally + if (returned == count) { + packet->data[1] = returned; + } + + packet->length = out - packet->data; + return packet; +} + +// GET_REON_CONFIG_VALUE (0x31) +// Request: [option_id:1] [index:1]? (index only for arrays) +// Response: [option_id:1] [type:1] [value_len:1] [value:N] +struct mobile_packet *command_reon_get_value(struct mobile_adapter *adapter, + struct mobile_packet *packet) +{ + // Note: REON mode check is done at serial layer - commands don't exist when not in REON mode + + // Validate request + if (packet->length < 1) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + + unsigned char option_id = packet->data[0]; + + // Build option list and find the option + struct mobile_reon_option_desc options[20]; + int total = reon_build_option_list(adapter, options, 20); + const struct mobile_reon_option_desc *opt = reon_find_option(adapter, option_id, options, total); + + if (!opt) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + + unsigned char *out = packet->data; + *out++ = option_id; + + // Get value based on option ID + switch (option_id) { + case MOBILE_REON_OPT_IMPL_NAME: { + const char *name = mobile_cb_reon_impl_name(adapter); + if (!name) return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + size_t len = strlen(name); + if (len > 253) len = 253; + *out++ = MOBILE_REON_TYPE_STRING; + *out++ = len; + memcpy(out, name, len); + out += len; + break; + } + + case MOBILE_REON_OPT_USER_NUMBER: { + const char *number = mobile_cb_reon_get_number(adapter); + if (!number) return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + size_t len = strlen(number); + if (len > MOBILE_MAX_NUMBER_SIZE) len = MOBILE_MAX_NUMBER_SIZE; + *out++ = MOBILE_REON_TYPE_STRING; + *out++ = len; + memcpy(out, number, len); + out += len; + break; + } + + case MOBILE_REON_OPT_CURRENT_IP: { + struct mobile_addr addr; + if (!mobile_cb_reon_get_current_ip(adapter, &addr)) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + if (addr.type == MOBILE_ADDRTYPE_IPV4) { + struct mobile_addr4 *addr4 = (struct mobile_addr4 *)&addr; + *out++ = MOBILE_REON_TYPE_IPV4; + *out++ = MOBILE_HOSTLEN_IPV4; + memcpy(out, addr4->host, MOBILE_HOSTLEN_IPV4); + out += MOBILE_HOSTLEN_IPV4; + } else if (addr.type == MOBILE_ADDRTYPE_IPV6) { + struct mobile_addr6 *addr6 = (struct mobile_addr6 *)&addr; + *out++ = MOBILE_REON_TYPE_IPV6; + *out++ = MOBILE_HOSTLEN_IPV6; + memcpy(out, addr6->host, MOBILE_HOSTLEN_IPV6); + out += MOBILE_HOSTLEN_IPV6; + } else { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + break; + } + + case MOBILE_REON_OPT_ADAPTER_TYPE: { + *out++ = MOBILE_REON_TYPE_BYTE; + *out++ = 1; + *out++ = adapter->serial.device; + break; + } + + case MOBILE_REON_OPT_UNMETERED: { + *out++ = MOBILE_REON_TYPE_BYTE; + *out++ = 1; + *out++ = adapter->serial.device_unmetered ? 0x01 : 0x00; + break; + } + + case MOBILE_REON_OPT_DNS1: { + struct mobile_addr dns; + mobile_config_get_dns(adapter, &dns, MOBILE_DNS1); + if (dns.type == MOBILE_ADDRTYPE_IPV4) { + struct mobile_addr4 *addr4 = (struct mobile_addr4 *)&dns; + *out++ = MOBILE_REON_TYPE_IPV4; + *out++ = MOBILE_HOSTLEN_IPV4; + memcpy(out, addr4->host, MOBILE_HOSTLEN_IPV4); + out += MOBILE_HOSTLEN_IPV4; + } else if (dns.type == MOBILE_ADDRTYPE_IPV6) { + struct mobile_addr6 *addr6 = (struct mobile_addr6 *)&dns; + *out++ = MOBILE_REON_TYPE_IPV6; + *out++ = MOBILE_HOSTLEN_IPV6; + memcpy(out, addr6->host, MOBILE_HOSTLEN_IPV6); + out += MOBILE_HOSTLEN_IPV6; + } else { + // No address configured + *out++ = MOBILE_REON_TYPE_IPV4; + *out++ = MOBILE_HOSTLEN_IPV4; + memset(out, 0, MOBILE_HOSTLEN_IPV4); + out += MOBILE_HOSTLEN_IPV4; + } + break; + } + + case MOBILE_REON_OPT_DNS1_PORT: { + struct mobile_addr dns; + mobile_config_get_dns(adapter, &dns, MOBILE_DNS1); + unsigned port = MOBILE_DNS_PORT; + if (dns.type == MOBILE_ADDRTYPE_IPV4) { + port = ((struct mobile_addr4 *)&dns)->port; + } else if (dns.type == MOBILE_ADDRTYPE_IPV6) { + port = ((struct mobile_addr6 *)&dns)->port; + } + *out++ = MOBILE_REON_TYPE_UINT16; + *out++ = 2; + *out++ = (port >> 8) & 0xFF; + *out++ = port & 0xFF; + break; + } + + case MOBILE_REON_OPT_DNS2: { + struct mobile_addr dns; + mobile_config_get_dns(adapter, &dns, MOBILE_DNS2); + if (dns.type == MOBILE_ADDRTYPE_IPV4) { + struct mobile_addr4 *addr4 = (struct mobile_addr4 *)&dns; + *out++ = MOBILE_REON_TYPE_IPV4; + *out++ = MOBILE_HOSTLEN_IPV4; + memcpy(out, addr4->host, MOBILE_HOSTLEN_IPV4); + out += MOBILE_HOSTLEN_IPV4; + } else if (dns.type == MOBILE_ADDRTYPE_IPV6) { + struct mobile_addr6 *addr6 = (struct mobile_addr6 *)&dns; + *out++ = MOBILE_REON_TYPE_IPV6; + *out++ = MOBILE_HOSTLEN_IPV6; + memcpy(out, addr6->host, MOBILE_HOSTLEN_IPV6); + out += MOBILE_HOSTLEN_IPV6; + } else { + *out++ = MOBILE_REON_TYPE_IPV4; + *out++ = MOBILE_HOSTLEN_IPV4; + memset(out, 0, MOBILE_HOSTLEN_IPV4); + out += MOBILE_HOSTLEN_IPV4; + } + break; + } + + case MOBILE_REON_OPT_DNS2_PORT: { + struct mobile_addr dns; + mobile_config_get_dns(adapter, &dns, MOBILE_DNS2); + unsigned port = MOBILE_DNS_PORT; + if (dns.type == MOBILE_ADDRTYPE_IPV4) { + port = ((struct mobile_addr4 *)&dns)->port; + } else if (dns.type == MOBILE_ADDRTYPE_IPV6) { + port = ((struct mobile_addr6 *)&dns)->port; + } + *out++ = MOBILE_REON_TYPE_UINT16; + *out++ = 2; + *out++ = (port >> 8) & 0xFF; + *out++ = port & 0xFF; + break; + } + + case MOBILE_REON_OPT_RELAY: { + struct mobile_addr relay; + mobile_config_get_relay(adapter, &relay); + if (relay.type == MOBILE_ADDRTYPE_IPV4) { + struct mobile_addr4 *addr4 = (struct mobile_addr4 *)&relay; + *out++ = MOBILE_REON_TYPE_IPV4; + *out++ = MOBILE_HOSTLEN_IPV4; + memcpy(out, addr4->host, MOBILE_HOSTLEN_IPV4); + out += MOBILE_HOSTLEN_IPV4; + } else if (relay.type == MOBILE_ADDRTYPE_IPV6) { + struct mobile_addr6 *addr6 = (struct mobile_addr6 *)&relay; + *out++ = MOBILE_REON_TYPE_IPV6; + *out++ = MOBILE_HOSTLEN_IPV6; + memcpy(out, addr6->host, MOBILE_HOSTLEN_IPV6); + out += MOBILE_HOSTLEN_IPV6; + } else { + *out++ = MOBILE_REON_TYPE_IPV4; + *out++ = MOBILE_HOSTLEN_IPV4; + memset(out, 0, MOBILE_HOSTLEN_IPV4); + out += MOBILE_HOSTLEN_IPV4; + } + break; + } + + case MOBILE_REON_OPT_P2P_PORT: { + unsigned p2p_port; + mobile_config_get_p2p_port(adapter, &p2p_port); + *out++ = MOBILE_REON_TYPE_UINT16; + *out++ = 2; + *out++ = (p2p_port >> 8) & 0xFF; + *out++ = p2p_port & 0xFF; + break; + } + + case MOBILE_REON_OPT_BAUD_RATE: { + unsigned baud_rate; + bool writable; + if (!mobile_cb_reon_get_baud_rate(adapter, &baud_rate, &writable)) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + *out++ = MOBILE_REON_TYPE_UINT32; + *out++ = 4; + *out++ = (baud_rate >> 24) & 0xFF; + *out++ = (baud_rate >> 16) & 0xFF; + *out++ = (baud_rate >> 8) & 0xFF; + *out++ = baud_rate & 0xFF; + break; + } + + case MOBILE_REON_OPT_WIFI_AP_LIST: { + // Array type - need index byte + if (packet->length < 2) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + unsigned char index = packet->data[1]; + unsigned ap_count; + if (!mobile_cb_reon_wifi_ap_count(adapter, &ap_count)) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + + if (index == MOBILE_REON_ARRAY_GET_COUNT) { + // Return count + *out++ = MOBILE_REON_TYPE_ARRAY | MOBILE_REON_TYPE_BYTE; + *out++ = 1; + *out++ = ap_count; + } else { + // Return element + if (index >= ap_count) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + char ssid[33]; + signed char rssi; + unsigned char security; + if (!mobile_cb_reon_wifi_ap_get(adapter, index, ssid, &rssi, &security)) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + // Format: [ssid_len:1] [ssid:N] [rssi:1] [security:1] + size_t ssid_len = strlen(ssid); + if (ssid_len > 32) ssid_len = 32; + *out++ = MOBILE_REON_TYPE_ARRAY | MOBILE_REON_TYPE_BYTE; + *out++ = 1 + ssid_len + 2; // ssid_len byte + ssid + rssi + security + *out++ = ssid_len; + memcpy(out, ssid, ssid_len); + out += ssid_len; + *out++ = (unsigned char)rssi; + *out++ = security; + } + break; + } + + case MOBILE_REON_OPT_BT_DEVICE_LIST: { + // Array type - need index byte + if (packet->length < 2) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + unsigned char index = packet->data[1]; + unsigned bt_count; + if (!mobile_cb_reon_bt_device_count(adapter, &bt_count)) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + + if (index == MOBILE_REON_ARRAY_GET_COUNT) { + // Return count + *out++ = MOBILE_REON_TYPE_ARRAY | MOBILE_REON_TYPE_BYTE; + *out++ = 1; + *out++ = bt_count; + } else { + // Return element + if (index >= bt_count) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + unsigned char mac[6]; + char name[249]; + if (!mobile_cb_reon_bt_device_get(adapter, index, mac, name)) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + // Format: [mac:6] [name_len:1] [name:N] + size_t name_len = strlen(name); + if (name_len > 248) name_len = 248; + *out++ = MOBILE_REON_TYPE_ARRAY | MOBILE_REON_TYPE_BYTE; + *out++ = 6 + 1 + name_len; // mac + name_len byte + name + memcpy(out, mac, 6); + out += 6; + *out++ = name_len; + memcpy(out, name, name_len); + out += name_len; + } + break; + } + + default: + // Check for custom options (0x30+) + if (option_id >= 0x30) { + // Let the adapter handle custom option value retrieval + // Buffer format: [type:1] [len:1] [value:N] + int written = mobile_cb_reon_custom_get_value(adapter, option_id, + out, MOBILE_MAX_TRANSFER_SIZE - (out - packet->data)); + if (written <= 0) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + out += written; + break; + } + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + + packet->length = out - packet->data; + return packet; +} + +// SET_REON_CONFIG_VALUE (0x32) +// Request: [option_id:1] [type:1] [value_len:1] [value:N] +// Response: [option_id:1] +struct mobile_packet *command_reon_set_value(struct mobile_adapter *adapter, + struct mobile_packet *packet) +{ + // Note: REON mode check is done at serial layer - commands don't exist when not in REON mode + + // Validate request + if (packet->length < 3) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + + unsigned char option_id = packet->data[0]; + unsigned char type = packet->data[1]; + unsigned char value_len = packet->data[2]; + unsigned char *value = packet->data + 3; + + if (packet->length < 3 + value_len) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + + // Build option list and find the option + struct mobile_reon_option_desc options[20]; + int total = reon_build_option_list(adapter, options, 20); + const struct mobile_reon_option_desc *opt = reon_find_option(adapter, option_id, options, total); + + if (!opt) { + // Custom options (0x30+) won't be in core list - allow them through + if (option_id < 0x30) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + } else { + // Check if read-only (only for core options that we found) + if (opt->flags & MOBILE_REON_FLAG_READONLY) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_READONLY); + } + } + + // Set value based on option ID + switch (option_id) { + case MOBILE_REON_OPT_ADAPTER_TYPE: { + if (type != MOBILE_REON_TYPE_BYTE || value_len != 1) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + enum mobile_adapter_device device; + bool unmetered; + mobile_config_get_device(adapter, &device, &unmetered); + device = (enum mobile_adapter_device)value[0]; + mobile_config_set_device(adapter, device, unmetered); + break; + } + + case MOBILE_REON_OPT_UNMETERED: { + if (type != MOBILE_REON_TYPE_BYTE || value_len != 1) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + enum mobile_adapter_device device; + bool unmetered; + mobile_config_get_device(adapter, &device, &unmetered); + unmetered = (value[0] != 0); + mobile_config_set_device(adapter, device, unmetered); + break; + } + + case MOBILE_REON_OPT_DNS1: { + struct mobile_addr dns = {0}; + if (type == MOBILE_REON_TYPE_IPV4) { + if (value_len != MOBILE_HOSTLEN_IPV4) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + struct mobile_addr4 *addr4 = (struct mobile_addr4 *)&dns; + addr4->type = MOBILE_ADDRTYPE_IPV4; + addr4->port = MOBILE_DNS_PORT; + memcpy(addr4->host, value, MOBILE_HOSTLEN_IPV4); + } else if (type == MOBILE_REON_TYPE_IPV6) { + if (value_len != MOBILE_HOSTLEN_IPV6) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + struct mobile_addr6 *addr6 = (struct mobile_addr6 *)&dns; + addr6->type = MOBILE_ADDRTYPE_IPV6; + addr6->port = MOBILE_DNS_PORT; + memcpy(addr6->host, value, MOBILE_HOSTLEN_IPV6); + } else { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + mobile_config_set_dns(adapter, &dns, MOBILE_DNS1); + break; + } + + case MOBILE_REON_OPT_DNS1_PORT: { + if (type != MOBILE_REON_TYPE_UINT16 || value_len != 2) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + // DNS port is stored within the address structure + // For now, we update the port in the existing DNS address + struct mobile_addr dns; + mobile_config_get_dns(adapter, &dns, MOBILE_DNS1); + unsigned port = (value[0] << 8) | value[1]; + if (dns.type == MOBILE_ADDRTYPE_IPV4) { + ((struct mobile_addr4 *)&dns)->port = port; + } else if (dns.type == MOBILE_ADDRTYPE_IPV6) { + ((struct mobile_addr6 *)&dns)->port = port; + } else { + // No address, create empty IPv4 with this port + struct mobile_addr4 *addr4 = (struct mobile_addr4 *)&dns; + addr4->type = MOBILE_ADDRTYPE_IPV4; + addr4->port = port; + memset(addr4->host, 0, MOBILE_HOSTLEN_IPV4); + } + mobile_config_set_dns(adapter, &dns, MOBILE_DNS1); + break; + } + + case MOBILE_REON_OPT_DNS2: { + struct mobile_addr dns = {0}; + if (type == MOBILE_REON_TYPE_IPV4) { + if (value_len != MOBILE_HOSTLEN_IPV4) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + struct mobile_addr4 *addr4 = (struct mobile_addr4 *)&dns; + addr4->type = MOBILE_ADDRTYPE_IPV4; + addr4->port = MOBILE_DNS_PORT; + memcpy(addr4->host, value, MOBILE_HOSTLEN_IPV4); + } else if (type == MOBILE_REON_TYPE_IPV6) { + if (value_len != MOBILE_HOSTLEN_IPV6) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + struct mobile_addr6 *addr6 = (struct mobile_addr6 *)&dns; + addr6->type = MOBILE_ADDRTYPE_IPV6; + addr6->port = MOBILE_DNS_PORT; + memcpy(addr6->host, value, MOBILE_HOSTLEN_IPV6); + } else { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + mobile_config_set_dns(adapter, &dns, MOBILE_DNS2); + break; + } + + case MOBILE_REON_OPT_DNS2_PORT: { + if (type != MOBILE_REON_TYPE_UINT16 || value_len != 2) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + struct mobile_addr dns; + mobile_config_get_dns(adapter, &dns, MOBILE_DNS2); + unsigned port = (value[0] << 8) | value[1]; + if (dns.type == MOBILE_ADDRTYPE_IPV4) { + ((struct mobile_addr4 *)&dns)->port = port; + } else if (dns.type == MOBILE_ADDRTYPE_IPV6) { + ((struct mobile_addr6 *)&dns)->port = port; + } else { + struct mobile_addr4 *addr4 = (struct mobile_addr4 *)&dns; + addr4->type = MOBILE_ADDRTYPE_IPV4; + addr4->port = port; + memset(addr4->host, 0, MOBILE_HOSTLEN_IPV4); + } + mobile_config_set_dns(adapter, &dns, MOBILE_DNS2); + break; + } + + case MOBILE_REON_OPT_RELAY: { + struct mobile_addr relay = {0}; + if (type == MOBILE_REON_TYPE_IPV4) { + if (value_len != MOBILE_HOSTLEN_IPV4) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + struct mobile_addr4 *addr4 = (struct mobile_addr4 *)&relay; + addr4->type = MOBILE_ADDRTYPE_IPV4; + addr4->port = MOBILE_DEFAULT_RELAY_PORT; + memcpy(addr4->host, value, MOBILE_HOSTLEN_IPV4); + } else if (type == MOBILE_REON_TYPE_IPV6) { + if (value_len != MOBILE_HOSTLEN_IPV6) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + struct mobile_addr6 *addr6 = (struct mobile_addr6 *)&relay; + addr6->type = MOBILE_ADDRTYPE_IPV6; + addr6->port = MOBILE_DEFAULT_RELAY_PORT; + memcpy(addr6->host, value, MOBILE_HOSTLEN_IPV6); + } else { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + mobile_config_set_relay(adapter, &relay); + break; + } + + case MOBILE_REON_OPT_P2P_PORT: { + if (type != MOBILE_REON_TYPE_UINT16 || value_len != 2) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + unsigned port = (value[0] << 8) | value[1]; + mobile_config_set_p2p_port(adapter, port); + break; + } + + case MOBILE_REON_OPT_BAUD_RATE: { + if (type != MOBILE_REON_TYPE_UINT32 || value_len != 4) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + unsigned baud = ((unsigned)value[0] << 24) | ((unsigned)value[1] << 16) | + ((unsigned)value[2] << 8) | value[3]; + if (!mobile_cb_reon_set_baud_rate(adapter, baud)) { + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + break; + } + + default: + // Check for custom options (0x30+) + if (option_id >= 0x30) { + // Let the adapter handle custom option value setting + int result = mobile_cb_reon_custom_set_value(adapter, option_id, + type, value, value_len); + if (result != 0) { + // Return error code from callback + return mobile_reon_error_packet(packet, result); + } + break; + } + return mobile_reon_error_packet(packet, MOBILE_REON_ERROR_INVALID_PARAM); + } + + // Success - return option ID + packet->data[0] = option_id; + packet->length = 1; + return packet; +} + +// Helper to create error packet for REON commands +struct mobile_packet *mobile_reon_error_packet(struct mobile_packet *packet, + unsigned char error) +{ + enum mobile_command command = packet->command; + packet->command = MOBILE_COMMAND_ERROR; + packet->data[0] = command; + packet->data[1] = error; + packet->length = 2; + return packet; +} diff --git a/reon.h b/reon.h new file mode 100644 index 0000000..9716380 --- /dev/null +++ b/reon.h @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +#pragma once + +// REON Config Protocol definitions + +#include +#include + +// REON Config Data Types +enum mobile_reon_type { + MOBILE_REON_TYPE_BYTE = 0x00, + MOBILE_REON_TYPE_UINT16 = 0x01, + MOBILE_REON_TYPE_STRING = 0x02, + MOBILE_REON_TYPE_IPV4 = 0x03, + MOBILE_REON_TYPE_IPV6 = 0x04, + MOBILE_REON_TYPE_IP = 0x05, // Polymorphic: can be IPV4 or IPV6 + MOBILE_REON_TYPE_MAC = 0x06, + MOBILE_REON_TYPE_UINT32 = 0x07, + + // Array flag (bit 7) + MOBILE_REON_TYPE_ARRAY = 0x80 +}; + +// REON Config Option Flags +enum mobile_reon_flags { + MOBILE_REON_FLAG_READONLY = 0x01, + MOBILE_REON_FLAG_MASKED = 0x02 // Value should be masked in UI (passwords) +}; + +// REON Config Option IDs +enum mobile_reon_option { + // Core Options (0x01-0x0B) + MOBILE_REON_OPT_IMPL_NAME = 0x01, // Implementation name (readonly) + MOBILE_REON_OPT_USER_NUMBER = 0x02, // User's number/friend code (readonly) + MOBILE_REON_OPT_CURRENT_IP = 0x03, // Adapter's current IP address (readonly) + MOBILE_REON_OPT_ADAPTER_TYPE = 0x04, // Emulated adapter model + MOBILE_REON_OPT_UNMETERED = 0x05, // Metered/unmetered connection + MOBILE_REON_OPT_DNS1 = 0x06, // Primary DNS server + MOBILE_REON_OPT_DNS1_PORT = 0x07, // Primary DNS port + MOBILE_REON_OPT_DNS2 = 0x08, // Secondary DNS server + MOBILE_REON_OPT_DNS2_PORT = 0x09, // Secondary DNS port + MOBILE_REON_OPT_RELAY = 0x0A, // P2P relay server + MOBILE_REON_OPT_P2P_PORT = 0x0B, // Direct P2P port + MOBILE_REON_OPT_BAUD_RATE = 0x0C, // Serial baud rate (may be readonly) + + // Reserved (0x0D-0x0F) + + // WiFi Options (0x10-0x12) + MOBILE_REON_OPT_WIFI_SSID = 0x10, + MOBILE_REON_OPT_WIFI_PASSWORD = 0x11, + MOBILE_REON_OPT_WIFI_AP_LIST = 0x12, + + // Bluetooth Options (0x13-0x17) + MOBILE_REON_OPT_BT_DEVICE_NAME = 0x13, + MOBILE_REON_OPT_BT_PAIRED_DEVICE = 0x14, + MOBILE_REON_OPT_BT_PAIRED_ADDRESS = 0x15, + MOBILE_REON_OPT_BT_PIN = 0x16, + MOBILE_REON_OPT_BT_DEVICE_LIST = 0x17, + + // Reserved (0x18-0x2F) + // Adapter-specific options start at 0x30 +}; + +// REON Config Option descriptor (used internally) +struct mobile_reon_option_desc { + unsigned char id; + unsigned char type; + unsigned char flags; + const char *name; +}; + +// Maximum options to return per page (protocol limit) +#define MOBILE_REON_MAX_OPTIONS_PER_PAGE 10 + +// Array index to get count +#define MOBILE_REON_ARRAY_GET_COUNT 0xFF + +// Error codes for REON commands +#define MOBILE_REON_ERROR_INVALID_PARAM 1 +#define MOBILE_REON_ERROR_READONLY 2 + +// Forward declarations +struct mobile_adapter; +struct mobile_packet; + +// REON command handlers +struct mobile_packet *command_reon_get_options(struct mobile_adapter *adapter, + struct mobile_packet *packet); +struct mobile_packet *command_reon_get_value(struct mobile_adapter *adapter, + struct mobile_packet *packet); +struct mobile_packet *command_reon_set_value(struct mobile_adapter *adapter, + struct mobile_packet *packet); + +// Helper function +struct mobile_packet *mobile_reon_error_packet(struct mobile_packet *packet, + unsigned char error); diff --git a/serial.c b/serial.c index 62c670f..6c78706 100644 --- a/serial.c +++ b/serial.c @@ -73,7 +73,7 @@ uint8_t mobile_serial_transfer(struct mobile_adapter *adapter, uint8_t c) } // If the command doesn't exist, set the error... - if (!mobile_commands_exists(b->header[0])) { + if (!mobile_commands_exists(adapter, b->header[0])) { b->error = MOBILE_SERIAL_ERROR_UNKNOWN_COMMAND; }