From 2261f8920cccb6f8bccc4f4488d306194e3ff7a8 Mon Sep 17 00:00:00 2001 From: heyuanjie87 Date: Thu, 29 Nov 2018 17:32:16 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=91=E5=B8=83=E7=AC=AC=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 31 ++ SConscript | 18 ++ core/adb.c | 218 +++++++++++++ core/adb.h | 68 ++++ core/adb_pque.c | 90 ++++++ core/adb_service.c | 158 +++++++++ core/transport.c | 340 +++++++++++++++++++ core/transport.h | 23 ++ core/transport_socket.c | 185 +++++++++++ docs/OVERVIEW.TXT | 138 ++++++++ docs/PROTOCOL.TXT | 287 +++++++++++++++++ docs/SERVICES.TXT | 259 +++++++++++++++ docs/SYNC.TXT | 80 +++++ inc/adb_pque.h | 23 ++ inc/adb_service.h | 65 ++++ inc/adbtypes.h | 36 +++ services/file_sync_service.c | 609 +++++++++++++++++++++++++++++++++++ services/file_sync_service.h | 79 +++++ services/shell_service.c | 454 ++++++++++++++++++++++++++ 19 files changed, 3161 insertions(+) create mode 100644 README.md create mode 100644 SConscript create mode 100644 core/adb.c create mode 100644 core/adb.h create mode 100644 core/adb_pque.c create mode 100644 core/adb_service.c create mode 100644 core/transport.c create mode 100644 core/transport.h create mode 100644 core/transport_socket.c create mode 100644 docs/OVERVIEW.TXT create mode 100644 docs/PROTOCOL.TXT create mode 100644 docs/SERVICES.TXT create mode 100644 docs/SYNC.TXT create mode 100644 inc/adb_pque.h create mode 100644 inc/adb_service.h create mode 100644 inc/adbtypes.h create mode 100644 services/file_sync_service.c create mode 100644 services/file_sync_service.h create mode 100644 services/shell_service.c diff --git a/README.md b/README.md new file mode 100644 index 0000000..a0777b4 --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# Android Debug Bridge daemon implementation in RT-Thread + +## 1. 介绍 + +以TCP为通信接口,在PC与RT-Thread之间建立文件传输与执行shell +的通道。 + +## 2. 依赖 + +- 传输 - LWIP +- 文件系统、POSIX、LIBC +- shell - 依赖finsh/msh + +- 上位机工具 - (win: adb.exe) + +## 2. 配置ADBD + +### 2.1 启用ADBD +在rtconfig.h写入如下定义: + +`#define PKG_USING_ADB` +`#define ADB_SERVICE_SHELL_ENABLE` /* 开启shell服务 */ +`#define ADB_SERVICE_FILESYNC_ENABLE` /* 开启文件服务 */ + +## 3. 参考文档 + +### 3.1 ADB官方文档 + +- [ADB简介](docs/OVERVIEW.TXT) +- [协议简介](docs/PROTOCOL.TXT) +- [文件服务](docs/SYNC.TXT) diff --git a/SConscript b/SConscript new file mode 100644 index 0000000..28daa73 --- /dev/null +++ b/SConscript @@ -0,0 +1,18 @@ +Import('RTT_ROOT') +Import('rtconfig') +from building import * + +cwd = GetCurrentDir() +src = Glob('core/*.c') + +CPPPATH = [cwd + '/inc'] + +if GetDepend('ADB_SERVICE_SHELL_ENABLE'): + src += ['services/shell_service.c'] + +if GetDepend('ADB_SERVICE_FILESYNC_ENABLE'): + src += ['services/file_sync_service.c'] + +group = DefineGroup('adb', src, depend = ['PKG_USING_ADB'], CPPPATH = CPPPATH) + +Return('group') diff --git a/core/adb.c b/core/adb.c new file mode 100644 index 0000000..5eb9c5b --- /dev/null +++ b/core/adb.c @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2018, Real-Thread Information Technology Ltd + * All rights reserved + * + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-11-11 heyuanjie87 the first version + */ + +#include "adb.h" +#include + +#if 1 +#define DBG_ENABLE +#else +#undef DBG_ENABLE +#endif +#define DBG_SECTION_NAME "ADB" +#define DBG_LEVEL DBG_LOG +#define DBG_COLOR +#include + +#ifdef DBG_ENABLE +#define LOG_CON(c, fmt, ...) \ + if (c) \ + LOG_E(fmt, ##__VA_ARGS__) +#else +#define LOG_CON(...) +#endif + +static rt_list_t _adb_list = RT_LIST_OBJECT_INIT(_adb_list); + +static void write_packet(struct adb *d, struct adb_packet *p) +{ + bool ret; + + p->msg.data_crc32 = adb_packet_checksum(p); + p->msg.magic = p->msg.command ^ 0xffffffff; + ret = adb_packet_enqueue(&d->send_que, p, 100); + if (!ret) + { + adb_packet_delete(p); + LOG_E("write packet fail"); + } +} + +static void send_connect(struct adb *d) +{ + struct adb_packet* cp; + int slen; + char *con_str = "device::" \ + "ro.product.name=mido;" \ + "ro.product.model=rtthread;" \ + "ro.product.device=mido;" \ + "features=cmd,shell_v1"; + + slen = rt_strlen(con_str); + cp = adb_packet_new(slen); + + cp->msg.command = A_CNXN; + // Send the max supported version, but because the transport is + // initialized to A_VERSION_MIN, this will be compatible with every + // device. + cp->msg.arg0 = A_VERSION; + cp->msg.arg1 = MAX_PAYLOAD; + + cp->msg.data_length = slen; + rt_memcpy(cp->payload, con_str, slen); + + write_packet(d, cp); +} + +static void send_ready(struct adb *d, unsigned local, unsigned remote) +{ + struct adb_packet* p = adb_packet_new(0); + + p->msg.command = A_OKAY; + p->msg.arg0 = local; + p->msg.arg1 = remote; + write_packet(d, p); +} + +void adb_send_close(struct adb *d, unsigned local, unsigned remote) +{ + struct adb_packet* p = adb_packet_new(0); + + p->msg.command = A_CLSE; + p->msg.arg0 = local; + p->msg.arg1 = remote; + write_packet(d, p); +} + +static void handle_new_connection(struct adb *d, struct adb_packet *p) +{ + send_connect(d); +} + +static bool _service_enqueue(struct adb_service *ser, struct adb_packet *p) +{ + bool ret; + + ret = ser->ops->enqueue(ser, p, 200); + + return ret; +} + +void adb_packet_handle(struct adb *d, struct adb_packet *p, bool pisnew) +{ + bool del = true; + + if (!p) + return; + + switch(p->msg.command) + { + case A_CNXN: /* CONNECT(version, maxdata, "system-id-string") */ + { + handle_new_connection(d, p); + }break; + case A_OPEN: /* OPEN(local-id, 0, "destination") */ + { + if (p->msg.arg0 != 0 && p->msg.arg1 == 0) + { + int localid; + + localid = adb_service_create(d, p->payload, p->msg.arg0); + if (localid) + { + send_ready(d, localid, p->msg.arg0); + } + else + { + adb_send_close(d, 0, p->msg.arg0); + } + } + }break; + case A_WRTE: + { + if (p->msg.arg0 != 0 && p->msg.arg1 != 0) + { + struct adb_service *ser; + int split = p->split; + + ser = adb_service_find(d, p->msg.arg1, p->msg.arg0); + if (ser) + { + if (_service_enqueue(ser, p)) + { + if (split == 0) + send_ready(d, ser->localid, ser->remoteid); + del = false; + } + } + } + }break; + case A_OKAY: + { + + }break; + case A_CLSE: + { + if (p->msg.arg1 != 0) + { + adb_service_destroy(d, p->msg.arg1, p->msg.arg0); + } + }break; + } + + if (del && pisnew) + adb_packet_delete(p); +} + +struct adb* adb_new(int trtype) +{ + struct adb *d; + + d = rt_calloc(sizeof(struct adb), 1); + if (!d) + return d; + + d->tr_type = trtype; + rt_list_init(&d->node); + rt_list_insert_after(&_adb_list, &d->node); + + rt_list_init(&d->s_list); + rt_mb_init(&d->send_que, "adbsque", d->sque_buf, + sizeof(d->sque_buf)/sizeof(d->sque_buf[0]), 0); + + return d; +} + +void adb_delete(struct adb *d) +{ + struct rt_list_node *node, *head; + + rt_list_remove(&d->node); + + head = &d->s_list; + for (node = head->next; node != head; ) + { + struct adb_service *ser; + struct adb_service_handler *h; + + ser = rt_list_entry(node, struct adb_service, list); + node = node->next; + h = ser->h; + + rt_list_remove(&ser->list); + ser->ops->close(ser); + h->destroy(h, ser); + } + rt_mb_detach(&d->send_que); + rt_free(d); +} diff --git a/core/adb.h b/core/adb.h new file mode 100644 index 0000000..d67a5e3 --- /dev/null +++ b/core/adb.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-11-11 heyuanjie87 the first version + */ + +#ifndef __ADB_H__ +#define __ADB_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_PAYLOAD 4*1024 +#define A_VERSION 0x01000000 + +#define A_SYNC 0x434e5953 +#define A_CNXN 0x4e584e43 +#define A_OPEN 0x4e45504f +#define A_OKAY 0x59414b4f +#define A_CLSE 0x45534c43 +#define A_WRTE 0x45545257 +#define A_AUTH 0x48545541 + +struct adb; +struct adb_tr_ops; +struct adb_service; + +struct adb +{ + rt_list_t node; + rt_list_t s_list; + + adb_queue_t send_que; + int sque_buf[8]; + + int tr_type; + int tr_fd; + int tr_refcnt; + rt_thread_t tr_rtid; + rt_thread_t tr_wtid; + const struct adb_tr_ops *ops; + + long user_data; +}; + +struct adb* adb_new(int trtype); +void adb_delete(struct adb* d); + +void adb_send_close(struct adb *d, unsigned local, unsigned remote); + +void adb_packet_handle(struct adb *d, struct adb_packet *p, bool pisnew); + +struct adb_service* adb_service_find(struct adb *d, unsigned localid, unsigned remoteid); +unsigned adb_service_create(struct adb *d, char *name, unsigned remoteid); +void adb_service_destroy(struct adb *d, unsigned localid, unsigned remoteid); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/adb_pque.c b/core/adb_pque.c new file mode 100644 index 0000000..0cba3bc --- /dev/null +++ b/core/adb_pque.c @@ -0,0 +1,90 @@ +#include + +#if 1 +#define DBG_ENABLE +#else +#undef DBG_ENABLE +#endif +#define DBG_SECTION_NAME "ADB pque" +#define DBG_LEVEL DBG_LOG +#define DBG_COLOR +#include + +#ifdef DBG_ENABLE +#define LOG_CON(c, fmt, ...) \ + if (c) \ + LOG_E(fmt, ##__VA_ARGS__) +#else +#define LOG_CON(...) +#endif + +struct adb_packet* adb_packet_new(int datlen) +{ + struct adb_packet *p; + + p = rt_malloc(sizeof(struct adb_packet) + datlen); + if (p) + { + p->msg.data_length = datlen; + p->split = 0; + } + + LOG_CON(!p, "no mem to new packet"); + + return p; +} + +void adb_packet_delete(struct adb_packet *p) +{ + if (!p) + return; + + rt_free(p); +} + +bool adb_packet_enqueue(adb_queue_t *q, struct adb_packet *p, int ms) +{ + int ret; + int tick; + + tick = rt_tick_from_millisecond(ms); + ret = rt_mb_send_wait(q, (rt_uint32_t)p, tick); + LOG_CON(ret, "enqueue fail"); + + return ret == 0; +} + +bool adb_packet_dequeue(adb_queue_t *q, struct adb_packet **p, int ms) +{ + int ret; + int tick; + + *p = 0; + tick = rt_tick_from_millisecond(ms); + ret = rt_mb_recv(q, (rt_uint32_t*)p, tick); + + return ret == 0; +} + +void adb_packet_clear(adb_queue_t *q) +{ + struct adb_packet *p; + + while (rt_mb_recv(q, (rt_uint32_t*)&p, 0) == 0) + { + adb_packet_delete(p); + } +} + +unsigned adb_packet_checksum(struct adb_packet *p) +{ + unsigned sum = 0; + int i; + + for (i = 0; i < p->msg.data_length; ++i) + { + sum += (unsigned char)(p->payload[i]); + } + + return sum; +} diff --git a/core/adb_service.c b/core/adb_service.c new file mode 100644 index 0000000..7bd2d51 --- /dev/null +++ b/core/adb_service.c @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-11-11 heyuanjie87 the first version + */ + +#include "adb.h" +#include +#include +#include +#include + +#define DBG_ENABLE +#define DBG_SECTION_NAME "ADB" +#define DBG_LEVEL DBG_LOG +#define DBG_COLOR +#include + +static rt_list_t _shr_list = RT_LIST_OBJECT_INIT(_shr_list); + +struct adb_service* adb_service_find(struct adb *d, unsigned localid, unsigned remoteid) +{ + struct rt_list_node *node, *head; + struct adb_service *ret = 0; + + head = &d->s_list; + for (node = head->next; node != head; node = node->next) + { + struct adb_service *ser; + + ser = rt_list_entry(node, struct adb_service, list); + if (ser->localid == localid && ser->remoteid == remoteid) + { + ret = ser; + goto _ok; + } + } + LOG_D("service: no id [%X][%X]", localid, remoteid); +_ok: + return ret; +} + +bool adb_service_sendpacket(struct adb_service *ser, + struct adb_packet *p, int ms) +{ + p->msg.command = A_WRTE; + p->msg.arg0 = ser->localid; + p->msg.arg1 = ser->remoteid; + p->msg.data_crc32 = adb_packet_checksum(p); + p->msg.magic = p->msg.command ^ 0xffffffff; + + return adb_packet_enqueue(&ser->d->send_que, p, ms); +} + +static unsigned _service_init(struct adb_service_handler *h, + struct adb_service *ser, + struct adb *d, unsigned rid, char *args) +{ + if (!ser) + { + return 0; + } + if (ser->ops->open(ser, args) != 0) + { + h->destroy(h, ser); + return 0; + } + + ser->h = h; + ser->d = d; + ser->localid = rt_tick_get(); + ser->remoteid = rid; + rt_list_init(&ser->list); + rt_list_insert_after(&d->s_list, &ser->list); + + return ser->localid; +} + +void adb_service_destroy(struct adb *d, unsigned localid, unsigned remoteid) +{ + struct adb_service *ser; + + ser = adb_service_find(d, localid, remoteid); + if (ser) + { + struct adb_service_handler *h = ser->h; + + rt_list_remove(&ser->list); + ser->ops->close(ser); + h->destroy(h, ser); + } +} + +unsigned adb_service_create(struct adb *d, char *name, unsigned remoteid) +{ + struct rt_list_node *node, *head; + struct adb_service_handler *h; + + head = &_shr_list; + for (node = head->next; node != head; node = node->next) + { + int n; + + h = rt_list_entry(node, struct adb_service_handler, list); + n = rt_strlen(h->name); + if (rt_strncmp(h->name, name, n) == 0) + { + struct adb_service *ser; + + ser = h->create(h); + return _service_init(h, ser, d, remoteid, &name[n]); + } + } + + return 0; +} + +int adb_service_handler_register(struct adb_service_handler *h) +{ + struct rt_list_node *node, *head; + struct adb_service_handler *entry; + + if (!h->name || !h->create || !h->destroy) + return -1; + + head = &_shr_list; + for (node = head->next; node != head; node = node->next) + { + entry = rt_list_entry(node, struct adb_service_handler, list); + if (rt_strcmp(h->name, entry->name) == 0) + return -1; + } + + rt_list_init(&h->list); + rt_list_insert_after(head, &h->list); + + return 0; +} + +struct adb_service* adb_service_alloc(const struct adb_service_ops *ops, + int extsize) +{ + struct adb_service* ser; + + ser = rt_calloc(sizeof(*ser) + extsize, 1); + if (ser && extsize) + { + ser->ops = ops; + ser->extptr = (char*)ser + sizeof(*ser); + } + + return ser; +} + diff --git a/core/transport.c b/core/transport.c new file mode 100644 index 0000000..c8ee84b --- /dev/null +++ b/core/transport.c @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-11-11 heyuanjie87 the first version + */ + +#include "adb.h" +#include "transport.h" +#include +#include + +//#define DBG_ENABLE +#define DBG_SECTION_NAME "ADB TR" +#define DBG_LEVEL DBG_WARNING +#define DBG_COLOR +#include + +#ifdef DBG_ENABLE +#define LOG_CON(c, fmt, ...) \ + if (c) \ + LOG_E(fmt, ##__VA_ARGS__) +#else +#define LOG_CON(...) +#endif + +#define TR_READ_SPLIT_SIZE 512 + +#ifndef ADB_TR_STACK_SIZE +#define ADB_TR_STACK_SIZE 1280 +#endif + +static bool tr_read(struct adb *d, void *buf, int size, int ms) +{ + bool ret = false; + char *pos; + int fail = 0; + + pos = (char*)buf; + while (1) + { + int len; + + if (size == 0) + return true; + len = d->ops->read(d->tr_fd, pos, size); + if (len > 0) + { + pos += len; + size -= len; + fail = 0; + } + else + { + if (fail) + break; + ret = d->ops->poll(d->tr_fd, TRE_READ, ms); + if ((ret == 0) || (ret & TRE_ERROR)) + fail = 1; + } + } + + return ret; +} + +static bool tr_write(struct adb *d, void *buf, int size, int ms) +{ + bool ret = false; + char *pos; + int fail = 0; + + pos = (char*)buf; + while (1) + { + int len; + + if (size == 0) + return true; + len = d->ops->write(d->tr_fd, pos, size); + if (len > 0) + { + pos += len; + size -= len; + fail = 0; + } + else + { + if (fail) + break; + ret = d->ops->poll(d->tr_fd, TRE_WRITE, ms); + if ((ret == 0) || (ret & TRE_ERROR)) + fail = 1; + } + } + + return ret; +} + +static bool write_packet(struct adb *d, struct adb_packet *p) +{ + if (!tr_write(d, &p->msg, sizeof(p->msg), 100)) + return false; + + if (!tr_write(d, p->payload, p->msg.data_length, 200)) + return false; + + return true; +} + +static struct adb_packet* _packet_msgdup(struct adb_msg *msg, int datlen) +{ + struct adb_packet *p; + + p = adb_packet_new(datlen); + if (p) + { + rt_memcpy(&p->msg, msg, sizeof(*msg)); + p->msg.data_length = datlen; + } + LOG_CON(!p, "msgdup - no mem"); + return p; +} + +static bool check_header(struct adb_packet *p) +{ + if (p->msg.magic != (p->msg.command ^ 0xffffffff)) + { + LOG_E("magic command err"); + return false; + } + + if (p->msg.data_length > MAX_PAYLOAD) + { + LOG_E("payload too long"); + return false; + } + + return true; +} + +static int read_packet_spilt(struct adb *d, struct adb_packet *ck) +{ + struct adb_packet *p; + int ret; + + ret = d->ops->poll(d->tr_fd, TRE_READ, 1000); + if (ret & TRE_ERROR) + return -1; + if (ret == 0) + return 0; + + if (!tr_read(d, &ck->msg, sizeof(struct adb_msg), 0)) + return -1; + if (!check_header(ck)) + { + /* clear remain data */ + while (tr_read(d, ck, sizeof(*ck), 20)); + return 1; + } + + LOG_D("r:%c%c%c%c,len %d", ((char*) (&(ck->msg.command)))[0], + ((char*) (&(ck->msg.command)))[1], + ((char*) (&(ck->msg.command)))[2], + ((char*) (&(ck->msg.command)))[3], + ck->msg.data_length); + + if (ck->msg.data_length == 0) + { + adb_packet_handle(d, ck, false); + } + else + { + ck->split = (ck->msg.data_length + TR_READ_SPLIT_SIZE - 1)/ + TR_READ_SPLIT_SIZE; + + while (ck->msg.data_length) + { + int rlen = ck->msg.data_length > TR_READ_SPLIT_SIZE ? + TR_READ_SPLIT_SIZE : ck->msg.data_length; + + p = _packet_msgdup(&ck->msg, rlen); + if (!p)//todo + return 2; + + if (!tr_read(d, p->payload, rlen, 100)) + { + adb_packet_delete(p); + return -1; + } + ck->msg.data_length -= rlen; + p->split = --(ck->split); + adb_packet_handle(d, p, true); + } + } + + return 0; +} + +static void transport_unref(struct adb *d) +{ + d->tr_refcnt --;//todo lock + if (d->tr_refcnt == 0) + { + d->ops->close(d->tr_fd); + adb_delete(d); + } +} + +static bool send_sync(struct adb *d, unsigned a0, unsigned a1) +{ + struct adb_packet *p; + + p = adb_packet_new(0); + if (!p) + return false; + + p->msg.command = A_SYNC; + p->msg.arg0 = a0; + p->msg.arg1 = a1; + p->msg.magic = A_SYNC ^ 0xffffffff; + if (!adb_packet_enqueue(&d->send_que, p, 100)) + { + adb_packet_delete(p); + return false; + } + + return true; +} + +static void read_thread(void *arg) +{ + struct adb *d = (struct adb *)arg; + struct adb_packet p; + int ret; + + d->tr_refcnt ++; + if (!send_sync(d, 1, 1)) + goto _exit; + + while (1) + { + ret = read_packet_spilt(d, &p); + if (ret == -1) + { + LOG_D("remote read failed"); + break; + } + } + + if (!send_sync(d, 0, 0)) + { + + } + +_exit: + transport_unref(d); +} + +static void write_thread(void *arg) +{ + struct adb *d = (struct adb *)arg; + struct adb_packet *p = 0; + bool ret; + + d->tr_refcnt ++; + + /* wait read thread online */ + if (!adb_packet_dequeue(&d->send_que, &p, 500)) + goto _exit; + adb_packet_delete(p); + + while (1) + { + if (!adb_packet_dequeue(&d->send_que, &p, 50)) + continue; + + LOG_D("w:%c%c%c%c,len %d", ((char*) (&(pkt->msg.command)))[0], + ((char*) (&(pkt->msg.command)))[1], + ((char*) (&(pkt->msg.command)))[2], + ((char*) (&(pkt->msg.command)))[3], + pkt->msg.data_length); + + if (p->msg.command == A_SYNC) + { + if (p->msg.arg0 == 0) + { + LOG_D("transport SYNC offline"); + adb_packet_delete(p); + break; + } + } + + ret = write_packet(d, p); + adb_packet_delete(p); + if (!ret) + { + LOG_D("remote write failed"); + break; + } + } + +_exit: + transport_unref(d); +} + +int adb_transport_register(int trtype, int fd, const struct adb_tr_ops *ops) +{ + struct adb *d; + int ret; + + d = adb_new(trtype); + if (!d) + return -1; + + d->ops = ops; + d->tr_fd = fd; + + d->tr_wtid = rt_thread_create("adb-trw", + write_thread, + d, + ADB_TR_STACK_SIZE, + 22, + 20); + d->tr_rtid = rt_thread_create("adb-trr", + read_thread, + d, + ADB_TR_STACK_SIZE, + 22, + 20); + + if (rt_thread_startup(d->tr_wtid) == 0) + { + ret = rt_thread_startup(d->tr_rtid); + } + + return ret; +} diff --git a/core/transport.h b/core/transport.h new file mode 100644 index 0000000..1b84e76 --- /dev/null +++ b/core/transport.h @@ -0,0 +1,23 @@ +#ifndef __ADB_TR_H_ +#define __ADB_TR_H_ + +struct adb; + +#define TR_TCPIP 1 +#define TR_USB 2 + +#define TRE_READ 0x01 +#define TRE_WRITE 0x02 +#define TRE_ERROR 0x04 + +struct adb_tr_ops +{ + int (*read)(int fd, void *buf, int size); + int (*write)(int fd, void *buf, int size); + int (*poll)(int fd, int evt, int ms); + void (*close)(int fd); +}; + +int adb_transport_register(int trtype, int fd, const struct adb_tr_ops *ops); + +#endif diff --git a/core/transport_socket.c b/core/transport_socket.c new file mode 100644 index 0000000..7ee3768 --- /dev/null +++ b/core/transport_socket.c @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-11-11 heyuanjie87 the first version + */ + +#include +#include +#include +#include +#include "transport.h" + +//#define DBG_ENABLE +#define DBG_SECTION_NAME "ADB iosk" +#define DBG_LEVEL DBG_LOG +#define DBG_COLOR +#include + +static int started = 0; +static int is_running = 0; + +static int sk_read(int fd, void *buf, int size) +{ + return recv(fd, buf, size, 0); +} + +static int sk_write(int fd, void *buf, int size) +{ + return send(fd, buf, size, 0); +} + +static int sk_poll(int fd, int evt, int ms) +{ + struct pollfd pfd = {0}; + int ret; + + pfd.fd = fd; + if (evt & TRE_READ) + pfd.events |= POLLIN; + if (evt & TRE_WRITE) + pfd.events |= POLLOUT; + + ret = poll(&pfd, 1, ms); + if (ret == 0) + return 0; + + ret = 0; + if (pfd.revents & POLLIN) + ret |= TRE_READ; + if (pfd.revents & POLLOUT) + ret |= TRE_WRITE; + if (pfd.revents & (POLLERR | POLLHUP)) + ret |= TRE_ERROR; + + return ret; +} + +static void sk_close(int fd) +{ + closesocket(fd); +} + +static const struct adb_tr_ops _ops = +{ + sk_read, + sk_write, + sk_poll, + sk_close, +}; + +static void tcp_server(void *arg) +{ + int ret; + int sock, connected; + struct sockaddr_in server_addr, client_addr; + + struct timeval timeout; + fd_set readset; + socklen_t sin_size = sizeof(struct sockaddr_in); + int port; + + is_running = 1; + port = (int)arg; + + if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) + { + LOG_E("Create socket error"); + goto __exit; + } + + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + server_addr.sin_addr.s_addr = INADDR_ANY; + rt_memset(&(server_addr.sin_zero), 0x0, sizeof(server_addr.sin_zero)); + + if (bind(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) + { + LOG_E("Unable to bind"); + goto __exit; + } + + if (listen(sock, 1) == -1) + { + LOG_E("Listen error"); + goto __exit; + } + + started = 1; + + while (is_running) + { + FD_ZERO(&readset); + FD_SET(sock, &readset); + + timeout.tv_sec = 3; + timeout.tv_usec = 0; + + /* Wait for read or write */ + if (select(sock + 1, &readset, RT_NULL, RT_NULL, &timeout) == 0) + continue; + + /* 接受一个客户端连接socket的请求,这个函数调用是阻塞式的 */ + connected = accept(sock, (struct sockaddr *)&client_addr, &sin_size); + /* 返回的是连接成功的socket */ + if (connected < 0) + { + LOG_E("accept connection failed! errno = %d", errno); + continue; + } + LOG_D("accept connection"); + + ret = adb_transport_register(TR_TCPIP, connected, &_ops); + if (ret != 0) + { + closesocket(connected); + LOG_E("register transport tcpip fail"); + } + } + +__exit: + if (sock >= 0) + { + closesocket(sock); + } + started = 0; + is_running = 0; +} + +int adb_tcpip(int port) +{ + rt_thread_t tid; + int ret; + + if (is_running) + { + LOG_E("adbd tcpip is exist"); + } + + tid = rt_thread_create("adbd-sk", + tcp_server, + (void *)port, + 1024, + 22, + 20); + if (tid) + { + ret = rt_thread_startup(tid); + } + + return ret; +} + +int adb_socket_init(void) +{ + int ret; + + ret = adb_tcpip(5555); + + return ret; +} +INIT_APP_EXPORT(adb_socket_init); diff --git a/docs/OVERVIEW.TXT b/docs/OVERVIEW.TXT new file mode 100644 index 0000000..29a6992 --- /dev/null +++ b/docs/OVERVIEW.TXT @@ -0,0 +1,138 @@ +Implementation notes regarding ADB. + +I. General Overview: + +The Android Debug Bridge (ADB) is used to: + +- keep track of all Android devices and emulators instances + connected to or running on a given host developer machine + +- implement various control commands (e.g. "adb shell", "adb pull", etc.) + for the benefit of clients (command-line users, or helper programs like + DDMS). These commands are called 'services' in ADB. + +As a whole, everything works through the following components: + + 1. The ADB server + + This is a background process that runs on the host machine. Its purpose + is to sense the USB ports to know when devices are attached/removed, + as well as when emulator instances start/stop. + + It thus maintains a list of "connected devices" and assigns a 'state' + to each one of them: OFFLINE, BOOTLOADER, RECOVERY or ONLINE (more on + this below). + + The ADB server is really one giant multiplexing loop whose purpose is + to orchestrate the exchange of data (packets, really) between clients, + services and devices. + + + 2. The ADB daemon (adbd) + + The 'adbd' program runs as a background process within an Android device + or emulated system. Its purpose is to connect to the ADB server + (through USB for devices, through TCP for emulators) and provide a + few services for clients that run on the host. + + The ADB server considers that a device is ONLINE when it has successfully + connected to the adbd program within it. Otherwise, the device is OFFLINE, + meaning that the ADB server detected a new device/emulator, but could not + connect to the adbd daemon. + + The BOOTLOADER and RECOVERY states correspond to alternate states of + devices when they are in the bootloader or recovery mode. + + 3. The ADB command-line client + + The 'adb' command-line program is used to run adb commands from a shell + or a script. It first tries to locate the ADB server on the host machine, + and will start one automatically if none is found. + + Then, the client sends its service requests to the ADB server. + + Currently, a single 'adb' binary is used for both the server and client. + this makes distribution and starting the server easier. + + + 4. Services + + There are essentially two kinds of services that a client can talk to. + + Host Services: + These services run within the ADB Server and thus do not need to + communicate with a device at all. A typical example is "adb devices" + which is used to return the list of currently known devices and their + states. They are a few other services though. + + Local Services: + These services either run within the adbd daemon, or are started by + it on the device. The ADB server is used to multiplex streams + between the client and the service running in adbd. In this case + its role is to initiate the connection, then of being a pass-through + for the data. + + +II. Protocol details: + + 1. Client <-> Server protocol: + + This details the protocol used between ADB clients and the ADB + server itself. The ADB server listens on TCP:localhost:5037. + + A client sends a request using the following format: + + 1. A 4-byte hexadecimal string giving the length of the payload + 2. Followed by the payload itself. + + For example, to query the ADB server for its internal version number, + the client will do the following: + + 1. Connect to tcp:localhost:5037 + 2. Send the string "000Chost:version" to the corresponding socket + + The 'host:' prefix is used to indicate that the request is addressed + to the server itself (we will talk about other kinds of requests later). + The content length is encoded in ASCII for easier debugging. + + The server should answer a request with one of the following: + + 1. For success, the 4-byte "OKAY" string + + 2. For failure, the 4-byte "FAIL" string, followed by a + 4-byte hex length, followed by a string giving the reason + for failure. + + 3. As a special exception, for 'host:version', a 4-byte + hex string corresponding to the server's internal version number + + Note that the connection is still alive after an OKAY, which allows the + client to make other requests. But in certain cases, an OKAY will even + change the state of the connection. + + For example, the case of the 'host:transport:' request, + where '' is used to identify a given device/emulator; after + the "OKAY" answer, all further requests made by the client will go + directly to the corresponding adbd daemon. + + The file SERVICES.TXT lists all services currently implemented by ADB. + + + 2. Transports: + + An ADB transport models a connection between the ADB server and one device + or emulator. There are currently two kinds of transports: + + - USB transports, for physical devices through USB + + - Local transports, for emulators running on the host, connected to + the server through TCP + + In theory, it should be possible to write a local transport that proxies + a connection between an ADB server and a device/emulator connected to/ + running on another machine. This hasn't been done yet though. + + Each transport can carry one or more multiplexed streams between clients + and the device/emulator they point to. The ADB server must handle + unexpected transport disconnections (e.g. when a device is physically + unplugged) properly. diff --git a/docs/PROTOCOL.TXT b/docs/PROTOCOL.TXT new file mode 100644 index 0000000..55ea87f --- /dev/null +++ b/docs/PROTOCOL.TXT @@ -0,0 +1,287 @@ + +--- a replacement for aproto ------------------------------------------- + +When it comes down to it, aproto's primary purpose is to forward +various streams between the host computer and client device (in either +direction). + +This replacement further simplifies the concept, reducing the protocol +to an extremely straightforward model optimized to accomplish the +forwarding of these streams and removing additional state or +complexity. + +The host side becomes a simple comms bridge with no "UI", which will +be used by either commandline or interactive tools to communicate with +a device or emulator that is connected to the bridge. + +The protocol is designed to be straightforward and well-defined enough +that if it needs to be reimplemented in another environment (Java +perhaps), there should not problems ensuring perfect interoperability. + +The protocol discards the layering aproto has and should allow the +implementation to be much more robust. + + +--- protocol overview and basics --------------------------------------- + +The transport layer deals in "messages", which consist of a 24 byte +header followed (optionally) by a payload. The header consists of 6 +32 bit words which are sent across the wire in little endian format. + +struct message { + unsigned command; /* command identifier constant (A_CNXN, ...) */ + unsigned arg0; /* first argument */ + unsigned arg1; /* second argument */ + unsigned data_length; /* length of payload (0 is allowed) */ + unsigned data_crc32; /* crc32 of data payload */ + unsigned magic; /* command ^ 0xffffffff */ +}; + +Receipt of an invalid message header, corrupt message payload, or an +unrecognized command MUST result in the closing of the remote +connection. The protocol depends on shared state and any break in the +message stream will result in state getting out of sync. + +The following sections describe the six defined message types in +detail. Their format is COMMAND(arg0, arg1, payload) where the payload +is represented by a quoted string or an empty string if none should be +sent. + +The identifiers "local-id" and "remote-id" are always relative to the +*sender* of the message, so for a receiver, the meanings are effectively +reversed. + + + +--- CONNECT(version, maxdata, "system-identity-string") ---------------- + +Command constant: A_CNXN + +The CONNECT message establishes the presence of a remote system. +The version is used to ensure protocol compatibility and maxdata +declares the maximum message body size that the remote system +is willing to accept. + +Currently, version=0x01000000 and maxdata=256*1024. Older versions of adb +hard-coded maxdata=4096, so CONNECT and AUTH packets sent to a device must not +be larger than that because they're sent before the CONNECT from the device +that tells the adb server what maxdata the device can support. + +Both sides send a CONNECT message when the connection between them is +established. Until a CONNECT message is received no other messages may +be sent. Any messages received before a CONNECT message MUST be ignored. + +If a CONNECT message is received with an unknown version or insufficiently +large maxdata value, the connection with the other side must be closed. + +The system identity string should be "::" +where systemtype is "bootloader", "device", or "host", serialno is some +kind of unique ID (or empty), and banner is a human-readable version +or identifier string. The banner is used to transmit useful properties. + + +--- AUTH(type, 0, "data") ---------------------------------------------- + +Command constant: A_AUTH + +The AUTH message informs the recipient that authentication is required to +connect to the sender. If type is TOKEN(1), data is a random token that +the recipient can sign with a private key. The recipient replies with an +AUTH packet where type is SIGNATURE(2) and data is the signature. If the +signature verification succeeds, the sender replies with a CONNECT packet. + +If the signature verification fails, the sender replies with a new AUTH +packet and a new random token, so that the recipient can retry signing +with a different private key. + +Once the recipient has tried all its private keys, it can reply with an +AUTH packet where type is RSAPUBLICKEY(3) and data is the public key. If +possible, an on-screen confirmation may be displayed for the user to +confirm they want to install the public key on the device. + + +--- OPEN(local-id, 0, "destination") ----------------------------------- + +Command constant: A_OPEN + +The OPEN message informs the recipient that the sender has a stream +identified by local-id that it wishes to connect to the named +destination in the message payload. The local-id may not be zero. + +The OPEN message MUST result in either a READY message indicating that +the connection has been established (and identifying the other end) or +a CLOSE message, indicating failure. An OPEN message also implies +a READY message sent at the same time. + +Common destination naming conventions include: + +* "tcp::" - host may be omitted to indicate localhost +* "udp::" - host may be omitted to indicate localhost +* "local-dgram:" +* "local-stream:" +* "shell" - local shell service +* "upload" - service for pushing files across (like aproto's /sync) +* "fs-bridge" - FUSE protocol filesystem bridge + + +--- READY(local-id, remote-id, "") ------------------------------------- + +Command constant: A_OKAY + +The READY message informs the recipient that the sender's stream +identified by local-id is ready for write messages and that it is +connected to the recipient's stream identified by remote-id. + +Neither the local-id nor the remote-id may be zero. + +A READY message containing a remote-id which does not map to an open +stream on the recipient's side is ignored. The stream may have been +closed while this message was in-flight. + +The local-id is ignored on all but the first READY message (where it +is used to establish the connection). Nonetheless, the local-id MUST +not change on later READY messages sent to the same stream. + + +--- WRITE(local-id, remote-id, "data") --------------------------------- + +Command constant: A_WRTE + +The WRITE message sends data to the recipient's stream identified by +remote-id. The payload MUST be <= maxdata in length. + +A WRITE message containing a remote-id which does not map to an open +stream on the recipient's side is ignored. The stream may have been +closed while this message was in-flight. + +A WRITE message may not be sent until a READY message is received. +Once a WRITE message is sent, an additional WRITE message may not be +sent until another READY message has been received. Recipients of +a WRITE message that is in violation of this requirement will CLOSE +the connection. + + +--- CLOSE(local-id, remote-id, "") ------------------------------------- + +Command constant: A_CLSE + +The CLOSE message informs recipient that the connection between the +sender's stream (local-id) and the recipient's stream (remote-id) is +broken. The remote-id MUST not be zero, but the local-id MAY be zero +if this CLOSE indicates a failed OPEN. + +A CLOSE message containing a remote-id which does not map to an open +stream on the recipient's side is ignored. The stream may have +already been closed by the recipient while this message was in-flight. + +The recipient should not respond to a CLOSE message in any way. The +recipient should cancel pending WRITEs or CLOSEs, but this is not a +requirement, since they will be ignored. + + +--- SYNC(online, sequence, "") ----------------------------------------- + +Command constant: A_SYNC + +The SYNC message is used by the io pump to make sure that stale +outbound messages are discarded when the connection to the remote side +is broken. It is only used internally to the bridge and never valid +to send across the wire. + +* when the connection to the remote side goes offline, the io pump + sends a SYNC(0, 0) and starts discarding all messages +* when the connection to the remote side is established, the io pump + sends a SYNC(1, token) and continues to discard messages +* when the io pump receives a matching SYNC(1, token), it once again + starts accepting messages to forward to the remote side + + +--- message command constants ------------------------------------------ + +#define A_SYNC 0x434e5953 +#define A_CNXN 0x4e584e43 +#define A_AUTH 0x48545541 +#define A_OPEN 0x4e45504f +#define A_OKAY 0x59414b4f +#define A_CLSE 0x45534c43 +#define A_WRTE 0x45545257 + + + +--- implementation details --------------------------------------------- + +The core of the bridge program will use three threads. One thread +will be a select/epoll loop to handle io between various inbound and +outbound connections and the connection to the remote side. + +The remote side connection will be implemented as two threads (one for +reading, one for writing) and a datagram socketpair to provide the +channel between the main select/epoll thread and the remote connection +threadpair. The reason for this is that for usb connections, the +kernel interface on linux and osx does not allow you to do meaningful +nonblocking IO. + +The endian swapping for the message headers will happen (as needed) in +the remote connection threadpair and that the rest of the program will +always treat message header values as native-endian. + +The bridge program will be able to have a number of mini-servers +compiled in. They will be published under known names (examples +"shell", "fs-bridge", etc) and upon receiving an OPEN() to such a +service, the bridge program will create a stream socketpair and spawn +a thread or subprocess to handle the io. + + +--- simplified / embedded implementation ------------------------------- + +For limited environments, like the bootloader, it is allowable to +support a smaller, fixed number of channels using pre-assigned channel +ID numbers such that only one stream may be connected to a bootloader +endpoint at any given time. The protocol remains unchanged, but the +"embedded" version of it is less dynamic. + +The bootloader will support two streams. A "bootloader:debug" stream, +which may be opened to get debug messages from the bootloader and a +"bootloader:control", stream which will support the set of basic +bootloader commands. + +Example command stream dialogues: + "flash_kernel,2515049,........\n" "okay\n" + "flash_ramdisk,5038,........\n" "fail,flash write error\n" + "bogus_command......" + + +--- future expansion --------------------------------------------------- + +I plan on providing either a message or a special control stream so that +the client device could ask the host computer to setup inbound socket +translations on the fly on behalf of the client device. + + +The initial design does handshaking to provide flow control, with a +message flow that looks like: + + >OPEN WRITE WRITE WRITE +server: "OKAY" + +client: +server: "FAIL" + diff --git a/docs/SERVICES.TXT b/docs/SERVICES.TXT new file mode 100644 index 0000000..30c21f7 --- /dev/null +++ b/docs/SERVICES.TXT @@ -0,0 +1,259 @@ +This file tries to document all requests a client can make +to the ADB server of an adbd daemon. See the OVERVIEW.TXT document +to understand what's going on here. + +HOST SERVICES: + +host:version + Ask the ADB server for its internal version number. + + As a special exception, the server will respond with a 4-byte + hex string corresponding to its internal version number, without + any OKAY or FAIL. + +host:kill + Ask the ADB server to quit immediately. This is used when the + ADB client detects that an obsolete server is running after an + upgrade. + +host:devices +host:devices-l + Ask to return the list of available Android devices and their + state. devices-l includes the device paths in the state. + After the OKAY, this is followed by a 4-byte hex len, + and a string that will be dumped as-is by the client, then + the connection is closed + +host:track-devices + This is a variant of host:devices which doesn't close the + connection. Instead, a new device list description is sent + each time a device is added/removed or the state of a given + device changes (hex4 + content). This allows tools like DDMS + to track the state of connected devices in real-time without + polling the server repeatedly. + +host:emulator: + This is a special query that is sent to the ADB server when a + new emulator starts up. is a decimal number corresponding + to the emulator's ADB control port, i.e. the TCP port that the + emulator will forward automatically to the adbd daemon running + in the emulator system. + + This mechanism allows the ADB server to know when new emulator + instances start. + +host:transport: + Ask to switch the connection to the device/emulator identified by + . After the OKAY response, every client request will + be sent directly to the adbd daemon running on the device. + (Used to implement the -s option) + +host:transport-usb + Ask to switch the connection to one device connected through USB + to the host machine. This will fail if there are more than one such + devices. (Used to implement the -d convenience option) + +host:transport-local + Ask to switch the connection to one emulator connected through TCP. + This will fail if there is more than one such emulator instance + running. (Used to implement the -e convenience option) + +host:transport-any + Another host:transport variant. Ask to switch the connection to + either the device or emulator connect to/running on the host. + Will fail if there is more than one such device/emulator available. + (Used when neither -s, -d or -e are provided) + +host-serial:: + This is a special form of query, where the 'host-serial::' + prefix can be used to indicate that the client is asking the ADB server + for information related to a specific device. can be in one + of the format described below. + +host-usb: + A variant of host-serial used to target the single USB device connected + to the host. This will fail if there is none or more than one. + +host-local: + A variant of host-serial used to target the single emulator instance + running on the host. This will fail if there is none or more than one. + +host: + When asking for information related to a device, 'host:' can also be + interpreted as 'any single device or emulator connected to/running on + the host'. + +:get-product + XXX + +:get-serialno + Returns the serial number of the corresponding device/emulator. + Note that emulator serial numbers are of the form "emulator-5554" + +:get-devpath + Returns the device path of the corresponding device/emulator. + +:get-state + Returns the state of a given device as a string. + +:forward:; + Asks the ADB server to forward local connections from + to the address on a given device. + + There, can be one of the + host-serial/host-usb/host-local/host prefixes as described previously + and indicates which device/emulator to target. + + the format of is one of: + + tcp: -> TCP connection on localhost: + local: -> Unix local domain socket on + + the format of is one of: + + tcp: -> TCP localhost: on device + local: -> Unix local domain socket on device + jdwp: -> JDWP thread on VM process + + or even any one of the local services described below. + +:forward:norebind:; + Same as :forward:; except that it will + fail it there is already a forward connection from . + + Used to implement 'adb forward --no-rebind ' + +:killforward: + Remove any existing forward local connection from . + This is used to implement 'adb forward --remove ' + +:killforward-all + Remove all forward network connections. + This is used to implement 'adb forward --remove-all'. + +:list-forward + List all existing forward connections from this server. + This returns something that looks like the following: + + : The length of the payload, as 4 hexadecimal chars. + : A series of lines of the following format: + + " " " " "\n" + + Where is a device serial number. + is the host-specific endpoint (e.g. tcp:9000). + is the device-specific endpoint. + + Used to implement 'adb forward --list'. + +LOCAL SERVICES: + +All the queries below assumed that you already switched the transport +to a real device, or that you have used a query prefix as described +above. + +shell:command arg1 arg2 ... + Run 'command arg1 arg2 ...' in a shell on the device, and return + its output and error streams. Note that arguments must be separated + by spaces. If an argument contains a space, it must be quoted with + double-quotes. Arguments cannot contain double quotes or things + will go very wrong. + + Note that this is the non-interactive version of "adb shell" + +shell: + Start an interactive shell session on the device. Redirect + stdin/stdout/stderr as appropriate. Note that the ADB server uses + this to implement "adb shell", but will also cook the input before + sending it to the device (see interactive_shell() in commandline.c) + +remount: + Ask adbd to remount the device's filesystem in read-write mode, + instead of read-only. This is usually necessary before performing + an "adb sync" or "adb push" request. + + This request may not succeed on certain builds which do not allow + that. + +dev: + Opens a device file and connects the client directly to it for + read/write purposes. Useful for debugging, but may require special + privileges and thus may not run on all devices. is a full + path from the root of the filesystem. + +tcp: + Tries to connect to tcp port on localhost. + +tcp:: + Tries to connect to tcp port on machine from + the device. This can be useful to debug some networking/proxy + issues that can only be revealed on the device itself. + +local: + Tries to connect to a Unix domain socket on the device + +localreserved: +localabstract: +localfilesystem: + Variants of local: that are used to access other Android + socket namespaces. + +framebuffer: + This service is used to send snapshots of the framebuffer to a client. + It requires sufficient privileges but works as follow: + + After the OKAY, the service sends 16-byte binary structure + containing the following fields (little-endian format): + + depth: uint32_t: framebuffer depth + size: uint32_t: framebuffer size in bytes + width: uint32_t: framebuffer width in pixels + height: uint32_t: framebuffer height in pixels + + With the current implementation, depth is always 16, and + size is always width*height*2 + + Then, each time the client wants a snapshot, it should send + one byte through the channel, which will trigger the service + to send it 'size' bytes of framebuffer data. + + If the adbd daemon doesn't have sufficient privileges to open + the framebuffer device, the connection is simply closed immediately. + +jdwp: + Connects to the JDWP thread running in the VM of process . + +track-jdwp + This is used to send the list of JDWP pids periodically to the client. + The format of the returned data is the following: + + : the length of all content as a 4-char hexadecimal string + : a series of ASCII lines of the following format: + "\n" + + This service is used by DDMS to know which debuggable processes are running + on the device/emulator. + + Note that there is no single-shot service to retrieve the list only once. + +sync: + This starts the file synchronization service, used to implement "adb push" + and "adb pull". Since this service is pretty complex, it will be detailed + in a companion document named SYNC.TXT + +reverse: + This implements the 'adb reverse' feature, i.e. the ability to reverse + socket connections from a device to the host. is one + of the forwarding commands that are described above, as in: + + list-forward + forward:; + forward:norebind:; + killforward-all + killforward: + + Note that in this case, corresponds to the socket on the device + and corresponds to the socket on the host. + + The output of reverse:list-forward is the same as host:list-forward + except that will be just 'host'. diff --git a/docs/SYNC.TXT b/docs/SYNC.TXT new file mode 100644 index 0000000..4445a76 --- /dev/null +++ b/docs/SYNC.TXT @@ -0,0 +1,80 @@ +This file tries to document file-related requests a client can make +to the ADB server of an adbd daemon. See the OVERVIEW.TXT document +to understand what's going on here. See the SERVICES.TXT to learn more +about the other requests that are possible. + +SYNC SERVICES: + + +Requesting the sync service ("sync:") using the protocol as described in +SERVICES.TXT sets the connection in sync mode. This mode is a binary mode that +differs from the regular adb protocol. The connection stays in sync mode until +explicitly terminated (see below). + +After the initial "sync:" command is sent the server must respond with either +"OKAY" or "FAIL" as per usual. + +In sync mode both the server and the client will frequently use eight-byte +packets to communicate. In this document these are called sync requests and sync +responses. The first four bytes are an id that specifies the sync request. It is +represented by four utf-8 characters. The last four bytes are a Little-Endian +integer, with various uses. This number will be called "length" below. In fact +all binary integers are Little-Endian in the sync mode. Sync mode is +implicitly exited after each sync request, and normal adb communication +follows as described in SERVICES.TXT. + +The following sync requests are accepted: +LIST - List the files in a folder +RECV - Retrieve a file from device +SEND - Send a file to device +STAT - Stat a file + +All of the sync requests above must be followed by "length": the number of +bytes containing a utf-8 string with a remote filename. + +LIST: +Lists files in the directory specified by the remote filename. The server will +respond with zero or more directory entries or "dents". + +The directory entries will be returned in the following form +1. A four-byte sync response id "DENT" +2. A four-byte integer representing file mode. +3. A four-byte integer representing file size. +4. A four-byte integer representing last modified time. +5. A four-byte integer representing file name length. +6. length number of bytes containing an utf-8 string representing the file + name. + +When a sync response "DONE" is received the listing is done. + +SEND: +The remote file name is split into two parts separated by the last +comma (","). The first part is the actual path, while the second is a decimal +encoded file mode containing the permissions of the file on device. + +Note that some file types will be deleted before the copying starts, and if +the transfer fails. Some file types will not be deleted, which allows + adb push disk_image /some_block_device +to work. + +After this the actual file is sent in chunks. Each chunk has the following +format. +A sync request with id "DATA" and length equal to the chunk size. After +follows chunk size number of bytes. This is repeated until the file is +transferred. Each chunk must not be larger than 64k. + +When the file is transferred a sync request "DONE" is sent, where length is set +to the last modified time for the file. The server responds to this last +request (but not to chunk requests) with an "OKAY" sync response (length can +be ignored). + + +RECV: +Retrieves a file from device to a local file. The remote path is the path to +the file that will be returned. Just as for the SEND sync request the file +received is split up into chunks. The sync response id is "DATA" and length is +the chunk size. After follows chunk size number of bytes. This is repeated +until the file is transferred. Each chunk will not be larger than 64k. + +When the file is transferred a sync response "DONE" is retrieved where the +length can be ignored. diff --git a/inc/adb_pque.h b/inc/adb_pque.h new file mode 100644 index 0000000..4618de5 --- /dev/null +++ b/inc/adb_pque.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-11-28 heyuanjie87 the first version + */ + +#ifndef __ADB_PQUE_H_ +#define __ADB_PQUE_H_ + +#include "adbtypes.h" + +struct adb_packet* adb_packet_new(int datlen); +void adb_packet_delete(struct adb_packet *p); +bool adb_packet_enqueue(adb_queue_t *q, struct adb_packet *p, int ms); +bool adb_packet_dequeue(adb_queue_t *q, struct adb_packet **p, int ms); +void adb_packet_clear(adb_queue_t *q); +unsigned adb_packet_checksum(struct adb_packet *p); + +#endif diff --git a/inc/adb_service.h b/inc/adb_service.h new file mode 100644 index 0000000..a1ffb89 --- /dev/null +++ b/inc/adb_service.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-11-11 heyuanjie87 the first version + */ + +#ifndef __ADB_SERVICE_H__ +#define __ADB_SERVICE_H__ + +#include "adb_pque.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct adb; +struct adb_service; + +struct adb_service_ops +{ + int (*open)(struct adb_service * ser, char *args); + int (*close)(struct adb_service * ser); + bool (*enqueue)(struct adb_service * ser, struct adb_packet *p, int ms); +}; + +struct adb_service_handler +{ + rt_list_t list; + const char *name; + struct adb_service* (*create)(struct adb_service_handler *h); + void (*destroy)(struct adb_service_handler *h, struct adb_service *s); +}; + +struct adb_service +{ + rt_list_t list; + int online; + + unsigned localid; + unsigned remoteid; + struct adb *d; + struct adb_service_handler *h; + const struct adb_service_ops *ops; + void *extptr; +}; + + +bool adb_service_sendpacket(struct adb_service *ser, + struct adb_packet *p, int ms); + + +int adb_service_handler_register(struct adb_service_handler *h); +struct adb_service* adb_service_alloc(const struct adb_service_ops *ops, + int extsize); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/inc/adbtypes.h b/inc/adbtypes.h new file mode 100644 index 0000000..dec8bf6 --- /dev/null +++ b/inc/adbtypes.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-11-28 heyuanjie87 the first version + */ + +#ifndef __ADBTYPES_H_ +#define __ADBTYPES_H_ + +#include +#include + +typedef struct rt_mailbox adb_queue_t; + +struct adb_msg +{ + unsigned command; /* command identifier constant (A_CNXN, ...) */ + unsigned arg0; /* first argument */ + unsigned arg1; /* second argument */ + unsigned data_length; /* length of payload (0 is allowed) */ + unsigned data_crc32; /* crc32 of data payload */ + unsigned magic; /* command ^ 0xffffffff */ +}; + +struct adb_packet +{ + int split; /* split MAX_PAYLOAD into small packet */ + struct adb_msg msg; + char payload[1]; /* variable-length */ +}; + +#endif diff --git a/services/file_sync_service.c b/services/file_sync_service.c new file mode 100644 index 0000000..d1ebba4 --- /dev/null +++ b/services/file_sync_service.c @@ -0,0 +1,609 @@ +/* + * Copyright (c) 2018, Real-Thread Information Technology Ltd + * All rights reserved + * + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-11-11 heyuanjie87 the first version + */ + +#include +#include "file_sync_service.h" +#include + +#ifndef FILESYNC_STACK_SIZE +#define FILESYNC_STACK_SIZE 2304 +#endif + +#define DBG_ENABLE +#define DBG_SECTION_NAME "ADB sync" +#define DBG_LEVEL DBG_LOG +#define DBG_COLOR +#include + +#define DATA_WRITE 1 //used for test,0:not write file + +struct filesync_ext +{ + rt_thread_t worker; + adb_queue_t recv_que; + int rque_buf[4]; + struct adb_packet *cur; + struct rt_event join; +}; + +static char* sync_string_new(char *s1, char *s2, int s2n) +{ + char *s; + int len; + + len = rt_strlen(s1); + s = rt_malloc(len + s2n + 1); + if (s) + { + rt_memcpy(s, s1, len); + rt_memcpy(&s[len], s2, s2n); + s[len + s2n] = 0; + } + + return s; +} + +static bool sync_read(struct adb_service *ser, void *buf, int size, int ms) +{ + struct filesync_ext *ext = (struct filesync_ext *)ser->extptr; + int len; + struct adb_packet *p; + char *dpos = (char*)buf; + char *spos; + + while (size) + { + if (!ext->cur) + { + if (!adb_packet_dequeue(&ext->recv_que, &ext->cur, ms)) + { + return false; + } + ext->cur->split = 0; //used as postion of read + } + + p = ext->cur; + spos = (char*)(p->payload + p->split); + + len = size > p->msg.data_length ? p->msg.data_length : size; + rt_memcpy(dpos, spos, len); + p->msg.data_length -= len; + p->split += len; + dpos += len; + size -= len; + + if (p->msg.data_length == 0) + { + ext->cur = 0; + adb_packet_delete(p); + } + } + + return true; +} + +static bool sync_write(struct adb_service *ser, void *buf, int size, int ms) +{ + struct adb_packet *p; + + p = adb_packet_new(size); + if (!p) + { + return false; + } + rt_memcpy(p->payload, buf, size); + if (!adb_service_sendpacket(ser, p, ms)) + { + adb_packet_delete(p); + return false; + } + + return true; +} + +static bool SendSyncFail(struct adb_service *ser, const char *reason) +{ + union file_syncmsg msg; + bool ret; + + LOG_D("fail - %s", reason); + + msg.data.id = ID_FAIL; + msg.data.size = rt_strlen(reason); + + ret = sync_write(ser, &msg.data, sizeof(msg.data), 50); + if (ret) + ret = sync_write(ser, (void*)reason, msg.data.size, 50); + + return ret; +} + +static bool sync_read_path(struct adb_service *ser, char **name, int len) +{ + *name = 0; + if (len == 0) + return true; + + if (len > 1024) + { + SendSyncFail(ser, "path too long"); + return false; + } + *name = rt_malloc(len + 1); + if (!*name) + { + SendSyncFail(ser, "alloc memory failure"); + return false; + } + if (!sync_read(ser, *name, len, 2000)) + { + SendSyncFail(ser, "filename read failure"); + rt_free(*name); + *name = 0; + return false; + } + (*name)[len] = 0; + + return true; +} + +static bool do_lstat_v1(struct adb_service *ser, const char* path) +{ + union file_syncmsg msg = {0}; + struct stat st = {0}; + + msg.stat_v1.id = ID_LSTAT_V1; + + stat(path, &st); + msg.stat_v1.mode = st.st_mode; + msg.stat_v1.size = st.st_size; + msg.stat_v1.time = st.st_mtime; + + return sync_write(ser, &msg.stat_v1, sizeof(msg.stat_v1), 50); +} + +static bool do_recv(struct adb_service *ser, char* path, char* buffer) +{ + union file_syncmsg msg; + int fd; + bool ret = false; + + fd = open(path, O_RDONLY); + if (fd < 0) + { + SendSyncFail(ser, "open failed"); + return false; + } + + msg.data.id = ID_DATA; + while (1) + { + int r = read(fd, &buffer[0], SYNC_DATA_MAX); + if (r <= 0) + { + if (r == 0) + break; + SendSyncFail(ser, "read failed"); + close(fd); + return false; + } + + msg.data.size = r; + ret = sync_write(ser, &msg.data, sizeof(msg.data), 400); + if (ret) + ret = sync_write(ser, &buffer[0], r, 100); + if (!ret) + { + close(fd); + return false; + } + } + + close(fd); + + msg.data.id = ID_DONE; + msg.data.size = 0; + return sync_write(ser, &msg.data, sizeof(msg.data), 200); +} + +static bool do_list(struct adb_service *ser, char* path) +{ + union file_syncmsg msg; + struct dirent *de; + DIR* d; + bool ret = true; + + msg.dent.id = ID_DENT; + + d = opendir(path); + if (!d) + goto done; + + while (1) + { + struct stat st = {0}; + char *filename; + int d_name_length; + + de = readdir(d); + if (!de) + break; + + d_name_length = rt_strlen(de->d_name); + filename = sync_string_new(path, de->d_name, d_name_length); + if (!filename) + { + ret = false; + SendSyncFail(ser, "alloc memory fail"); + goto fail; + } + if (stat(filename, &st) == 0) + { + msg.dent.mode = st.st_mode; + msg.dent.size = st.st_size; + msg.dent.time = st.st_mtime; + msg.dent.namelen = d_name_length; + + ret = sync_write(ser, &msg.dent, sizeof(msg.dent), 200); + if (ret) + ret = sync_write(ser, de->d_name, d_name_length, 100); + if (!ret) + { + LOG_D("sync: list write fail"); + rt_free(filename); + goto fail; + } + } + rt_free(filename); + } + +fail: + closedir(d); + +done: + if (ret) + { + msg.dent.id = ID_DONE; + msg.dent.mode = 0; + msg.dent.size = 0; + msg.dent.time = 0; + msg.dent.namelen = 0; + + ret = sync_write(ser, &msg.dent, sizeof(msg.dent), 50); + } + + return ret; +} + +static bool hasdir(char *path) +{ + struct stat st = {0}; + + if (stat(path, &st) < 0) + return false; + + if (st.st_mode & S_IFDIR) + return true; + + return false; +} + +static bool secure_mkdirs(char *path) +{ + int plen; + int pos; + int sub; + + if (path[0] != '/') + return false; + + plen = rt_strlen(path); + for (pos = 0; pos < plen; pos ++) + { + if (path[pos] != '/') + continue; + + for (sub = pos + 1; sub < plen; sub ++) + { + if (path[sub] != '/') + continue; + path[sub] = 0; + if (hasdir(path)) + { + path[sub] = '/'; + break; + } + + if (mkdir(path, 0) < 0) + return false; + path[sub] = '/'; + } + } + + return true; +} + +static bool handle_send_file(struct adb_service *ser, char* path, + mode_t mode, char* buffer, bool do_unlink) +{ + union file_syncmsg msg; + int fd; + + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, mode); + + if (fd < 0) + { + int eno; + + eno = rt_get_errno(); + if (eno == ENOENT || eno == -ENOENT) + { + if (!secure_mkdirs(path)) + { + SendSyncFail(ser, "secure_mkdirs failed"); + goto fail; + } + } + + fd = open(path, O_WRONLY | O_CREAT | O_EXCL, mode); + } + + if (fd < 0) + { + SendSyncFail(ser, "couldn't create file"); + goto fail; + } + + while (1) + { + if (!sync_read(ser, &msg.data, sizeof(msg.data), 100)) + { + SendSyncFail(ser, "read data head fail"); + goto fail; + } + + if (msg.data.id != ID_DATA) + { + if (msg.data.id == ID_DONE) + { + break; + } + SendSyncFail(ser, "invalid data message"); + goto abort; + } + + /* recv file data */ + while (msg.data.size > 0) + { + int size = (msg.data.size > SYNC_DATA_MAX) ? + SYNC_DATA_MAX : msg.data.size; + + if (!sync_read(ser, &buffer[0], size, 800)) + { + SendSyncFail(ser, "read packet timeout"); + goto abort; + } +#if DATA_WRITE + if (write(fd, &buffer[0], size) != size) + { + SendSyncFail(ser, "write failed"); + goto fail; + } +#endif + msg.data.size -= size; + } + } + + close(fd); + + msg.status.id = ID_OKAY; + msg.status.msglen = 0; + return sync_write(ser, &msg.status, sizeof(msg.status), 100); + +fail: +abort: + if (fd >= 0) + close(fd); + if (do_unlink) + unlink(path); + + return false; +} + +static bool do_send(struct adb_service *ser, char* spec, char *buffer) +{ + char *path; + mode_t mode = 0; + bool do_unlink = true; + int comma = 0; + + while (spec[comma] && (spec[comma] != ',')) + comma ++; + + if (comma == rt_strlen(spec)) + { + SendSyncFail(ser, "missing , in ID_SEND"); + return false; + } + + spec[comma] = 0; + path = spec; + + return handle_send_file(ser, path, mode, buffer, do_unlink); +} + +static bool handle_sync_command(struct adb_service *ser, char *buffer) +{ + bool ret = false; + struct file_syncreq req; + char *name; + + if (!buffer) + { + SendSyncFail(ser, "buffer is null"); + return false; + } + + if (!sync_read(ser, &req, sizeof(req), 2000)) + { + SendSyncFail(ser, "command read failure"); + return false; + } + + if (!sync_read_path(ser, &name, req.path_length)) + return false; + + switch (req.id) + { + case ID_LSTAT_V1: + ret = do_lstat_v1(ser, name); + break; + case ID_LSTAT_V2: + case ID_STAT_V2: + break; + case ID_LIST: + ret = do_list(ser, name); + break; + case ID_SEND: + ret = do_send(ser, name, buffer); + break; + case ID_RECV: + ret = do_recv(ser, name, buffer); + break; + case ID_QUIT: + break; + default: + SendSyncFail(ser, "unknown command"); + break; + } + rt_free(name); + + return ret; +} + +static void filesync_thread(void *arg) +{ + struct adb_service *ser = (struct adb_service *)arg; + struct filesync_ext *ext = (struct filesync_ext *)ser->extptr; + char *buf; + + LOG_D("start"); + buf = rt_malloc(SYNC_DATA_MAX); + + while (handle_sync_command(ser, buf)){} + + rt_free(ext->cur); + adb_send_close(ser->d, ser->localid, ser->remoteid); + ser->online = 0; + rt_free(buf); + adb_packet_clear(&ext->recv_que); + rt_event_send(&ext->join, 1); + + LOG_D("quit"); +} + +static int _filesync_open(struct adb_service *ser, char *args) +{ + int ret = -1; + struct filesync_ext *ext; + + ext = (struct filesync_ext *)ser->extptr; + rt_mb_init(&ext->recv_que, "sync", ext->rque_buf, + sizeof(ext->rque_buf)/sizeof(ext->rque_buf[0]), 0); + rt_event_init(&ext->join, "sync", 0); + + ret = rt_thread_startup(ext->worker); + ser->online = 1; + + return ret; +} + +static int _filesync_close(struct adb_service *ser) +{ + int ret = 0; + struct filesync_ext *ext; + + ext = (struct filesync_ext *)ser->extptr; + + rt_event_recv(&ext->join, 1, RT_EVENT_FLAG_OR, + rt_tick_from_millisecond(2000), 0); + rt_event_detach(&ext->join); + rt_mb_detach(&ext->recv_que); + + return ret; +} + +static bool _filesync_enqueue(struct adb_service * ser, struct adb_packet *p, int ms) +{ + struct filesync_ext *ext; + bool ret; + + if (!ser->online) + { + return false; + } + + ext = (struct filesync_ext *)ser->extptr; + ret = adb_packet_enqueue(&ext->recv_que, p, ms); + + return ret; +} + +static const struct adb_service_ops _ops = +{ + _filesync_open, + _filesync_close, + _filesync_enqueue +}; + +static struct adb_service *_filesync_create(struct adb_service_handler *h) +{ + struct adb_service *ser; + struct filesync_ext *ext; + + ser = adb_service_alloc(&_ops, sizeof(struct filesync_ext)); + if (!ser) + return ser; + ext = (struct filesync_ext *)ser->extptr; + + ext->worker = rt_thread_create("sync:", + filesync_thread, + ser, + FILESYNC_STACK_SIZE, + 22, + 20); + if (!ext->worker) + { + rt_free(ser); + return RT_NULL; + } + + return ser; +} + +static void _filesync_destroy(struct adb_service_handler *h, struct adb_service *s) +{ + rt_free(s); +} + +int adb_filesync_init(void) +{ + static struct adb_service_handler _h; + + _h.name = "sync:"; + _h.create = _filesync_create; + _h.destroy = _filesync_destroy; + + return adb_service_handler_register(&_h); +} +INIT_APP_EXPORT(adb_filesync_init); diff --git a/services/file_sync_service.h b/services/file_sync_service.h new file mode 100644 index 0000000..8a762ab --- /dev/null +++ b/services/file_sync_service.h @@ -0,0 +1,79 @@ +#ifndef _FILE_SYNC_S_H +#define _FILE_SYNC_S_H + +#include + +#define MKID(a, b, c, d) ((a) | ((b) << 8) | ((c) << 16) | ((d) << 24)) + +#define ID_LSTAT_V1 MKID('S', 'T', 'A', 'T') +#define ID_STAT_V2 MKID('S', 'T', 'A', '2') +#define ID_LSTAT_V2 MKID('L', 'S', 'T', '2') +#define ID_LIST MKID('L', 'I', 'S', 'T') +#define ID_SEND MKID('S', 'E', 'N', 'D') +#define ID_RECV MKID('R', 'E', 'C', 'V') +#define ID_DENT MKID('D', 'E', 'N', 'T') +#define ID_DONE MKID('D', 'O', 'N', 'E') +#define ID_DATA MKID('D', 'A', 'T', 'A') +#define ID_OKAY MKID('O', 'K', 'A', 'Y') +#define ID_FAIL MKID('F', 'A', 'I', 'L') +#define ID_QUIT MKID('Q', 'U', 'I', 'T') + +#pragma pack(1) + +struct file_syncreq +{ + uint32_t id; // ID_STAT, et cetera. + uint32_t path_length; // <= 1024 + // Followed by 'path_length' bytes of path (not NUL-terminated). +}; + +union file_syncmsg +{ + struct + { + uint32_t id; + uint32_t mode; + uint32_t size; + uint32_t time; + } stat_v1; +#if 0 //not support in rtthread + struct + { + uint32_t id; + uint32_t error; + uint64_t dev; + uint64_t ino; + uint32_t mode; + uint32_t nlink; + uint32_t uid; + uint32_t gid; + uint64_t size; + int64_t atime; + int64_t mtime; + int64_t ctime; + } stat_v2; +#endif + struct + { + uint32_t id; + uint32_t mode; + uint32_t size; + uint32_t time; + uint32_t namelen; + } dent; + struct + { + uint32_t id; + uint32_t size; + } data; + struct + { + uint32_t id; + uint32_t msglen; + } status; +}; +#pragma pack() + +#define SYNC_DATA_MAX (512) + +#endif diff --git a/services/shell_service.c b/services/shell_service.c new file mode 100644 index 0000000..b0408d3 --- /dev/null +++ b/services/shell_service.c @@ -0,0 +1,454 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-11-25 heyuanjie87 the first version + */ + +#include +#include +#include + +struct adb_shdev +{ + struct rt_device parent; + struct rt_ringbuffer *rbuf; + struct rt_ringbuffer *wbuf; + struct rt_event notify; +}; + +#define ADEV_READ 1 +#define ADEV_WRITE 2 +#define ADEV_EXIT 4 + +struct shell_ext +{ + struct adb_packet *cur; + adb_queue_t recv_que; + int rque_buf[4]; + struct adb_shdev *dev; + rt_thread_t shid; + struct rt_event notify; + rt_thread_t worker; + int old_flag; + int mode; +}; + +static struct adb_shdev _shdev; +static struct rt_mutex _lock; + +static int _adwait(struct adb_shdev *ad, int ev, int ms) +{ + int r = 0; + + rt_event_recv(&ad->notify, ev, + RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, + rt_tick_from_millisecond(ms), + (unsigned *)&r); + + return r; +} + +static rt_err_t _shell_service_device_init(struct rt_device *dev) +{ + struct adb_shdev *ad = (struct adb_shdev *)dev; + + ad->rbuf = rt_ringbuffer_create(32); + ad->wbuf = rt_ringbuffer_create(64); + if (!ad->rbuf || !ad->wbuf) + { + if (ad->rbuf) + rt_ringbuffer_destroy(ad->rbuf); + if (ad->wbuf) + rt_ringbuffer_destroy(ad->wbuf); + } + + return 0; +} + +static rt_err_t _shell_service_device_open(struct rt_device *dev, rt_uint16_t oflag) +{ + struct adb_shdev *ad = (struct adb_shdev *)dev; + + dev->open_flag = oflag & 0xff; + rt_event_init(&ad->notify, "adshdev", 0); + + return 0; +} + +static rt_err_t _shell_service_device_close(struct rt_device *dev) +{ + struct adb_shdev *ad = (struct adb_shdev *)dev; + + rt_event_detach(&ad->notify); + + return 0; +} + +static rt_size_t _shell_service_device_read(rt_device_t dev, rt_off_t pos, + void *buffer, rt_size_t size) +{ + struct adb_shdev *ad = (struct adb_shdev *)dev; + int len; + + if (!dev->user_data) + return (rt_size_t)-EAGAIN; + +_retry: + rt_mutex_take(&_lock, -1); + len = rt_ringbuffer_get(ad->rbuf, buffer, size); + rt_mutex_release(&_lock); + if (len == 0) + { + int ret = _adwait(ad, ADEV_READ, 100); + if (ret & ADEV_READ) + goto _retry; + } + + if (len == 0) + len = -EAGAIN; + + return len; +} + +static rt_size_t _shell_service_device_write(rt_device_t dev, rt_off_t pos, + const void *buffer, rt_size_t size) +{ + struct adb_shdev *ad = (struct adb_shdev *)dev; + int wlen; + char *spos = (char *)buffer; + int cnt = 0; + + if (!dev->user_data) + return 0; + + if (rt_interrupt_get_nest()) + { + return rt_ringbuffer_put(ad->wbuf, (unsigned char *)spos, size); + } + + while (size && dev->user_data) + { + rt_mutex_take(&_lock, -1); + wlen = rt_ringbuffer_put(ad->wbuf, (unsigned char *)spos, size); + rt_mutex_release(&_lock); + + rt_event_send(&ad->notify, ADEV_WRITE); + + if (wlen) + { + spos += wlen; + size -= wlen; + cnt += wlen; + } + else + rt_thread_yield(); + } + + return cnt; +} + +static rt_err_t _shell_service_device_ctrl(rt_device_t dev, + int cmd, void *args) +{ + int ret = 0; + + return ret; +} + +#ifdef RT_USING_DEVICE_OPS +const static struct rt_device_ops shell_ops = +{ + _shell_service_device_init, + _shell_service_device_open, + _shell_service_device_close, + _shell_service_device_read, + _shell_service_device_write, + _shell_service_device_ctrl +}; +#endif + +static int _device_init(rt_device_t shell_device, void *usrdat) +{ + int ret; + + shell_device->type = RT_Device_Class_Char; +#ifdef RT_USING_DEVICE_OPS + device->ops = &shell_ops; +#else + shell_device->init = _shell_service_device_init; + shell_device->open = _shell_service_device_open; + shell_device->close = _shell_service_device_close; + shell_device->read = _shell_service_device_read; + shell_device->write = _shell_service_device_write; + shell_device->control = _shell_service_device_ctrl; +#endif + shell_device->user_data = usrdat; + + ret = rt_device_register(shell_device, "as-sh", RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX); + + return ret; +} + +static bool send_ready(struct shell_ext *ext, char *args) +{ + struct adb_packet *p; + + ext->mode = rt_strlen(args); + p = adb_packet_new(ext->mode + 1); + if (!p) + return false; + + p->msg.data_length = ext->mode + 1; + rt_memcpy(p->payload, args, ext->mode); + p->payload[ext->mode] = '\n'; + if (!adb_packet_enqueue(&ext->recv_que, p, 0)) + { + adb_packet_delete(p); + return false; + } + + return true; +} + +static int _shell_open(struct adb_service *ser, char *args) +{ + int ret = -1; + struct shell_ext *ext; + + ext = (struct shell_ext *)ser->extptr; + ext->shid = rt_thread_find(FINSH_THREAD_NAME); + if (!ext->shid) + { + return -1; + } + + ret = _device_init(&_shdev.parent, ser); + if (ret == 0) + { + int pr = 23; + + rt_mb_init(&ext->recv_que, "as-sh", ext->rque_buf, + sizeof(ext->rque_buf) / sizeof(ext->rque_buf[0]), 0); + rt_event_init(&ext->notify, "as-sh", 0); + + ext->dev = &_shdev; + + ext->old_flag = ioctl(libc_stdio_get_console(), F_GETFL, 0); + ioctl(libc_stdio_get_console(), F_SETFL, (void *)(ext->old_flag | O_NONBLOCK)); + + rt_thread_control(ext->shid, RT_THREAD_CTRL_CHANGE_PRIORITY, + (void *)&pr); + libc_stdio_set_console("as-sh", O_RDWR); + rt_console_set_device("as-sh"); + rt_thread_resume(ext->shid); + rt_thread_mdelay(50); + + ret = rt_thread_startup(ext->worker); + if (ret == 0) + { + if (!send_ready(ext, args)) + { + //todo + ret = -1; + } + } + } + + return ret; +} + +static int _shell_close(struct adb_service *ser) +{ + int ret = 0; + struct shell_ext *ext; + + ext = (struct shell_ext *)ser->extptr; + rt_console_set_device(RT_CONSOLE_DEVICE_NAME); + libc_stdio_set_console(RT_CONSOLE_DEVICE_NAME, O_RDWR); + ser->online = 0; + + rt_thread_resume(ext->shid); + rt_thread_mdelay(50); + rt_mutex_take(&_lock, -1); + + rt_mutex_release(&_lock); + + rt_event_send(&ext->notify, 2); + rt_mb_detach(&ext->recv_que); + rt_event_detach(&ext->notify); + + rt_device_unregister(&ext->dev->parent); + + return ret; +} + +static bool _shell_enqueue(struct adb_service *ser, struct adb_packet *p, int ms) +{ + struct shell_ext *ext; + bool ret; + + if (!ser->online) + return false; + + ext = (struct shell_ext *)ser->extptr; + ret = adb_packet_enqueue(&ext->recv_que, p, ms); + + return ret; +} + +static const struct adb_service_ops _ops = +{ + _shell_open, + _shell_close, + _shell_enqueue +}; + +static void do_readdev(struct adb_service *ser, struct shell_ext *ext) +{ + int size; + struct adb_packet *p; + + size = rt_ringbuffer_data_len(ext->dev->wbuf); + p = adb_packet_new(size); + if (!p) + return; + + rt_ringbuffer_get(ext->dev->wbuf, (unsigned char *)p->payload, size); + if (!adb_service_sendpacket(ser, p, 60)) + { + adb_packet_delete(p); + } +} + +static void do_writedev(struct adb_service *ser, struct shell_ext *ext) +{ + struct adb_packet *p; + char *pos; + int len; + + if (!ext->cur) + { + if (!adb_packet_dequeue(&ext->recv_que, &ext->cur, 20)) + return; + ext->cur->split = 0; + } + p = ext->cur; + + pos = p->payload + p->split; + len = rt_ringbuffer_put(ext->dev->rbuf, (const unsigned char *)pos, + p->msg.data_length); + p->split += len; + p->msg.data_length -= len; + if (p->msg.data_length == 0) + { + ext->cur = 0; + adb_packet_delete(p); + } + + rt_event_send(&ext->dev->notify, ADEV_READ); +} + +static void service_thread(void *arg) +{ + struct adb_service *ser; + struct shell_ext *ext; + unsigned revt; + int exit = 50; + + ser = arg; + ext = ser->extptr; + ser->online = 1; + + while (ser->online) + { + do_writedev(ser, ext); + + revt = _adwait(ext->dev, ADEV_WRITE, 20); + if (revt & ADEV_WRITE) + { + exit = 20; + do_readdev(ser, ext); + } + else if (ext->mode != 0) + { + if (--exit == 0) + break; + } + } + ser->online = 0; + ext->dev->parent.user_data = 0; + adb_packet_clear(&ext->recv_que); + adb_send_close(ser->d, ser->localid, ser->remoteid); +} + +static struct adb_service *_shell_create(struct adb_service_handler *h) +{ + struct adb_service *ser; + struct shell_ext *ext; + + if (_shdev.parent.ref_count) + return RT_NULL; + if (rt_thread_find("as-sh")) + return RT_NULL; + + ser = adb_service_alloc(&_ops, sizeof(struct shell_ext)); + if (ser) + { + ext = (struct shell_ext *)ser->extptr; + ext->dev = &_shdev; + ext->worker = rt_thread_create("as-sh", + service_thread, + ser, + 2048, + 22, + 20); + } + + return ser; +} + +static void _shell_destroy(struct adb_service_handler *h, struct adb_service *s) +{ + rt_free(s); +} + +int adb_shell_init(void) +{ + static struct adb_service_handler _h; + + _h.name = "shell:"; + _h.create = _shell_create; + _h.destroy = _shell_destroy; + + rt_mutex_init(&_lock, "as-sh", 0); + + return adb_service_handler_register(&_h); +} +INIT_APP_EXPORT(adb_shell_init); + +static void exitas(int argc, char **argv) +{ + rt_thread_t tid; + + if (_shdev.parent.ref_count == 0) + { + rt_kprintf("adb shell service not run"); + return; + } + + rt_mutex_take(&_lock, -1); + tid = rt_thread_find("as-sh"); + if (tid) + { + struct adb_service *ser; + + ser = tid->parameter; + ser->online = 0; + } + rt_mutex_release(&_lock); +} +MSH_CMD_EXPORT(exitas, exit adb shell service);