Add adb devices parser
Add a parser of `adb device -l` output, to extract a list of devices with their serial, state and model. PR #3005 <https://github.com/Genymobile/scrcpy/pull/3005>
This commit is contained in:
parent
85ff70fc95
commit
4389de1c23
6 changed files with 391 additions and 1 deletions
|
@ -1,6 +1,7 @@
|
|||
src = [
|
||||
'src/main.c',
|
||||
'src/adb/adb.c',
|
||||
'src/adb/adb_device.c',
|
||||
'src/adb/adb_parser.c',
|
||||
'src/adb/adb_tunnel.c',
|
||||
'src/cli.c',
|
||||
|
@ -221,6 +222,7 @@ if get_option('buildtype') == 'debug'
|
|||
tests = [
|
||||
['test_adb_parser', [
|
||||
'tests/test_adb_parser.c',
|
||||
'src/adb/adb_device.c',
|
||||
'src/adb/adb_parser.c',
|
||||
'src/util/str.c',
|
||||
'src/util/strbuf.c',
|
||||
|
|
26
app/src/adb/adb_device.c
Normal file
26
app/src/adb/adb_device.c
Normal file
|
@ -0,0 +1,26 @@
|
|||
#include "adb_device.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
void
|
||||
sc_adb_device_destroy(struct sc_adb_device *device) {
|
||||
free(device->serial);
|
||||
free(device->state);
|
||||
free(device->model);
|
||||
}
|
||||
|
||||
void
|
||||
sc_adb_device_move(struct sc_adb_device *dst, struct sc_adb_device *src) {
|
||||
*dst = *src;
|
||||
src->serial = NULL;
|
||||
src->state = NULL;
|
||||
src->model = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
sc_adb_devices_destroy_all(struct sc_adb_device *devices, size_t count) {
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
sc_adb_device_destroy(&devices[i]);
|
||||
}
|
||||
}
|
||||
|
33
app/src/adb/adb_device.h
Normal file
33
app/src/adb/adb_device.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
#ifndef SC_ADB_DEVICE_H
|
||||
#define SC_ADB_DEVICE_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
struct sc_adb_device {
|
||||
char *serial;
|
||||
char *state;
|
||||
char *model;
|
||||
};
|
||||
|
||||
void
|
||||
sc_adb_device_destroy(struct sc_adb_device *device);
|
||||
|
||||
/**
|
||||
* Move src to dst
|
||||
*
|
||||
* After this call, the content of src is undefined, except that
|
||||
* sc_adb_device_destroy() can be called.
|
||||
*
|
||||
* This is useful to take a device from a list that will be destroyed, without
|
||||
* making unnecessary copies.
|
||||
*/
|
||||
void
|
||||
sc_adb_device_move(struct sc_adb_device *dst, struct sc_adb_device *src);
|
||||
|
||||
void
|
||||
sc_adb_devices_destroy_all(struct sc_adb_device *devices, size_t count);
|
||||
|
||||
#endif
|
|
@ -1,11 +1,168 @@
|
|||
#include "adb_parser.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/str.h"
|
||||
|
||||
bool
|
||||
sc_adb_parse_device(char *line, struct sc_adb_device *device) {
|
||||
// One device line looks like:
|
||||
// "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
|
||||
// "device:MyDevice transport_id:1"
|
||||
|
||||
if (line[0] == '*') {
|
||||
// Garbage lines printed by adb daemon while starting start with a '*'
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!strncmp("adb server", line, sizeof("adb server") - 1)) {
|
||||
// Ignore lines starting with "adb server":
|
||||
// adb server version (41) doesn't match this client (39); killing...
|
||||
return false;
|
||||
}
|
||||
|
||||
char *s = line; // cursor in the 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");
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
device->serial = strdup(serial);
|
||||
if (!device->serial) {
|
||||
return false;
|
||||
}
|
||||
|
||||
device->state = strdup(state);
|
||||
if (!device->state) {
|
||||
free(device->serial);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (model) {
|
||||
device->model = strdup(model);
|
||||
if (!device->model) {
|
||||
LOG_OOM();
|
||||
// model is optional, do not fail
|
||||
}
|
||||
} else {
|
||||
device->model = NULL;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ssize_t
|
||||
sc_adb_parse_devices(char *str, struct sc_adb_device *devices,
|
||||
size_t devices_len) {
|
||||
size_t dev_count = 0;
|
||||
|
||||
#define HEADER "List of devices attached"
|
||||
#define HEADER_LEN (sizeof(HEADER) - 1)
|
||||
bool header_found = false;
|
||||
|
||||
size_t idx_line = 0;
|
||||
while (str[idx_line] != '\0') {
|
||||
char *line = &str[idx_line];
|
||||
size_t len = strcspn(line, "\n");
|
||||
|
||||
// The next line starts after the '\n' (replaced by `\0`)
|
||||
idx_line += len;
|
||||
|
||||
if (str[idx_line] != '\0') {
|
||||
// The next line starts after the '\n'
|
||||
++idx_line;
|
||||
}
|
||||
|
||||
if (!header_found) {
|
||||
if (!strncmp(line, HEADER, HEADER_LEN)) {
|
||||
header_found = true;
|
||||
}
|
||||
// Skip everything until the header, there might be garbage lines
|
||||
// related to daemon starting before
|
||||
continue;
|
||||
}
|
||||
|
||||
// The line, but without any trailing '\r'
|
||||
size_t line_len = sc_str_remove_trailing_cr(line, len);
|
||||
line[line_len] = '\0';
|
||||
|
||||
bool ok = sc_adb_parse_device(line, &devices[dev_count]);
|
||||
if (!ok) {
|
||||
continue;
|
||||
}
|
||||
|
||||
++dev_count;
|
||||
|
||||
assert(dev_count <= devices_len);
|
||||
if (dev_count == devices_len) {
|
||||
// Max number of devices reached
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!header_found) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return dev_count;
|
||||
}
|
||||
|
||||
static char *
|
||||
sc_adb_parse_device_ip_from_line(char *line) {
|
||||
// One line from "ip route" looks like:
|
||||
|
@ -64,7 +221,7 @@ sc_adb_parse_device_ip_from_output(char *str) {
|
|||
|
||||
if (str[idx_line] != '\0') {
|
||||
// The next line starts after the '\n'
|
||||
idx_line += 1;
|
||||
++idx_line;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,19 @@
|
|||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "adb_device.h"
|
||||
|
||||
/**
|
||||
* Parse the available devices from the output of `adb devices`
|
||||
*
|
||||
* The parameter must be a NUL-terminated string.
|
||||
*
|
||||
* Warning: this function modifies the buffer for optimization purposes.
|
||||
*/
|
||||
ssize_t
|
||||
sc_adb_parse_devices(char *str, struct sc_adb_device *devices,
|
||||
size_t devices_len);
|
||||
|
||||
/**
|
||||
* Parse the ip from the output of `adb shell ip route`
|
||||
*
|
||||
|
|
|
@ -2,8 +2,158 @@
|
|||
|
||||
#include <assert.h>
|
||||
|
||||
#include "adb/adb_device.h"
|
||||
#include "adb/adb_parser.h"
|
||||
|
||||
static void test_adb_devices() {
|
||||
char output[] =
|
||||
"List of devices attached\n"
|
||||
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
|
||||
"device:MyDevice transport_id:1\n"
|
||||
"192.168.1.1:5555 device product:MyWifiProduct model:MyWifiModel "
|
||||
"device:MyWifiDevice trandport_id:2\n";
|
||||
|
||||
struct sc_adb_device devices[16];
|
||||
ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices));
|
||||
assert(count == 2);
|
||||
|
||||
struct sc_adb_device *device = &devices[0];
|
||||
assert(!strcmp("0123456789abcdef", device->serial));
|
||||
assert(!strcmp("device", device->state));
|
||||
assert(!strcmp("MyModel", device->model));
|
||||
|
||||
device = &devices[1];
|
||||
assert(!strcmp("192.168.1.1:5555", device->serial));
|
||||
assert(!strcmp("device", device->state));
|
||||
assert(!strcmp("MyWifiModel", device->model));
|
||||
|
||||
sc_adb_devices_destroy_all(devices, count);
|
||||
}
|
||||
|
||||
static void test_adb_devices_cr() {
|
||||
char output[] =
|
||||
"List of devices attached\r\n"
|
||||
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
|
||||
"device:MyDevice transport_id:1\r\n"
|
||||
"192.168.1.1:5555 device product:MyWifiProduct model:MyWifiModel "
|
||||
"device:MyWifiDevice trandport_id:2\r\n";
|
||||
|
||||
struct sc_adb_device devices[16];
|
||||
ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices));
|
||||
assert(count == 2);
|
||||
|
||||
struct sc_adb_device *device = &devices[0];
|
||||
assert(!strcmp("0123456789abcdef", device->serial));
|
||||
assert(!strcmp("device", device->state));
|
||||
assert(!strcmp("MyModel", device->model));
|
||||
|
||||
device = &devices[1];
|
||||
assert(!strcmp("192.168.1.1:5555", device->serial));
|
||||
assert(!strcmp("device", device->state));
|
||||
assert(!strcmp("MyWifiModel", device->model));
|
||||
|
||||
sc_adb_devices_destroy_all(devices, count);
|
||||
}
|
||||
|
||||
static void test_adb_devices_daemon_start() {
|
||||
char output[] =
|
||||
"* daemon not running; starting now at tcp:5037\n"
|
||||
"* daemon started successfully\n"
|
||||
"List of devices attached\n"
|
||||
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
|
||||
"device:MyDevice transport_id:1\n";
|
||||
|
||||
struct sc_adb_device devices[16];
|
||||
ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices));
|
||||
assert(count == 1);
|
||||
|
||||
struct sc_adb_device *device = &devices[0];
|
||||
assert(!strcmp("0123456789abcdef", device->serial));
|
||||
assert(!strcmp("device", device->state));
|
||||
assert(!strcmp("MyModel", device->model));
|
||||
|
||||
sc_adb_device_destroy(device);
|
||||
}
|
||||
|
||||
static void test_adb_devices_daemon_start_mixed() {
|
||||
char output[] =
|
||||
"List of devices attached\n"
|
||||
"adb server version (41) doesn't match this client (39); killing...\n"
|
||||
"* daemon started successfully *\n"
|
||||
"0123456789abcdef unauthorized usb:1-1\n"
|
||||
"87654321 device usb:2-1 product:MyProduct model:MyModel "
|
||||
"device:MyDevice\n";
|
||||
|
||||
struct sc_adb_device devices[16];
|
||||
ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices));
|
||||
assert(count == 2);
|
||||
|
||||
struct sc_adb_device *device = &devices[0];
|
||||
assert(!strcmp("0123456789abcdef", device->serial));
|
||||
assert(!strcmp("unauthorized", device->state));
|
||||
fprintf(stderr, "==== [%s]\n", device->model);
|
||||
assert(!device->model);
|
||||
|
||||
device = &devices[1];
|
||||
assert(!strcmp("87654321", device->serial));
|
||||
assert(!strcmp("device", device->state));
|
||||
assert(!strcmp("MyModel", device->model));
|
||||
|
||||
sc_adb_devices_destroy_all(devices, count);
|
||||
}
|
||||
|
||||
static void test_adb_devices_without_eol() {
|
||||
char output[] =
|
||||
"List of devices attached\n"
|
||||
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
|
||||
"device:MyDevice transport_id:1";
|
||||
struct sc_adb_device devices[16];
|
||||
ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices));
|
||||
assert(count == 1);
|
||||
|
||||
struct sc_adb_device *device = &devices[0];
|
||||
assert(!strcmp("0123456789abcdef", device->serial));
|
||||
assert(!strcmp("device", device->state));
|
||||
assert(!strcmp("MyModel", device->model));
|
||||
|
||||
sc_adb_device_destroy(device);
|
||||
}
|
||||
|
||||
static void test_adb_devices_without_header() {
|
||||
char output[] =
|
||||
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
|
||||
"device:MyDevice transport_id:1\n";
|
||||
struct sc_adb_device devices[16];
|
||||
ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices));
|
||||
assert(count == -1);
|
||||
}
|
||||
|
||||
static void test_adb_devices_corrupted() {
|
||||
char output[] =
|
||||
"List of devices attached\n"
|
||||
"corrupted_garbage\n";
|
||||
struct sc_adb_device devices[16];
|
||||
ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices));
|
||||
assert(count == 0);
|
||||
}
|
||||
|
||||
static void test_adb_devices_spaces() {
|
||||
char output[] =
|
||||
"List of devices attached\n"
|
||||
"0123456789abcdef unauthorized usb:1-4 transport_id:3\n";
|
||||
|
||||
struct sc_adb_device devices[16];
|
||||
ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices));
|
||||
assert(count == 1);
|
||||
|
||||
struct sc_adb_device *device = &devices[0];
|
||||
assert(!strcmp("0123456789abcdef", device->serial));
|
||||
assert(!strcmp("unauthorized", device->state));
|
||||
assert(!device->model);
|
||||
|
||||
sc_adb_device_destroy(device);
|
||||
}
|
||||
|
||||
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";
|
||||
|
@ -86,6 +236,15 @@ int main(int argc, char *argv[]) {
|
|||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
test_adb_devices();
|
||||
test_adb_devices_cr();
|
||||
test_adb_devices_daemon_start();
|
||||
test_adb_devices_daemon_start_mixed();
|
||||
test_adb_devices_without_eol();
|
||||
test_adb_devices_without_header();
|
||||
test_adb_devices_corrupted();
|
||||
test_adb_devices_spaces();
|
||||
|
||||
test_get_ip_single_line();
|
||||
test_get_ip_single_line_without_eol();
|
||||
test_get_ip_single_line_with_trailing_space();
|
||||
|
|
Loading…
Reference in a new issue