diff --git a/app/src/adb/adb_parser.c b/app/src/adb/adb_parser.c index 90a1b30b..72fd141d 100644 --- a/app/src/adb/adb_parser.c +++ b/app/src/adb/adb_parser.c @@ -8,6 +8,49 @@ #include "util/log.h" #include "util/str.h" +static size_t +rstrip_len(const char *s, size_t len) { + size_t i = len; + + // Ignore trailing whitespaces + while (i > 0 && (s[i-1] == ' ' || s[i-1] == '\t')) { + --i; + } + + return i; +} + +static void +locate_last_token(const char *s, size_t len, size_t *start, size_t *end) { + size_t i = rstrip_len(s, len); + *end = i; // excluded + + // The token contains non-whitespace chars + while (i > 0 && (s[i-1] != ' ' && s[i-1] != '\t')) { + --i; + } + + *start = i; // included +} + +static bool +is_device_state(const char *s) { + // + // "device", "unauthorized" and "offline" are the most common states, so + // check them first. + return !strcmp(s, "device") + || !strcmp(s, "unauthorized") + || !strcmp(s, "offline") + || !strcmp(s, "bootloader") + || !strcmp(s, "host") + || !strcmp(s, "recovery") + || !strcmp(s, "rescue") + || !strcmp(s, "sideload") + || !strcmp(s, "authorizing") + || !strcmp(s, "connecting") + || !strcmp(s, "detached"); +} + static bool sc_adb_parse_device(char *line, struct sc_adb_device *device) { // One device line looks like: @@ -25,64 +68,54 @@ sc_adb_parse_device(char *line, struct sc_adb_device *device) { return false; } - char *s = line; // cursor in the line + size_t len = strlen(line); - // After the serial: - // - "adb devices" writes a single '\t' - // - "adb devices -l" writes multiple spaces - // For flexibility, accept both. - size_t serial_len = strcspn(s, " \t"); + size_t start; + size_t end; + + // The serial (the first token) may contain spaces, which are also token + // separators. To avoid ambiguity, parse the string backwards: + // - first, parse all the trailing values until the device state, + // identified using a list of well-known values; + // - finally, treat the remaining leading token as the device serial. + // + // Refs: + // - + // - + const char *state; + const char *model = NULL; + for (;;) { + locate_last_token(line, len, &start, &end); + if (start == end) { + // No more tokens, unexpected + return false; + } + + const char *token = &line[start]; + line[end] = '\0'; + + if (!strncmp("model:", token, sizeof("model:") - 1)) { + model = &token[sizeof("model:") - 1]; + // We only need the model + } else if (is_device_state(token)) { + state = token; + // The device state is the item immediately after the device serial + break; + } + + // Remove the trailing parts already handled + len = start; + } + + assert(state); + + size_t serial_len = rstrip_len(line, start); if (!serial_len) { // empty serial return false; } - bool eol = s[serial_len] == '\0'; - if (eol) { - // serial alone is unexpected - return false; - } - s[serial_len] = '\0'; - char *serial = s; - s += serial_len + 1; - // After the serial, there might be several spaces - s += strspn(s, " \t"); // consume all separators - - size_t state_len = strcspn(s, " "); - if (!state_len) { - // empty state - return false; - } - eol = s[state_len] == '\0'; - s[state_len] = '\0'; - char *state = s; - - char *model = NULL; - if (!eol) { - s += state_len + 1; - - // Iterate over all properties "key:value key:value ..." - for (;;) { - size_t token_len = strcspn(s, " "); - if (!token_len) { - break; - } - eol = s[token_len] == '\0'; - s[token_len] = '\0'; - char *token = s; - - if (!strncmp("model:", token, sizeof("model:") - 1)) { - model = &token[sizeof("model:") - 1]; - // We only need the model - break; - } - - if (eol) { - break; - } else { - s+= token_len + 1; - } - } - } + char *serial = line; + line[serial_len] = '\0'; device->serial = strdup(serial); if (!device->serial) { diff --git a/app/tests/test_adb_parser.c b/app/tests/test_adb_parser.c index 362b254f..822ed798 100644 --- a/app/tests/test_adb_parser.c +++ b/app/tests/test_adb_parser.c @@ -163,6 +163,45 @@ static void test_adb_devices_spaces(void) { sc_adb_devices_destroy(&vec); } +static void test_adb_devices_serial_with_spaces(void) { + char output[] = + "List of devices attached\n" + "adb-0123456789AB-CDdefg (2)._adb-tls-connect._tcp device " + "product:blazer model:Pixel_10_Pro device:blazer transport_id:3\n"; + + struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER; + bool ok = sc_adb_parse_devices(output, &vec); + assert(ok); + assert(vec.size == 1); + + struct sc_adb_device *device = &vec.data[0]; + assert(!strcmp("adb-0123456789AB-CDdefg (2)._adb-tls-connect._tcp", + device->serial)); + assert(!strcmp("device", device->state)); + assert(!strcmp("Pixel_10_Pro", device->model)); + + sc_adb_devices_destroy(&vec); +} + +static void test_adb_devices_with_devpath_without_colon(void) { + char output[] = + "List of devices attached\n" + "12345678 device 3-1 product:manet model:23117RK66C " + "device:manet transport_id:2\n"; + + struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER; + bool ok = sc_adb_parse_devices(output, &vec); + assert(ok); + assert(vec.size == 1); + + struct sc_adb_device *device = &vec.data[0]; + assert(!strcmp("12345678", device->serial)); + assert(!strcmp("device", device->state)); + assert(!strcmp("23117RK66C", device->model)); + + sc_adb_devices_destroy(&vec); +} + static void test_get_ip_single_line(void) { char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " "192.168.12.34\r\r\n"; @@ -265,6 +304,8 @@ int main(int argc, char *argv[]) { test_adb_devices_without_header(); test_adb_devices_corrupted(); test_adb_devices_spaces(); + test_adb_devices_serial_with_spaces(); + test_adb_devices_with_devpath_without_colon(); test_get_ip_single_line(); test_get_ip_single_line_without_eol();