ADB通信协议

2023-03-23 09:17:19 浏览数 (2)

Overview

Client、Server和Daemon

  PC端有一个ADB.exe,它既是Client,也是Server,而Daemon则是存在于手机端,Daemon需要在开发者模式中手动开启。

  Client为我们使用ADB功能提供API,我们通过Client来执行ADB操作,但它只是请求的发起者而非执行者,具体执行请求是在Server和Daemon中完成。Server在后台运行,它的作用是作为沟通Client与Daemon的桥梁,由于Client与Daemon并未直接建立连接,Client与Daemon的交互由Server代为进行,Server也可以将Client指定的Daemon交给Client直接操作。

Service

  ADB的具体功能即Service分两种:Host Service和Local Service。Host Service由Server完成,例如adb devices命令就是Client向Server发起的请求,Server来查询连接到Server的Daemon数量和信息后返回给Client。Local Service由Daemon完成,例如adb shell。

启动流程

  使用adb start-server会在PC端启动ADB server并监听5037端口等待Client连接。此外,执行adb shelladb devices等部分命令会默认向localhost:5037进行请求,如果连接超时则会自动在5037端口启动Server。另外,在启动Server以及使用Client时,我们可以指定IP和Port,实现远程请求。

  首先在一台机器上以1234端口启动ADB Server

代码语言:javascript复制
adb -a -P 1234 nodaemon server

  然后在另一台机器上用Client指定ip和端口进行连接

代码语言:javascript复制
adb -H 10.176.36.22 -P 1234 devices

通信协议

  Server启动后Client可以通过TCP向Server发起请求,格式如下

  1. 开头为4位16进制字符串头部,用于描述payload长度,长度不包括头部部分
  2. Payload的具体内容

  比如查询adb的版本号,我们首先使用adb start-server启动Server,然后使用socket连接localhost:5037,再使用socket发送000Chost:version,随后Server会返回OKAY或者FAIL,如果为OKAY则会继续返回具体的版本信息。其中000C即为头部,host:version即为Payload,000C代表Payload的长度为12。其中Payload的前缀host:代表将执行Host Service中的操作。对于Local Service在下文中会提到。在接收到OKAY消息以后,Client和Server的连接不会断开,可以复用该连接继续进行请求,但是某些特定的命令会在接收到OKAY后改变连接的状态,比如host:transport:<serialnumber>会将连接转移到指定设备的Daemon中,来直接与其进行通信。

misc

  不难想象会存在Client和Server版本不匹配的情况,adb默认的操作是将server关闭并从adb.exe启动自己版本的server

API

参考文档

ADB Protocol

Python实现(部分功能)

代码语言:javascript复制
import socket

class ADB:

    OKAY = b"OKAY"
    FAIL = b"FAIL"
    _socket = None

    def build(self,payload:str) -> bytes:
        header = hex(len(payload.encode()))
        header = str(header)[2:].rjust(4,"0")
        return (header   payload).encode()

    def send(self,msg:str) -> str:
        _msg = self.build(msg)
        self._socket.send(_msg)
        isOK = self._socket.recv(0x4)
        if isOK != self.OKAY:
            return False
        return True

    def recv(self,raw=False):
        if raw:
            return self._socket.recv(0x100)

        msglen = self._socket.recv(0x4)
        msglen = eval("0x"   msglen.decode())
        msg = self._socket.recv(msglen).decode()
        return msg
        

    def connectServer(self,ip="localhost",port=5037,timeout=3):
        self._socket = socket.create_connection((ip,5037),timeout=timeout)

    def getVersion(self):
        self.send("host:version")
        return self.recv()

if __name__ == "__main__":
    adb = ADB()
    adb.connectServer()

    print(adb.getVersion())
    # print(adb.recv(True))
    # print(adb.recv(True))

0 人点赞