refactor: replace gadb with optimized version authoried by @appl3s

This commit is contained in:
lilong.129
2023-04-13 22:41:45 +08:00
parent 6600473855
commit 3d4dffbd0b
13 changed files with 509 additions and 34 deletions

View File

@@ -26,7 +26,7 @@ var listAndroidDevicesCmd = &cobra.Command{
return errors.Wrap(err, "list android devices failed")
}
var deviceList []gadb.Device
var deviceList []*gadb.Device
// filter by serial
for _, d := range devices {
if serial != "" && serial != d.Serial() {

View File

@@ -1,5 +1,11 @@
# gadb
This module is initially forked from [electricbubble/gadb@v0.0.7].
This module is initially forked from [electricbubble/gadb@v0.0.7] and optimized by [@appl3s].
- feat: add reverse forward command
- feat: add `RunShellCommandV2` which supports running nohup
- feat: add `InstallAPK` with feature judgment
- feat: add `Uninstall` for specified package name
[electricbubble/gadb@v0.0.7]: https://github.com/electricbubble/gadb/tree/v0.0.7
[@appl3s]: https://github.com/appl3s

View File

@@ -38,6 +38,16 @@ func NewClientWith(host string, port ...int) (adbClient Client, err error) {
return
}
func NewClientWithoutTransport(host string, port ...int) (adbClient Client, err error) {
if len(port) == 0 {
port = []int{AdbServerPort}
}
adbClient.host = host
adbClient.port = port[0]
return
}
func (c Client) ServerVersion() (version int, err error) {
var resp string
if resp, err = c.executeCommand("host:version"); err != nil {
@@ -73,14 +83,14 @@ func (c Client) DeviceSerialList() (serials []string, err error) {
return
}
func (c Client) DeviceList() (devices []Device, err error) {
func (c Client) DeviceList() (devices []*Device, err error) {
var resp string
if resp, err = c.executeCommand("host:devices-l"); err != nil {
return
}
lines := strings.Split(resp, "\n")
devices = make([]Device, 0, len(lines))
devices = make([]*Device, 0, len(lines))
for i := range lines {
line := strings.TrimSpace(lines[i])
@@ -101,7 +111,7 @@ func (c Client) DeviceList() (devices []Device, err error) {
key, val := split[0], split[1]
mapAttrs[key] = val
}
devices = append(devices, Device{adbClient: c, serial: fields[0], attrs: mapAttrs})
devices = append(devices, &Device{adbClient: c, serial: fields[0], attrs: mapAttrs})
}
return

View File

@@ -3,6 +3,7 @@
package gadb
import (
"io/ioutil"
"testing"
)
@@ -127,3 +128,22 @@ func TestClient_KillServer(t *testing.T) {
t.Fatal(err)
}
}
func TestScreenCap(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
dl, err := adbClient.DeviceList()
if err != nil {
t.Error(err)
}
d := dl[0]
res, err := d.RunShellCommandV2WithBytes("screencap", "-p")
if err != nil {
t.Error(err)
}
t.Log(len(res))
ioutil.WriteFile("/tmp/1.png", res, 0o644)
}

View File

@@ -1,6 +1,8 @@
package gadb
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
@@ -48,9 +50,10 @@ func deviceStateConv(k string) (deviceState DeviceState) {
}
type DeviceForward struct {
Serial string
Local string
Remote string
Serial string
Local string
Remote string
Reverse bool
// LocalProtocol string
// RemoteProtocol string
}
@@ -59,51 +62,92 @@ type Device struct {
adbClient Client
serial string
attrs map[string]string
feat Features
}
func (d Device) Product() string {
func (d *Device) HasFeature(name Feature) bool {
feats, err := d.GetFeatures()
if err != nil || len(feats) == 0 {
return false
}
return feats.HasFeature(name)
}
func (d *Device) GetFeatures() (features Features, err error) {
if len(d.feat) > 0 {
return d.feat, nil
}
return d.features()
}
func (d *Device) features() (features Features, err error) {
res, err := d.executeCommand("host:features")
if err != nil {
return nil, err
}
if len(res) > 4 {
// stip hash
res = res[4:]
}
fs := strings.Split(string(res), ",")
features = make(Features, len(fs))
for _, f := range fs {
features[Feature(f)] = struct{}{}
}
d.feat = features
return features, nil
}
func (d *Device) Product() string {
return d.attrs["product"]
}
func (d Device) Model() string {
func (d *Device) Model() string {
return d.attrs["model"]
}
func (d Device) Usb() string {
func (d *Device) Usb() string {
return d.attrs["usb"]
}
func (d Device) transportId() string {
func (d *Device) transportId() string {
return d.attrs["transport_id"]
}
func (d Device) DeviceInfo() map[string]string {
func (d *Device) DeviceInfo() map[string]string {
return d.attrs
}
func (d Device) Serial() string {
func (d *Device) Serial() string {
// resp, err := d.adbClient.executeCommand(fmt.Sprintf("host-serial:%s:get-serialno", d.serial))
return d.serial
}
func (d Device) IsUsb() bool {
func (d *Device) IsUsb() bool {
return d.Usb() != ""
}
func (d Device) State() (DeviceState, error) {
func (d *Device) State() (DeviceState, error) {
resp, err := d.adbClient.executeCommand(fmt.Sprintf("host-serial:%s:get-state", d.serial))
return deviceStateConv(resp), err
}
func (d Device) DevicePath() (string, error) {
func (d *Device) DevicePath() (string, error) {
resp, err := d.adbClient.executeCommand(fmt.Sprintf("host-serial:%s:get-devpath", d.serial))
return resp, err
}
func (d Device) Forward(localPort, remotePort int, noRebind ...bool) (err error) {
func (d *Device) Forward(localPort int, remoteInterface interface{}, noRebind ...bool) (err error) {
command := ""
var remote string
local := fmt.Sprintf("tcp:%d", localPort)
remote := fmt.Sprintf("tcp:%d", remotePort)
switch r := remoteInterface.(type) {
// for unix sockets
case string:
remote = r
case int:
remote = fmt.Sprintf("tcp:%d", r)
}
if len(noRebind) != 0 && noRebind[0] {
command = fmt.Sprintf("host-serial:%s:forward:norebind:%s;%s", d.serial, local, remote)
@@ -115,7 +159,7 @@ func (d Device) Forward(localPort, remotePort int, noRebind ...bool) (err error)
return
}
func (d Device) ForwardList() (deviceForwardList []DeviceForward, err error) {
func (d *Device) ForwardList() (deviceForwardList []DeviceForward, err error) {
var forwardList []DeviceForward
if forwardList, err = d.adbClient.ForwardList(); err != nil {
return nil, err
@@ -131,18 +175,81 @@ func (d Device) ForwardList() (deviceForwardList []DeviceForward, err error) {
return
}
func (d Device) ForwardKill(localPort int) (err error) {
func (d *Device) ForwardKill(localPort int) (err error) {
local := fmt.Sprintf("tcp:%d", localPort)
_, err = d.adbClient.executeCommand(fmt.Sprintf("host-serial:%s:killforward:%s", d.serial, local), true)
return
}
func (d Device) RunShellCommand(cmd string, args ...string) (string, error) {
func (d *Device) ReverseForward(localPort int, remoteInterface interface{}, noRebind ...bool) (err error) {
var command string
var remote string
local := fmt.Sprintf("tcp:%d", localPort)
switch r := remoteInterface.(type) {
// for unix sockets
case string:
remote = r
case int:
remote = fmt.Sprintf("tcp:%d", r)
}
if len(noRebind) != 0 && noRebind[0] {
command = fmt.Sprintf("reverse:forward:norebind:%s;%s", remote, local)
} else {
command = fmt.Sprintf("reverse:forward:%s;%s", remote, local)
}
_, err = d.executeCommand(command, true)
return
}
func (d *Device) ReverseForwardList() (deviceForwardList []DeviceForward, err error) {
res, err := d.executeCommand("reverse:list-forward")
if err != nil {
return nil, err
}
resStr := string(res)
lines := strings.Split(resStr, "\n")
for _, line := range lines {
groups := strings.Split(line, " ")
if len(groups) == 3 {
deviceForwardList = append(deviceForwardList, DeviceForward{
Reverse: true,
Serial: d.serial,
Remote: groups[1],
Local: groups[2],
})
}
}
return
}
func (d *Device) ReverseForwardKill(remoteInterface interface{}) error {
remote := ""
switch r := remoteInterface.(type) {
case string:
remote = r
case int:
remote = fmt.Sprintf("tcp:%d", r)
}
_, err := d.executeCommand(fmt.Sprintf("reverse:killforward:%s", remote), true)
return err
}
func (d *Device) ReverseForwardKillAll() error {
_, err := d.executeCommand("reverse:killforward-all")
return err
}
func (d *Device) RunShellCommand(cmd string, args ...string) (string, error) {
raw, err := d.RunShellCommandWithBytes(cmd, args...)
return string(raw), err
}
func (d Device) RunShellCommandWithBytes(cmd string, args ...string) ([]byte, error) {
func (d *Device) RunShellCommandWithBytes(cmd string, args ...string) ([]byte, error) {
if d.HasFeature(FeatShellV2) {
raw, err := d.RunShellCommandV2WithBytes(cmd, args...)
return raw, err
}
if len(args) > 0 {
cmd = fmt.Sprintf("%s %s", cmd, strings.Join(args, " "))
}
@@ -156,7 +263,86 @@ func (d Device) RunShellCommandWithBytes(cmd string, args ...string) ([]byte, er
return raw, err
}
func (d Device) EnableAdbOverTCP(port ...int) (err error) {
func (d *Device) RunShellCommandV2(cmd string, args ...string) (string, error) {
raw, err := d.RunShellCommandV2WithBytes(cmd, args...)
return string(raw), err
}
// RunShellCommandV2WithBytes shell v2, 支持后台运行而不会阻断
func (d *Device) RunShellCommandV2WithBytes(cmd string, args ...string) ([]byte, error) {
if len(args) > 0 {
cmd = fmt.Sprintf("%s %s", cmd, strings.Join(args, " "))
}
if strings.TrimSpace(cmd) == "" {
return nil, errors.New("adb shell: command cannot be empty")
}
log.Debug().Str("cmd",
fmt.Sprintf("adb -s %s shell %s", d.serial, cmd)).
Msg("run adb command")
raw, err := d.executeCommand(fmt.Sprintf("shell,v2,raw:%s", cmd))
if err != nil {
return raw, err
}
return d.parseV2CommandWithBytes(raw)
}
func (d *Device) parseV2CommandWithBytes(input []byte) (res []byte, err error) {
if len(input) == 0 {
return input, nil
}
reader := bytes.NewReader(input)
sizeBuf := make([]byte, 4)
var (
resBuf []byte
exitCode int
)
loop:
for {
msgCode, err := reader.ReadByte()
if err != nil {
return input, err
}
switch msgCode {
case 0x01, 0x02: // STDOUT, STDERR
_, err = io.ReadFull(reader, sizeBuf)
if err != nil {
return input, err
}
size := binary.LittleEndian.Uint32(sizeBuf)
if cap(resBuf) < int(size) {
resBuf = make([]byte, int(size))
}
_, err = io.ReadFull(reader, resBuf[:size])
if err != nil {
return input, err
}
res = append(res, resBuf[:size]...)
case 0x03: // EXIT
_, err = io.ReadFull(reader, sizeBuf)
if err != nil {
return input, err
}
size := binary.LittleEndian.Uint32(sizeBuf)
if cap(resBuf) < int(size) {
resBuf = make([]byte, int(size))
}
ec, err := reader.ReadByte()
if err != nil {
return input, err
}
exitCode = int(ec)
break loop
default:
return input, nil
}
}
if exitCode != 0 {
return nil, errors.New(string(res))
}
return res, nil
}
func (d *Device) EnableAdbOverTCP(port ...int) (err error) {
if len(port) == 0 {
port = []int{AdbDaemonPort}
}
@@ -168,7 +354,7 @@ func (d Device) EnableAdbOverTCP(port ...int) (err error) {
return
}
func (d Device) createDeviceTransport() (tp transport, err error) {
func (d *Device) createDeviceTransport() (tp transport, err error) {
if tp, err = newTransport(fmt.Sprintf("%s:%d", d.adbClient.host, d.adbClient.port)); err != nil {
return transport{}, err
}
@@ -180,7 +366,7 @@ func (d Device) createDeviceTransport() (tp transport, err error) {
return
}
func (d Device) executeCommand(command string, onlyVerifyResponse ...bool) (raw []byte, err error) {
func (d *Device) executeCommand(command string, onlyVerifyResponse ...bool) (raw []byte, err error) {
if len(onlyVerifyResponse) == 0 {
onlyVerifyResponse = []bool{false}
}
@@ -207,7 +393,7 @@ func (d Device) executeCommand(command string, onlyVerifyResponse ...bool) (raw
return
}
func (d Device) List(remotePath string) (devFileInfos []DeviceFileInfo, err error) {
func (d *Device) List(remotePath string) (devFileInfos []DeviceFileInfo, err error) {
var tp transport
if tp, err = d.createDeviceTransport(); err != nil {
return nil, err
@@ -237,7 +423,7 @@ func (d Device) List(remotePath string) (devFileInfos []DeviceFileInfo, err erro
return
}
func (d Device) PushFile(local *os.File, remotePath string, modification ...time.Time) (err error) {
func (d *Device) PushFile(local *os.File, remotePath string, modification ...time.Time) (err error) {
if len(modification) == 0 {
var stat os.FileInfo
if stat, err = local.Stat(); err != nil {
@@ -249,7 +435,7 @@ func (d Device) PushFile(local *os.File, remotePath string, modification ...time
return d.Push(local, remotePath, modification[0], DefaultFileMode)
}
func (d Device) Push(source io.Reader, remotePath string, modification time.Time, mode ...os.FileMode) (err error) {
func (d *Device) Push(source io.Reader, remotePath string, modification time.Time, mode ...os.FileMode) (err error) {
if len(mode) == 0 {
mode = []os.FileMode{DefaultFileMode}
}
@@ -285,7 +471,7 @@ func (d Device) Push(source io.Reader, remotePath string, modification time.Time
return
}
func (d Device) Pull(remotePath string, dest io.Writer) (err error) {
func (d *Device) Pull(remotePath string, dest io.Writer) (err error) {
var tp transport
if tp, err = d.createDeviceTransport(); err != nil {
return err
@@ -305,3 +491,83 @@ func (d Device) Pull(remotePath string, dest io.Writer) (err error) {
err = sync.WriteStream(dest)
return
}
func (d *Device) installViaABBExec(apk io.ReadSeeker) (raw []byte, err error) {
var (
tp transport
filesize int64
)
filesize, err = apk.Seek(0, io.SeekEnd)
if err != nil {
return nil, err
}
if tp, err = d.createDeviceTransport(); err != nil {
return nil, err
}
defer func() { _ = tp.Close() }()
if err = tp.Send(fmt.Sprintf("abb_exec:package\x00install\x00-t\x00-S\x00%d", filesize)); err != nil {
return nil, err
}
if err = tp.VerifyResponse(); err != nil {
return nil, err
}
_, err = apk.Seek(0, io.SeekStart)
if err != nil {
return nil, err
}
_, err = io.Copy(tp.Conn(), apk)
if err != nil {
return nil, err
}
raw, err = tp.ReadBytesAll()
return
}
func (d *Device) InstallAPK(apk io.ReadSeeker) (string, error) {
haserr := func(ret string) bool {
return strings.Contains(ret, "Failure")
}
if d.HasFeature(FeatAbbExec) {
raw, err := d.installViaABBExec(apk)
if err != nil {
return "", fmt.Errorf("error installing: %v", err)
}
if haserr(string(raw)) {
return "", errors.New(string(raw))
}
return string(raw), err
}
remote := fmt.Sprintf("/data/local/tmp/gadb_remote_%d.apk", time.Now().Unix())
err := d.Push(apk, remote, time.Now())
if err != nil {
return "", fmt.Errorf("error pushing: %v", err)
}
res, err := d.RunShellCommand("pm", "install", "-f", remote)
if err != nil {
return "", fmt.Errorf("error installing: %v", err)
}
if haserr(res) {
return "", errors.New(res)
}
return res, nil
}
func (d *Device) Uninstall(packageName string, keepData ...bool) (string, error) {
if len(keepData) == 0 {
keepData = []bool{false}
}
packageName = strings.ReplaceAll(packageName, " ", "")
if len(packageName) == 0 {
return "", fmt.Errorf("invalid package name")
}
args := []string{"uninstall"}
if keepData[0] {
args = append(args, "-k")
}
args = append(args, packageName)
return d.RunShellCommandV2("pm", args...)
}

View File

@@ -6,6 +6,7 @@ import (
"bytes"
"io/ioutil"
"os"
"reflect"
"strings"
"testing"
"time"
@@ -145,6 +146,42 @@ func TestDevice_Forward(t *testing.T) {
}
}
func TestDevice_ReverseForward(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
devices, err := adbClient.DeviceList()
if err != nil {
t.Fatal(err)
}
localPort := 5005
err = devices[0].ReverseForward(localPort, "localabstract:scrcpy")
if err != nil {
t.Fatal(err)
}
err = devices[0].ReverseForward(localPort, "localabstract:scrcpy1")
if err != nil {
t.Fatal(err)
}
_, err = devices[0].ReverseForwardList()
if err != nil {
t.Fatal(err)
}
err = devices[0].ReverseForwardKill("localabstract:scrcpy1")
if err != nil {
t.Fatal(err)
}
err = devices[0].ReverseForwardKillAll()
if err != nil {
t.Fatal(err)
}
}
func TestDevice_ForwardList(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
@@ -314,3 +351,94 @@ func TestDevice_Pull(t *testing.T) {
t.Fatal(err)
}
}
func TestDevice_RunShellCommandBackgroundWithBytes(t *testing.T) {
type fields struct {
adbClient Client
serial string
attrs map[string]string
}
type args struct {
cmd string
args []string
}
tests := []struct {
name string
fields fields
args args
want []byte
wantErr bool
}{
{
name: "runShellCommandBackground",
fields: fields{
adbClient: func() Client {
c, _ := NewClient()
return c
}(),
serial: "63c1ee94",
},
args: args{
cmd: "nohup sleep 10 2>/dev/null 1>/dev/null &",
// cmd: "sleep 10",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := Device{
adbClient: tt.fields.adbClient,
serial: tt.fields.serial,
attrs: tt.fields.attrs,
}
got, err := d.RunShellCommandV2WithBytes(tt.args.cmd, tt.args.args...)
if (err != nil) != tt.wantErr {
t.Errorf("Device.RunShellCommandBackgroundWithBytes() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Device.RunShellCommandBackgroundWithBytes() = %v, want %v", got, tt.want)
}
})
}
}
func TestDevice_InstallAPK(t *testing.T) {
apk, _ := os.Open("test.apk")
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
devices, err := adbClient.DeviceList()
if err != nil {
t.Fatal(err)
}
dev := devices[len(devices)-1]
dev = devices[0]
res, err := dev.InstallAPK(apk)
if err != nil {
t.Fatal(err)
}
t.Log(res)
}
func TestDevice_HasFeature(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
devices, err := adbClient.DeviceList()
if err != nil {
t.Fatal(err)
}
dev := devices[len(devices)-1]
dev = devices[0]
t.Log(dev.GetFeatures())
}

26
hrp/pkg/gadb/features.go Normal file
View File

@@ -0,0 +1,26 @@
package gadb
type (
Feature string
Features map[Feature]struct{}
)
var (
FeatSendrecvV2Brotli = Feature("sendrecv_v2_brotli")
FeatRemountShell = Feature("remount_shell")
FeatSendrecvV2 = Feature("sendrecv_v2")
FeatAbbExec = Feature("abb_exec")
FeatFixedPushMkdir = Feature("fixed_push_mkdir")
FeatFixedPushSymlinkTimestamp = Feature("fixed_push_symlink_timestamp")
FeatAbb = Feature("abb")
FeatShellV2 = Feature("shell_v2")
FeatCmd = Feature("cmd")
FeatLsV2 = Feature("ls_v2")
FeatApex = Feature("apex")
FeatStatV2 = Feature("stat_v2")
)
func (fs Features) HasFeature(name Feature) bool {
_, has := fs[name]
return has
}

View File

@@ -250,5 +250,6 @@ func (sync syncTransport) Close() (err error) {
if sync.sock == nil {
return nil
}
_ = DisableTimeWait(sync.sock.(*net.TCPConn))
return sync.sock.Close()
}

View File

@@ -34,9 +34,18 @@ func newTransport(address string, readTimeout ...time.Duration) (tp transport, e
func (t transport) Send(command string) (err error) {
msg := fmt.Sprintf("%04x%s", len(command), command)
log.Debug().Str("cmd", command).Msg("run adb command")
return _send(t.sock, []byte(msg))
}
func (t transport) SendBytes(b []byte) (err error) {
return _send(t.sock, b)
}
func (t transport) Conn() net.Conn {
return t.sock
}
func (t transport) VerifyResponse() (err error) {
var status string
if status, err = t.ReadStringN(4); err != nil {
@@ -103,6 +112,7 @@ func (t transport) Close() (err error) {
if t.sock == nil {
return nil
}
_ = DisableTimeWait(t.sock.(*net.TCPConn))
return t.sock.Close()
}

View File

@@ -13,7 +13,6 @@ func Test_transport_VerifyResponse(t *testing.T) {
}
defer transport.Close()
// err = transport.Send("host:123version")
err = transport.Send("host:version")
if err != nil {
t.Fatal(err)

9
hrp/pkg/gadb/utils.go Normal file
View File

@@ -0,0 +1,9 @@
package gadb
import (
"net"
)
func DisableTimeWait(conn *net.TCPConn) error {
return conn.SetLinger(0)
}

View File

@@ -17,7 +17,7 @@ import (
type adbDriver struct {
Driver
adbClient gadb.Device
adbClient *gadb.Device
logcat *AdbLogcat
}

View File

@@ -115,7 +115,7 @@ func NewAndroidDevice(options ...AndroidDeviceOption) (device *AndroidDevice, er
fmt.Sprintf("device %s not found", device.SerialNumber))
}
func DeviceList() (devices []gadb.Device, err error) {
func DeviceList() (devices []*gadb.Device, err error) {
var adbClient gadb.Client
if adbClient, err = gadb.NewClientWith(AdbServerHost, AdbServerPort); err != nil {
return nil, errors.Wrap(code.AndroidDeviceConnectionError, err.Error())
@@ -125,7 +125,7 @@ func DeviceList() (devices []gadb.Device, err error) {
}
type AndroidDevice struct {
d gadb.Device
d *gadb.Device
logcat *AdbLogcat
SerialNumber string `json:"serial,omitempty" yaml:"serial,omitempty"`
UIA2 bool `json:"uia2,omitempty" yaml:"uia2,omitempty"` // use uiautomator2