diff --git a/app/meson.build b/app/meson.build index 1baf34fc..720d9c8c 100644 --- a/app/meson.build +++ b/app/meson.build @@ -1,6 +1,7 @@ src = [ 'src/main.c', 'src/adb.c', + 'src/adb_parser.c', 'src/adb_tunnel.c', 'src/cli.c', 'src/clock.c', @@ -204,8 +205,14 @@ install_data('../data/icon.png', # do not build tests in release (assertions would not be executed at all) if get_option('buildtype') == 'debug' tests = [ + ['test_adb_parser', [ + 'tests/test_adb_parser.c', + 'src/adb_parser.c', + 'src/util/str.c', + 'src/util/strbuf.c', + ]], ['test_buffer_util', [ - 'tests/test_buffer_util.c' + 'tests/test_buffer_util.c', ]], ['test_cbuf', [ 'tests/test_cbuf.c', diff --git a/app/src/adb.c b/app/src/adb.c index 819066c0..69bf551a 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -5,6 +5,7 @@ #include #include +#include "adb_parser.h" #include "util/file.h" #include "util/log.h" #include "util/process_intr.h" @@ -389,3 +390,40 @@ adb_get_serialno(struct sc_intr *intr, unsigned flags) { return strdup(buf); } + +char * +adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) { + const char *const cmd[] = {"shell", "ip", "route"}; + + sc_pipe pout; + sc_pid pid = adb_execute_p(serial, cmd, ARRAY_LEN(cmd), flags, &pout); + if (pid == SC_PROCESS_NONE) { + LOGD("Could not execute \"ip route\""); + return NULL; + } + + // "adb shell ip route" output should contain only a few lines + char buf[1024]; + ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf)); + sc_pipe_close(pout); + + bool ok = process_check_success_intr(intr, pid, "ip route", flags); + if (!ok) { + return NULL; + } + + if (r == -1) { + return false; + } + + assert((size_t) r <= sizeof(buf)); + if (r == sizeof(buf) && buf[sizeof(buf) - 1] != '\0') { + // The implementation assumes that the output of "ip route" fits in the + // buffer in a single pass + LOGW("Result of \"ip route\" does not fit in 1Kb. " + "Please report an issue.\n"); + return NULL; + } + + return sc_adb_parse_device_ip_from_output(buf, r); +} diff --git a/app/src/adb.h b/app/src/adb.h index d5d9bb37..ba0c2bde 100644 --- a/app/src/adb.h +++ b/app/src/adb.h @@ -66,4 +66,13 @@ adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags); char * adb_get_serialno(struct sc_intr *intr, unsigned flags); +/** + * Attempt to retrieve the device IP + * + * Return the IP as a string of the form "xxx.xxx.xxx.xxx", to be freed by the + * caller, or NULL on error. + */ +char * +adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags); + #endif diff --git a/app/src/adb_parser.c b/app/src/adb_parser.c new file mode 100644 index 00000000..5ac21ede --- /dev/null +++ b/app/src/adb_parser.c @@ -0,0 +1,65 @@ +#include "adb_parser.h" + +#include +#include + +#include "util/log.h" +#include "util/str.h" + +static char * +sc_adb_parse_device_ip_from_line(char *line, size_t len) { + // One line from "ip route" looks lile: + // "192.168.1.0/24 dev wlan0 proto kernel scope link src 192.168.1.x" + + // Get the location of the device name (index of "wlan0" in the example) + ssize_t idx_dev_name = sc_str_index_of_column(line, 2, " "); + if (idx_dev_name == -1) { + return NULL; + } + + // Get the location of the ip address (column 8, but column 6 if we start + // from column 2). Must be computed before truncating individual columns. + ssize_t idx_ip = sc_str_index_of_column(&line[idx_dev_name], 6, " "); + if (idx_ip == -1) { + return NULL; + } + // idx_ip is searched from &line[idx_dev_name] + idx_ip += idx_dev_name; + + char *dev_name = &line[idx_dev_name]; + sc_str_truncate(dev_name, len - idx_dev_name + 1, " \t"); + + char *ip = &line[idx_ip]; + sc_str_truncate(ip, len - idx_ip + 1, " \t"); + + // Only consider lines where the device name starts with "wlan" + if (strncmp(dev_name, "wlan", sizeof("wlan") - 1)) { + LOGD("Device ip lookup: ignoring %s (%s)", ip, dev_name); + return NULL; + } + + return strdup(ip); +} + +char * +sc_adb_parse_device_ip_from_output(char *buf, size_t buf_len) { + size_t idx_line = 0; + while (idx_line < buf_len && buf[idx_line] != '\0') { + char *line = &buf[idx_line]; + size_t len = sc_str_truncate(line, buf_len - idx_line, "\n"); + + // The same, but without any trailing '\r' + size_t line_len = sc_str_remove_trailing_cr(line, len); + + char *ip = sc_adb_parse_device_ip_from_line(line, line_len); + if (ip) { + // Found + return ip; + } + + // The next line starts after the '\n' (replaced by `\0`) + idx_line += len + 1; + } + + return NULL; +} diff --git a/app/src/adb_parser.h b/app/src/adb_parser.h new file mode 100644 index 00000000..79f26631 --- /dev/null +++ b/app/src/adb_parser.h @@ -0,0 +1,14 @@ +#ifndef SC_ADB_PARSER_H +#define SC_ADB_PARSER_H + +#include "common.h" + +#include "stddef.h" + +/** + * Parse the ip from the output of `adb shell ip route` + */ +char * +sc_adb_parse_device_ip_from_output(char *buf, size_t buf_len); + +#endif diff --git a/app/tests/test_adb_parser.c b/app/tests/test_adb_parser.c new file mode 100644 index 00000000..fbc65649 --- /dev/null +++ b/app/tests/test_adb_parser.c @@ -0,0 +1,83 @@ +#include "common.h" + +#include + +#include "adb_parser.h" + +static void test_get_ip_single_line() { + char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " + "192.168.12.34\r\r\n"; + + char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); + assert(ip); + assert(!strcmp(ip, "192.168.12.34")); +} + +static void test_get_ip_single_line_without_eol() { + char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " + "192.168.12.34"; + + char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); + assert(ip); + assert(!strcmp(ip, "192.168.12.34")); +} + +static void test_get_ip_single_line_with_trailing_space() { + char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " + "192.168.12.34 \n"; + + char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); + assert(ip); + assert(!strcmp(ip, "192.168.12.34")); +} + +static void test_get_ip_multiline_first_ok() { + char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " + "192.168.1.2\r\n" + "10.0.0.0/24 dev rmnet proto kernel scope link src " + "10.0.0.2\r\n"; + + char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); + assert(ip); + assert(!strcmp(ip, "192.168.1.2")); +} + +static void test_get_ip_multiline_second_ok() { + char ip_route[] = "10.0.0.0/24 dev rmnet proto kernel scope link src " + "10.0.0.3\r\n" + "192.168.1.0/24 dev wlan0 proto kernel scope link src " + "192.168.1.3\r\n"; + + char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); + assert(ip); + assert(!strcmp(ip, "192.168.1.3")); +} + +static void test_get_ip_no_wlan() { + char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src " + "192.168.12.34\r\r\n"; + + char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); + assert(!ip); +} + +static void test_get_ip_truncated() { + char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src " + "\n"; + + char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); + assert(!ip); +} + +int main(int argc, char *argv[]) { + (void) argc; + (void) argv; + + test_get_ip_single_line(); + test_get_ip_single_line_without_eol(); + test_get_ip_single_line_with_trailing_space(); + test_get_ip_multiline_first_ok(); + test_get_ip_multiline_second_ok(); + test_get_ip_no_wlan(); + test_get_ip_truncated(); +}