mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-11 10:00:23 +08:00
Merge pull request #1504 from httprunner/dev-v4.3
refactor: integrate with gidevice and gadb
This commit is contained in:
@@ -1,12 +1,23 @@
|
||||
# Release History
|
||||
|
||||
## v4.3.0 (2022-10-21)
|
||||
## v4.3.0 (2022-10-24)
|
||||
|
||||
Release hrp sub package `uixt` to support iOS/Android UI automation 🎉
|
||||
|
||||
- feat: support iOS UI automation with [WebDriverAgent] and [gwda]
|
||||
- feat: support Android UI automation with [uiautomator2] and [guia2]
|
||||
- feat: support UI recognition with [OCR service] and [gwda-ext-opencv]
|
||||
|
||||
For iOS/Android device management:
|
||||
|
||||
- feat: support iOS UI automation with [WebDriverAgent]
|
||||
- feat support Android UI automation with [uiautomator2]
|
||||
- feat: integrage ios device management with [gidevice]
|
||||
- feat: add specified exit code for different exceptions
|
||||
- refactor: make boomer/uixt/httpstat as sub package
|
||||
- feat: integrage android device management with [gadb]
|
||||
- feat: add simple commands to interact with iOS/Android devices, try `hrp ios` and `hrp adb`
|
||||
|
||||
Other improvements:
|
||||
|
||||
- feat: exit with specified code for different exceptions
|
||||
- refactor: make uixt/gadb/gidevice/boomer/httpstat as hrp sub package
|
||||
|
||||
## v4.2.1 (2022-09-01)
|
||||
|
||||
@@ -681,3 +692,8 @@ reference: [v2-changelog]
|
||||
[WebDriverAgent]: https://github.com/appium/WebDriverAgent
|
||||
[uiautomator2]: https://github.com/appium/appium-uiautomator2-server
|
||||
[gidevice]: https://github.com/electricbubble/gidevice
|
||||
[gwda]: https://github.com/electricbubble/gwda
|
||||
[guia2]: https://github.com/electricbubble/guia2
|
||||
[gadb]: https://github.com/electricbubble/gadb
|
||||
[OCR service]: https://www.volcengine.com/product/text-recognition
|
||||
[gwda-ext-opencv]: https://github.com/electricbubble/gwda-ext-opencv
|
||||
|
||||
14
go.mod
14
go.mod
@@ -5,20 +5,17 @@ go 1.18
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.4
|
||||
github.com/denisbrodbeck/machineid v1.0.1
|
||||
github.com/electricbubble/gadb v0.0.7
|
||||
github.com/electricbubble/gidevice v0.6.2
|
||||
github.com/electricbubble/opencv-helper v0.0.3
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/getsentry/sentry-go v0.13.0
|
||||
github.com/go-openapi/spec v0.20.7
|
||||
github.com/go-ping/ping v1.1.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/httprunner/funplugin v0.5.0
|
||||
github.com/jinzhu/copier v0.3.5
|
||||
github.com/jmespath/go-jmespath v0.4.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||
github.com/maja42/goval v1.2.1
|
||||
github.com/miekg/dns v1.1.50
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
@@ -26,14 +23,17 @@ require (
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.13.0
|
||||
github.com/rs/zerolog v1.28.0
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
gocv.io/x/gocv v0.31.0
|
||||
golang.org/x/net v0.0.0-20220919232410-f2f64ebce3c1
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
|
||||
google.golang.org/grpc v1.49.0
|
||||
google.golang.org/protobuf v1.28.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
howett.net/plist v1.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -48,12 +48,12 @@ require (
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/hashicorp/go-hclog v1.3.0 // indirect
|
||||
github.com/hashicorp/go-plugin v1.4.5 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
@@ -69,12 +69,10 @@ require (
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/satori/go.uuid v1.2.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
||||
github.com/tklauser/numcpus v0.5.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
gocv.io/x/gocv v0.31.0 // indirect
|
||||
golang.org/x/mod v0.4.2 // indirect
|
||||
golang.org/x/sync v0.0.0-20220907140024-f12130a52804 // indirect
|
||||
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect
|
||||
@@ -84,8 +82,6 @@ require (
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
)
|
||||
|
||||
// replace github.com/httprunner/funplugin => ../funplugin
|
||||
replace github.com/electricbubble/gidevice => github.com/debugtalk/gidevice v0.6.3-0.20221012071407-9b59e12ecc77
|
||||
|
||||
8
go.sum
8
go.sum
@@ -96,14 +96,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/debugtalk/gidevice v0.6.3-0.20221012071407-9b59e12ecc77 h1:wP/2aKW6YV0ityxp0Ecv8JDwA/cy6gayVhA/t+roO+w=
|
||||
github.com/debugtalk/gidevice v0.6.3-0.20221012071407-9b59e12ecc77/go.mod h1:bRHL2M9qgeEKju8KRvKMZUVEg7t5zMnTiG3SJ3QDH5o=
|
||||
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
|
||||
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
||||
github.com/electricbubble/gadb v0.0.7 h1:fxvVLVNs3IFKuYAEXDF2tDZUjT9jNCltoTSirjM5dgo=
|
||||
github.com/electricbubble/gadb v0.0.7/go.mod h1:3293YJ6OWHv/Q6NA5dwSbK43MbmYm8+Vz2d7h5J3IA8=
|
||||
github.com/electricbubble/opencv-helper v0.0.3 h1:p0sHTUPPPm8GqzVUtYH+wQbJoguzotUXVRAS7Ibk7nI=
|
||||
github.com/electricbubble/opencv-helper v0.0.3/go.mod h1:VHB21p5xsIjXUsUleWSaKGJosRsRAO7cuJoZKf7uCcc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
@@ -426,7 +420,6 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
gocv.io/x/gocv v0.27.0/go.mod h1:n4LnYjykU6y9gn48yZf4eLCdtuSb77XxSkW6g0wGf/A=
|
||||
gocv.io/x/gocv v0.31.0 h1:BHDtK8v+YPvoSPQTTiZB2fM/7BLg6511JqkruY2z6LQ=
|
||||
gocv.io/x/gocv v0.31.0/go.mod h1:oc6FvfYqfBp99p+yOEzs9tbYF9gOrAQSeL/dyIPefJU=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@@ -920,7 +913,6 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
howett.net/plist v0.0.0-20201203080718-1454fab16a06/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
|
||||
@@ -5,10 +5,10 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/electricbubble/gadb"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gadb"
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,9 +3,10 @@ package ios
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
giDevice "github.com/electricbubble/gidevice"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
||||
)
|
||||
|
||||
type Application struct {
|
||||
@@ -24,21 +25,21 @@ var listAppsCmd = &cobra.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
var applicationType giDevice.ApplicationType
|
||||
var applicationType gidevice.ApplicationType
|
||||
switch appType {
|
||||
case "user":
|
||||
applicationType = giDevice.ApplicationTypeUser
|
||||
applicationType = gidevice.ApplicationTypeUser
|
||||
case "system":
|
||||
applicationType = giDevice.ApplicationTypeSystem
|
||||
applicationType = gidevice.ApplicationTypeSystem
|
||||
case "internal":
|
||||
applicationType = giDevice.ApplicationTypeInternal
|
||||
applicationType = gidevice.ApplicationTypeInternal
|
||||
case "all":
|
||||
applicationType = giDevice.ApplicationTypeAny
|
||||
applicationType = gidevice.ApplicationTypeAny
|
||||
}
|
||||
|
||||
result, errList := device.InstallationProxyBrowse(
|
||||
giDevice.WithApplicationType(applicationType),
|
||||
giDevice.WithReturnAttributes("CFBundleVersion", "CFBundleDisplayName", "CFBundleIdentifier"))
|
||||
gidevice.WithApplicationType(applicationType),
|
||||
gidevice.WithReturnAttributes("CFBundleVersion", "CFBundleDisplayName", "CFBundleIdentifier"))
|
||||
if errList != nil {
|
||||
return fmt.Errorf("get app list failed")
|
||||
}
|
||||
|
||||
@@ -5,15 +5,15 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
giDevice "github.com/electricbubble/gidevice"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
||||
)
|
||||
|
||||
type Device struct {
|
||||
d giDevice.Device
|
||||
d gidevice.Device
|
||||
UDID string `json:"UDID"`
|
||||
Status string `json:"status"`
|
||||
ConnectionType string `json:"connectionType"`
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
giDevice "github.com/electricbubble/gidevice"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ var iosRootCmd = &cobra.Command{
|
||||
Short: "simple utils for ios device management",
|
||||
}
|
||||
|
||||
func getDevice(udid string) (giDevice.Device, error) {
|
||||
func getDevice(udid string) (gidevice.Device, error) {
|
||||
devices, err := uixt.IOSDevices(udid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
|
||||
"github.com/denisbrodbeck/machineid"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog/log"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/env"
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/version"
|
||||
@@ -23,8 +23,7 @@ func init() {
|
||||
// init GA client
|
||||
clientID, err := machineid.ProtectedID("hrp")
|
||||
if err != nil {
|
||||
nodeUUID, _ := uuid.NewUUID()
|
||||
clientID = nodeUUID.String()
|
||||
clientID = uuid.NewV1().String()
|
||||
}
|
||||
gaClient = NewGAClient(trackingID, clientID)
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
v4.3.0
|
||||
v4.3.0-10241627
|
||||
@@ -8,4 +8,4 @@ import (
|
||||
var VERSION string
|
||||
|
||||
// httprunner python version
|
||||
const HttpRunnerMinimumVersion = "v4.2.0"
|
||||
const HttpRunnerMinimumVersion = "v4.3.0"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# boomer
|
||||
|
||||
This module is initially forked from [myzhan/boomer] and made a lot of changes.
|
||||
This module is initially forked from [myzhan/boomer@v1.6.0] and made a lot of changes.
|
||||
|
||||
[myzhan/boomer]: https://github.com/myzhan/boomer
|
||||
[myzhan/boomer@v1.6.0]: https://github.com/myzhan/boomer/tree/v1.6.0
|
||||
|
||||
@@ -9,12 +9,12 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/push"
|
||||
"github.com/rs/zerolog/log"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/json"
|
||||
)
|
||||
@@ -461,10 +461,9 @@ var (
|
||||
|
||||
// NewPrometheusPusherOutput returns a PrometheusPusherOutput.
|
||||
func NewPrometheusPusherOutput(gatewayURL, jobName string, mode string) *PrometheusPusherOutput {
|
||||
nodeUUID, _ := uuid.NewUUID()
|
||||
return &PrometheusPusherOutput{
|
||||
pusher: push.New(gatewayURL, jobName).
|
||||
Grouping("instance", nodeUUID.String()).
|
||||
Grouping("instance", uuid.NewV1().String()).
|
||||
Grouping("mode", mode),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog/log"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"github.com/shirou/gopsutil/cpu"
|
||||
"github.com/shirou/gopsutil/mem"
|
||||
"github.com/shirou/gopsutil/process"
|
||||
@@ -84,7 +84,7 @@ func startCPUProfile(file string, duration time.Duration) (err error) {
|
||||
// generate a random nodeID like locust does, using the same algorithm.
|
||||
func getNodeID() (nodeID string) {
|
||||
hostname, _ := os.Hostname()
|
||||
id := strings.Replace(uuid.New().String(), "-", "", -1)
|
||||
id := strings.Replace(uuid.NewV4().String(), "-", "", -1)
|
||||
nodeID = fmt.Sprintf("%s_%s", hostname, id)
|
||||
return
|
||||
}
|
||||
|
||||
5
hrp/pkg/gadb/README.md
Normal file
5
hrp/pkg/gadb/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# gadb
|
||||
|
||||
This module is initially forked from [electricbubble/gadb@v0.0.7].
|
||||
|
||||
[electricbubble/gadb@v0.0.7]: https://github.com/electricbubble/gadb/tree/v0.0.7
|
||||
216
hrp/pkg/gadb/client.go
Normal file
216
hrp/pkg/gadb/client.go
Normal file
@@ -0,0 +1,216 @@
|
||||
package gadb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const AdbServerPort = 5037
|
||||
const AdbDaemonPort = 5555
|
||||
|
||||
type Client struct {
|
||||
host string
|
||||
port int
|
||||
}
|
||||
|
||||
func NewClient() (Client, error) {
|
||||
return NewClientWith("localhost")
|
||||
}
|
||||
|
||||
func NewClientWith(host string, port ...int) (adbClient Client, err error) {
|
||||
if len(port) == 0 {
|
||||
port = []int{AdbServerPort}
|
||||
}
|
||||
adbClient.host = host
|
||||
adbClient.port = port[0]
|
||||
|
||||
var tp transport
|
||||
if tp, err = adbClient.createTransport(); err != nil {
|
||||
return Client{}, err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) ServerVersion() (version int, err error) {
|
||||
var resp string
|
||||
if resp, err = c.executeCommand("host:version"); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var v int64
|
||||
if v, err = strconv.ParseInt(resp, 16, 64); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
version = int(v)
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) DeviceSerialList() (serials []string, err error) {
|
||||
var resp string
|
||||
if resp, err = c.executeCommand("host:devices"); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
lines := strings.Split(resp, "\n")
|
||||
serials = make([]string, 0, len(lines))
|
||||
|
||||
for i := range lines {
|
||||
fields := strings.Fields(lines[i])
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
serials = append(serials, fields[0])
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
for i := range lines {
|
||||
line := strings.TrimSpace(lines[i])
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 5 || len(fields[0]) == 0 {
|
||||
debugLog(fmt.Sprintf("can't parse: %s", line))
|
||||
continue
|
||||
}
|
||||
|
||||
sliceAttrs := fields[2:]
|
||||
mapAttrs := map[string]string{}
|
||||
for _, field := range sliceAttrs {
|
||||
split := strings.Split(field, ":")
|
||||
key, val := split[0], split[1]
|
||||
mapAttrs[key] = val
|
||||
}
|
||||
devices = append(devices, Device{adbClient: c, serial: fields[0], attrs: mapAttrs})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) ForwardList() (deviceForward []DeviceForward, err error) {
|
||||
var resp string
|
||||
if resp, err = c.executeCommand("host:list-forward"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lines := strings.Split(resp, "\n")
|
||||
deviceForward = make([]DeviceForward, 0, len(lines))
|
||||
|
||||
for i := range lines {
|
||||
line := strings.TrimSpace(lines[i])
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
deviceForward = append(deviceForward, DeviceForward{Serial: fields[0], Local: fields[1], Remote: fields[2]})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) ForwardKillAll() (err error) {
|
||||
_, err = c.executeCommand("host:killforward-all", true)
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) Connect(ip string, port ...int) (err error) {
|
||||
if len(port) == 0 {
|
||||
port = []int{AdbDaemonPort}
|
||||
}
|
||||
|
||||
var resp string
|
||||
if resp, err = c.executeCommand(fmt.Sprintf("host:connect:%s:%d", ip, port[0])); err != nil {
|
||||
return err
|
||||
}
|
||||
if !strings.HasPrefix(resp, "connected to") && !strings.HasPrefix(resp, "already connected to") {
|
||||
return fmt.Errorf("adb connect: %s", resp)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) Disconnect(ip string, port ...int) (err error) {
|
||||
cmd := fmt.Sprintf("host:disconnect:%s", ip)
|
||||
if len(port) != 0 {
|
||||
cmd = fmt.Sprintf("host:disconnect:%s:%d", ip, port[0])
|
||||
}
|
||||
|
||||
var resp string
|
||||
if resp, err = c.executeCommand(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
if !strings.HasPrefix(resp, "disconnected") {
|
||||
return fmt.Errorf("adb disconnect: %s", resp)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) DisconnectAll() (err error) {
|
||||
var resp string
|
||||
if resp, err = c.executeCommand("host:disconnect:"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(resp, "disconnected everything") {
|
||||
return fmt.Errorf("adb disconnect all: %s", resp)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) KillServer() (err error) {
|
||||
var tp transport
|
||||
if tp, err = c.createTransport(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
|
||||
err = tp.Send("host:kill")
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) createTransport() (tp transport, err error) {
|
||||
return newTransport(fmt.Sprintf("%s:%d", c.host, c.port))
|
||||
}
|
||||
|
||||
func (c Client) executeCommand(command string, onlyVerifyResponse ...bool) (resp string, err error) {
|
||||
if len(onlyVerifyResponse) == 0 {
|
||||
onlyVerifyResponse = []bool{false}
|
||||
}
|
||||
|
||||
var tp transport
|
||||
if tp, err = c.createTransport(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
|
||||
if err = tp.Send(command); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err = tp.VerifyResponse(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if onlyVerifyResponse[0] {
|
||||
return
|
||||
}
|
||||
|
||||
if resp, err = tp.UnpackString(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return
|
||||
}
|
||||
147
hrp/pkg/gadb/client_test.go
Normal file
147
hrp/pkg/gadb/client_test.go
Normal file
@@ -0,0 +1,147 @@
|
||||
//go:build localtest
|
||||
|
||||
package gadb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClient_ServerVersion(t *testing.T) {
|
||||
SetDebug(true)
|
||||
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
adbServerVersion, err := adbClient.ServerVersion()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Log(adbServerVersion)
|
||||
}
|
||||
|
||||
func TestClient_DeviceSerialList(t *testing.T) {
|
||||
SetDebug(true)
|
||||
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
serials, err := adbClient.DeviceSerialList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := range serials {
|
||||
t.Log(serials[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_DeviceList(t *testing.T) {
|
||||
SetDebug(true)
|
||||
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := adbClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := range devices {
|
||||
t.Log(devices[i].serial, devices[i].DeviceInfo())
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_ForwardList(t *testing.T) {
|
||||
SetDebug(true)
|
||||
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
deviceForwardList, err := adbClient.ForwardList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := range deviceForwardList {
|
||||
t.Log(deviceForwardList[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_ForwardKillAll(t *testing.T) {
|
||||
SetDebug(true)
|
||||
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = adbClient.ForwardKillAll()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_Connect(t *testing.T) {
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
SetDebug(true)
|
||||
|
||||
err = adbClient.Connect("192.168.1.28")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_Disconnect(t *testing.T) {
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
SetDebug(true)
|
||||
|
||||
err = adbClient.Disconnect("192.168.1.28")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_DisconnectAll(t *testing.T) {
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
SetDebug(true)
|
||||
|
||||
err = adbClient.DisconnectAll()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_KillServer(t *testing.T) {
|
||||
SetDebug(true)
|
||||
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = adbClient.KillServer()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
299
hrp/pkg/gadb/device.go
Normal file
299
hrp/pkg/gadb/device.go
Normal file
@@ -0,0 +1,299 @@
|
||||
package gadb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DeviceFileInfo struct {
|
||||
Name string
|
||||
Mode os.FileMode
|
||||
Size uint32
|
||||
LastModified time.Time
|
||||
}
|
||||
|
||||
func (info DeviceFileInfo) IsDir() bool {
|
||||
return (info.Mode & (1 << 14)) == (1 << 14)
|
||||
}
|
||||
|
||||
const DefaultFileMode = os.FileMode(0664)
|
||||
|
||||
type DeviceState string
|
||||
|
||||
const (
|
||||
StateUnknown DeviceState = "UNKNOWN"
|
||||
StateOnline DeviceState = "online"
|
||||
StateOffline DeviceState = "offline"
|
||||
StateDisconnected DeviceState = "disconnected"
|
||||
)
|
||||
|
||||
var deviceStateStrings = map[string]DeviceState{
|
||||
"": StateDisconnected,
|
||||
"offline": StateOffline,
|
||||
"device": StateOnline,
|
||||
}
|
||||
|
||||
func deviceStateConv(k string) (deviceState DeviceState) {
|
||||
var ok bool
|
||||
if deviceState, ok = deviceStateStrings[k]; !ok {
|
||||
return StateUnknown
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type DeviceForward struct {
|
||||
Serial string
|
||||
Local string
|
||||
Remote string
|
||||
// LocalProtocol string
|
||||
// RemoteProtocol string
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
adbClient Client
|
||||
serial string
|
||||
attrs map[string]string
|
||||
}
|
||||
|
||||
func (d Device) Product() string {
|
||||
return d.attrs["product"]
|
||||
}
|
||||
|
||||
func (d Device) Model() string {
|
||||
return d.attrs["model"]
|
||||
}
|
||||
|
||||
func (d Device) Usb() string {
|
||||
return d.attrs["usb"]
|
||||
}
|
||||
|
||||
func (d Device) transportId() string {
|
||||
return d.attrs["transport_id"]
|
||||
}
|
||||
|
||||
func (d Device) DeviceInfo() map[string]string {
|
||||
return d.attrs
|
||||
}
|
||||
|
||||
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 {
|
||||
return d.Usb() != ""
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
command := ""
|
||||
local := fmt.Sprintf("tcp:%d", localPort)
|
||||
remote := fmt.Sprintf("tcp:%d", remotePort)
|
||||
|
||||
if len(noRebind) != 0 && noRebind[0] {
|
||||
command = fmt.Sprintf("host-serial:%s:forward:norebind:%s;%s", d.serial, local, remote)
|
||||
} else {
|
||||
command = fmt.Sprintf("host-serial:%s:forward:%s;%s", d.serial, local, remote)
|
||||
}
|
||||
|
||||
_, err = d.adbClient.executeCommand(command, true)
|
||||
return
|
||||
}
|
||||
|
||||
func (d Device) ForwardList() (deviceForwardList []DeviceForward, err error) {
|
||||
var forwardList []DeviceForward
|
||||
if forwardList, err = d.adbClient.ForwardList(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deviceForwardList = make([]DeviceForward, 0, len(deviceForwardList))
|
||||
for i := range forwardList {
|
||||
if forwardList[i].Serial == d.serial {
|
||||
deviceForwardList = append(deviceForwardList, forwardList[i])
|
||||
}
|
||||
}
|
||||
// resp, err := d.adbClient.executeCommand(fmt.Sprintf("host-serial:%s:list-forward", d.serial))
|
||||
return
|
||||
}
|
||||
|
||||
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) {
|
||||
raw, err := d.RunShellCommandWithBytes(cmd, args...)
|
||||
return string(raw), err
|
||||
}
|
||||
|
||||
func (d Device) RunShellCommandWithBytes(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")
|
||||
}
|
||||
raw, err := d.executeCommand(fmt.Sprintf("shell:%s", cmd))
|
||||
return raw, err
|
||||
}
|
||||
|
||||
func (d Device) EnableAdbOverTCP(port ...int) (err error) {
|
||||
if len(port) == 0 {
|
||||
port = []int{AdbDaemonPort}
|
||||
}
|
||||
|
||||
_, err = d.executeCommand(fmt.Sprintf("tcpip:%d", port[0]), true)
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if err = tp.Send(fmt.Sprintf("host:transport:%s", d.serial)); err != nil {
|
||||
return transport{}, err
|
||||
}
|
||||
err = tp.VerifyResponse()
|
||||
return
|
||||
}
|
||||
|
||||
func (d Device) executeCommand(command string, onlyVerifyResponse ...bool) (raw []byte, err error) {
|
||||
if len(onlyVerifyResponse) == 0 {
|
||||
onlyVerifyResponse = []bool{false}
|
||||
}
|
||||
|
||||
var tp transport
|
||||
if tp, err = d.createDeviceTransport(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
|
||||
if err = tp.Send(command); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = tp.VerifyResponse(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if onlyVerifyResponse[0] {
|
||||
return
|
||||
}
|
||||
|
||||
raw, err = tp.ReadBytesAll()
|
||||
return
|
||||
}
|
||||
|
||||
func (d Device) List(remotePath string) (devFileInfos []DeviceFileInfo, err error) {
|
||||
var tp transport
|
||||
if tp, err = d.createDeviceTransport(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
|
||||
var sync syncTransport
|
||||
if sync, err = tp.CreateSyncTransport(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = sync.Close() }()
|
||||
|
||||
if err = sync.Send("LIST", remotePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
devFileInfos = make([]DeviceFileInfo, 0)
|
||||
|
||||
var entry DeviceFileInfo
|
||||
for entry, err = sync.ReadDirectoryEntry(); err == nil; entry, err = sync.ReadDirectoryEntry() {
|
||||
if entry == (DeviceFileInfo{}) {
|
||||
break
|
||||
}
|
||||
devFileInfos = append(devFileInfos, entry)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
modification = []time.Time{stat.ModTime()}
|
||||
}
|
||||
|
||||
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) {
|
||||
if len(mode) == 0 {
|
||||
mode = []os.FileMode{DefaultFileMode}
|
||||
}
|
||||
|
||||
var tp transport
|
||||
if tp, err = d.createDeviceTransport(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
|
||||
var sync syncTransport
|
||||
if sync, err = tp.CreateSyncTransport(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = sync.Close() }()
|
||||
|
||||
data := fmt.Sprintf("%s,%d", remotePath, mode[0])
|
||||
if err = sync.Send("SEND", data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = sync.SendStream(source); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = sync.SendStatus("DONE", uint32(modification.Unix())); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = sync.VerifyStatus(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d Device) Pull(remotePath string, dest io.Writer) (err error) {
|
||||
var tp transport
|
||||
if tp, err = d.createDeviceTransport(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
|
||||
var sync syncTransport
|
||||
if sync, err = tp.CreateSyncTransport(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = sync.Close() }()
|
||||
|
||||
if err = sync.Send("RECV", remotePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = sync.WriteStream(dest)
|
||||
return
|
||||
}
|
||||
334
hrp/pkg/gadb/device_test.go
Normal file
334
hrp/pkg/gadb/device_test.go
Normal file
@@ -0,0 +1,334 @@
|
||||
//go:build localtest
|
||||
|
||||
package gadb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDevice_State(t *testing.T) {
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := adbClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := range devices {
|
||||
dev := devices[i]
|
||||
state, err := dev.State()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(dev.Serial(), state)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_DevicePath(t *testing.T) {
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := adbClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := range devices {
|
||||
dev := devices[i]
|
||||
devPath, err := dev.DevicePath()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(dev.Serial(), devPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_Product(t *testing.T) {
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := adbClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := range devices {
|
||||
dev := devices[i]
|
||||
product := dev.Product()
|
||||
t.Log(dev.Serial(), product)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_Model(t *testing.T) {
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := adbClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := range devices {
|
||||
dev := devices[i]
|
||||
t.Log(dev.Serial(), dev.Model())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_Usb(t *testing.T) {
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := adbClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := range devices {
|
||||
dev := devices[i]
|
||||
t.Log(dev.Serial(), dev.Usb(), dev.IsUsb())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDevice_DeviceInfo(t *testing.T) {
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := adbClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := range devices {
|
||||
dev := devices[i]
|
||||
t.Log(dev.DeviceInfo())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_Forward(t *testing.T) {
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := adbClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
SetDebug(true)
|
||||
|
||||
localPort := 61000
|
||||
err = devices[0].Forward(localPort, 6790)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = devices[0].ForwardKill(localPort)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_ForwardList(t *testing.T) {
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := adbClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
SetDebug(true)
|
||||
|
||||
for i := range devices {
|
||||
dev := devices[i]
|
||||
forwardList, err := dev.ForwardList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(dev.serial, "->", forwardList)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_ForwardKill(t *testing.T) {
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := adbClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
SetDebug(true)
|
||||
|
||||
err = devices[0].ForwardKill(6790)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_RunShellCommand(t *testing.T) {
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := adbClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// for i := range devices {
|
||||
// dev := devices[i]
|
||||
// // cmdOutput, err := dev.RunShellCommand(`pm list packages | grep "bili"`)
|
||||
// // cmdOutput, err := dev.RunShellCommand(`pm list packages`, `| grep "bili"`)
|
||||
// // cmdOutput, err := dev.RunShellCommand("dumpsys activity | grep mFocusedActivity")
|
||||
// cmdOutput, err := dev.RunShellCommand("monkey", "-p", "tv.danmaku.bili", "-c", "android.intent.category.LAUNCHER", "1")
|
||||
// if err != nil {
|
||||
// t.Fatal(dev.serial, err)
|
||||
// }
|
||||
// t.Log("\n"+dev.serial, cmdOutput)
|
||||
// }
|
||||
|
||||
// SetDebug(true)
|
||||
|
||||
dev := devices[len(devices)-1]
|
||||
dev = devices[0]
|
||||
|
||||
// cmdOutput, err := dev.RunShellCommand("monkey", "-p", "tv.danmaku.bili", "-c", "android.intent.category.LAUNCHER", "1")
|
||||
cmdOutput, err := dev.RunShellCommand("ls /sdcard")
|
||||
// cmdOutput, err := dev.RunShellCommandWithBytes("screencap -p")
|
||||
if err != nil {
|
||||
t.Fatal(dev.serial, err)
|
||||
}
|
||||
t.Log("\n⬇️"+dev.serial+"⬇️\n", cmdOutput)
|
||||
|
||||
}
|
||||
|
||||
func TestDevice_EnableAdbOverTCP(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]
|
||||
|
||||
SetDebug(true)
|
||||
|
||||
err = dev.EnableAdbOverTCP()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_List(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]
|
||||
|
||||
SetDebug(true)
|
||||
|
||||
// fileEntries, err := dev.List("/sdcard")
|
||||
fileEntries, err := dev.List("/sdcard/Download")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := range fileEntries {
|
||||
t.Log(fileEntries[i].Name, "\t", fileEntries[i].IsDir())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_Push(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]
|
||||
|
||||
SetDebug(true)
|
||||
|
||||
file, _ := os.Open("/Users/hero/Documents/temp/MuMu共享文件夹/test.txt")
|
||||
err = dev.PushFile(file, "/sdcard/Download/push.txt", time.Now())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = dev.Push(strings.NewReader("world"), "/sdcard/Download/hello.txt", time.Now())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_Pull(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]
|
||||
|
||||
SetDebug(true)
|
||||
|
||||
buffer := bytes.NewBufferString("")
|
||||
err = dev.Pull("/sdcard/Download/hello.txt", buffer)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
userHomeDir, _ := os.UserHomeDir()
|
||||
if err = ioutil.WriteFile(userHomeDir+"/Desktop/hello.txt", buffer.Bytes(), DefaultFileMode); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
59
hrp/pkg/gadb/examples/main.go
Normal file
59
hrp/pkg/gadb/examples/main.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gadb"
|
||||
)
|
||||
|
||||
func main() {
|
||||
adbClient, err := gadb.NewClient()
|
||||
checkErr(err, "fail to connect adb server")
|
||||
|
||||
devices, err := adbClient.DeviceList()
|
||||
checkErr(err)
|
||||
|
||||
if len(devices) == 0 {
|
||||
log.Fatalln("list of devices is empty")
|
||||
}
|
||||
|
||||
dev := devices[0]
|
||||
|
||||
userHomeDir, _ := os.UserHomeDir()
|
||||
apk, err := os.Open(userHomeDir + "/Desktop/xuexi_android_10002068.apk")
|
||||
checkErr(err)
|
||||
|
||||
log.Println("starting to push apk")
|
||||
|
||||
remotePath := "/data/local/tmp/xuexi_android_10002068.apk"
|
||||
err = dev.PushFile(apk, remotePath)
|
||||
checkErr(err, "adb push")
|
||||
|
||||
log.Println("push completed")
|
||||
|
||||
log.Println("starting to install apk")
|
||||
|
||||
shellOutput, err := dev.RunShellCommand("pm install", remotePath)
|
||||
checkErr(err, "pm install")
|
||||
if !strings.Contains(shellOutput, "Success") {
|
||||
log.Fatalln("fail to install: ", shellOutput)
|
||||
}
|
||||
|
||||
log.Println("install completed")
|
||||
|
||||
}
|
||||
|
||||
func checkErr(err error, msg ...string) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var output string
|
||||
if len(msg) != 0 {
|
||||
output = msg[0] + " "
|
||||
}
|
||||
output += err.Error()
|
||||
log.Fatalln(output)
|
||||
}
|
||||
17
hrp/pkg/gadb/gadb.go
Normal file
17
hrp/pkg/gadb/gadb.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package gadb
|
||||
|
||||
import "log"
|
||||
|
||||
var debugFlag = false
|
||||
|
||||
// SetDebug set debug mode
|
||||
func SetDebug(debug bool) {
|
||||
debugFlag = debug
|
||||
}
|
||||
|
||||
func debugLog(msg string) {
|
||||
if !debugFlag {
|
||||
return
|
||||
}
|
||||
log.Println("[DEBUG] [gadb] " + msg)
|
||||
}
|
||||
252
hrp/pkg/gadb/sync_transport.go
Normal file
252
hrp/pkg/gadb/sync_transport.go
Normal file
@@ -0,0 +1,252 @@
|
||||
package gadb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type syncTransport struct {
|
||||
sock net.Conn
|
||||
readTimeout time.Duration
|
||||
}
|
||||
|
||||
func newSyncTransport(sock net.Conn, readTimeout time.Duration) syncTransport {
|
||||
return syncTransport{sock: sock, readTimeout: readTimeout}
|
||||
}
|
||||
|
||||
func (sync syncTransport) Send(command, data string) (err error) {
|
||||
if len(command) != 4 {
|
||||
return errors.New("sync commands must have length 4")
|
||||
}
|
||||
msg := bytes.NewBufferString(command)
|
||||
if err = binary.Write(msg, binary.LittleEndian, int32(len(data))); err != nil {
|
||||
return fmt.Errorf("sync transport write: %w", err)
|
||||
}
|
||||
msg.WriteString(data)
|
||||
|
||||
debugLog(fmt.Sprintf("--> %s", msg.String()))
|
||||
return _send(sync.sock, msg.Bytes())
|
||||
}
|
||||
|
||||
func (sync syncTransport) SendStream(reader io.Reader) (err error) {
|
||||
syncMaxChunkSize := 64 * 1024
|
||||
for err == nil {
|
||||
tmp := make([]byte, syncMaxChunkSize)
|
||||
var n int
|
||||
n, err = reader.Read(tmp)
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
if err == nil {
|
||||
err = sync.sendChunk(tmp[:n])
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (sync syncTransport) SendStatus(statusCode string, n uint32) (err error) {
|
||||
msg := bytes.NewBufferString(statusCode)
|
||||
if err = binary.Write(msg, binary.LittleEndian, n); err != nil {
|
||||
return fmt.Errorf("sync transport write: %w", err)
|
||||
}
|
||||
debugLog(fmt.Sprintf("--> %s", msg.String()))
|
||||
return _send(sync.sock, msg.Bytes())
|
||||
}
|
||||
|
||||
func (sync syncTransport) sendChunk(buffer []byte) (err error) {
|
||||
msg := bytes.NewBufferString("DATA")
|
||||
if err = binary.Write(msg, binary.LittleEndian, int32(len(buffer))); err != nil {
|
||||
return fmt.Errorf("sync transport write: %w", err)
|
||||
}
|
||||
debugLog(fmt.Sprintf("--> %s ......", msg.String()))
|
||||
msg.Write(buffer)
|
||||
return _send(sync.sock, msg.Bytes())
|
||||
}
|
||||
|
||||
func (sync syncTransport) VerifyStatus() (err error) {
|
||||
var status string
|
||||
if status, err = sync.ReadStringN(4); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log := bytes.NewBufferString(fmt.Sprintf("<-- %s", status))
|
||||
defer func() {
|
||||
debugLog(log.String())
|
||||
}()
|
||||
|
||||
var tmpUint32 uint32
|
||||
if tmpUint32, err = sync.ReadUint32(); err != nil {
|
||||
return fmt.Errorf("sync transport read (status): %w", err)
|
||||
}
|
||||
log.WriteString(fmt.Sprintf(" %d\t", tmpUint32))
|
||||
|
||||
var msg string
|
||||
if msg, err = sync.ReadStringN(int(tmpUint32)); err != nil {
|
||||
return err
|
||||
}
|
||||
log.WriteString(msg)
|
||||
|
||||
if status == "FAIL" {
|
||||
err = fmt.Errorf("sync verify status (fail): %s", msg)
|
||||
return
|
||||
}
|
||||
|
||||
if status != "OKAY" {
|
||||
err = fmt.Errorf("sync verify status: Unknown error: %s", msg)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var syncReadChunkDone = errors.New("sync read chunk done")
|
||||
|
||||
func (sync syncTransport) WriteStream(dest io.Writer) (err error) {
|
||||
var chunk []byte
|
||||
save := func() error {
|
||||
if chunk, err = sync.readChunk(); err != nil && err != syncReadChunkDone {
|
||||
return fmt.Errorf("sync read chunk: %w", err)
|
||||
}
|
||||
if err == syncReadChunkDone {
|
||||
return err
|
||||
}
|
||||
if err = _send(dest, chunk); err != nil {
|
||||
return fmt.Errorf("sync write stream: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for err == nil {
|
||||
err = save()
|
||||
}
|
||||
|
||||
if err == syncReadChunkDone {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (sync syncTransport) readChunk() (chunk []byte, err error) {
|
||||
var status string
|
||||
if status, err = sync.ReadStringN(4); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log := bytes.NewBufferString("")
|
||||
defer func() { debugLog(log.String()) }()
|
||||
|
||||
var tmpUint32 uint32
|
||||
if tmpUint32, err = sync.ReadUint32(); err != nil {
|
||||
return nil, fmt.Errorf("read chunk (length): %w", err)
|
||||
}
|
||||
|
||||
if status == "FAIL" {
|
||||
log.WriteString(fmt.Sprintf("<-- %s\t%d\t", status, tmpUint32))
|
||||
var sError string
|
||||
if sError, err = sync.ReadStringN(int(tmpUint32)); err != nil {
|
||||
return nil, fmt.Errorf("read chunk (error message): %w", err)
|
||||
}
|
||||
err = fmt.Errorf("status (fail): %s", sError)
|
||||
log.WriteString(sError)
|
||||
return
|
||||
}
|
||||
|
||||
switch status {
|
||||
case "DONE":
|
||||
log.WriteString(fmt.Sprintf("<-- %s", status))
|
||||
err = syncReadChunkDone
|
||||
return
|
||||
case "DATA":
|
||||
log.WriteString(fmt.Sprintf("<-- %s\t%d\t", status, tmpUint32))
|
||||
if chunk, err = sync.ReadBytesN(int(tmpUint32)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
log.WriteString(fmt.Sprintf("<-- %s\t%d\t", status, tmpUint32))
|
||||
err = errors.New("unknown error")
|
||||
}
|
||||
|
||||
log.WriteString("......")
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func (sync syncTransport) ReadDirectoryEntry() (entry DeviceFileInfo, err error) {
|
||||
var status string
|
||||
if status, err = sync.ReadStringN(4); err != nil {
|
||||
return DeviceFileInfo{}, err
|
||||
}
|
||||
|
||||
log := bytes.NewBufferString(fmt.Sprintf("<-- %s", status))
|
||||
defer func() {
|
||||
debugLog(log.String())
|
||||
}()
|
||||
|
||||
if status == "DONE" {
|
||||
return
|
||||
}
|
||||
|
||||
log = bytes.NewBufferString(fmt.Sprintf("<-- %s\t", status))
|
||||
|
||||
if err = binary.Read(sync.sock, binary.LittleEndian, &entry.Mode); err != nil {
|
||||
return DeviceFileInfo{}, fmt.Errorf("sync transport read (mode): %w", err)
|
||||
}
|
||||
log.WriteString(entry.Mode.String() + "\t")
|
||||
|
||||
if entry.Size, err = sync.ReadUint32(); err != nil {
|
||||
return DeviceFileInfo{}, fmt.Errorf("sync transport read (size): %w", err)
|
||||
}
|
||||
log.WriteString(fmt.Sprintf("%10d", entry.Size) + "\t")
|
||||
|
||||
var tmpUint32 uint32
|
||||
if tmpUint32, err = sync.ReadUint32(); err != nil {
|
||||
return DeviceFileInfo{}, fmt.Errorf("sync transport read (time): %w", err)
|
||||
}
|
||||
entry.LastModified = time.Unix(int64(tmpUint32), 0)
|
||||
log.WriteString(entry.LastModified.String() + "\t")
|
||||
|
||||
if tmpUint32, err = sync.ReadUint32(); err != nil {
|
||||
return DeviceFileInfo{}, fmt.Errorf("sync transport read (file name length): %w", err)
|
||||
}
|
||||
log.WriteString(fmt.Sprintf("%d\t", tmpUint32))
|
||||
|
||||
if entry.Name, err = sync.ReadStringN(int(tmpUint32)); err != nil {
|
||||
return DeviceFileInfo{}, fmt.Errorf("sync transport read (file name): %w", err)
|
||||
}
|
||||
log.WriteString(entry.Name + "\t")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (sync syncTransport) ReadUint32() (n uint32, err error) {
|
||||
err = binary.Read(sync.sock, binary.LittleEndian, &n)
|
||||
return
|
||||
}
|
||||
|
||||
func (sync syncTransport) ReadStringN(size int) (s string, err error) {
|
||||
var raw []byte
|
||||
if raw, err = sync.ReadBytesN(size); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
func (sync syncTransport) ReadBytesN(size int) (raw []byte, err error) {
|
||||
_ = sync.sock.SetReadDeadline(time.Now().Add(time.Second * sync.readTimeout))
|
||||
return _readN(sync.sock, size)
|
||||
}
|
||||
|
||||
func (sync syncTransport) Close() (err error) {
|
||||
if sync.sock == nil {
|
||||
return nil
|
||||
}
|
||||
return sync.sock.Close()
|
||||
}
|
||||
150
hrp/pkg/gadb/transport.go
Normal file
150
hrp/pkg/gadb/transport.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package gadb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrConnBroken = errors.New("socket connection broken")
|
||||
|
||||
var DefaultAdbReadTimeout time.Duration = 60
|
||||
|
||||
type transport struct {
|
||||
sock net.Conn
|
||||
readTimeout time.Duration
|
||||
}
|
||||
|
||||
func newTransport(address string, readTimeout ...time.Duration) (tp transport, err error) {
|
||||
if len(readTimeout) == 0 {
|
||||
readTimeout = []time.Duration{DefaultAdbReadTimeout}
|
||||
}
|
||||
tp.readTimeout = readTimeout[0]
|
||||
if tp.sock, err = net.Dial("tcp", address); err != nil {
|
||||
err = fmt.Errorf("adb transport: %w", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t transport) Send(command string) (err error) {
|
||||
msg := fmt.Sprintf("%04x%s", len(command), command)
|
||||
debugLog(fmt.Sprintf("--> %s", command))
|
||||
return _send(t.sock, []byte(msg))
|
||||
}
|
||||
|
||||
func (t transport) VerifyResponse() (err error) {
|
||||
var status string
|
||||
if status, err = t.ReadStringN(4); err != nil {
|
||||
return err
|
||||
}
|
||||
if status == "OKAY" {
|
||||
debugLog(fmt.Sprintf("<-- %s", status))
|
||||
return nil
|
||||
}
|
||||
|
||||
var sError string
|
||||
if sError, err = t.UnpackString(); err != nil {
|
||||
return err
|
||||
}
|
||||
err = fmt.Errorf("command failed: %s", sError)
|
||||
debugLog(fmt.Sprintf("<-- %s %s", status, sError))
|
||||
return
|
||||
}
|
||||
|
||||
func (t transport) ReadStringAll() (s string, err error) {
|
||||
var raw []byte
|
||||
raw, err = t.ReadBytesAll()
|
||||
return string(raw), err
|
||||
}
|
||||
|
||||
func (t transport) ReadBytesAll() (raw []byte, err error) {
|
||||
raw, err = ioutil.ReadAll(t.sock)
|
||||
debugLog(fmt.Sprintf("\r%s", raw))
|
||||
return
|
||||
}
|
||||
|
||||
func (t transport) UnpackString() (s string, err error) {
|
||||
var raw []byte
|
||||
raw, err = t.UnpackBytes()
|
||||
return string(raw), err
|
||||
}
|
||||
|
||||
func (t transport) UnpackBytes() (raw []byte, err error) {
|
||||
var length string
|
||||
if length, err = t.ReadStringN(4); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var size int64
|
||||
if size, err = strconv.ParseInt(length, 16, 64); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
raw, err = t.ReadBytesN(int(size))
|
||||
debugLog(fmt.Sprintf("\r%s", raw))
|
||||
return
|
||||
}
|
||||
|
||||
func (t transport) ReadStringN(size int) (s string, err error) {
|
||||
var raw []byte
|
||||
if raw, err = t.ReadBytesN(size); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
func (t transport) ReadBytesN(size int) (raw []byte, err error) {
|
||||
_ = t.sock.SetReadDeadline(time.Now().Add(time.Second * t.readTimeout))
|
||||
return _readN(t.sock, size)
|
||||
}
|
||||
|
||||
func (t transport) Close() (err error) {
|
||||
if t.sock == nil {
|
||||
return nil
|
||||
}
|
||||
return t.sock.Close()
|
||||
}
|
||||
|
||||
func (t transport) CreateSyncTransport() (sTp syncTransport, err error) {
|
||||
if err = t.Send("sync:"); err != nil {
|
||||
return syncTransport{}, err
|
||||
}
|
||||
if err = t.VerifyResponse(); err != nil {
|
||||
return syncTransport{}, err
|
||||
}
|
||||
sTp = newSyncTransport(t.sock, t.readTimeout)
|
||||
return
|
||||
}
|
||||
|
||||
func _send(writer io.Writer, msg []byte) (err error) {
|
||||
for totalSent := 0; totalSent < len(msg); {
|
||||
var sent int
|
||||
if sent, err = writer.Write(msg[totalSent:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if sent == 0 {
|
||||
return ErrConnBroken
|
||||
}
|
||||
totalSent += sent
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func _readN(reader io.Reader, size int) (raw []byte, err error) {
|
||||
raw = make([]byte, 0, size)
|
||||
for len(raw) < size {
|
||||
buf := make([]byte, size-len(raw))
|
||||
var n int
|
||||
if n, err = io.ReadFull(reader, buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
return nil, ErrConnBroken
|
||||
}
|
||||
raw = append(raw, buf...)
|
||||
}
|
||||
return
|
||||
}
|
||||
28
hrp/pkg/gadb/transport_test.go
Normal file
28
hrp/pkg/gadb/transport_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
//go:build localtest
|
||||
|
||||
package gadb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_transport_VerifyResponse(t *testing.T) {
|
||||
SetDebug(true)
|
||||
|
||||
transport, err := newTransport("localhost:5037")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer transport.Close()
|
||||
|
||||
// err = transport.Send("host:123version")
|
||||
err = transport.Send("host:version")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = transport.VerifyResponse()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
446
hrp/pkg/gidevice/README.md
Normal file
446
hrp/pkg/gidevice/README.md
Normal file
@@ -0,0 +1,446 @@
|
||||
# gidevice
|
||||
|
||||
This module is initially forked from [electricbubble/gidevice@v0.6.2].
|
||||
|
||||
#### Devices
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
usbmux, err := gidevice.NewUsbmux()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
devices, err := usbmux.Devices()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, dev := range devices {
|
||||
log.Println(dev.Properties().SerialNumber, dev.Properties().ProductID, dev.Properties().DeviceID)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### GetValue
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
||||
)
|
||||
|
||||
type DeviceDetail struct {
|
||||
DeviceName string `json:"DeviceName,omitempty"`
|
||||
DeviceColor string `json:"DeviceColor,omitempty"`
|
||||
DeviceClass string `json:"DeviceClass,omitempty"`
|
||||
ProductVersion string `json:"ProductVersion,omitempty"`
|
||||
ProductType string `json:"ProductType,omitempty"`
|
||||
ProductName string `json:"ProductName,omitempty"`
|
||||
ModelNumber string `json:"ModelNumber,omitempty"`
|
||||
SerialNumber string `json:"SerialNumber,omitempty"`
|
||||
SIMStatus string `json:"SIMStatus,omitempty"`
|
||||
PhoneNumber string `json:"PhoneNumber,omitempty"`
|
||||
CPUArchitecture string `json:"CPUArchitecture,omitempty"`
|
||||
ProtocolVersion string `json:"ProtocolVersion,omitempty"`
|
||||
RegionInfo string `json:"RegionInfo,omitempty"`
|
||||
TelephonyCapability bool `json:"TelephonyCapability,omitempty"`
|
||||
TimeZone string `json:"TimeZone,omitempty"`
|
||||
UniqueDeviceID string `json:"UniqueDeviceID,omitempty"`
|
||||
WiFiAddress string `json:"WiFiAddress,omitempty"`
|
||||
WirelessBoardSerialNumber string `json:"WirelessBoardSerialNumber,omitempty"`
|
||||
BluetoothAddress string `json:"BluetoothAddress,omitempty"`
|
||||
BuildVersion string `json:"BuildVersion,omitempty"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
usbmux, err := gidevice.NewUsbmux()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := usbmux.Devices()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if len(devices) == 0 {
|
||||
log.Fatal("No Device")
|
||||
}
|
||||
|
||||
d := devices[0]
|
||||
|
||||
detail, err1 := d.GetValue("", "")
|
||||
if err1 != nil {
|
||||
fmt.Errorf("get %s device detail fail : %w", d.Properties().SerialNumber, err1)
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(detail)
|
||||
d1 := &DeviceDetail{}
|
||||
json.Unmarshal(data, d1)
|
||||
fmt.Println(d1)
|
||||
}
|
||||
```
|
||||
|
||||
#### DeveloperDiskImage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"log"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
usbmux, err := gidevice.NewUsbmux()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := usbmux.Devices()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if len(devices) == 0 {
|
||||
log.Fatal("No Device")
|
||||
}
|
||||
|
||||
d := devices[0]
|
||||
|
||||
imageSignatures, err := d.Images()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
for i, imgSign := range imageSignatures {
|
||||
log.Printf("[%d] %s\n", i+1, base64.StdEncoding.EncodeToString(imgSign))
|
||||
}
|
||||
|
||||
dmgPath := "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/14.4/DeveloperDiskImage.dmg"
|
||||
signaturePath := "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/14.4/DeveloperDiskImage.dmg.signature"
|
||||
|
||||
err = d.MountDeveloperDiskImage(dmgPath, signaturePath)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### App
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
usbmux, err := gidevice.NewUsbmux()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
devices, err := usbmux.Devices()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if len(devices) == 0 {
|
||||
log.Fatalln("No Device")
|
||||
}
|
||||
|
||||
d := devices[0]
|
||||
|
||||
bundleID := "com.apple.Preferences"
|
||||
pid, err := d.AppLaunch(bundleID)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
err = d.AppKill(pid)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
runningProcesses, err := d.AppRunningProcesses()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
for _, process := range runningProcesses {
|
||||
if process.IsApplication {
|
||||
log.Printf("%4d\t%-24s\t%-36s\t%s\n", process.Pid, process.Name, filepath.Base(process.RealAppName), process.StartDate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Screenshot
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
usbmux, err := gidevice.NewUsbmux()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
devices, err := usbmux.Devices()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if len(devices) == 0 {
|
||||
log.Fatalln("No Device")
|
||||
}
|
||||
|
||||
d := devices[0]
|
||||
|
||||
raw, err := d.Screenshot()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
img, format, err := image.Decode(raw)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
userHomeDir, _ := os.UserHomeDir()
|
||||
file, err := os.Create(userHomeDir + "/Desktop/s1." + format)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer func() { _ = file.Close() }()
|
||||
switch format {
|
||||
case "png":
|
||||
err = png.Encode(file, img)
|
||||
case "jpeg":
|
||||
err = jpeg.Encode(file, img, nil)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println(file.Name())
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### SimulateLocation
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
usbmux, err := gidevice.NewUsbmux()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
devices, err := usbmux.Devices()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if len(devices) == 0 {
|
||||
log.Fatalln("No Device")
|
||||
}
|
||||
|
||||
d := devices[0]
|
||||
|
||||
// https://api.map.baidu.com/lbsapi/getpoint/index.html
|
||||
if err = d.SimulateLocationUpdate(116.024067, 40.362639, gidevice.CoordinateSystemBD09); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// https://developer.amap.com/tools/picker
|
||||
// https://lbs.qq.com/tool/getpoint/index.html
|
||||
// if err = d.SimulateLocationUpdate(120.116979, 30.252876, gidevice.CoordinateSystemGCJ02); err != nil {
|
||||
// log.Fatalln(err)
|
||||
// }
|
||||
|
||||
// if err = d.SimulateLocationUpdate(121.499763, 31.239580,gidevice.CoordinateSystemWGS84); err != nil {
|
||||
// if err = d.SimulateLocationUpdate(121.499763, 31.239580); err != nil {
|
||||
// log.Fatalln(err)
|
||||
// }
|
||||
|
||||
// err = d.SimulateLocationRecover()
|
||||
// if err != nil {
|
||||
// log.Fatalln(err)
|
||||
// }
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### XCTest
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
usbmux, err := gidevice.NewUsbmux()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := usbmux.Devices()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if len(devices) == 0 {
|
||||
log.Fatal("No Device")
|
||||
}
|
||||
|
||||
d := devices[0]
|
||||
|
||||
out, cancel, err := d.XCTest("com.leixipaopao.WebDriverAgentRunner.xctrunner")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
done := make(chan os.Signal, 1)
|
||||
signal.Notify(done, os.Interrupt)
|
||||
|
||||
go func() {
|
||||
for s := range out {
|
||||
fmt.Print(s)
|
||||
}
|
||||
}()
|
||||
|
||||
<-done
|
||||
cancel()
|
||||
fmt.Println()
|
||||
log.Println("DONE")
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Connect and Forward
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
"syscall"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
usbmux, err := gidevice.NewUsbmux()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := usbmux.Devices()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if len(devices) == 0 {
|
||||
log.Fatal("No Device")
|
||||
}
|
||||
|
||||
d := devices[0]
|
||||
|
||||
localPort, remotePort := 8100, 8100
|
||||
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", localPort))
|
||||
|
||||
go func(listener net.Listener) {
|
||||
for {
|
||||
var accept net.Conn
|
||||
if accept, err = listener.Accept(); err != nil {
|
||||
log.Println("accept:", err)
|
||||
}
|
||||
|
||||
fmt.Println("accept", accept.RemoteAddr())
|
||||
|
||||
rInnerConn, err := d.NewConnect(remotePort)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
rConn := rInnerConn.RawConn()
|
||||
_ = rConn.SetDeadline(time.Time{})
|
||||
|
||||
go func(lConn net.Conn) {
|
||||
go func(lConn, rConn net.Conn) {
|
||||
if _, err := io.Copy(lConn, rConn); err != nil {
|
||||
//do sth
|
||||
}
|
||||
}(lConn, rConn)
|
||||
go func(lConn, rConn net.Conn) {
|
||||
if _, err := io.Copy(rConn, lConn); err != nil {
|
||||
//do sth
|
||||
}
|
||||
}(lConn, rConn)
|
||||
}(accept)
|
||||
}
|
||||
}(listener)
|
||||
|
||||
done := make(chan os.Signal, syscall.SIGTERM)
|
||||
signal.Notify(done, os.Interrupt, os.Kill)
|
||||
<-done
|
||||
}
|
||||
```
|
||||
|
||||
[electricbubble/gidevice@v0.6.2]: https://github.com/electricbubble/gidevice/tree/v0.6.2
|
||||
552
hrp/pkg/gidevice/afc.go
Normal file
552
hrp/pkg/gidevice/afc.go
Normal file
@@ -0,0 +1,552 @@
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/libimobiledevice"
|
||||
)
|
||||
|
||||
var ErrAfcStatNotExist = errors.New("afc stat: no such file or directory")
|
||||
|
||||
var _ Afc = (*afc)(nil)
|
||||
|
||||
func newAfc(client *libimobiledevice.AfcClient) *afc {
|
||||
return &afc{client: client}
|
||||
}
|
||||
|
||||
type afc struct {
|
||||
client *libimobiledevice.AfcClient
|
||||
}
|
||||
|
||||
func (c *afc) DiskInfo() (info *AfcDiskInfo, err error) {
|
||||
if err = c.client.Send(libimobiledevice.AfcOperationGetDeviceInfo, nil, nil); err != nil {
|
||||
return nil, fmt.Errorf("afc send 'DiskInfo': %w", err)
|
||||
}
|
||||
var respMsg *libimobiledevice.AfcMessage
|
||||
if respMsg, err = c.client.Receive(); err != nil {
|
||||
return nil, fmt.Errorf("afc receive 'DiskInfo': %w", err)
|
||||
}
|
||||
|
||||
m := respMsg.Map()
|
||||
info = &AfcDiskInfo{
|
||||
Model: m["Model"],
|
||||
}
|
||||
if info.TotalBytes, err = strconv.ParseUint(m["FSTotalBytes"], 10, 64); err != nil {
|
||||
return nil, fmt.Errorf("afc 'DiskInfo': %w", err)
|
||||
}
|
||||
if info.FreeBytes, err = strconv.ParseUint(m["FSFreeBytes"], 10, 64); err != nil {
|
||||
return nil, fmt.Errorf("afc 'DiskInfo': %w", err)
|
||||
}
|
||||
if info.BlockSize, err = strconv.ParseUint(m["FSBlockSize"], 10, 64); err != nil {
|
||||
return nil, fmt.Errorf("afc 'DiskInfo': %w", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *afc) ReadDir(dirname string) (names []string, err error) {
|
||||
if err = c.client.Send(libimobiledevice.AfcOperationReadDir, toCString(dirname), nil); err != nil {
|
||||
return nil, fmt.Errorf("afc send 'ReadDir': %w", err)
|
||||
}
|
||||
var respMsg *libimobiledevice.AfcMessage
|
||||
if respMsg, err = c.client.Receive(); err != nil {
|
||||
return nil, fmt.Errorf("afc receive 'ReadDir': %w", err)
|
||||
}
|
||||
if err = respMsg.Err(); err != nil {
|
||||
return nil, fmt.Errorf("afc 'ReadDir': %w", err)
|
||||
}
|
||||
|
||||
names = respMsg.Strings()
|
||||
return
|
||||
}
|
||||
|
||||
func (c *afc) Stat(filename string) (info *AfcFileInfo, err error) {
|
||||
if err = c.client.Send(libimobiledevice.AfcOperationGetFileInfo, toCString(filename), nil); err != nil {
|
||||
return nil, fmt.Errorf("afc send 'Stat': %w", err)
|
||||
}
|
||||
var respMsg *libimobiledevice.AfcMessage
|
||||
if respMsg, err = c.client.Receive(); err != nil {
|
||||
return nil, fmt.Errorf("afc receive 'Stat': %w", err)
|
||||
}
|
||||
|
||||
m := respMsg.Map()
|
||||
|
||||
if len(m) == 0 {
|
||||
return nil, ErrAfcStatNotExist
|
||||
}
|
||||
|
||||
info = &AfcFileInfo{
|
||||
source: m,
|
||||
name: path.Base(filename),
|
||||
ifmt: m["st_ifmt"],
|
||||
}
|
||||
if info.creationTime, err = strconv.ParseUint(m["st_birthtime"], 10, 64); err != nil {
|
||||
return nil, fmt.Errorf("afc 'Stat': %w", err)
|
||||
}
|
||||
if info.blocks, err = strconv.ParseUint(m["st_blocks"], 10, 64); err != nil {
|
||||
return nil, fmt.Errorf("afc 'Stat': %w", err)
|
||||
}
|
||||
if info.modTime, err = strconv.ParseUint(m["st_mtime"], 10, 64); err != nil {
|
||||
return nil, fmt.Errorf("afc 'Stat': %w", err)
|
||||
}
|
||||
if info.nlink, err = strconv.ParseUint(m["st_nlink"], 10, 64); err != nil {
|
||||
return nil, fmt.Errorf("afc 'Stat': %w", err)
|
||||
}
|
||||
if info.size, err = strconv.ParseUint(m["st_size"], 10, 64); err != nil {
|
||||
return nil, fmt.Errorf("afc 'Stat': %w", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *afc) Open(filename string, mode AfcFileMode) (file *AfcFile, err error) {
|
||||
buf := new(bytes.Buffer)
|
||||
if err = binary.Write(buf, binary.LittleEndian, uint64(mode)); err != nil {
|
||||
return nil, fmt.Errorf("afc send 'Open': %w", err)
|
||||
}
|
||||
buf.Write(toCString(filename))
|
||||
|
||||
if err = c.client.Send(libimobiledevice.AfcOperationFileOpen, buf.Bytes(), nil); err != nil {
|
||||
return nil, fmt.Errorf("afc send 'Open': %w", err)
|
||||
}
|
||||
var respMsg *libimobiledevice.AfcMessage
|
||||
if respMsg, err = c.client.Receive(); err != nil {
|
||||
return nil, fmt.Errorf("afc receive 'Open': %w", err)
|
||||
}
|
||||
if err = respMsg.Err(); err != nil {
|
||||
return nil, fmt.Errorf("afc 'Open': %w", err)
|
||||
}
|
||||
|
||||
if respMsg.Operation != libimobiledevice.AfcOperationFileOpenResult {
|
||||
return nil, fmt.Errorf("afc operation mistake 'Open': '%d'", respMsg.Operation)
|
||||
}
|
||||
|
||||
file = &AfcFile{
|
||||
client: c.client,
|
||||
fd: respMsg.Uint64(),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *afc) Remove(filePath string) (err error) {
|
||||
if err = c.client.Send(libimobiledevice.AfcOperationRemovePath, toCString(filePath), nil); err != nil {
|
||||
return fmt.Errorf("afc send 'Remove': %w", err)
|
||||
}
|
||||
var respMsg *libimobiledevice.AfcMessage
|
||||
if respMsg, err = c.client.Receive(); err != nil {
|
||||
return fmt.Errorf("afc receive 'Remove': %w", err)
|
||||
}
|
||||
if err = respMsg.Err(); err != nil {
|
||||
return fmt.Errorf("afc 'Remove': %w", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *afc) Rename(oldPath string, newPath string) (err error) {
|
||||
if err = c.client.Send(libimobiledevice.AfcOperationRenamePath, toCString(oldPath, newPath), nil); err != nil {
|
||||
return fmt.Errorf("afc send 'Rename': %w", err)
|
||||
}
|
||||
var respMsg *libimobiledevice.AfcMessage
|
||||
if respMsg, err = c.client.Receive(); err != nil {
|
||||
return fmt.Errorf("afc receive 'Rename': %w", err)
|
||||
}
|
||||
if err = respMsg.Err(); err != nil {
|
||||
return fmt.Errorf("afc 'Rename': %w", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *afc) Mkdir(path string) (err error) {
|
||||
if err = c.client.Send(libimobiledevice.AfcOperationMakeDir, toCString(path), nil); err != nil {
|
||||
return fmt.Errorf("afc send 'Mkdir': %w", err)
|
||||
}
|
||||
var respMsg *libimobiledevice.AfcMessage
|
||||
if respMsg, err = c.client.Receive(); err != nil {
|
||||
return fmt.Errorf("afc receive 'Mkdir': %w", err)
|
||||
}
|
||||
if err = respMsg.Err(); err != nil {
|
||||
return fmt.Errorf("afc 'Mkdir': %w", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *afc) Link(oldName string, newName string, linkType AfcLinkType) (err error) {
|
||||
buf := new(bytes.Buffer)
|
||||
_ = binary.Write(buf, binary.LittleEndian, uint64(linkType))
|
||||
buf.Write(toCString(oldName, newName))
|
||||
|
||||
if err = c.client.Send(libimobiledevice.AfcOperationMakeLink, buf.Bytes(), nil); err != nil {
|
||||
return fmt.Errorf("afc send 'Link': %w", err)
|
||||
}
|
||||
var respMsg *libimobiledevice.AfcMessage
|
||||
if respMsg, err = c.client.Receive(); err != nil {
|
||||
return fmt.Errorf("afc receive 'Link': %w", err)
|
||||
}
|
||||
if err = respMsg.Err(); err != nil {
|
||||
return fmt.Errorf("afc 'Link': %w", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *afc) Truncate(filePath string, size int64) (err error) {
|
||||
buf := new(bytes.Buffer)
|
||||
_ = binary.Write(buf, binary.LittleEndian, uint64(size))
|
||||
buf.Write(toCString(filePath))
|
||||
|
||||
if err = c.client.Send(libimobiledevice.AfcOperationTruncateFile, buf.Bytes(), nil); err != nil {
|
||||
return fmt.Errorf("afc send 'Truncate': %w", err)
|
||||
}
|
||||
var respMsg *libimobiledevice.AfcMessage
|
||||
if respMsg, err = c.client.Receive(); err != nil {
|
||||
return fmt.Errorf("afc receive 'Truncate': %w", err)
|
||||
}
|
||||
if err = respMsg.Err(); err != nil {
|
||||
return fmt.Errorf("afc 'Truncate': %w", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *afc) SetFileModTime(filePath string, modTime time.Time) (err error) {
|
||||
buf := new(bytes.Buffer)
|
||||
_ = binary.Write(buf, binary.LittleEndian, uint64(modTime.Unix()))
|
||||
buf.Write(toCString(filePath))
|
||||
|
||||
if err = c.client.Send(libimobiledevice.AfcOperationSetFileModTime, buf.Bytes(), nil); err != nil {
|
||||
return fmt.Errorf("afc send 'SetFileModTime': %w", err)
|
||||
}
|
||||
var respMsg *libimobiledevice.AfcMessage
|
||||
if respMsg, err = c.client.Receive(); err != nil {
|
||||
return fmt.Errorf("afc receive 'SetFileModTime': %w", err)
|
||||
}
|
||||
if err = respMsg.Err(); err != nil {
|
||||
return fmt.Errorf("afc 'SetFileModTime': %w", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *afc) Hash(filePath string) ([]byte, error) {
|
||||
var err error
|
||||
if err = c.client.Send(libimobiledevice.AfcOperationGetFileHash, toCString(filePath), nil); err != nil {
|
||||
return nil, fmt.Errorf("afc send 'Hash': %w", err)
|
||||
}
|
||||
var respMsg *libimobiledevice.AfcMessage
|
||||
if respMsg, err = c.client.Receive(); err != nil {
|
||||
return nil, fmt.Errorf("afc receive 'Hash': %w", err)
|
||||
}
|
||||
if err = respMsg.Err(); err != nil {
|
||||
return nil, fmt.Errorf("afc 'Hash': %w", err)
|
||||
}
|
||||
|
||||
return respMsg.Payload, nil
|
||||
}
|
||||
|
||||
func (c *afc) HashWithRange(filePath string, start, end uint64) ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
_ = binary.Write(buf, binary.LittleEndian, start)
|
||||
_ = binary.Write(buf, binary.LittleEndian, end)
|
||||
buf.Write(toCString(filePath))
|
||||
|
||||
var err error
|
||||
if err = c.client.Send(libimobiledevice.AfcOperationGetFileHashRange, buf.Bytes(), nil); err != nil {
|
||||
return nil, fmt.Errorf("afc send 'HashWithRange': %w", err)
|
||||
}
|
||||
var respMsg *libimobiledevice.AfcMessage
|
||||
if respMsg, err = c.client.Receive(); err != nil {
|
||||
return nil, fmt.Errorf("afc receive 'HashWithRange': %w", err)
|
||||
}
|
||||
if err = respMsg.Err(); err != nil {
|
||||
return nil, fmt.Errorf("afc 'HashWithRange': %w", err)
|
||||
}
|
||||
|
||||
return respMsg.Payload, nil
|
||||
}
|
||||
|
||||
// RemoveAll since iOS6+
|
||||
func (c *afc) RemoveAll(path string) (err error) {
|
||||
if err = c.client.Send(libimobiledevice.AfcOperationRemovePathAndContents, toCString(path), nil); err != nil {
|
||||
return fmt.Errorf("afc send 'RemoveAll': %w", err)
|
||||
}
|
||||
var respMsg *libimobiledevice.AfcMessage
|
||||
if respMsg, err = c.client.Receive(); err != nil {
|
||||
return fmt.Errorf("afc receive 'RemoveAll': %w", err)
|
||||
}
|
||||
if err = respMsg.Err(); err != nil {
|
||||
return fmt.Errorf("afc 'RemoveAll': %w", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *afc) WriteFile(filename string, data []byte, perm AfcFileMode) (err error) {
|
||||
var file *AfcFile
|
||||
if file, err = c.Open(filename, perm); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
err = file.Close()
|
||||
}()
|
||||
|
||||
if _, err = file.Write(data); err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func toCString(s ...string) []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
for _, v := range s {
|
||||
buf.WriteString(v)
|
||||
buf.WriteByte(0)
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
type AfcDiskInfo struct {
|
||||
Model string
|
||||
TotalBytes uint64
|
||||
FreeBytes uint64
|
||||
BlockSize uint64
|
||||
}
|
||||
|
||||
type AfcFileInfo struct {
|
||||
name string
|
||||
|
||||
creationTime uint64
|
||||
blocks uint64
|
||||
ifmt string
|
||||
modTime uint64
|
||||
nlink uint64
|
||||
size uint64
|
||||
|
||||
source map[string]string
|
||||
}
|
||||
|
||||
func (f *AfcFileInfo) Name() string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
func (f *AfcFileInfo) Size() int64 {
|
||||
return int64(f.size)
|
||||
}
|
||||
|
||||
// func (f *AfcFileInfo) Mode() os.FileMode {
|
||||
// return os.ModeType
|
||||
// }
|
||||
|
||||
func (f *AfcFileInfo) ModTime() time.Time {
|
||||
return time.Unix(0, int64(f.modTime))
|
||||
}
|
||||
|
||||
func (f *AfcFileInfo) IsDir() bool {
|
||||
return f.ifmt == "S_IFDIR"
|
||||
}
|
||||
|
||||
// func (f *AfcFileInfo) Sys() interface{} {
|
||||
// return f.source
|
||||
// }
|
||||
|
||||
func (f *AfcFileInfo) CreationTime() time.Time {
|
||||
return time.Unix(0, int64(f.creationTime))
|
||||
}
|
||||
|
||||
// func (f *AfcFileInfo) Blocks() uint64 {
|
||||
// return f.blocks
|
||||
// }
|
||||
|
||||
// func (f *AfcFileInfo) Format() string {
|
||||
// return f.ifmt
|
||||
// }
|
||||
|
||||
// func (f *AfcFileInfo) Link() uint64 {
|
||||
// return f.nlink
|
||||
// }
|
||||
|
||||
// func (f *AfcFileInfo) PhysicalSize(info *AfcDiskInfo) int64 {
|
||||
// return int64(f.blocks * (info.BlockSize / 8))
|
||||
// }
|
||||
|
||||
type AfcFileMode uint32
|
||||
|
||||
const (
|
||||
AfcFileModeRdOnly AfcFileMode = 0x00000001
|
||||
AfcFileModeRw AfcFileMode = 0x00000002
|
||||
AfcFileModeWrOnly AfcFileMode = 0x00000003
|
||||
AfcFileModeWr AfcFileMode = 0x00000004
|
||||
AfcFileModeAppend AfcFileMode = 0x00000005
|
||||
AfcFileModeRdAppend AfcFileMode = 0x00000006
|
||||
)
|
||||
|
||||
type AfcLockType int
|
||||
|
||||
const (
|
||||
AfcLockTypeSharedLock AfcLockType = 1 | 4
|
||||
AfcLockTypeExclusiveLock AfcLockType = 2 | 4
|
||||
AfcLockTypeUnlock AfcLockType = 8 | 4
|
||||
)
|
||||
|
||||
type AfcFile struct {
|
||||
client *libimobiledevice.AfcClient
|
||||
fd uint64
|
||||
reader *bytes.Reader
|
||||
}
|
||||
|
||||
func (f *AfcFile) op(o ...uint64) []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
_ = binary.Write(buf, binary.LittleEndian, f.fd)
|
||||
|
||||
if len(o) == 0 {
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
for _, v := range o {
|
||||
_ = binary.Write(buf, binary.LittleEndian, v)
|
||||
}
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (f *AfcFile) Lock(lockType AfcLockType) (err error) {
|
||||
if err = f.client.Send(libimobiledevice.AfcOperationFileRefLock, f.op(uint64(lockType)), nil); err != nil {
|
||||
return fmt.Errorf("afc file send 'Lock': %w", err)
|
||||
}
|
||||
var respMsg *libimobiledevice.AfcMessage
|
||||
if respMsg, err = f.client.Receive(); err != nil {
|
||||
return fmt.Errorf("afc file receive 'Lock': %w", err)
|
||||
}
|
||||
if err = respMsg.Err(); err != nil {
|
||||
return fmt.Errorf("afc file 'Lock': %w", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *AfcFile) Unlock() (err error) {
|
||||
return f.Lock(AfcLockTypeUnlock)
|
||||
}
|
||||
|
||||
func (f *AfcFile) Read(b []byte) (n int, err error) {
|
||||
if err = f.client.Send(libimobiledevice.AfcOperationFileRead, f.op(uint64(len(b))), nil); err != nil {
|
||||
return -1, fmt.Errorf("afc file send 'Read': %w", err)
|
||||
}
|
||||
var respMsg *libimobiledevice.AfcMessage
|
||||
if respMsg, err = f.client.Receive(); err != nil {
|
||||
return -1, fmt.Errorf("afc file receive 'Read': %w", err)
|
||||
}
|
||||
if err = respMsg.Err(); err != nil {
|
||||
return -1, fmt.Errorf("afc file 'Read': %w", err)
|
||||
}
|
||||
|
||||
if respMsg.Payload == nil {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
if f.reader == nil {
|
||||
f.reader = bytes.NewReader(respMsg.Payload)
|
||||
} else {
|
||||
f.reader.Reset(respMsg.Payload)
|
||||
}
|
||||
|
||||
return f.reader.Read(b)
|
||||
}
|
||||
|
||||
func (f *AfcFile) Write(b []byte) (n int, err error) {
|
||||
if err = f.client.Send(libimobiledevice.AfcOperationFileWrite, f.op(), b); err != nil {
|
||||
return -1, fmt.Errorf("afc file send 'Write': %w", err)
|
||||
}
|
||||
var respMsg *libimobiledevice.AfcMessage
|
||||
if respMsg, err = f.client.Receive(); err != nil {
|
||||
return -1, fmt.Errorf("afc file receive 'Write': %w", err)
|
||||
}
|
||||
if err = respMsg.Err(); err != nil {
|
||||
return -1, fmt.Errorf("afc file 'Write': %w", err)
|
||||
}
|
||||
|
||||
n = len(b)
|
||||
return
|
||||
}
|
||||
|
||||
func (f *AfcFile) Tell() (n uint64, err error) {
|
||||
if err = f.client.Send(libimobiledevice.AfcOperationFileTell, f.op(), nil); err != nil {
|
||||
return 0, fmt.Errorf("afc file 'Tell': %w", err)
|
||||
}
|
||||
var respMsg *libimobiledevice.AfcMessage
|
||||
if respMsg, err = f.client.Receive(); err != nil {
|
||||
return 0, fmt.Errorf("afc file receive 'Tell': %w", err)
|
||||
}
|
||||
if err = respMsg.Err(); err != nil {
|
||||
return 0, fmt.Errorf("afc file 'Tell': %w", err)
|
||||
}
|
||||
|
||||
if respMsg.Operation != libimobiledevice.AfcOperationFileTellResult {
|
||||
return 0, fmt.Errorf("afc operation mistake 'Tell': '%d'", respMsg.Operation)
|
||||
}
|
||||
|
||||
n = respMsg.Uint64()
|
||||
return
|
||||
}
|
||||
|
||||
func (f *AfcFile) Seek(offset int64, whence int) (ret int64, err error) {
|
||||
if err = f.client.Send(libimobiledevice.AfcOperationFileSeek, f.op(uint64(whence), uint64(offset)), nil); err != nil {
|
||||
return -1, fmt.Errorf("afc file 'Seek': %w", err)
|
||||
}
|
||||
var respMsg *libimobiledevice.AfcMessage
|
||||
if respMsg, err = f.client.Receive(); err != nil {
|
||||
return -1, fmt.Errorf("afc file receive 'Seek': %w", err)
|
||||
}
|
||||
if err = respMsg.Err(); err != nil {
|
||||
return -1, fmt.Errorf("afc file 'Seek': %w", err)
|
||||
}
|
||||
|
||||
var tell uint64
|
||||
if tell, err = f.Tell(); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
ret = int64(tell)
|
||||
return
|
||||
}
|
||||
|
||||
func (f *AfcFile) Truncate(size int64) (err error) {
|
||||
if err = f.client.Send(libimobiledevice.AfcOperationFileSetSize, f.op(uint64(size)), nil); err != nil {
|
||||
return fmt.Errorf("afc file 'Truncate': %w", err)
|
||||
}
|
||||
var respMsg *libimobiledevice.AfcMessage
|
||||
if respMsg, err = f.client.Receive(); err != nil {
|
||||
return fmt.Errorf("afc file receive 'Truncate': %w", err)
|
||||
}
|
||||
if err = respMsg.Err(); err != nil {
|
||||
return fmt.Errorf("afc file 'Truncate': %w", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (f *AfcFile) Close() (err error) {
|
||||
if err = f.client.Send(libimobiledevice.AfcOperationFileClose, f.op(), nil); err != nil {
|
||||
return fmt.Errorf("afc file 'Close': %w", err)
|
||||
}
|
||||
if _, err = f.client.Receive(); err != nil {
|
||||
return fmt.Errorf("afc file receive 'Close': %w", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type AfcLinkType int
|
||||
|
||||
const (
|
||||
AfcLinkTypeHardLink AfcLinkType = 1
|
||||
AfcLinkTypeSymLink AfcLinkType = 2
|
||||
)
|
||||
102
hrp/pkg/gidevice/afc_test.go
Normal file
102
hrp/pkg/gidevice/afc_test.go
Normal file
@@ -0,0 +1,102 @@
|
||||
//go:build localtest
|
||||
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var afcSrv Afc
|
||||
|
||||
func setupAfcSrv(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
var err error
|
||||
if lockdownSrv, err = dev.lockdownService(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if afcSrv, err = lockdownSrv.AfcService(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_afc_DiskInfo(t *testing.T) {
|
||||
setupAfcSrv(t)
|
||||
|
||||
info, err := afcSrv.DiskInfo()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
log.Printf("%10s: %s\n", "Model", info.Model)
|
||||
log.Printf("%10s: %d\n", "BlockSize", info.BlockSize/8)
|
||||
log.Printf("%10s: %s\n", "FreeSpace", byteCountDecimal(int64(info.FreeBytes)))
|
||||
log.Printf("%10s: %s\n", "UsedSpace", byteCountDecimal(int64(info.TotalBytes-info.FreeBytes)))
|
||||
log.Printf("%10s: %s\n", "TotalSpace", byteCountDecimal(int64(info.TotalBytes)))
|
||||
}
|
||||
|
||||
func byteCountDecimal(b int64) string {
|
||||
const unit = 1000
|
||||
if b < unit {
|
||||
return fmt.Sprintf("%dB", b)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := b / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f%cB", float64(b)/float64(div), "kMGTPE"[exp])
|
||||
}
|
||||
|
||||
func Test_afc_ReadDir(t *testing.T) {
|
||||
setupAfcSrv(t)
|
||||
|
||||
names, err := afcSrv.ReadDir("Downloads")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, name := range names {
|
||||
t.Log(name)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_afc_Stat(t *testing.T) {
|
||||
setupAfcSrv(t)
|
||||
|
||||
fileInfo, err := afcSrv.Stat("Downloads/downloads.28.sqlitedb")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(fileInfo.Name())
|
||||
t.Log(fileInfo.IsDir())
|
||||
t.Log(fileInfo.CreationTime())
|
||||
t.Log(fileInfo.ModTime())
|
||||
t.Log(fileInfo.Size())
|
||||
t.Log(byteCountDecimal(fileInfo.Size()))
|
||||
}
|
||||
|
||||
func Test_afc_Open(t *testing.T) {
|
||||
setupAfcSrv(t)
|
||||
|
||||
afcFile, err := afcSrv.Open("DCIM/105APPLE/IMG_5977.JPEG", AfcFileModeRdOnly)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
_ = afcFile.Close()
|
||||
}()
|
||||
|
||||
userHomeDir, _ := os.UserHomeDir()
|
||||
file, err := os.Create(userHomeDir + "/Desktop/tmp.jpeg")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err = io.Copy(file, afcFile); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
169
hrp/pkg/gidevice/crashreportmover.go
Normal file
169
hrp/pkg/gidevice/crashreportmover.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"howett.net/plist"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/libimobiledevice"
|
||||
)
|
||||
|
||||
var _ CrashReportMover = (*crashReportMover)(nil)
|
||||
|
||||
func newCrashReportMover(client *libimobiledevice.CrashReportMoverClient) *crashReportMover {
|
||||
return &crashReportMover{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
type crashReportMover struct {
|
||||
client *libimobiledevice.CrashReportMoverClient
|
||||
afc Afc
|
||||
}
|
||||
|
||||
func (c *crashReportMover) readPing() (err error) {
|
||||
var data []byte
|
||||
if data, err = c.client.InnerConn().Read(4); err != nil {
|
||||
return err
|
||||
}
|
||||
if string(data) != "ping" {
|
||||
return fmt.Errorf("crashReportMover ping: %v", data)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *crashReportMover) Move(hostDir string, opts ...CrashReportMoverOption) (err error) {
|
||||
opt := defaultCrashReportMoverOption()
|
||||
for _, fn := range opts {
|
||||
fn(opt)
|
||||
}
|
||||
|
||||
toExtract := make([]string, 0, 64)
|
||||
|
||||
fn := func(cwd string, info *AfcFileInfo) {
|
||||
if info.IsDir() {
|
||||
return
|
||||
}
|
||||
if cwd == "." {
|
||||
cwd = ""
|
||||
}
|
||||
|
||||
devFilename := path.Join(cwd, info.Name())
|
||||
hostElem := strings.Split(devFilename, "/")
|
||||
hostFilename := filepath.Join(hostDir, filepath.Join(hostElem...))
|
||||
hostFilename = strings.TrimSuffix(hostFilename, ".synced")
|
||||
|
||||
if opt.extract && strings.HasSuffix(hostFilename, ".plist") {
|
||||
toExtract = append(toExtract, hostFilename)
|
||||
}
|
||||
|
||||
var afcFile *AfcFile
|
||||
if afcFile, err = c.afc.Open(devFilename, AfcFileModeRdOnly); err != nil {
|
||||
debugLog(fmt.Sprintf("crashReportMover open %s: %s", devFilename, err))
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err = afcFile.Close(); err != nil {
|
||||
debugLog(fmt.Sprintf("crashReportMover device file close: %s", err))
|
||||
}
|
||||
}()
|
||||
|
||||
if err = os.MkdirAll(filepath.Dir(hostFilename), 0o755); err != nil {
|
||||
debugLog(fmt.Sprintf("crashReportMover mkdir %s: %s", filepath.Dir(hostFilename), err))
|
||||
return
|
||||
}
|
||||
var hostFile *os.File
|
||||
if hostFile, err = os.Create(hostFilename); err != nil {
|
||||
debugLog(fmt.Sprintf("crashReportMover create %s: %s", hostFilename, err))
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err = hostFile.Close(); err != nil {
|
||||
debugLog(fmt.Sprintf("crashReportMover host file close: %s", err))
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err = io.Copy(hostFile, afcFile); err != nil {
|
||||
debugLog(fmt.Sprintf("crashReportMover copy %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
opt.whenDone(devFilename)
|
||||
|
||||
if opt.keep {
|
||||
return
|
||||
}
|
||||
|
||||
if err = c.afc.Remove(devFilename); err != nil {
|
||||
debugLog(fmt.Sprintf("crashReportMover remove %s: %s", devFilename, err))
|
||||
return
|
||||
}
|
||||
}
|
||||
if err = c.walkDir(".", fn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !opt.extract {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, name := range toExtract {
|
||||
data, err := os.ReadFile(name)
|
||||
if err != nil {
|
||||
debugLog(fmt.Sprintf("crashReportMover extract read %s: %s", name, err))
|
||||
continue
|
||||
}
|
||||
m := make(map[string]interface{})
|
||||
if _, err = plist.Unmarshal(data, &m); err != nil {
|
||||
debugLog(fmt.Sprintf("crashReportMover extract plist %s: %s", name, err))
|
||||
continue
|
||||
}
|
||||
|
||||
desc, ok := m["description"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
hostExtCrash := strings.TrimSuffix(name, ".plist") + ".crash"
|
||||
if err = os.WriteFile(hostExtCrash, []byte(fmt.Sprintf("%v", desc)), 0o755); err != nil {
|
||||
debugLog(fmt.Sprintf("crashReportMover extract save %s: %s", name, err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *crashReportMover) walkDir(dirname string, fn func(path string, info *AfcFileInfo)) (err error) {
|
||||
var names []string
|
||||
if names, err = c.afc.ReadDir(dirname); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cwd := dirname
|
||||
|
||||
for _, n := range names {
|
||||
if n == "." || n == ".." {
|
||||
continue
|
||||
}
|
||||
|
||||
var info *AfcFileInfo
|
||||
if info, err = c.afc.Stat(path.Join(cwd, n)); err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
if err = c.walkDir(path.Join(cwd, info.name), fn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fn(cwd, info)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
43
hrp/pkg/gidevice/crashreportmover_test.go
Normal file
43
hrp/pkg/gidevice/crashreportmover_test.go
Normal file
@@ -0,0 +1,43 @@
|
||||
//go:build localtest
|
||||
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var crashReportMoverSrv CrashReportMover
|
||||
|
||||
func setupCrashReportMoverSrv(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
var err error
|
||||
if lockdownSrv, err = dev.lockdownService(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if crashReportMoverSrv, err = lockdownSrv.CrashReportMoverService(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_crashReportMover_Move(t *testing.T) {
|
||||
setupCrashReportMoverSrv(t)
|
||||
|
||||
SetDebug(true)
|
||||
userHomeDir, _ := os.UserHomeDir()
|
||||
// err := crashReportMoverSrv.Move(userHomeDir + "/Documents/temp/2021-04/out_gidevice")
|
||||
// err := crashReportMoverSrv.Move(userHomeDir+"/Documents/temp/2021-04/out_gidevice",
|
||||
err := crashReportMoverSrv.Move(userHomeDir+"/Documents/temp/2021-04/out_gidevice_extract",
|
||||
WithKeepCrashReport(true),
|
||||
WithExtractRawCrashReport(true),
|
||||
WithWhenMoveIsDone(func(filename string) {
|
||||
fmt.Println("Copy:", filename)
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
940
hrp/pkg/gidevice/device.go
Normal file
940
hrp/pkg/gidevice/device.go
Normal file
@@ -0,0 +1,940 @@
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"howett.net/plist"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/ipa"
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/libimobiledevice"
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/nskeyedarchiver"
|
||||
)
|
||||
|
||||
const LockdownPort = 62078
|
||||
|
||||
var _ Device = (*device)(nil)
|
||||
|
||||
func newDevice(client *libimobiledevice.UsbmuxClient, properties DeviceProperties) *device {
|
||||
return &device{
|
||||
umClient: client,
|
||||
properties: &properties,
|
||||
}
|
||||
}
|
||||
|
||||
type device struct {
|
||||
umClient *libimobiledevice.UsbmuxClient
|
||||
lockdownClient *libimobiledevice.LockdownClient
|
||||
|
||||
properties *DeviceProperties
|
||||
|
||||
lockdown *lockdown
|
||||
imageMounter ImageMounter
|
||||
screenshot Screenshot
|
||||
simulateLocation SimulateLocation
|
||||
installationProxy InstallationProxy
|
||||
instruments Instruments
|
||||
afc Afc
|
||||
houseArrest HouseArrest
|
||||
syslogRelay SyslogRelay
|
||||
diagnosticsRelay DiagnosticsRelay
|
||||
springBoard SpringBoard
|
||||
crashReportMover CrashReportMover
|
||||
pcapd Pcapd
|
||||
perfd []Perfd
|
||||
}
|
||||
|
||||
func (d *device) Properties() DeviceProperties {
|
||||
return *d.properties
|
||||
}
|
||||
|
||||
func (d *device) NewConnect(port int, timeout ...time.Duration) (InnerConn, error) {
|
||||
newClient, err := libimobiledevice.NewUsbmuxClient(timeout...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = newClient.NewPlistPacket(
|
||||
newClient.NewConnectRequest(d.properties.DeviceID, port),
|
||||
); err != nil {
|
||||
newClient.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = newClient.SendPacket(pkt); err != nil {
|
||||
newClient.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err = newClient.ReceivePacket(); err != nil {
|
||||
newClient.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newClient.InnerConn(), err
|
||||
}
|
||||
|
||||
func (d *device) ReadPairRecord() (pairRecord *PairRecord, err error) {
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = d.umClient.NewPlistPacket(
|
||||
d.umClient.NewReadPairRecordRequest(d.properties.SerialNumber),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = d.umClient.SendPacket(pkt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var respPkt libimobiledevice.Packet
|
||||
if respPkt, err = d.umClient.ReceivePacket(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reply := struct {
|
||||
Data []byte `plist:"PairRecordData"`
|
||||
}{}
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var record PairRecord
|
||||
if _, err = plist.Unmarshal(reply.Data, &record); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pairRecord = &record
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) SavePairRecord(pairRecord *PairRecord) (err error) {
|
||||
var data []byte
|
||||
if data, err = plist.Marshal(pairRecord, plist.XMLFormat); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = d.umClient.NewPlistPacket(
|
||||
d.umClient.NewSavePairRecordRequest(d.properties.SerialNumber, d.properties.DeviceID, data),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = d.umClient.SendPacket(pkt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = d.umClient.ReceivePacket(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) DeletePairRecord() (err error) {
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = d.umClient.NewPlistPacket(
|
||||
d.umClient.NewDeletePairRecordRequest(d.properties.SerialNumber),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = d.umClient.SendPacket(pkt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = d.umClient.ReceivePacket(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) lockdownService() (lockdown Lockdown, err error) {
|
||||
// if d.lockdown != nil {
|
||||
// return d.lockdown, nil
|
||||
// }
|
||||
|
||||
var innerConn InnerConn
|
||||
if innerConn, err = d.NewConnect(LockdownPort, 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.lockdownClient = libimobiledevice.NewLockdownClient(innerConn)
|
||||
d.lockdown = newLockdown(d)
|
||||
_, err = d.lockdown._getProductVersion()
|
||||
lockdown = d.lockdown
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) QueryType() (LockdownType, error) {
|
||||
if _, err := d.lockdownService(); err != nil {
|
||||
return LockdownType{}, err
|
||||
}
|
||||
return d.lockdown.QueryType()
|
||||
}
|
||||
|
||||
func (d *device) GetValue(domain, key string) (v interface{}, err error) {
|
||||
if _, err = d.lockdownService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.lockdown.pairRecord == nil {
|
||||
if err = d.lockdown.handshake(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err = d.lockdown.startSession(d.lockdown.pairRecord); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if v, err = d.lockdown.GetValue(domain, key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = d.lockdown.stopSession()
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) Pair() (pairRecord *PairRecord, err error) {
|
||||
if _, err = d.lockdownService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.lockdown.Pair()
|
||||
}
|
||||
|
||||
func (d *device) imageMounterService() (imageMounter ImageMounter, err error) {
|
||||
if d.imageMounter != nil {
|
||||
return d.imageMounter, nil
|
||||
}
|
||||
if _, err = d.lockdownService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.imageMounter, err = d.lockdown.ImageMounterService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imageMounter = d.imageMounter
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) Images(imgType ...string) (imageSignatures [][]byte, err error) {
|
||||
if _, err = d.imageMounterService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(imgType) == 0 {
|
||||
imgType = []string{"Developer"}
|
||||
}
|
||||
return d.imageMounter.Images(imgType[0])
|
||||
}
|
||||
|
||||
func (d *device) MountDeveloperDiskImage(dmgPath string, signaturePath string) (err error) {
|
||||
if _, err = d.imageMounterService(); err != nil {
|
||||
return err
|
||||
}
|
||||
devImgPath := "/private/var/mobile/Media/PublicStaging/staging.dimage"
|
||||
return d.imageMounter.UploadImageAndMount("Developer", devImgPath, dmgPath, signaturePath)
|
||||
}
|
||||
|
||||
func (d *device) screenshotService() (screenshot Screenshot, err error) {
|
||||
if d.screenshot != nil {
|
||||
return d.screenshot, nil
|
||||
}
|
||||
|
||||
if _, err = d.lockdownService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.screenshot, err = d.lockdown.ScreenshotService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
screenshot = d.screenshot
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) Screenshot() (raw *bytes.Buffer, err error) {
|
||||
if _, err = d.screenshotService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.screenshot.Take()
|
||||
}
|
||||
|
||||
func (d *device) simulateLocationService() (simulateLocation SimulateLocation, err error) {
|
||||
if d.simulateLocation != nil {
|
||||
return d.simulateLocation, nil
|
||||
}
|
||||
if _, err = d.lockdownService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.simulateLocation, err = d.lockdown.SimulateLocationService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
simulateLocation = d.simulateLocation
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) SimulateLocationUpdate(longitude float64, latitude float64, coordinateSystem ...CoordinateSystem) (err error) {
|
||||
if _, err = d.simulateLocationService(); err != nil {
|
||||
return err
|
||||
}
|
||||
return d.simulateLocation.Update(longitude, latitude, coordinateSystem...)
|
||||
}
|
||||
|
||||
func (d *device) SimulateLocationRecover() (err error) {
|
||||
if _, err = d.simulateLocationService(); err != nil {
|
||||
return err
|
||||
}
|
||||
return d.simulateLocation.Recover()
|
||||
}
|
||||
|
||||
func (d *device) installationProxyService() (installationProxy InstallationProxy, err error) {
|
||||
if d.installationProxy != nil {
|
||||
return d.installationProxy, nil
|
||||
}
|
||||
if _, err = d.lockdownService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.installationProxy, err = d.lockdown.InstallationProxyService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
installationProxy = d.installationProxy
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) InstallationProxyBrowse(opts ...InstallationProxyOption) (currentList []interface{}, err error) {
|
||||
if _, err = d.installationProxyService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.installationProxy.Browse(opts...)
|
||||
}
|
||||
|
||||
func (d *device) InstallationProxyLookup(opts ...InstallationProxyOption) (lookupResult interface{}, err error) {
|
||||
if _, err = d.installationProxyService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.installationProxy.Lookup(opts...)
|
||||
}
|
||||
|
||||
func (d *device) newInstrumentsService() (instruments Instruments, err error) {
|
||||
// NOTICE: each instruments service should have individual connection, otherwise it will be blocked
|
||||
if _, err = d.lockdownService(); err != nil {
|
||||
return
|
||||
}
|
||||
return d.lockdown.InstrumentsService()
|
||||
}
|
||||
|
||||
func (d *device) instrumentsService() (instruments Instruments, err error) {
|
||||
if d.instruments != nil {
|
||||
return d.instruments, nil
|
||||
}
|
||||
if d.instruments, err = d.newInstrumentsService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instruments = d.instruments
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) AppLaunch(bundleID string, opts ...AppLaunchOption) (pid int, err error) {
|
||||
if _, err = d.instrumentsService(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return d.instruments.AppLaunch(bundleID, opts...)
|
||||
}
|
||||
|
||||
func (d *device) AppKill(pid int) (err error) {
|
||||
if _, err = d.instrumentsService(); err != nil {
|
||||
return err
|
||||
}
|
||||
return d.instruments.AppKill(pid)
|
||||
}
|
||||
|
||||
func (d *device) AppRunningProcesses() (processes []Process, err error) {
|
||||
if _, err = d.instrumentsService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.instruments.AppRunningProcesses()
|
||||
}
|
||||
|
||||
func (d *device) AppList(opts ...AppListOption) (apps []Application, err error) {
|
||||
if _, err = d.instrumentsService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.instruments.AppList(opts...)
|
||||
}
|
||||
|
||||
func (d *device) DeviceInfo() (devInfo *DeviceInfo, err error) {
|
||||
if _, err = d.instrumentsService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.instruments.DeviceInfo()
|
||||
}
|
||||
|
||||
func (d *device) testmanagerdService() (testmanagerd Testmanagerd, err error) {
|
||||
if _, err = d.lockdownService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if testmanagerd, err = d.lockdown.TestmanagerdService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) AfcService() (afc Afc, err error) {
|
||||
if d.afc != nil {
|
||||
return d.afc, nil
|
||||
}
|
||||
if _, err = d.lockdownService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.afc, err = d.lockdown.AfcService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
afc = d.afc
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) AppInstall(ipaPath string) (err error) {
|
||||
if _, err = d.AfcService(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stagingPath := "PublicStaging"
|
||||
if _, err = d.afc.Stat(stagingPath); err != nil {
|
||||
if err != ErrAfcStatNotExist {
|
||||
return err
|
||||
}
|
||||
if err = d.afc.Mkdir(stagingPath); err != nil {
|
||||
return fmt.Errorf("app install: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var info map[string]interface{}
|
||||
if info, err = ipa.Info(ipaPath); err != nil {
|
||||
return err
|
||||
}
|
||||
bundleID, ok := info["CFBundleIdentifier"]
|
||||
if !ok {
|
||||
return errors.New("can't find 'CFBundleIdentifier'")
|
||||
}
|
||||
|
||||
installationPath := path.Join(stagingPath, fmt.Sprintf("%s.ipa", bundleID))
|
||||
|
||||
var data []byte
|
||||
if data, err = os.ReadFile(ipaPath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = d.afc.WriteFile(installationPath, data, AfcFileModeWr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = d.installationProxyService(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.installationProxy.Install(fmt.Sprintf("%s", bundleID), installationPath)
|
||||
}
|
||||
|
||||
func (d *device) AppUninstall(bundleID string) (err error) {
|
||||
if _, err = d.installationProxyService(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.installationProxy.Uninstall(bundleID)
|
||||
}
|
||||
|
||||
func (d *device) HouseArrestService() (houseArrest HouseArrest, err error) {
|
||||
if d.houseArrest != nil {
|
||||
return d.houseArrest, nil
|
||||
}
|
||||
if _, err = d.lockdownService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.houseArrest, err = d.lockdown.HouseArrestService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
houseArrest = d.houseArrest
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) syslogRelayService() (syslogRelay SyslogRelay, err error) {
|
||||
if d.syslogRelay != nil {
|
||||
return d.syslogRelay, nil
|
||||
}
|
||||
if _, err = d.lockdownService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.syslogRelay, err = d.lockdown.SyslogRelayService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
syslogRelay = d.syslogRelay
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) Syslog() (lines <-chan string, err error) {
|
||||
if _, err = d.syslogRelayService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.syslogRelay.Lines(), nil
|
||||
}
|
||||
|
||||
func (d *device) SyslogStop() {
|
||||
if d.syslogRelay == nil {
|
||||
return
|
||||
}
|
||||
d.syslogRelay.Stop()
|
||||
}
|
||||
|
||||
func (d *device) Reboot() (err error) {
|
||||
if _, err = d.lockdownService(); err != nil {
|
||||
return
|
||||
}
|
||||
if d.diagnosticsRelay, err = d.lockdown.DiagnosticsRelayService(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = d.diagnosticsRelay.Reboot(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) Shutdown() (err error) {
|
||||
if _, err = d.lockdownService(); err != nil {
|
||||
return
|
||||
}
|
||||
if d.diagnosticsRelay, err = d.lockdown.DiagnosticsRelayService(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = d.diagnosticsRelay.Shutdown(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) springBoardService() (springBoard SpringBoard, err error) {
|
||||
if d.springBoard != nil {
|
||||
return d.springBoard, nil
|
||||
}
|
||||
if _, err = d.lockdownService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.springBoard, err = d.lockdown.SpringBoardService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
springBoard = d.springBoard
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) GetIconPNGData(bundleId string) (raw *bytes.Buffer, err error) {
|
||||
if _, err = d.lockdownService(); err != nil {
|
||||
return
|
||||
}
|
||||
if d.springBoard, err = d.lockdown.SpringBoardService(); err != nil {
|
||||
return
|
||||
}
|
||||
if raw, err = d.springBoard.GetIconPNGData(bundleId); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) GetInterfaceOrientation() (orientation libimobiledevice.OrientationState, err error) {
|
||||
if _, err = d.springBoardService(); err != nil {
|
||||
return
|
||||
}
|
||||
if orientation, err = d.springBoard.GetInterfaceOrientation(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) PcapdService() (pcapd Pcapd, err error) {
|
||||
// if d.pcapd != nil {
|
||||
// return d.pcapd, nil
|
||||
// }
|
||||
if _, err = d.lockdownService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.pcapd, err = d.lockdown.PcapdService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pcapd = d.pcapd
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) Pcap() (lines <-chan []byte, err error) {
|
||||
if _, err = d.PcapdService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.pcapd.Packet(), nil
|
||||
}
|
||||
|
||||
func (d *device) PcapStop() {
|
||||
if d.pcapd == nil {
|
||||
return
|
||||
}
|
||||
d.pcapd.Stop()
|
||||
}
|
||||
|
||||
func (d *device) PerfStart(opts ...PerfOption) (data <-chan []byte, err error) {
|
||||
perfOptions := defaulPerfOption()
|
||||
for _, fn := range opts {
|
||||
fn(perfOptions)
|
||||
}
|
||||
|
||||
// wait until get pid for bundle id
|
||||
if perfOptions.BundleID != "" {
|
||||
instruments, err := d.newInstrumentsService()
|
||||
if err != nil {
|
||||
fmt.Printf("get pid by bundle id failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for {
|
||||
pid, err := instruments.getPidByBundleID(perfOptions.BundleID)
|
||||
if err != nil {
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
perfOptions.Pid = pid
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// processAttributes must contain pid, or it can't get process info, reason unknown
|
||||
if !containString(perfOptions.ProcessAttributes, "pid") {
|
||||
perfOptions.ProcessAttributes = append(perfOptions.ProcessAttributes, "pid")
|
||||
}
|
||||
|
||||
outCh := make(chan []byte, 100)
|
||||
|
||||
if perfOptions.SysCPU || perfOptions.SysMem || perfOptions.SysDisk ||
|
||||
perfOptions.SysNetwork {
|
||||
perfd, err := d.newPerfdSysmontap(perfOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := perfd.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
outCh <- (<-data)
|
||||
}
|
||||
}()
|
||||
d.perfd = append(d.perfd, perfd)
|
||||
}
|
||||
|
||||
if perfOptions.Network {
|
||||
perfd, err := d.newPerfdNetworking(perfOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := perfd.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
outCh <- (<-data)
|
||||
}
|
||||
}()
|
||||
d.perfd = append(d.perfd, perfd)
|
||||
}
|
||||
|
||||
if perfOptions.FPS || perfOptions.gpu {
|
||||
perfd, err := d.newPerfdGraphicsOpengl(perfOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := perfd.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
outCh <- (<-data)
|
||||
}
|
||||
}()
|
||||
d.perfd = append(d.perfd, perfd)
|
||||
}
|
||||
|
||||
return outCh, nil
|
||||
}
|
||||
|
||||
func (d *device) PerfStop() {
|
||||
if d.perfd == nil {
|
||||
return
|
||||
}
|
||||
for _, p := range d.perfd {
|
||||
p.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *device) crashReportMoverService() (crashReportMover CrashReportMover, err error) {
|
||||
if d.crashReportMover != nil {
|
||||
return d.crashReportMover, nil
|
||||
}
|
||||
if _, err = d.lockdownService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.crashReportMover, err = d.lockdown.CrashReportMoverService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
crashReportMover = d.crashReportMover
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) MoveCrashReport(hostDir string, opts ...CrashReportMoverOption) (err error) {
|
||||
if _, err = d.crashReportMoverService(); err != nil {
|
||||
return err
|
||||
}
|
||||
return d.crashReportMover.Move(hostDir, opts...)
|
||||
}
|
||||
|
||||
func (d *device) XCTest(bundleID string, opts ...XCTestOption) (out <-chan string, cancel context.CancelFunc, err error) {
|
||||
xcTestOpt := defaultXCTestOption()
|
||||
for _, fn := range opts {
|
||||
fn(xcTestOpt)
|
||||
}
|
||||
|
||||
ctx, cancelFunc := context.WithCancel(context.TODO())
|
||||
_out := make(chan string)
|
||||
|
||||
xcodeVersion := uint64(30)
|
||||
|
||||
var tmSrv1 Testmanagerd
|
||||
if tmSrv1, err = d.testmanagerdService(); err != nil {
|
||||
return _out, cancelFunc, err
|
||||
}
|
||||
|
||||
var xcTestManager1 XCTestManagerDaemon
|
||||
if xcTestManager1, err = tmSrv1.newXCTestManagerDaemon(); err != nil {
|
||||
return _out, cancelFunc, err
|
||||
}
|
||||
|
||||
var version []int
|
||||
if version, err = d.lockdown._getProductVersion(); err != nil {
|
||||
return _out, cancelFunc, err
|
||||
}
|
||||
|
||||
if DeviceVersion(version...) >= DeviceVersion(11, 0, 0) {
|
||||
if err = xcTestManager1.initiateControlSession(xcodeVersion); err != nil {
|
||||
return _out, cancelFunc, err
|
||||
}
|
||||
}
|
||||
|
||||
var tmSrv2 Testmanagerd
|
||||
if tmSrv2, err = d.testmanagerdService(); err != nil {
|
||||
return _out, cancelFunc, err
|
||||
}
|
||||
|
||||
var xcTestManager2 XCTestManagerDaemon
|
||||
if xcTestManager2, err = tmSrv2.newXCTestManagerDaemon(); err != nil {
|
||||
return _out, cancelFunc, err
|
||||
}
|
||||
|
||||
xcTestManager2.registerCallback("_XCT_logDebugMessage:", func(m libimobiledevice.DTXMessageResult) {
|
||||
// more information ( each operation )
|
||||
// fmt.Println("###### xcTestManager2 ### -->", m)
|
||||
if strings.Contains(fmt.Sprintf("%s", m), "Received test runner ready reply with error: (null)") {
|
||||
// fmt.Println("###### xcTestManager2 ### -->", fmt.Sprintf("%v", m.Aux[0]))
|
||||
time.Sleep(time.Second)
|
||||
if err = xcTestManager2.startExecutingTestPlan(xcodeVersion); err != nil {
|
||||
debugLog(fmt.Sprintf("startExecutingTestPlan %d: %s", xcodeVersion, err))
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
xcTestManager2.registerCallback("_Golang-iDevice_Unregistered", func(m libimobiledevice.DTXMessageResult) {
|
||||
// more information
|
||||
// _XCT_testRunnerReadyWithCapabilities:
|
||||
// _XCT_didBeginExecutingTestPlan
|
||||
// _XCT_didBeginInitializingForUITesting
|
||||
// _XCT_testSuite:didStartAt:
|
||||
// _XCT_testCase:method:willStartActivity:
|
||||
// _XCT_testCase:method:didFinishActivity:
|
||||
// _XCT_testCaseDidStartForTestClass:method:
|
||||
// fmt.Println("###### xcTestManager2 ### _Unregistered -->", m)
|
||||
})
|
||||
|
||||
sessionId := uuid.NewV4()
|
||||
if err = xcTestManager2.initiateSession(xcodeVersion, nskeyedarchiver.NewNSUUID(sessionId.Bytes())); err != nil {
|
||||
return _out, cancelFunc, err
|
||||
}
|
||||
|
||||
if _, err = d.installationProxyService(); err != nil {
|
||||
return _out, cancelFunc, err
|
||||
}
|
||||
|
||||
var vResult interface{}
|
||||
if vResult, err = d.installationProxy.Lookup(WithBundleIDs(bundleID)); err != nil {
|
||||
return _out, cancelFunc, err
|
||||
}
|
||||
|
||||
lookupResult := vResult.(map[string]interface{})
|
||||
lookupResult = lookupResult[bundleID].(map[string]interface{})
|
||||
appContainer := lookupResult["Container"].(string)
|
||||
appPath := lookupResult["Path"].(string)
|
||||
|
||||
var pathXCTestCfg string
|
||||
if pathXCTestCfg, err = d._uploadXCTestConfiguration(bundleID, sessionId, lookupResult); err != nil {
|
||||
return _out, cancelFunc, err
|
||||
}
|
||||
|
||||
if _, err = d.instrumentsService(); err != nil {
|
||||
return _out, cancelFunc, err
|
||||
}
|
||||
|
||||
if err = d.instruments.appProcess(bundleID); err != nil {
|
||||
return _out, cancelFunc, err
|
||||
}
|
||||
|
||||
pathXCTestConfiguration := appContainer + pathXCTestCfg
|
||||
|
||||
appEnv := map[string]interface{}{
|
||||
"CA_ASSERT_MAIN_THREAD_TRANSACTIONS": "0",
|
||||
"CA_DEBUG_TRANSACTIONS": "0",
|
||||
"DYLD_FRAMEWORK_PATH": appPath + "/Frameworks:",
|
||||
"DYLD_LIBRARY_PATH": appPath + "/Frameworks",
|
||||
"NSUnbufferedIO": "YES",
|
||||
"SQLITE_ENABLE_THREAD_ASSERTIONS": "1",
|
||||
"WDA_PRODUCT_BUNDLE_IDENTIFIER": "",
|
||||
"XCTestConfigurationFilePath": pathXCTestConfiguration, // Running tests with active test configuration:
|
||||
// "XCTestBundlePath": fmt.Sprintf("%s/PlugIns/%s.xctest", appPath, name), // !!! ERROR
|
||||
// "XCTestSessionIdentifier": sessionId.String(), // !!! ERROR
|
||||
// "XCTestSessionIdentifier": "",
|
||||
"XCODE_DBG_XPC_EXCLUSIONS": "com.apple.dt.xctestSymbolicator",
|
||||
"MJPEG_SERVER_PORT": "",
|
||||
"USE_PORT": "",
|
||||
"LLVM_PROFILE_FILE": appContainer + "/tmp/%p.profraw",
|
||||
}
|
||||
if DeviceVersion(version...) >= DeviceVersion(11, 0, 0) {
|
||||
appEnv["DYLD_INSERT_LIBRARIES"] = "/Developer/usr/lib/libMainThreadChecker.dylib"
|
||||
appEnv["OS_ACTIVITY_DT_MODE"] = "YES"
|
||||
}
|
||||
appArgs := []interface{}{
|
||||
"-NSTreatUnknownArgumentsAsOpen", "NO",
|
||||
"-ApplePersistenceIgnoreState", "YES",
|
||||
}
|
||||
appOpt := map[string]interface{}{
|
||||
"StartSuspendedKey": uint64(0),
|
||||
}
|
||||
if DeviceVersion(version...) >= DeviceVersion(12, 0, 0) {
|
||||
appOpt["ActivateSuspended"] = uint64(1)
|
||||
}
|
||||
|
||||
if len(xcTestOpt.appEnv) != 0 {
|
||||
for k, v := range xcTestOpt.appEnv {
|
||||
appEnv[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if len(xcTestOpt.appOpt) != 0 {
|
||||
for k, v := range xcTestOpt.appEnv {
|
||||
appOpt[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
d.instruments.registerCallback("outputReceived:fromProcess:atTime:", func(m libimobiledevice.DTXMessageResult) {
|
||||
// fmt.Println("###### instruments ### -->", m.Aux[0])
|
||||
_out <- fmt.Sprintf("%s", m.Aux[0])
|
||||
})
|
||||
|
||||
var pid int
|
||||
if pid, err = d.instruments.AppLaunch(bundleID,
|
||||
WithAppPath(appPath),
|
||||
WithEnvironment(appEnv),
|
||||
WithArguments(appArgs),
|
||||
WithOptions(appOpt),
|
||||
WithKillExisting(true),
|
||||
); err != nil {
|
||||
return _out, cancelFunc, err
|
||||
}
|
||||
|
||||
// see https://github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/issues/31
|
||||
// if err = d.instruments.startObserving(pid); err != nil {
|
||||
// return _out, cancelFunc, err
|
||||
// }
|
||||
|
||||
if DeviceVersion(version...) >= DeviceVersion(12, 0, 0) {
|
||||
err = xcTestManager1.authorizeTestSession(pid)
|
||||
} else if DeviceVersion(version...) <= DeviceVersion(9, 0, 0) {
|
||||
err = xcTestManager1.initiateControlSessionForTestProcessID(pid)
|
||||
} else {
|
||||
err = xcTestManager1.initiateControlSessionForTestProcessIDProtocolVersion(pid, xcodeVersion)
|
||||
}
|
||||
if err != nil {
|
||||
return _out, cancelFunc, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
d.instruments.registerCallback("_Golang-iDevice_Over", func(_ libimobiledevice.DTXMessageResult) {
|
||||
cancelFunc()
|
||||
})
|
||||
|
||||
<-ctx.Done()
|
||||
tmSrv1.close()
|
||||
tmSrv2.close()
|
||||
xcTestManager1.close()
|
||||
xcTestManager2.close()
|
||||
if _err := d.AppKill(pid); _err != nil {
|
||||
debugLog(fmt.Sprintf("xctest kill: %d", pid))
|
||||
}
|
||||
// time.Sleep(time.Second)
|
||||
close(_out)
|
||||
}()
|
||||
|
||||
return _out, cancelFunc, err
|
||||
}
|
||||
|
||||
func (d *device) _uploadXCTestConfiguration(bundleID string, sessionId uuid.UUID, lookupResult map[string]interface{}) (pathXCTestCfg string, err error) {
|
||||
if _, err = d.HouseArrestService(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var appAfc Afc
|
||||
if appAfc, err = d.houseArrest.Container(bundleID); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
appTmpFilenames, err := appAfc.ReadDir("/tmp")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, tName := range appTmpFilenames {
|
||||
if strings.HasSuffix(tName, ".xctestconfiguration") {
|
||||
if _err := appAfc.Remove(fmt.Sprintf("/tmp/%s", tName)); _err != nil {
|
||||
debugLog(fmt.Sprintf("remove /tmp/%s: %s", tName, err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nameExec := lookupResult["CFBundleExecutable"].(string)
|
||||
name := nameExec[:len(nameExec)-len("-Runner")]
|
||||
appPath := lookupResult["Path"].(string)
|
||||
|
||||
pathXCTestCfg = fmt.Sprintf("/tmp/%s-%s.xctestconfiguration", name, strings.ToUpper(sessionId.String()))
|
||||
|
||||
var content []byte
|
||||
if content, err = nskeyedarchiver.Marshal(
|
||||
nskeyedarchiver.NewXCTestConfiguration(
|
||||
nskeyedarchiver.NewNSUUID(sessionId.Bytes()),
|
||||
nskeyedarchiver.NewNSURL(fmt.Sprintf("%s/PlugIns/%s.xctest", appPath, name)),
|
||||
bundleID,
|
||||
appPath,
|
||||
),
|
||||
); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = appAfc.WriteFile(pathXCTestCfg, content, AfcFileModeWr); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
176
hrp/pkg/gidevice/device_test.go
Normal file
176
hrp/pkg/gidevice/device_test.go
Normal file
@@ -0,0 +1,176 @@
|
||||
//go:build localtest
|
||||
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var dev Device
|
||||
|
||||
func setupDevice(t *testing.T) {
|
||||
setupUsbmux(t)
|
||||
devices, err := um.Devices()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(devices) == 0 {
|
||||
t.Fatal("No Device")
|
||||
}
|
||||
|
||||
dev = devices[0]
|
||||
}
|
||||
func Test_device_ReadPairRecord(t *testing.T) {
|
||||
setupDevice(t)
|
||||
|
||||
pairRecord, err := dev.ReadPairRecord()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Log(pairRecord.HostID, pairRecord.SystemBUID, pairRecord.WiFiMACAddress)
|
||||
}
|
||||
|
||||
func Test_device_NewConnect(t *testing.T) {
|
||||
setupDevice(t)
|
||||
|
||||
if _, err := dev.NewConnect(LockdownPort); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_device_DeletePairRecord(t *testing.T) {
|
||||
setupDevice(t)
|
||||
|
||||
if err := dev.DeletePairRecord(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func Test_device_SavePairRecord(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
pairRecord, err := lockdownSrv.Pair()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = dev.SavePairRecord(pairRecord)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_device_XCTest(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
bundleID = "com.leixipaopao.WebDriverAgentRunner.xctrunner"
|
||||
out, cancel, err := dev.XCTest(bundleID)
|
||||
// out, cancel, err := dev.XCTest(bundleID, WithXCTestEnv(map[string]interface{}{"USE_PORT": 8222, "MJPEG_SERVER_PORT": 8333}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
done := make(chan os.Signal, 1)
|
||||
signal.Notify(done, os.Interrupt)
|
||||
|
||||
go func() {
|
||||
for s := range out {
|
||||
fmt.Print(s)
|
||||
}
|
||||
done <- os.Interrupt
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
cancel()
|
||||
fmt.Println()
|
||||
t.Log("DONE")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_device_AppInstall(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
ipaPath := "/private/tmp/derivedDataPath/Build/Products/Release-iphoneos/WebDriverAgentRunner-Runner.ipa"
|
||||
err := dev.AppInstall(ipaPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_device_AppUninstall(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
bundleID = "com.leixipaopao.WebDriverAgentRunner.xctrunner"
|
||||
err := dev.AppUninstall(bundleID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_device_Syslog(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
dev.SyslogStop()
|
||||
|
||||
lines, err := dev.Syslog()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
done := make(chan os.Signal, 1)
|
||||
|
||||
go func() {
|
||||
for line := range lines {
|
||||
fmt.Println(line)
|
||||
}
|
||||
done <- os.Interrupt
|
||||
t.Log("DONE!!!")
|
||||
}()
|
||||
|
||||
signal.Notify(done, os.Interrupt, os.Kill)
|
||||
|
||||
// <-done
|
||||
time.Sleep(3 * time.Second)
|
||||
dev.SyslogStop()
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
|
||||
func Test_device_Reboot(t *testing.T) {
|
||||
setupDevice(t)
|
||||
dev.Reboot()
|
||||
}
|
||||
|
||||
func Test_device_Shutdown(t *testing.T) {
|
||||
setupDevice(t)
|
||||
dev.Shutdown()
|
||||
}
|
||||
|
||||
func Test_device_InstallationProxyBrowse(t *testing.T) {
|
||||
setupDevice(t)
|
||||
|
||||
list, err := dev.InstallationProxyBrowse(
|
||||
WithApplicationType(ApplicationTypeUser),
|
||||
WithReturnAttributes("CFBundleDisplayName", "CFBundleIdentifier", "SequenceNumber", "SequenceNumber"),
|
||||
)
|
||||
// list, err := dev.InstallationProxyBrowse()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Log(len(list))
|
||||
|
||||
for _, l := range list {
|
||||
t.Logf("%#v", l)
|
||||
}
|
||||
}
|
||||
39
hrp/pkg/gidevice/diagnosticsrelay.go
Normal file
39
hrp/pkg/gidevice/diagnosticsrelay.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package gidevice
|
||||
|
||||
import "github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/libimobiledevice"
|
||||
|
||||
func newDiagnosticsRelay(client *libimobiledevice.DiagnosticsRelayClient) *diagnostics {
|
||||
return &diagnostics{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
type diagnostics struct {
|
||||
client *libimobiledevice.DiagnosticsRelayClient
|
||||
}
|
||||
|
||||
func (d *diagnostics) Reboot() (err error) {
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = d.client.NewXmlPacket(
|
||||
d.client.NewBasicRequest("Restart"),
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
if err = d.client.SendPacket(pkt); err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *diagnostics) Shutdown() (err error) {
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = d.client.NewXmlPacket(
|
||||
d.client.NewBasicRequest("Shutdown"),
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
if err = d.client.SendPacket(pkt); err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
57
hrp/pkg/gidevice/housearrest.go
Normal file
57
hrp/pkg/gidevice/housearrest.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package gidevice
|
||||
|
||||
import "github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/libimobiledevice"
|
||||
|
||||
var _ HouseArrest = (*houseArrest)(nil)
|
||||
|
||||
func newHouseArrest(client *libimobiledevice.HouseArrestClient) *houseArrest {
|
||||
return &houseArrest{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
type houseArrest struct {
|
||||
client *libimobiledevice.HouseArrestClient
|
||||
}
|
||||
|
||||
func (h *houseArrest) Documents(bundleID string) (afc Afc, err error) {
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = h.client.NewXmlPacket(
|
||||
h.client.NewDocumentsRequest(bundleID),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = h.client.SendPacket(pkt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err = h.client.ReceivePacket(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
afcClient := libimobiledevice.NewAfcClient(h.client.InnerConn())
|
||||
afc = newAfc(afcClient)
|
||||
return
|
||||
}
|
||||
|
||||
func (h *houseArrest) Container(bundleID string) (afc Afc, err error) {
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = h.client.NewXmlPacket(
|
||||
h.client.NewContainerRequest(bundleID),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = h.client.SendPacket(pkt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err = h.client.ReceivePacket(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
afcClient := libimobiledevice.NewAfcClient(h.client.InnerConn())
|
||||
afc = newAfc(afcClient)
|
||||
return
|
||||
}
|
||||
60
hrp/pkg/gidevice/housearrest_test.go
Normal file
60
hrp/pkg/gidevice/housearrest_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
//go:build localtest
|
||||
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var houseArrestSrv HouseArrest
|
||||
|
||||
func setupHouseArrestSrv(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
var err error
|
||||
if lockdownSrv, err = dev.lockdownService(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if houseArrestSrv, err = lockdownSrv.HouseArrestService(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_houseArrest_Documents(t *testing.T) {
|
||||
setupHouseArrestSrv(t)
|
||||
|
||||
bundleID = "com.apple.iMovie"
|
||||
appAfc, err := houseArrestSrv.Documents(bundleID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
names, err := appAfc.ReadDir("Documents")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
t.Log(name)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_houseArrest_Container(t *testing.T) {
|
||||
setupHouseArrestSrv(t)
|
||||
|
||||
bundleID = "com.apple.iMovie"
|
||||
appAfc, err := houseArrestSrv.Documents(bundleID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
names, err := appAfc.ReadDir("Documents")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
t.Log(name)
|
||||
}
|
||||
}
|
||||
482
hrp/pkg/gidevice/idevice.go
Normal file
482
hrp/pkg/gidevice/idevice.go
Normal file
@@ -0,0 +1,482 @@
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/libimobiledevice"
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/nskeyedarchiver"
|
||||
)
|
||||
|
||||
type Usbmux interface {
|
||||
Devices() ([]Device, error)
|
||||
ReadBUID() (string, error)
|
||||
Listen(chan Device) (context.CancelFunc, error)
|
||||
}
|
||||
|
||||
type Device interface {
|
||||
Properties() DeviceProperties
|
||||
|
||||
NewConnect(port int, timeout ...time.Duration) (InnerConn, error)
|
||||
ReadPairRecord() (pairRecord *PairRecord, err error)
|
||||
SavePairRecord(pairRecord *PairRecord) (err error)
|
||||
DeletePairRecord() (err error)
|
||||
|
||||
lockdownService() (lockdown Lockdown, err error)
|
||||
QueryType() (LockdownType, error)
|
||||
GetValue(domain, key string) (v interface{}, err error)
|
||||
Pair() (pairRecord *PairRecord, err error)
|
||||
|
||||
imageMounterService() (imageMounter ImageMounter, err error)
|
||||
Images(imgType ...string) (imageSignatures [][]byte, err error)
|
||||
MountDeveloperDiskImage(dmgPath string, signaturePath string) (err error)
|
||||
|
||||
screenshotService() (lockdown Screenshot, err error)
|
||||
Screenshot() (raw *bytes.Buffer, err error)
|
||||
|
||||
simulateLocationService() (simulateLocation SimulateLocation, err error)
|
||||
SimulateLocationUpdate(longitude float64, latitude float64, coordinateSystem ...CoordinateSystem) (err error)
|
||||
SimulateLocationRecover() (err error)
|
||||
|
||||
installationProxyService() (installationProxy InstallationProxy, err error)
|
||||
InstallationProxyBrowse(opts ...InstallationProxyOption) (currentList []interface{}, err error)
|
||||
InstallationProxyLookup(opts ...InstallationProxyOption) (lookupResult interface{}, err error)
|
||||
|
||||
instrumentsService() (instruments Instruments, err error)
|
||||
AppLaunch(bundleID string, opts ...AppLaunchOption) (pid int, err error)
|
||||
AppKill(pid int) (err error)
|
||||
AppRunningProcesses() (processes []Process, err error)
|
||||
AppList(opts ...AppListOption) (apps []Application, err error)
|
||||
DeviceInfo() (devInfo *DeviceInfo, err error)
|
||||
|
||||
AfcService() (afc Afc, err error)
|
||||
AppInstall(ipaPath string) (err error)
|
||||
AppUninstall(bundleID string) (err error)
|
||||
|
||||
HouseArrestService() (houseArrest HouseArrest, err error)
|
||||
|
||||
syslogRelayService() (syslogRelay SyslogRelay, err error)
|
||||
Syslog() (lines <-chan string, err error)
|
||||
SyslogStop()
|
||||
|
||||
PcapdService() (pcapd Pcapd, err error)
|
||||
Pcap() (packet <-chan []byte, err error)
|
||||
PcapStop()
|
||||
|
||||
Reboot() error
|
||||
Shutdown() error
|
||||
|
||||
crashReportMoverService() (crashReportMover CrashReportMover, err error)
|
||||
MoveCrashReport(hostDir string, opts ...CrashReportMoverOption) (err error)
|
||||
|
||||
XCTest(bundleID string, opts ...XCTestOption) (out <-chan string, cancel context.CancelFunc, err error)
|
||||
|
||||
springBoardService() (springBoard SpringBoard, err error)
|
||||
GetIconPNGData(bundleId string) (raw *bytes.Buffer, err error)
|
||||
GetInterfaceOrientation() (orientation OrientationState, err error)
|
||||
|
||||
PerfStart(opts ...PerfOption) (data <-chan []byte, err error)
|
||||
PerfStop()
|
||||
}
|
||||
|
||||
type DeviceProperties = libimobiledevice.DeviceProperties
|
||||
|
||||
type OrientationState = libimobiledevice.OrientationState
|
||||
|
||||
type Lockdown interface {
|
||||
QueryType() (LockdownType, error)
|
||||
GetValue(domain, key string) (v interface{}, err error)
|
||||
SetValue(domain, key string, value interface{}) (err error)
|
||||
Pair() (pairRecord *PairRecord, err error)
|
||||
EnterRecovery() (err error)
|
||||
|
||||
handshake() (err error)
|
||||
|
||||
startSession(pairRecord *PairRecord) (err error)
|
||||
stopSession() (err error)
|
||||
startService(service string, escrowBag []byte) (dynamicPort int, enableSSL bool, err error)
|
||||
|
||||
ImageMounterService() (imageMounter ImageMounter, err error)
|
||||
ScreenshotService() (screenshot Screenshot, err error)
|
||||
SimulateLocationService() (simulateLocation SimulateLocation, err error)
|
||||
InstallationProxyService() (installationProxy InstallationProxy, err error)
|
||||
InstrumentsService() (instruments Instruments, err error)
|
||||
TestmanagerdService() (testmanagerd Testmanagerd, err error)
|
||||
AfcService() (afc Afc, err error)
|
||||
HouseArrestService() (houseArrest HouseArrest, err error)
|
||||
SyslogRelayService() (syslogRelay SyslogRelay, err error)
|
||||
DiagnosticsRelayService() (diagnostics DiagnosticsRelay, err error)
|
||||
CrashReportMoverService() (crashReportMover CrashReportMover, err error)
|
||||
SpringBoardService() (springBoard SpringBoard, err error)
|
||||
}
|
||||
|
||||
type ImageMounter interface {
|
||||
Images(imgType string) (imageSignatures [][]byte, err error)
|
||||
UploadImage(imgType, dmgPath string, signatureData []byte) (err error)
|
||||
Mount(imgType, devImgPath string, signatureData []byte) (err error)
|
||||
|
||||
UploadImageAndMount(imgType, devImgPath, dmgPath, signaturePath string) (err error)
|
||||
}
|
||||
|
||||
type Screenshot interface {
|
||||
exchange() (err error)
|
||||
Take() (raw *bytes.Buffer, err error)
|
||||
}
|
||||
|
||||
type SimulateLocation interface {
|
||||
Update(longitude float64, latitude float64, coordinateSystem ...CoordinateSystem) (err error)
|
||||
// Recover try to revert back
|
||||
Recover() (err error)
|
||||
}
|
||||
|
||||
type InstallationProxy interface {
|
||||
Browse(opts ...InstallationProxyOption) (currentList []interface{}, err error)
|
||||
Lookup(opts ...InstallationProxyOption) (lookupResult interface{}, err error)
|
||||
Install(bundleID, packagePath string) (err error)
|
||||
Uninstall(bundleID string) (err error)
|
||||
}
|
||||
|
||||
type Instruments interface {
|
||||
AppLaunch(bundleID string, opts ...AppLaunchOption) (pid int, err error)
|
||||
AppKill(pid int) (err error)
|
||||
AppRunningProcesses() (processes []Process, err error)
|
||||
AppList(opts ...AppListOption) (apps []Application, err error)
|
||||
DeviceInfo() (devInfo *DeviceInfo, err error)
|
||||
|
||||
getPidByBundleID(bundleID string) (pid int, err error)
|
||||
appProcess(bundleID string) (err error)
|
||||
startObserving(pid int) (err error)
|
||||
|
||||
notifyOfPublishedCapabilities() (err error)
|
||||
requestChannel(channel string) (id uint32, err error)
|
||||
call(channel, selector string, auxiliaries ...interface{}) (result *libimobiledevice.DTXMessageResult, err error)
|
||||
|
||||
// sysMonSetConfig(cfg ...interface{}) (err error)
|
||||
// SysMonStart(cfg ...interface{}) (_ interface{}, err error)
|
||||
|
||||
registerCallback(obj string, cb func(m libimobiledevice.DTXMessageResult))
|
||||
}
|
||||
|
||||
type Testmanagerd interface {
|
||||
notifyOfPublishedCapabilities() (err error)
|
||||
requestChannel(channel string) (id uint32, err error)
|
||||
newXCTestManagerDaemon() (xcTestManager XCTestManagerDaemon, err error)
|
||||
|
||||
invoke(selector string, args *libimobiledevice.AuxBuffer, channel uint32, expectsReply bool) (*libimobiledevice.DTXMessageResult, error)
|
||||
|
||||
registerCallback(obj string, cb func(m libimobiledevice.DTXMessageResult))
|
||||
close()
|
||||
}
|
||||
|
||||
type Afc interface {
|
||||
DiskInfo() (diskInfo *AfcDiskInfo, err error)
|
||||
ReadDir(dirname string) (names []string, err error)
|
||||
Stat(filename string) (info *AfcFileInfo, err error)
|
||||
Open(filename string, mode AfcFileMode) (file *AfcFile, err error)
|
||||
Remove(filePath string) (err error)
|
||||
Rename(oldPath string, newPath string) (err error)
|
||||
Mkdir(path string) (err error)
|
||||
Link(oldName string, newName string, linkType AfcLinkType) (err error)
|
||||
Truncate(filePath string, size int64) (err error)
|
||||
SetFileModTime(filePath string, modTime time.Time) (err error)
|
||||
// Hash sha1 algorithm
|
||||
Hash(filePath string) ([]byte, error)
|
||||
// HashWithRange sha1 algorithm with file range
|
||||
HashWithRange(filePath string, start, end uint64) ([]byte, error)
|
||||
RemoveAll(path string) (err error)
|
||||
|
||||
WriteFile(filename string, data []byte, perm AfcFileMode) (err error)
|
||||
}
|
||||
|
||||
type HouseArrest interface {
|
||||
Documents(bundleID string) (afc Afc, err error)
|
||||
Container(bundleID string) (afc Afc, err error)
|
||||
}
|
||||
|
||||
type XCTestManagerDaemon interface {
|
||||
// initiateControlSession iOS 11+
|
||||
initiateControlSession(XcodeVersion uint64) (err error)
|
||||
startExecutingTestPlan(XcodeVersion uint64) (err error)
|
||||
initiateSession(XcodeVersion uint64, nsUUID *nskeyedarchiver.NSUUID) (err error)
|
||||
// authorizeTestSession iOS 12+
|
||||
authorizeTestSession(pid int) (err error)
|
||||
// initiateControlSessionForTestProcessID <= iOS 9
|
||||
initiateControlSessionForTestProcessID(pid int) (err error)
|
||||
// initiateControlSessionForTestProcessIDProtocolVersion iOS > 9 && iOS < 12
|
||||
initiateControlSessionForTestProcessIDProtocolVersion(pid int, XcodeVersion uint64) (err error)
|
||||
|
||||
registerCallback(obj string, cb func(m libimobiledevice.DTXMessageResult))
|
||||
close()
|
||||
}
|
||||
|
||||
type SyslogRelay interface {
|
||||
Lines() <-chan string
|
||||
Stop()
|
||||
}
|
||||
|
||||
type Pcapd interface {
|
||||
Packet() <-chan []byte
|
||||
Stop()
|
||||
}
|
||||
|
||||
type Perfd interface {
|
||||
Start() (data <-chan []byte, err error)
|
||||
Stop()
|
||||
}
|
||||
|
||||
type DiagnosticsRelay interface {
|
||||
Reboot() error
|
||||
Shutdown() error
|
||||
}
|
||||
|
||||
type CrashReportMover interface {
|
||||
Move(hostDir string, opts ...CrashReportMoverOption) (err error)
|
||||
walkDir(dirname string, fn func(path string, info *AfcFileInfo)) (err error)
|
||||
}
|
||||
|
||||
type SpringBoard interface {
|
||||
GetIconPNGData(bundleId string) (raw *bytes.Buffer, err error)
|
||||
GetInterfaceOrientation() (orientation OrientationState, err error)
|
||||
}
|
||||
|
||||
type InnerConn = libimobiledevice.InnerConn
|
||||
|
||||
type LockdownType = libimobiledevice.LockdownType
|
||||
|
||||
type PairRecord = libimobiledevice.PairRecord
|
||||
|
||||
type CoordinateSystem = libimobiledevice.CoordinateSystem
|
||||
|
||||
const (
|
||||
CoordinateSystemWGS84 = libimobiledevice.CoordinateSystemWGS84
|
||||
CoordinateSystemBD09 = libimobiledevice.CoordinateSystemBD09
|
||||
CoordinateSystemGCJ02 = libimobiledevice.CoordinateSystemGCJ02
|
||||
)
|
||||
|
||||
type ApplicationType = libimobiledevice.ApplicationType
|
||||
|
||||
const (
|
||||
ApplicationTypeSystem = libimobiledevice.ApplicationTypeSystem
|
||||
ApplicationTypeUser = libimobiledevice.ApplicationTypeUser
|
||||
ApplicationTypeInternal = libimobiledevice.ApplicationTypeInternal
|
||||
ApplicationTypeAny = libimobiledevice.ApplicationTypeAny
|
||||
)
|
||||
|
||||
type installationProxyOption = libimobiledevice.InstallationProxyOption
|
||||
|
||||
type InstallationProxyOption func(*installationProxyOption)
|
||||
|
||||
func WithApplicationType(appType ApplicationType) InstallationProxyOption {
|
||||
return func(opt *installationProxyOption) {
|
||||
opt.ApplicationType = appType
|
||||
}
|
||||
}
|
||||
|
||||
func WithReturnAttributes(attrs ...string) InstallationProxyOption {
|
||||
return func(opt *installationProxyOption) {
|
||||
if len(opt.ReturnAttributes) == 0 {
|
||||
opt.ReturnAttributes = attrs
|
||||
} else {
|
||||
opt.ReturnAttributes = append(opt.ReturnAttributes, attrs...)
|
||||
}
|
||||
opt.ReturnAttributes = _removeDuplicate(opt.ReturnAttributes)
|
||||
}
|
||||
}
|
||||
|
||||
func WithBundleIDs(BundleIDs ...string) InstallationProxyOption {
|
||||
return func(opt *installationProxyOption) {
|
||||
if len(opt.BundleIDs) == 0 {
|
||||
opt.BundleIDs = BundleIDs
|
||||
} else {
|
||||
opt.BundleIDs = append(opt.BundleIDs, BundleIDs...)
|
||||
}
|
||||
opt.BundleIDs = _removeDuplicate(opt.BundleIDs)
|
||||
}
|
||||
}
|
||||
|
||||
func WithMetaData(b bool) InstallationProxyOption {
|
||||
return func(opt *installationProxyOption) {
|
||||
opt.MetaData = b
|
||||
}
|
||||
}
|
||||
|
||||
type appLaunchOption struct {
|
||||
appPath string
|
||||
environment map[string]interface{}
|
||||
arguments []interface{}
|
||||
options map[string]interface{}
|
||||
}
|
||||
|
||||
type AppLaunchOption func(option *appLaunchOption)
|
||||
|
||||
func WithAppPath(appPath string) AppLaunchOption {
|
||||
return func(opt *appLaunchOption) {
|
||||
opt.appPath = appPath
|
||||
}
|
||||
}
|
||||
|
||||
func WithEnvironment(environment map[string]interface{}) AppLaunchOption {
|
||||
return func(opt *appLaunchOption) {
|
||||
opt.environment = environment
|
||||
}
|
||||
}
|
||||
|
||||
func WithArguments(arguments []interface{}) AppLaunchOption {
|
||||
return func(opt *appLaunchOption) {
|
||||
opt.arguments = arguments
|
||||
}
|
||||
}
|
||||
|
||||
func WithOptions(options map[string]interface{}) AppLaunchOption {
|
||||
return func(opt *appLaunchOption) {
|
||||
for k, v := range options {
|
||||
opt.options[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithKillExisting(b bool) AppLaunchOption {
|
||||
return func(opt *appLaunchOption) {
|
||||
v := uint64(0)
|
||||
if b {
|
||||
v = uint64(1)
|
||||
}
|
||||
opt.options["KillExisting"] = v
|
||||
}
|
||||
}
|
||||
|
||||
type appListOption struct {
|
||||
appsMatching map[string]interface{}
|
||||
updateToken string
|
||||
}
|
||||
|
||||
type AppListOption func(option *appListOption)
|
||||
|
||||
func WithAppsMatching(appsMatching map[string]interface{}) AppListOption {
|
||||
return func(opt *appListOption) {
|
||||
opt.appsMatching = appsMatching
|
||||
}
|
||||
}
|
||||
|
||||
func WithUpdateToken(updateToken string) AppListOption {
|
||||
return func(opt *appListOption) {
|
||||
opt.updateToken = updateToken
|
||||
}
|
||||
}
|
||||
|
||||
type Process struct {
|
||||
IsApplication bool `json:"isApplication"`
|
||||
Name string `json:"name"`
|
||||
Pid int `json:"pid"`
|
||||
RealAppName string `json:"realAppName"`
|
||||
StartDate time.Time `json:"startDate"`
|
||||
}
|
||||
|
||||
type crashReportMoverOption struct {
|
||||
whenDone func(filename string)
|
||||
keep bool
|
||||
extract bool
|
||||
}
|
||||
|
||||
func defaultCrashReportMoverOption() *crashReportMoverOption {
|
||||
return &crashReportMoverOption{
|
||||
whenDone: func(filename string) {},
|
||||
keep: false,
|
||||
}
|
||||
}
|
||||
|
||||
type CrashReportMoverOption func(opt *crashReportMoverOption)
|
||||
|
||||
func WithKeepCrashReport(b bool) CrashReportMoverOption {
|
||||
return func(opt *crashReportMoverOption) {
|
||||
opt.keep = b
|
||||
}
|
||||
}
|
||||
|
||||
func WithExtractRawCrashReport(b bool) CrashReportMoverOption {
|
||||
return func(opt *crashReportMoverOption) {
|
||||
opt.extract = b
|
||||
}
|
||||
}
|
||||
|
||||
func WithWhenMoveIsDone(whenDone func(filename string)) CrashReportMoverOption {
|
||||
return func(opt *crashReportMoverOption) {
|
||||
opt.whenDone = whenDone
|
||||
}
|
||||
}
|
||||
|
||||
type xcTestOption struct {
|
||||
appEnv map[string]interface{}
|
||||
appArgs []interface{}
|
||||
appOpt map[string]interface{}
|
||||
}
|
||||
|
||||
func defaultXCTestOption() *xcTestOption {
|
||||
return &xcTestOption{
|
||||
appEnv: make(map[string]interface{}),
|
||||
appArgs: make([]interface{}, 0, 2),
|
||||
appOpt: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
type XCTestOption func(opt *xcTestOption)
|
||||
|
||||
func WithXCTestEnv(env map[string]interface{}) XCTestOption {
|
||||
return func(opt *xcTestOption) {
|
||||
opt.appEnv = env
|
||||
}
|
||||
}
|
||||
|
||||
// func WithXCTestArgs(args []interface{}) XCTestOption {
|
||||
// return func(opt *xcTestOption) {
|
||||
// opt.appArgs = args
|
||||
// }
|
||||
// }
|
||||
|
||||
func WithXCTestOpt(appOpt map[string]interface{}) XCTestOption {
|
||||
return func(opt *xcTestOption) {
|
||||
opt.appOpt = appOpt
|
||||
}
|
||||
}
|
||||
|
||||
func _removeDuplicate(strSlice []string) []string {
|
||||
existed := make(map[string]bool, len(strSlice))
|
||||
noRepeat := make([]string, 0, len(strSlice))
|
||||
for _, str := range strSlice {
|
||||
if _, ok := existed[str]; ok {
|
||||
continue
|
||||
}
|
||||
existed[str] = true
|
||||
noRepeat = append(noRepeat, str)
|
||||
}
|
||||
return noRepeat
|
||||
}
|
||||
|
||||
func DeviceVersion(version ...int) int {
|
||||
if len(version) < 3 {
|
||||
tmp := make([]int, 3)
|
||||
copy(tmp, version)
|
||||
version = tmp
|
||||
}
|
||||
maj, min, patch := version[0], version[1], version[2]
|
||||
return ((maj & 0xFF) << 16) | ((min & 0xFF) << 8) | (patch & 0xFF)
|
||||
}
|
||||
|
||||
var debugFlag = false
|
||||
|
||||
// SetDebug sets debug mode
|
||||
func SetDebug(debug bool, libDebug ...bool) {
|
||||
debugFlag = debug
|
||||
if len(libDebug) >= 1 {
|
||||
libimobiledevice.SetDebug(libDebug[0])
|
||||
}
|
||||
}
|
||||
|
||||
func debugLog(msg string) {
|
||||
if !debugFlag {
|
||||
return
|
||||
}
|
||||
fmt.Printf("[go-iDevice-debug] %s\n", msg)
|
||||
}
|
||||
142
hrp/pkg/gidevice/imagemounter.go
Normal file
142
hrp/pkg/gidevice/imagemounter.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/libimobiledevice"
|
||||
)
|
||||
|
||||
var _ ImageMounter = (*imageMounter)(nil)
|
||||
|
||||
func newImageMounter(client *libimobiledevice.ImageMounterClient) *imageMounter {
|
||||
return &imageMounter{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
type imageMounter struct {
|
||||
client *libimobiledevice.ImageMounterClient
|
||||
}
|
||||
|
||||
func (m *imageMounter) Images(imgType string) (imageSignatures [][]byte, err error) {
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = m.client.NewXmlPacket(
|
||||
m.client.NewBasicRequest(libimobiledevice.CommandTypeLookupImage, imgType),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = m.client.SendPacket(pkt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var respPkt libimobiledevice.Packet
|
||||
if respPkt, err = m.client.ReceivePacket(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var reply libimobiledevice.ImageMounterLookupImageResponse
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
imageSignatures = reply.ImageSignature
|
||||
return
|
||||
}
|
||||
|
||||
func (m *imageMounter) UploadImage(imgType, dmgPath string, signatureData []byte) (err error) {
|
||||
var dmgFileInfo os.FileInfo
|
||||
if dmgFileInfo, err = os.Stat(dmgPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = m.client.NewXmlPacket(
|
||||
m.client.NewReceiveBytesRequest(imgType, uint32(dmgFileInfo.Size()), signatureData),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = m.client.SendPacket(pkt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var respPkt libimobiledevice.Packet
|
||||
if respPkt, err = m.client.ReceivePacket(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var reply libimobiledevice.ImageMounterBasicResponse
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if reply.Status != "ReceiveBytesAck" {
|
||||
return fmt.Errorf("image mounter 'ReceiveBytes' status: %s", reply.Status)
|
||||
}
|
||||
|
||||
var dmgData []byte
|
||||
if dmgData, err = os.ReadFile(dmgPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = m.client.SendDmg(dmgData); err != nil {
|
||||
return err
|
||||
}
|
||||
if respPkt, err = m.client.ReceivePacket(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if reply.Status != "Complete" {
|
||||
return fmt.Errorf("image mounter 'SendDmg' status: %s", reply.Status)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (m *imageMounter) Mount(imgType, devImgPath string, signatureData []byte) (err error) {
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = m.client.NewXmlPacket(
|
||||
m.client.NewMountImageRequest(imgType, devImgPath, signatureData),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = m.client.SendPacket(pkt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var respPkt libimobiledevice.Packet
|
||||
if respPkt, err = m.client.ReceivePacket(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var reply libimobiledevice.ImageMounterBasicResponse
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if reply.Status != "Complete" {
|
||||
return fmt.Errorf("image mounter 'MountImage' status: %s", reply.Status)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *imageMounter) UploadImageAndMount(imgType, devImgPath, dmgPath, signaturePath string) (err error) {
|
||||
var signatureData []byte
|
||||
if signatureData, err = os.ReadFile(signaturePath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = m.UploadImage(imgType, dmgPath, signatureData); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = m.Mount(imgType, devImgPath, signatureData); err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
51
hrp/pkg/gidevice/imagemounter_test.go
Normal file
51
hrp/pkg/gidevice/imagemounter_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
//go:build localtest
|
||||
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var imageMounterSrv ImageMounter
|
||||
|
||||
func setupImageMounterSrv(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
var err error
|
||||
if lockdownSrv, err = dev.lockdownService(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Once
|
||||
// dev.Images()
|
||||
if imageMounterSrv, err = lockdownSrv.ImageMounterService(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_imageMounter_Images(t *testing.T) {
|
||||
setupImageMounterSrv(t)
|
||||
|
||||
// imageSignatures, err := dev.Images()
|
||||
imageSignatures, err := imageMounterSrv.Images("Developer")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i, imgSign := range imageSignatures {
|
||||
t.Logf("%2d, %s", i+1, base64.StdEncoding.EncodeToString(imgSign))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_imageMounter_UploadImageAndMount(t *testing.T) {
|
||||
setupImageMounterSrv(t)
|
||||
|
||||
devImgPath := "/private/var/mobile/Media/PublicStaging/staging.dimage"
|
||||
dmgPath := "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/14.4/DeveloperDiskImage.dmg"
|
||||
signaturePath := "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/14.4/DeveloperDiskImage.dmg.signature"
|
||||
|
||||
if err := imageMounterSrv.UploadImageAndMount("Developer", devImgPath, dmgPath, signaturePath); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
167
hrp/pkg/gidevice/installationproxy.go
Normal file
167
hrp/pkg/gidevice/installationproxy.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/libimobiledevice"
|
||||
)
|
||||
|
||||
var _ InstallationProxy = (*installationProxy)(nil)
|
||||
|
||||
func newInstallationProxy(client *libimobiledevice.InstallationProxyClient) *installationProxy {
|
||||
return &installationProxy{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
type installationProxy struct {
|
||||
client *libimobiledevice.InstallationProxyClient
|
||||
}
|
||||
|
||||
func (p *installationProxy) Browse(opts ...InstallationProxyOption) (currentList []interface{}, err error) {
|
||||
opt := new(installationProxyOption)
|
||||
if len(opts) == 0 {
|
||||
opt = nil
|
||||
} else {
|
||||
for _, optFunc := range opts {
|
||||
optFunc(opt)
|
||||
}
|
||||
}
|
||||
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = p.client.NewXmlPacket(
|
||||
p.client.NewBasicRequest(libimobiledevice.CommandTypeBrowse, opt),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = p.client.SendPacket(pkt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var respPkt libimobiledevice.Packet
|
||||
if respPkt, err = p.client.ReceivePacket(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var reply libimobiledevice.InstallationProxyBrowseResponse
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for reply.Status != "Complete" {
|
||||
if respPkt, err = p.client.ReceivePacket(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
currentList = reply.CurrentList
|
||||
return
|
||||
}
|
||||
|
||||
func (p *installationProxy) Lookup(opts ...InstallationProxyOption) (lookupResult interface{}, err error) {
|
||||
opt := new(installationProxyOption)
|
||||
if len(opts) == 0 {
|
||||
opt = nil
|
||||
} else {
|
||||
for _, optFunc := range opts {
|
||||
optFunc(opt)
|
||||
}
|
||||
}
|
||||
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = p.client.NewXmlPacket(
|
||||
p.client.NewBasicRequest(libimobiledevice.CommandTypeLookup, opt),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = p.client.SendPacket(pkt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var respPkt libimobiledevice.Packet
|
||||
if respPkt, err = p.client.ReceivePacket(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var reply libimobiledevice.InstallationProxyLookupResponse
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if reply.Status != "Complete" {
|
||||
return nil, fmt.Errorf("installation proxy 'Lookup' status: %s", reply.Status)
|
||||
}
|
||||
|
||||
lookupResult = reply.LookupResult
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *installationProxy) Install(bundleID, packagePath string) (err error) {
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = p.client.NewXmlPacket(
|
||||
p.client.NewInstallRequest(bundleID, packagePath),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = p.client.SendPacket(pkt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var reply libimobiledevice.InstallationProxyInstallResponse
|
||||
for len(reply.Error) == 0 {
|
||||
var respPkt libimobiledevice.Packet
|
||||
if respPkt, err = p.client.ReceivePacket(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return err
|
||||
}
|
||||
if reply.Status == "Complete" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(reply.Error) != 0 {
|
||||
return fmt.Errorf("installation proxy 'Install' status: %s (err: %s, desc: %s)", reply.Status, reply.Error, reply.ErrorDescription)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *installationProxy) Uninstall(bundleID string) (err error) {
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = p.client.NewXmlPacket(
|
||||
p.client.NewUninstallRequest(bundleID),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = p.client.SendPacket(pkt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var reply libimobiledevice.InstallationProxyInstallResponse
|
||||
for len(reply.Error) == 0 {
|
||||
var respPkt libimobiledevice.Packet
|
||||
if respPkt, err = p.client.ReceivePacket(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return err
|
||||
}
|
||||
if reply.Status == "Complete" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(reply.Error) != 0 {
|
||||
return fmt.Errorf("installation proxy 'Uninstall' status: %s (err: %s, desc: %s)", reply.Status, reply.Error, reply.ErrorDescription)
|
||||
}
|
||||
return
|
||||
}
|
||||
73
hrp/pkg/gidevice/installationproxy_test.go
Normal file
73
hrp/pkg/gidevice/installationproxy_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
//go:build localtest
|
||||
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var installationProxySrv InstallationProxy
|
||||
|
||||
func setupInstallationProxySrv(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
var err error
|
||||
if lockdownSrv, err = dev.lockdownService(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if installationProxySrv, err = lockdownSrv.InstallationProxyService(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_installationProxy_Browse(t *testing.T) {
|
||||
setupInstallationProxySrv(t)
|
||||
|
||||
// currentList, err := installationProxySrv.Browse(WithMetaData(true))
|
||||
// currentList, err := installationProxySrv.Browse(WithReturnAttributes("CFBundleIdentifier", "SequenceNumber", "SequenceNumber"))
|
||||
// currentList, err := installationProxySrv.Browse(WithApplicationType(ApplicationTypeSystem))
|
||||
// currentList, err := installationProxySrv.Browse(WithApplicationType(ApplicationTypeSystem), WithReturnAttributes("ApplicationType", "ApplicationType"))
|
||||
// currentList, err := dev.InstallationProxyBrowse()
|
||||
currentList, err := installationProxySrv.Browse()
|
||||
// currentList, err := installationProxySrv.Browse(WithBundleIDs("com.apple.MusicUIService"), WithBundleIDs("com.apple.Home.HomeControlService"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Log(len(currentList))
|
||||
|
||||
for _, cl := range currentList {
|
||||
app, ok := cl.(map[string]interface{})
|
||||
if ok {
|
||||
t.Log(app)
|
||||
} else {
|
||||
t.Log(cl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_installationProxy_Lookup(t *testing.T) {
|
||||
setupInstallationProxySrv(t)
|
||||
|
||||
// lookupResult, err := installationProxySrv.Lookup()
|
||||
// lookupResult, err := dev.InstallationProxyLookup(
|
||||
lookupResult, err := installationProxySrv.Lookup(
|
||||
// WithApplicationType(ApplicationTypeUser),
|
||||
// WithApplicationType(ApplicationTypeSystem),
|
||||
// WithReturnAttributes("CFBundleDevelopmentRegion"),
|
||||
// WithReturnAttributes("CFBundleDisplayName", "CFBundleIdentifier"),
|
||||
// WithBundleIDs("com.apple.mobilephone"),
|
||||
WithBundleIDs("com.leixipaopao.WebDriverAgentRunner.xctrunner"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ret := lookupResult.(map[string]interface{})
|
||||
t.Log(len(ret))
|
||||
|
||||
for k, v := range ret {
|
||||
t.Log(k, "-->", v)
|
||||
}
|
||||
}
|
||||
348
hrp/pkg/gidevice/instruments.go
Normal file
348
hrp/pkg/gidevice/instruments.go
Normal file
@@ -0,0 +1,348 @@
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/libimobiledevice"
|
||||
)
|
||||
|
||||
// instruments services
|
||||
const (
|
||||
instrumentsServiceDeviceInfo = "com.apple.instruments.server.services.deviceinfo"
|
||||
instrumentsServiceProcessControl = "com.apple.instruments.server.services.processcontrol"
|
||||
instrumentsServiceDeviceApplictionListing = "com.apple.instruments.server.services.device.applictionListing"
|
||||
instrumentsServiceGraphicsOpengl = "com.apple.instruments.server.services.graphics.opengl" // 获取 GPU/FPS
|
||||
instrumentsServiceSysmontap = "com.apple.instruments.server.services.sysmontap" // 获取 CPU/Mem/Disk/Network 性能数据
|
||||
instrumentsServiceNetworking = "com.apple.instruments.server.services.networking" // 获取所有网络详情数据
|
||||
instrumentsServiceMobileNotifications = "com.apple.instruments.server.services.mobilenotifications" // 监控应用状态
|
||||
)
|
||||
|
||||
const (
|
||||
instrumentsServiceXcodeNetworkStatistics = "com.apple.xcode.debug-gauge-data-providers.NetworkStatistics" // 获取单进程网络数据
|
||||
instrumentsServiceXcodeEnergyStatistics = "com.apple.xcode.debug-gauge-data-providers.Energy" // 获取功耗数据
|
||||
)
|
||||
|
||||
var _ Instruments = (*instruments)(nil)
|
||||
|
||||
func newInstruments(client *libimobiledevice.InstrumentsClient) *instruments {
|
||||
return &instruments{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
type instruments struct {
|
||||
client *libimobiledevice.InstrumentsClient
|
||||
}
|
||||
|
||||
func (i *instruments) notifyOfPublishedCapabilities() (err error) {
|
||||
_, err = i.client.NotifyOfPublishedCapabilities()
|
||||
return
|
||||
}
|
||||
|
||||
func (i *instruments) requestChannel(channel string) (id uint32, err error) {
|
||||
return i.client.RequestChannel(channel)
|
||||
}
|
||||
|
||||
func (i *instruments) call(channel, selector string, auxiliaries ...interface{}) (
|
||||
result *libimobiledevice.DTXMessageResult, err error) {
|
||||
|
||||
chanID, err := i.requestChannel(channel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
args := libimobiledevice.NewAuxBuffer()
|
||||
for _, aux := range auxiliaries {
|
||||
if err = args.AppendObject(aux); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return i.client.Invoke(selector, args, chanID, true)
|
||||
}
|
||||
|
||||
func (i *instruments) getPidByBundleID(bundleID string) (pid int, err error) {
|
||||
apps, err := i.AppList()
|
||||
if err != nil {
|
||||
fmt.Printf("get app list error: %v\n", err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
mapper := make(map[string]interface{})
|
||||
for _, app := range apps {
|
||||
mapper[app.ExecutableName] = app.CFBundleIdentifier
|
||||
}
|
||||
|
||||
processes, err := i.AppRunningProcesses()
|
||||
if err != nil {
|
||||
fmt.Printf("get running app processes error: %v\n", err)
|
||||
return 0, err
|
||||
}
|
||||
for _, proc := range processes {
|
||||
b, ok := mapper[proc.Name]
|
||||
if ok && bundleID == b {
|
||||
fmt.Printf("get pid %d by bundleId %s\n", proc.Pid, bundleID)
|
||||
return proc.Pid, nil
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("can't find pid by bundleID: %s\n", bundleID)
|
||||
return 0, fmt.Errorf("can't find pid by bundleID: %s", bundleID)
|
||||
}
|
||||
|
||||
func (i *instruments) AppLaunch(bundleID string, opts ...AppLaunchOption) (pid int, err error) {
|
||||
opt := new(appLaunchOption)
|
||||
opt.appPath = ""
|
||||
opt.options = map[string]interface{}{
|
||||
"StartSuspendedKey": uint64(0),
|
||||
"KillExisting": uint64(0),
|
||||
}
|
||||
if len(opts) != 0 {
|
||||
for _, optFunc := range opts {
|
||||
optFunc(opt)
|
||||
}
|
||||
}
|
||||
|
||||
var id uint32
|
||||
if id, err = i.requestChannel(instrumentsServiceProcessControl); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
args := libimobiledevice.NewAuxBuffer()
|
||||
if err = args.AppendObject(opt.appPath); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err = args.AppendObject(bundleID); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err = args.AppendObject(opt.environment); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err = args.AppendObject(opt.arguments); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err = args.AppendObject(opt.options); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var result *libimobiledevice.DTXMessageResult
|
||||
selector := "launchSuspendedProcessWithDevicePath:bundleIdentifier:environment:arguments:options:"
|
||||
if result, err = i.client.Invoke(selector, args, id, true); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if nsErr, ok := result.Obj.(libimobiledevice.NSError); ok {
|
||||
return 0, fmt.Errorf("%s", nsErr.NSUserInfo.(map[string]interface{})["NSLocalizedDescription"])
|
||||
}
|
||||
|
||||
return int(result.Obj.(uint64)), nil
|
||||
}
|
||||
|
||||
func (i *instruments) appProcess(bundleID string) (err error) {
|
||||
var id uint32
|
||||
if id, err = i.requestChannel(instrumentsServiceProcessControl); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := libimobiledevice.NewAuxBuffer()
|
||||
if err = args.AppendObject(bundleID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
selector := "processIdentifierForBundleIdentifier:"
|
||||
if _, err = i.client.Invoke(selector, args, id, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (i *instruments) startObserving(pid int) (err error) {
|
||||
var id uint32
|
||||
if id, err = i.requestChannel(instrumentsServiceProcessControl); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := libimobiledevice.NewAuxBuffer()
|
||||
if err = args.AppendObject(pid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var result *libimobiledevice.DTXMessageResult
|
||||
selector := "startObservingPid:"
|
||||
if result, err = i.client.Invoke(selector, args, id, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if nsErr, ok := result.Obj.(libimobiledevice.NSError); ok {
|
||||
return fmt.Errorf("%s", nsErr.NSUserInfo.(map[string]interface{})["NSLocalizedDescription"])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (i *instruments) AppKill(pid int) (err error) {
|
||||
var id uint32
|
||||
if id, err = i.requestChannel(instrumentsServiceProcessControl); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := libimobiledevice.NewAuxBuffer()
|
||||
if err = args.AppendObject(pid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
selector := "killPid:"
|
||||
if _, err = i.client.Invoke(selector, args, id, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (i *instruments) AppRunningProcesses() (processes []Process, err error) {
|
||||
var id uint32
|
||||
if id, err = i.requestChannel(instrumentsServiceDeviceInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
selector := "runningProcesses"
|
||||
|
||||
var result *libimobiledevice.DTXMessageResult
|
||||
if result, err = i.client.Invoke(selector, libimobiledevice.NewAuxBuffer(), id, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objs := result.Obj.([]interface{})
|
||||
|
||||
processes = make([]Process, 0, len(objs))
|
||||
|
||||
for _, v := range objs {
|
||||
m := v.(map[string]interface{})
|
||||
|
||||
var data []byte
|
||||
if data, err = json.Marshal(m); err != nil {
|
||||
debugLog(fmt.Sprintf("process marshal: %v\n%v\n", err, m))
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
|
||||
var tp Process
|
||||
if err = json.Unmarshal(data, &tp); err != nil {
|
||||
debugLog(fmt.Sprintf("process unmarshal: %v\n%v\n", err, m))
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
|
||||
processes = append(processes, tp)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (i *instruments) AppList(opts ...AppListOption) (apps []Application, err error) {
|
||||
opt := new(appListOption)
|
||||
opt.updateToken = ""
|
||||
opt.appsMatching = make(map[string]interface{})
|
||||
if len(opts) != 0 {
|
||||
for _, optFunc := range opts {
|
||||
optFunc(opt)
|
||||
}
|
||||
}
|
||||
|
||||
var id uint32
|
||||
if id, err = i.requestChannel(instrumentsServiceDeviceApplictionListing); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
args := libimobiledevice.NewAuxBuffer()
|
||||
if err = args.AppendObject(opt.appsMatching); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = args.AppendObject(opt.updateToken); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
selector := "installedApplicationsMatching:registerUpdateToken:"
|
||||
|
||||
var result *libimobiledevice.DTXMessageResult
|
||||
if result, err = i.client.Invoke(selector, args, id, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objs := result.Obj.([]interface{})
|
||||
|
||||
for _, v := range objs {
|
||||
m := v.(map[string]interface{})
|
||||
|
||||
var data []byte
|
||||
if data, err = json.Marshal(m); err != nil {
|
||||
debugLog(fmt.Sprintf("application marshal: %v\n%v\n", err, m))
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
|
||||
var app Application
|
||||
if err = json.Unmarshal(data, &app); err != nil {
|
||||
debugLog(fmt.Sprintf("application unmarshal: %v\n%v\n", err, m))
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
apps = append(apps, app)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (i *instruments) DeviceInfo() (devInfo *DeviceInfo, err error) {
|
||||
var id uint32
|
||||
if id, err = i.requestChannel(instrumentsServiceDeviceInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
selector := "systemInformation"
|
||||
|
||||
var result *libimobiledevice.DTXMessageResult
|
||||
if result, err = i.client.Invoke(selector, libimobiledevice.NewAuxBuffer(), id, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := json.Marshal(result.Obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
devInfo = new(DeviceInfo)
|
||||
err = json.Unmarshal(data, devInfo)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (i *instruments) registerCallback(obj string, cb func(m libimobiledevice.DTXMessageResult)) {
|
||||
i.client.RegisterCallback(obj, cb)
|
||||
}
|
||||
|
||||
type Application struct {
|
||||
AppExtensionUUIDs []string `json:"AppExtensionUUIDs,omitempty"`
|
||||
BundlePath string `json:"BundlePath"`
|
||||
CFBundleIdentifier string `json:"CFBundleIdentifier"`
|
||||
ContainerBundleIdentifier string `json:"ContainerBundleIdentifier,omitempty"`
|
||||
ContainerBundlePath string `json:"ContainerBundlePath,omitempty"`
|
||||
DisplayName string `json:"DisplayName"`
|
||||
ExecutableName string `json:"ExecutableName,omitempty"`
|
||||
Placeholder bool `json:"Placeholder,omitempty"`
|
||||
PluginIdentifier string `json:"PluginIdentifier,omitempty"`
|
||||
PluginUUID string `json:"PluginUUID,omitempty"`
|
||||
Restricted int `json:"Restricted"`
|
||||
Type string `json:"Type"`
|
||||
Version string `json:"Version"`
|
||||
}
|
||||
|
||||
type DeviceInfo struct {
|
||||
Description string `json:"_deviceDescription"`
|
||||
DisplayName string `json:"_deviceDisplayName"`
|
||||
Identifier string `json:"_deviceIdentifier"`
|
||||
Version string `json:"_deviceVersion"`
|
||||
ProductType string `json:"_productType"`
|
||||
ProductVersion string `json:"_productVersion"`
|
||||
XRDeviceClassName string `json:"_xrdeviceClassName"`
|
||||
}
|
||||
99
hrp/pkg/gidevice/instruments_test.go
Normal file
99
hrp/pkg/gidevice/instruments_test.go
Normal file
@@ -0,0 +1,99 @@
|
||||
//go:build localtest
|
||||
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
instrumentsSrv Instruments
|
||||
bundleID = "com.apple.Preferences"
|
||||
)
|
||||
|
||||
func setupInstrumentsSrv(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
var err error
|
||||
if lockdownSrv, err = dev.lockdownService(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if instrumentsSrv, err = lockdownSrv.InstrumentsService(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_instruments_AppLaunch(t *testing.T) {
|
||||
setupInstrumentsSrv(t)
|
||||
|
||||
// bundleID = "com.leixipaopao.WebDriverAgentRunner.xctrunner"
|
||||
|
||||
// pid, err := dev.AppLaunch(bundleID)
|
||||
pid, err := instrumentsSrv.AppLaunch(bundleID)
|
||||
// pid, err := instrumentsSrv.AppLaunch(bundleID, WithKillExisting(true))
|
||||
// pid, err := instrumentsSrv.AppLaunch(bundleID, WithKillExisting(true), WithArguments([]interface{}{"-AppleLanguages", "(Russian)"}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(pid)
|
||||
}
|
||||
|
||||
func Test_instruments_AppKill(t *testing.T) {
|
||||
setupInstrumentsSrv(t)
|
||||
|
||||
pid, err := instrumentsSrv.AppLaunch(bundleID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(pid)
|
||||
|
||||
// if err = dev.AppKill(pid); err != nil {
|
||||
if err = instrumentsSrv.AppKill(pid); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_instruments_AppRunningProcesses(t *testing.T) {
|
||||
setupInstrumentsSrv(t)
|
||||
|
||||
// processes, err := dev.AppRunningProcesses()
|
||||
processes, err := instrumentsSrv.AppRunningProcesses()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, p := range processes {
|
||||
t.Log(p.IsApplication, "\t", p.Pid, "\t", p.Name, "\t", p.RealAppName, "\t", p.StartDate)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_instruments_AppList(t *testing.T) {
|
||||
setupInstrumentsSrv(t)
|
||||
|
||||
// apps, err := dev.AppList()
|
||||
apps, err := instrumentsSrv.AppList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, app := range apps {
|
||||
t.Logf("%v\t%v\t%v\t%v\t%v\n", app.Type, app.DisplayName, app.ExecutableName, app.AppExtensionUUIDs, app.BundlePath)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_instruments_DeviceInfo(t *testing.T) {
|
||||
setupInstrumentsSrv(t)
|
||||
|
||||
devInfo, err := instrumentsSrv.DeviceInfo()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(devInfo.Description)
|
||||
t.Log(devInfo.DisplayName)
|
||||
t.Log(devInfo.Identifier)
|
||||
t.Log(devInfo.Version)
|
||||
t.Log(devInfo.ProductType)
|
||||
t.Log(devInfo.ProductVersion)
|
||||
t.Log(devInfo.XRDeviceClassName)
|
||||
}
|
||||
684
hrp/pkg/gidevice/lockdown.go
Normal file
684
hrp/pkg/gidevice/lockdown.go
Normal file
@@ -0,0 +1,684 @@
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
uuid "github.com/satori/go.uuid"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/libimobiledevice"
|
||||
)
|
||||
|
||||
var _ Lockdown = (*lockdown)(nil)
|
||||
|
||||
func newLockdown(dev *device) *lockdown {
|
||||
return &lockdown{
|
||||
umClient: dev.umClient,
|
||||
client: dev.lockdownClient,
|
||||
dev: dev,
|
||||
}
|
||||
}
|
||||
|
||||
type lockdown struct {
|
||||
umClient *libimobiledevice.UsbmuxClient
|
||||
client *libimobiledevice.LockdownClient
|
||||
sessionID string
|
||||
|
||||
dev *device
|
||||
iOSVersion []int
|
||||
pairRecord *PairRecord
|
||||
}
|
||||
|
||||
func (c *lockdown) QueryType() (LockdownType, error) {
|
||||
pkt, err := c.client.NewXmlPacket(
|
||||
c.client.NewBasicRequest(libimobiledevice.RequestTypeQueryType),
|
||||
)
|
||||
if err != nil {
|
||||
return LockdownType{}, err
|
||||
}
|
||||
|
||||
if err = c.client.SendPacket(pkt); err != nil {
|
||||
return LockdownType{}, err
|
||||
}
|
||||
|
||||
var respPkt libimobiledevice.Packet
|
||||
if respPkt, err = c.client.ReceivePacket(); err != nil {
|
||||
return LockdownType{}, err
|
||||
}
|
||||
|
||||
var reply libimobiledevice.LockdownTypeResponse
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return LockdownType{}, err
|
||||
}
|
||||
|
||||
return LockdownType{Type: reply.Type}, nil
|
||||
}
|
||||
|
||||
func (c *lockdown) GetValue(domain, key string) (v interface{}, err error) {
|
||||
pkt, err := c.client.NewXmlPacket(
|
||||
c.client.NewGetValueRequest(domain, key),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = c.client.SendPacket(pkt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var respPkt libimobiledevice.Packet
|
||||
if respPkt, err = c.client.ReceivePacket(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var reply libimobiledevice.LockdownValueResponse
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v = reply.Value
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *lockdown) SetValue(domain, key string, value interface{}) (err error) {
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = c.client.NewXmlPacket(
|
||||
c.client.NewSetValueRequest(domain, key, value),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = c.client.SendPacket(pkt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var respPkt libimobiledevice.Packet
|
||||
if respPkt, err = c.client.ReceivePacket(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var reply libimobiledevice.LockdownValueResponse
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !reply.Value.(bool) {
|
||||
return errors.New("lockdown SetValue: Failed")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *lockdown) EnterRecovery() (err error) {
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = c.client.NewXmlPacket(
|
||||
c.client.NewEnterRecoveryRequest(),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = c.client.SendPacket(pkt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = c.client.ReceivePacket(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *lockdown) handshake() (err error) {
|
||||
var lockdownType LockdownType
|
||||
if lockdownType, err = c.QueryType(); err != nil {
|
||||
return err
|
||||
}
|
||||
if lockdownType.Type != "com.apple.mobile.lockdown" {
|
||||
return fmt.Errorf("lockdown handshake 'QueryType': %s", lockdownType.Type)
|
||||
}
|
||||
|
||||
// if (device->version < DEVICE_VERSION(7,0,0))
|
||||
// for older devices, we need to validate pairing to receive trusted host status
|
||||
|
||||
if c.pairRecord, err = c.dev.ReadPairRecord(); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), libimobiledevice.ReplyCodeBadDevice.String()) {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.pairRecord, err = c.Pair(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.dev.SavePairRecord(c.pairRecord)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *lockdown) Pair() (pairRecord *PairRecord, err error) {
|
||||
var buid string
|
||||
if buid, err = newUsbmux(c.umClient).ReadBUID(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var devPublicKeyPem []byte
|
||||
var devWiFiAddr string
|
||||
|
||||
if lockdownValue, err := c.GetValue("", "DevicePublicKey"); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
devPublicKeyPem = lockdownValue.([]byte)
|
||||
}
|
||||
if lockdownValue, err := c.GetValue("", "WiFiAddress"); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
devWiFiAddr = lockdownValue.(string)
|
||||
}
|
||||
|
||||
if pairRecord, err = generatePairRecord(devPublicKeyPem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pairRecord.SystemBUID = buid
|
||||
pairRecord.HostID = strings.ToUpper(uuid.NewV4().String())
|
||||
hostPrivateKey := pairRecord.HostPrivateKey
|
||||
pairRecord.HostPrivateKey = nil
|
||||
rootPrivateKey := pairRecord.RootPrivateKey
|
||||
pairRecord.RootPrivateKey = nil
|
||||
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = c.client.NewXmlPacket(
|
||||
c.client.NewPairRequest(pairRecord),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = c.client.SendPacket(pkt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var respPkt libimobiledevice.Packet
|
||||
if respPkt, err = c.client.ReceivePacket(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var reply libimobiledevice.LockdownPairResponse
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pairRecord.EscrowBag = reply.EscrowBag
|
||||
pairRecord.WiFiMACAddress = devWiFiAddr
|
||||
pairRecord.HostPrivateKey = hostPrivateKey
|
||||
pairRecord.RootPrivateKey = rootPrivateKey
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *lockdown) startSession(pairRecord *PairRecord) (err error) {
|
||||
// if we have a running session, stop current one first
|
||||
if c.sessionID != "" {
|
||||
if err = c.stopSession(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = c.client.NewXmlPacket(
|
||||
c.client.NewStartSessionRequest(pairRecord.SystemBUID, pairRecord.HostID),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = c.client.SendPacket(pkt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var respPkt libimobiledevice.Packet
|
||||
if respPkt, err = c.client.ReceivePacket(); err != nil {
|
||||
return fmt.Errorf("lockdown start session: %w", err)
|
||||
}
|
||||
|
||||
var reply libimobiledevice.LockdownStartSessionResponse
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if reply.EnableSessionSSL {
|
||||
if err = c.client.EnableSSL(c.iOSVersion, pairRecord); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c.sessionID = reply.SessionID
|
||||
return
|
||||
}
|
||||
|
||||
func (c *lockdown) stopSession() (err error) {
|
||||
if c.sessionID == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = c.client.NewXmlPacket(
|
||||
c.client.NewStopSessionRequest(c.sessionID),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = c.client.SendPacket(pkt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var respPkt libimobiledevice.Packet
|
||||
if respPkt, err = c.client.ReceivePacket(); err != nil {
|
||||
return fmt.Errorf("lockdown stop session: %w", err)
|
||||
}
|
||||
|
||||
var reply libimobiledevice.LockdownBasicResponse
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.sessionID = ""
|
||||
return
|
||||
}
|
||||
|
||||
func (c *lockdown) startService(service string, escrowBag []byte) (dynamicPort int, enableSSL bool, err error) {
|
||||
req := c.client.NewStartServiceRequest(service)
|
||||
if escrowBag != nil {
|
||||
req.EscrowBag = escrowBag
|
||||
}
|
||||
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = c.client.NewXmlPacket(req); err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
|
||||
if err = c.client.SendPacket(pkt); err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
|
||||
respPkt, err := c.client.ReceivePacket()
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
|
||||
var reply libimobiledevice.LockdownStartServiceResponse
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
|
||||
if reply.Error != "" {
|
||||
return 0, false, fmt.Errorf("lockdown start service: %s", reply.Error)
|
||||
}
|
||||
|
||||
dynamicPort = reply.Port
|
||||
enableSSL = reply.EnableServiceSSL
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *lockdown) ImageMounterService() (imageMounter ImageMounter, err error) {
|
||||
var innerConn InnerConn
|
||||
if innerConn, err = c._startService(libimobiledevice.ImageMounterServiceName, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imageMounterClient := libimobiledevice.NewImageMounterClient(innerConn)
|
||||
imageMounter = newImageMounter(imageMounterClient)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *lockdown) ScreenshotService() (screenshot Screenshot, err error) {
|
||||
var innerConn InnerConn
|
||||
if innerConn, err = c._startService(libimobiledevice.ScreenshotServiceName, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
screenshotClient := libimobiledevice.NewScreenshotClient(innerConn)
|
||||
screenshot = newScreenshot(screenshotClient)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *lockdown) SimulateLocationService() (simulateLocation SimulateLocation, err error) {
|
||||
var innerConn InnerConn
|
||||
if innerConn, err = c._startService(libimobiledevice.SimulateLocationServiceName, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
simulateLocationClient := libimobiledevice.NewSimulateLocationClient(innerConn)
|
||||
simulateLocation = newSimulateLocation(simulateLocationClient)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *lockdown) InstallationProxyService() (installationProxy InstallationProxy, err error) {
|
||||
var innerConn InnerConn
|
||||
if innerConn, err = c._startService(libimobiledevice.InstallationProxyServiceName, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
installationProxyClient := libimobiledevice.NewInstallationProxyClient(innerConn)
|
||||
installationProxy = newInstallationProxy(installationProxyClient)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *lockdown) InstrumentsService() (instruments Instruments, err error) {
|
||||
service := libimobiledevice.InstrumentsServiceName
|
||||
if DeviceVersion(c.iOSVersion...) >= DeviceVersion(14, 0, 0) {
|
||||
service = libimobiledevice.InstrumentsSecureProxyServiceName
|
||||
}
|
||||
|
||||
var innerConn InnerConn
|
||||
if innerConn, err = c._startService(service, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instrumentsClient := libimobiledevice.NewInstrumentsClient(innerConn)
|
||||
instruments = newInstruments(instrumentsClient)
|
||||
|
||||
if service == libimobiledevice.InstrumentsServiceName {
|
||||
_ = innerConn.DismissSSL()
|
||||
}
|
||||
|
||||
if err = instruments.notifyOfPublishedCapabilities(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *lockdown) TestmanagerdService() (testmanagerd Testmanagerd, err error) {
|
||||
service := libimobiledevice.TestmanagerdServiceName
|
||||
if DeviceVersion(c.iOSVersion...) >= DeviceVersion(14, 0, 0) {
|
||||
service = libimobiledevice.TestmanagerdSecureServiceName
|
||||
}
|
||||
|
||||
var innerConn InnerConn
|
||||
if innerConn, err = c._startService(service, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
testmanagerdClient := libimobiledevice.NewTestmanagerdClient(innerConn)
|
||||
testmanagerd = newTestmanagerd(testmanagerdClient, c.iOSVersion)
|
||||
|
||||
if service == libimobiledevice.TestmanagerdServiceName {
|
||||
_ = innerConn.DismissSSL()
|
||||
}
|
||||
|
||||
if err = testmanagerd.notifyOfPublishedCapabilities(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *lockdown) AfcService() (afc Afc, err error) {
|
||||
var innerConn InnerConn
|
||||
if innerConn, err = c._startService(libimobiledevice.AfcServiceName, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
afcClient := libimobiledevice.NewAfcClient(innerConn)
|
||||
afc = newAfc(afcClient)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *lockdown) HouseArrestService() (houseArrest HouseArrest, err error) {
|
||||
var innerConn InnerConn
|
||||
if innerConn, err = c._startService(libimobiledevice.HouseArrestServiceName, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
houseArrestClient := libimobiledevice.NewHouseArrestClient(innerConn)
|
||||
houseArrest = newHouseArrest(houseArrestClient)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *lockdown) SyslogRelayService() (syslogRelay SyslogRelay, err error) {
|
||||
var innerConn InnerConn
|
||||
if innerConn, err = c._startService(libimobiledevice.SyslogRelayServiceName, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
syslogRelayClient := libimobiledevice.NewSyslogRelayClient(innerConn)
|
||||
syslogRelay = newSyslogRelay(syslogRelayClient)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *lockdown) PcapdService() (pcapd Pcapd, err error) {
|
||||
var innerConn InnerConn
|
||||
if innerConn, err = c._startService(libimobiledevice.PcapdServiceName, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pcapdClient := libimobiledevice.NewPcapdClient(innerConn)
|
||||
return newPcapdClient(pcapdClient), nil
|
||||
}
|
||||
|
||||
func (c *lockdown) DiagnosticsRelayService() (diagnostics DiagnosticsRelay, err error) {
|
||||
var innerConn InnerConn
|
||||
if innerConn, err = c._startService(libimobiledevice.DiagnosticsRelayServiceName, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
diagnosticsRelayClient := libimobiledevice.NewDiagnosticsRelayClient(innerConn)
|
||||
diagnostics = newDiagnosticsRelay(diagnosticsRelayClient)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *lockdown) SpringBoardService() (springboard SpringBoard, err error) {
|
||||
var innerConn InnerConn
|
||||
if innerConn, err = c._startService(libimobiledevice.SpringBoardServiceName, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
springBoardServiceClient := libimobiledevice.NewSpringBoardClient(innerConn)
|
||||
springboard = newSpringBoard(springBoardServiceClient)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *lockdown) CrashReportMoverService() (crashReportMover CrashReportMover, err error) {
|
||||
var innerConn InnerConn
|
||||
if innerConn, err = c._startService(libimobiledevice.CrashReportMoverServiceName, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mover := newCrashReportMover(libimobiledevice.NewCrashReportMoverClient(innerConn))
|
||||
if err = mover.readPing(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if innerConn, err = c._startService(libimobiledevice.CrashReportCopyMobileServiceName, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mover.afc = newAfc(libimobiledevice.NewAfcClient(innerConn))
|
||||
|
||||
crashReportMover = mover
|
||||
return
|
||||
}
|
||||
|
||||
func (c *lockdown) _startService(serviceName string, escrowBag []byte) (innerConn InnerConn, err error) {
|
||||
if err = c.handshake(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = c.startSession(c.pairRecord); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dynamicPort, enableSSL, err := c.startService(serviceName, escrowBag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = c.stopSession(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if innerConn, err = c.dev.NewConnect(dynamicPort, 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// clean deadline
|
||||
innerConn.Timeout(0)
|
||||
|
||||
if enableSSL {
|
||||
if err = innerConn.Handshake(c.iOSVersion, c.pairRecord); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *lockdown) _getProductVersion() (version []int, err error) {
|
||||
if c.iOSVersion != nil {
|
||||
return c.iOSVersion, nil
|
||||
}
|
||||
|
||||
var devProductVersion []string
|
||||
if lockdownValue, err := c.GetValue("", "ProductVersion"); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
devProductVersion = strings.Split(lockdownValue.(string), ".")
|
||||
}
|
||||
|
||||
version = make([]int, len(devProductVersion))
|
||||
for i, v := range devProductVersion {
|
||||
version[i], _ = strconv.Atoi(v)
|
||||
}
|
||||
|
||||
// if len(version) == 2 {
|
||||
// version = append(version, 0)
|
||||
// }
|
||||
c.iOSVersion = version
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func generatePairRecord(devPublicKeyPem []byte) (pairRecord *PairRecord, err error) {
|
||||
block, _ := pem.Decode(devPublicKeyPem)
|
||||
var deviceKey *rsa.PublicKey
|
||||
if deviceKey, err = x509.ParsePKCS1PublicKey(block.Bytes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var rootKey, hostKey *rsa.PrivateKey
|
||||
if rootKey, err = rsa.GenerateKey(rand.Reader, 2048); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hostKey, err = rsa.GenerateKey(rand.Reader, 2048); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serialNumber := big.NewInt(0)
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(time.Hour * (24 * 365) * 10)
|
||||
|
||||
rootTemplate := x509.Certificate{
|
||||
IsCA: true,
|
||||
SerialNumber: serialNumber,
|
||||
Version: 2,
|
||||
SignatureAlgorithm: x509.SHA1WithRSA,
|
||||
PublicKeyAlgorithm: x509.RSA,
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
var caCert, cert []byte
|
||||
if caCert, err = x509.CreateCertificate(rand.Reader, &rootTemplate, &rootTemplate, rootKey.Public(), rootKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostTemplate := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Version: 2,
|
||||
SignatureAlgorithm: x509.SHA1WithRSA,
|
||||
PublicKeyAlgorithm: x509.RSA,
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
if cert, err = x509.CreateCertificate(rand.Reader, &hostTemplate, &rootTemplate, hostKey.Public(), rootKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h := sha1.New()
|
||||
if _, err = h.Write(rootKey.N.Bytes()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subjectKeyId := h.Sum(nil)
|
||||
|
||||
deviceTemplate := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Version: 2,
|
||||
SignatureAlgorithm: x509.SHA1WithRSA,
|
||||
PublicKeyAlgorithm: x509.RSA,
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
BasicConstraintsValid: true,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
SubjectKeyId: subjectKeyId,
|
||||
}
|
||||
|
||||
var deviceCert []byte
|
||||
if deviceCert, err = x509.CreateCertificate(rand.Reader, &deviceTemplate, &rootTemplate, deviceKey, rootKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var deviceCertPEM []byte
|
||||
if deviceCertPEM, err = encodePemCertificate(deviceCert); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var caPEM, caPrivatePEM []byte
|
||||
if caPEM, caPrivatePEM, err = encodePairPemFormat(caCert, rootKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var certPEM, certPrivatePEM []byte
|
||||
if certPEM, certPrivatePEM, err = encodePairPemFormat(cert, hostKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pairRecord = new(PairRecord)
|
||||
|
||||
pairRecord.DeviceCertificate = deviceCertPEM
|
||||
pairRecord.HostCertificate = certPEM
|
||||
pairRecord.HostPrivateKey = certPrivatePEM
|
||||
pairRecord.RootCertificate = caPEM
|
||||
pairRecord.RootPrivateKey = caPrivatePEM
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func encodePairPemFormat(cert []byte, key *rsa.PrivateKey) ([]byte, []byte, error) {
|
||||
p, err := encodePemCertificate(cert)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if err := pem.Encode(buf, &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(key),
|
||||
}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
privy := buf.Bytes()
|
||||
|
||||
return p, privy, nil
|
||||
}
|
||||
|
||||
func encodePemCertificate(cert []byte) ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
if err := pem.Encode(buf, &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: cert,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
108
hrp/pkg/gidevice/lockdown_test.go
Normal file
108
hrp/pkg/gidevice/lockdown_test.go
Normal file
@@ -0,0 +1,108 @@
|
||||
//go:build localtest
|
||||
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var lockdownSrv Lockdown
|
||||
|
||||
func setupLockdownSrv(t *testing.T) {
|
||||
setupDevice(t)
|
||||
|
||||
var err error
|
||||
if lockdownSrv, err = dev.lockdownService(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_lockdown_QueryType(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
lockdownType, err := lockdownSrv.QueryType()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Log(lockdownType.Type)
|
||||
}
|
||||
|
||||
func Test_lockdown_GetValue(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
// v, err := dev.GetValue("com.apple.mobile.iTunes", "")
|
||||
// v, err := dev.GetValue("com.apple.mobile.internal", "")
|
||||
v, err := dev.GetValue("com.apple.mobile.battery", "")
|
||||
// v, err := lockdownSrv.GetValue("", "ProductVersion")
|
||||
// v, err := lockdownSrv.GetValue("", "DeviceName")
|
||||
// v, err := lockdownSrv.GetValue("com.apple.mobile.iTunes", "")
|
||||
// v, err := lockdownSrv.GetValue("com.apple.mobile.battery", "")
|
||||
// v, err := lockdownSrv.GetValue("com.apple.disk_usage", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Log(v)
|
||||
}
|
||||
|
||||
func Test_lockdown_SyslogRelayService(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
syslogRelaySrv, err := lockdownSrv.SyslogRelayService()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
syslogRelaySrv.Stop()
|
||||
|
||||
lines := syslogRelaySrv.Lines()
|
||||
|
||||
done := make(chan os.Signal, 1)
|
||||
|
||||
go func() {
|
||||
for line := range lines {
|
||||
fmt.Println(line)
|
||||
}
|
||||
done <- os.Interrupt
|
||||
fmt.Println("DONE!!!")
|
||||
}()
|
||||
|
||||
signal.Notify(done, os.Interrupt, os.Kill)
|
||||
|
||||
<-done
|
||||
syslogRelaySrv.Stop()
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
func Test_lockdown_CrashReportMoverService(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
crashReportMoverSrv, err := lockdownSrv.CrashReportMoverService()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
filenames := make([]string, 0, 36)
|
||||
fn := func(cwd string, info *AfcFileInfo) {
|
||||
if cwd == "." {
|
||||
cwd = ""
|
||||
}
|
||||
filenames = append(filenames, path.Join(cwd, info.Name()))
|
||||
// fmt.Println(path.Join(cwd, name))
|
||||
}
|
||||
err = crashReportMoverSrv.walkDir(".", fn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, n := range filenames {
|
||||
fmt.Println(n)
|
||||
}
|
||||
|
||||
t.Log(len(filenames))
|
||||
}
|
||||
60
hrp/pkg/gidevice/pcapd.go
Normal file
60
hrp/pkg/gidevice/pcapd.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/libimobiledevice"
|
||||
)
|
||||
|
||||
type pcapdClient struct {
|
||||
stop chan struct{}
|
||||
c *libimobiledevice.PcapdClient
|
||||
}
|
||||
|
||||
func newPcapdClient(c *libimobiledevice.PcapdClient) *pcapdClient {
|
||||
return &pcapdClient{
|
||||
stop: make(chan struct{}),
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *pcapdClient) Packet() <-chan []byte {
|
||||
packetCh := make(chan []byte, 10)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-c.stop:
|
||||
return
|
||||
default:
|
||||
pkt, err := c.c.ReceivePacket()
|
||||
if err != nil {
|
||||
close(packetCh)
|
||||
return
|
||||
}
|
||||
var payload []byte
|
||||
_ = pkt.Unmarshal(&payload)
|
||||
raw, err := c.c.GetPacket(payload)
|
||||
if err != nil {
|
||||
close(packetCh)
|
||||
return
|
||||
}
|
||||
res, err := c.c.CreatePacket(raw)
|
||||
if err != nil {
|
||||
log.Println("failed to create packet")
|
||||
return
|
||||
}
|
||||
packetCh <- res
|
||||
}
|
||||
}
|
||||
}()
|
||||
return packetCh
|
||||
}
|
||||
|
||||
func (c *pcapdClient) Stop() {
|
||||
select {
|
||||
case <-c.stop:
|
||||
default:
|
||||
close(c.stop)
|
||||
}
|
||||
c.c.Close()
|
||||
}
|
||||
889
hrp/pkg/gidevice/perfd.go
Normal file
889
hrp/pkg/gidevice/perfd.go
Normal file
@@ -0,0 +1,889 @@
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/libimobiledevice"
|
||||
)
|
||||
|
||||
type PerfOptions struct {
|
||||
// system
|
||||
SysCPU bool `json:"sys_cpu,omitempty" yaml:"sys_cpu,omitempty"`
|
||||
SysMem bool `json:"sys_mem,omitempty" yaml:"sys_mem,omitempty"`
|
||||
SysDisk bool `json:"sys_disk,omitempty" yaml:"sys_disk,omitempty"`
|
||||
SysNetwork bool `json:"sys_network,omitempty" yaml:"sys_network,omitempty"`
|
||||
gpu bool
|
||||
FPS bool `json:"fps,omitempty" yaml:"fps,omitempty"`
|
||||
Network bool `json:"network,omitempty" yaml:"network,omitempty"`
|
||||
// process
|
||||
BundleID string `json:"bundle_id,omitempty" yaml:"bundle_id,omitempty"`
|
||||
Pid int `json:"pid,omitempty" yaml:"pid,omitempty"`
|
||||
// config
|
||||
OutputInterval int `json:"output_interval,omitempty" yaml:"output_interval,omitempty"` // ms
|
||||
SystemAttributes []string `json:"system_attributes,omitempty" yaml:"system_attributes,omitempty"`
|
||||
ProcessAttributes []string `json:"process_attributes,omitempty" yaml:"process_attributes,omitempty"`
|
||||
}
|
||||
|
||||
func defaulPerfOption() *PerfOptions {
|
||||
return &PerfOptions{
|
||||
SysCPU: true, // default on
|
||||
SysMem: true, // default on
|
||||
SysDisk: false,
|
||||
SysNetwork: false,
|
||||
gpu: false,
|
||||
FPS: false,
|
||||
Network: false,
|
||||
OutputInterval: 1000, // default 1000ms
|
||||
SystemAttributes: []string{
|
||||
// disk
|
||||
"diskBytesRead",
|
||||
"diskBytesWritten",
|
||||
"diskReadOps",
|
||||
"diskWriteOps",
|
||||
// memory
|
||||
"vmCompressorPageCount",
|
||||
"vmExtPageCount",
|
||||
"vmFreeCount",
|
||||
"vmIntPageCount",
|
||||
"vmPurgeableCount",
|
||||
"vmWireCount",
|
||||
"vmUsedCount",
|
||||
"__vmSwapUsage",
|
||||
// network
|
||||
"netBytesIn",
|
||||
"netBytesOut",
|
||||
"netPacketsIn",
|
||||
"netPacketsOut",
|
||||
},
|
||||
ProcessAttributes: []string{
|
||||
"pid",
|
||||
"cpuUsage",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type PerfOption func(*PerfOptions)
|
||||
|
||||
func WithPerfSystemCPU(b bool) PerfOption {
|
||||
return func(opt *PerfOptions) {
|
||||
opt.SysCPU = b
|
||||
}
|
||||
}
|
||||
|
||||
func WithPerfSystemMem(b bool) PerfOption {
|
||||
return func(opt *PerfOptions) {
|
||||
opt.SysMem = b
|
||||
}
|
||||
}
|
||||
|
||||
func WithPerfSystemDisk(b bool) PerfOption {
|
||||
return func(opt *PerfOptions) {
|
||||
opt.SysDisk = b
|
||||
}
|
||||
}
|
||||
|
||||
func WithPerfSystemNetwork(b bool) PerfOption {
|
||||
return func(opt *PerfOptions) {
|
||||
opt.SysNetwork = b
|
||||
}
|
||||
}
|
||||
|
||||
func WithPerfBundleID(bundleID string) PerfOption {
|
||||
return func(opt *PerfOptions) {
|
||||
opt.BundleID = bundleID
|
||||
}
|
||||
}
|
||||
|
||||
func WithPerfPID(pid int) PerfOption {
|
||||
return func(opt *PerfOptions) {
|
||||
opt.Pid = pid
|
||||
}
|
||||
}
|
||||
|
||||
func WithPerfGPU(b bool) PerfOption {
|
||||
return func(opt *PerfOptions) {
|
||||
opt.gpu = b
|
||||
}
|
||||
}
|
||||
|
||||
func WithPerfFPS(b bool) PerfOption {
|
||||
return func(opt *PerfOptions) {
|
||||
opt.FPS = b
|
||||
}
|
||||
}
|
||||
|
||||
func WithPerfNetwork(b bool) PerfOption {
|
||||
return func(opt *PerfOptions) {
|
||||
opt.Network = b
|
||||
}
|
||||
}
|
||||
|
||||
func WithPerfOutputInterval(intervalMilliseconds int) PerfOption {
|
||||
return func(opt *PerfOptions) {
|
||||
opt.OutputInterval = intervalMilliseconds
|
||||
}
|
||||
}
|
||||
|
||||
func WithPerfProcessAttributes(attrs ...string) PerfOption {
|
||||
return func(opt *PerfOptions) {
|
||||
opt.ProcessAttributes = attrs
|
||||
}
|
||||
}
|
||||
|
||||
func WithPerfSystemAttributes(attrs ...string) PerfOption {
|
||||
return func(opt *PerfOptions) {
|
||||
opt.SystemAttributes = attrs
|
||||
}
|
||||
}
|
||||
|
||||
type perfdClient struct {
|
||||
options *PerfOptions
|
||||
i Instruments
|
||||
stop chan struct{} // used to stop perf client
|
||||
cancel context.CancelFunc // used to cancel all iterators
|
||||
}
|
||||
|
||||
func (d *device) newPerfdSysmontap(options *PerfOptions) (*perfdSysmontap, error) {
|
||||
instruments, err := d.newInstrumentsService()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &perfdSysmontap{
|
||||
perfdClient: perfdClient{
|
||||
i: instruments,
|
||||
options: options,
|
||||
stop: make(chan struct{}),
|
||||
},
|
||||
chanSysCPU: make(chan []byte, 10),
|
||||
chanSysMem: make(chan []byte, 10),
|
||||
chanSysDisk: make(chan []byte, 10),
|
||||
chanSysNetwork: make(chan []byte, 10),
|
||||
chanProcess: make(chan []byte, 10),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type perfdSysmontap struct {
|
||||
perfdClient
|
||||
chanSysCPU chan []byte // system cpu channel
|
||||
chanSysMem chan []byte // system mem channel
|
||||
chanSysDisk chan []byte // system disk channel
|
||||
chanSysNetwork chan []byte // system network channel
|
||||
chanProcess chan []byte // process channel
|
||||
}
|
||||
|
||||
func (c *perfdSysmontap) Start() (data <-chan []byte, err error) {
|
||||
// set config
|
||||
config := map[string]interface{}{
|
||||
"bm": 0,
|
||||
"cpuUsage": true,
|
||||
"sampleInterval": time.Second * 1, // 1s
|
||||
"ur": c.options.OutputInterval, // 输出频率
|
||||
"procAttrs": c.options.ProcessAttributes, // process performance
|
||||
"sysAttrs": c.options.SystemAttributes, // system performance
|
||||
}
|
||||
if _, err = c.i.call(
|
||||
instrumentsServiceSysmontap,
|
||||
"setConfig:",
|
||||
config,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// start
|
||||
if _, err = c.i.call(
|
||||
instrumentsServiceSysmontap,
|
||||
"start",
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// register listener
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
c.i.registerCallback("", func(m libimobiledevice.DTXMessageResult) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
c.i.call(instrumentsServiceSysmontap, "stop")
|
||||
return
|
||||
default:
|
||||
dataArray, ok := m.Obj.([]interface{})
|
||||
if !ok || len(dataArray) != 2 {
|
||||
return
|
||||
}
|
||||
|
||||
if c.options.Pid != 0 {
|
||||
c.parseProcessData(dataArray)
|
||||
} else {
|
||||
c.parseSystemData(dataArray)
|
||||
}
|
||||
}
|
||||
})
|
||||
c.cancel = cancel
|
||||
|
||||
outCh := make(chan []byte, 100)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-c.stop:
|
||||
c.cancel()
|
||||
return
|
||||
case cpuBytes, ok := <-c.chanSysCPU:
|
||||
if ok {
|
||||
outCh <- cpuBytes
|
||||
}
|
||||
case memBytes, ok := <-c.chanSysMem:
|
||||
if ok {
|
||||
outCh <- memBytes
|
||||
}
|
||||
case diskBytes, ok := <-c.chanSysDisk:
|
||||
if ok {
|
||||
outCh <- diskBytes
|
||||
}
|
||||
case networkBytes, ok := <-c.chanSysNetwork:
|
||||
if ok {
|
||||
outCh <- networkBytes
|
||||
}
|
||||
case processBytes, ok := <-c.chanProcess:
|
||||
if ok {
|
||||
outCh <- processBytes
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return outCh, nil
|
||||
}
|
||||
|
||||
func (c *perfdSysmontap) Stop() {
|
||||
close(c.stop)
|
||||
}
|
||||
|
||||
func (c *perfdSysmontap) parseProcessData(dataArray []interface{}) {
|
||||
// dataArray example:
|
||||
// [
|
||||
// map[
|
||||
// CPUCount:2
|
||||
// EnabledCPUs:2
|
||||
// PerCPUUsage:[
|
||||
// map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:3.6363636363636402 CPU_UserLoad:-1]
|
||||
// map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:2.7272727272727195 CPU_UserLoad:-1]
|
||||
// ]
|
||||
// System:[36408520704 6897049600 3031160 773697 15596 61940 1297 26942 588 17020 127346 1835008 119718056 107009899 174046 103548]
|
||||
// SystemCPUUsage:map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:6.36363636363636 CPU_UserLoad:-1]
|
||||
// StartMachAbsTime:5896602132889
|
||||
// EndMachAbsTime:5896628486761
|
||||
// Type:41
|
||||
// ]
|
||||
// map[
|
||||
// Processes:map[
|
||||
// 0:[1.3582834340402803 0]
|
||||
// 124:[0.011456702068519481 124]
|
||||
// 136:[0.05468332721703649 136]
|
||||
// ]
|
||||
// StartMachAbsTime:5896602295095
|
||||
// EndMachAbsTime:5896628780514
|
||||
// Type:5
|
||||
// ]
|
||||
// ]
|
||||
|
||||
processData := make(map[string]interface{})
|
||||
processData["type"] = "process"
|
||||
processData["timestamp"] = time.Now().Unix()
|
||||
processData["pid"] = c.options.Pid
|
||||
|
||||
defer func() {
|
||||
processBytes, _ := json.Marshal(processData)
|
||||
c.chanProcess <- processBytes
|
||||
}()
|
||||
|
||||
systemInfo := dataArray[0].(map[string]interface{})
|
||||
processInfo := dataArray[1].(map[string]interface{})
|
||||
if _, ok := systemInfo["System"]; !ok {
|
||||
systemInfo, processInfo = processInfo, systemInfo
|
||||
}
|
||||
|
||||
var targetProcessValue []interface{}
|
||||
processList := processInfo["Processes"].(map[string]interface{})
|
||||
for pid, v := range processList {
|
||||
if pid != strconv.Itoa(c.options.Pid) {
|
||||
continue
|
||||
}
|
||||
targetProcessValue = v.([]interface{})
|
||||
}
|
||||
|
||||
if targetProcessValue == nil {
|
||||
processData["msg"] = fmt.Sprintf("process %d not found", c.options.Pid)
|
||||
return
|
||||
}
|
||||
|
||||
processAttributesMap := make(map[string]interface{})
|
||||
for idx, value := range c.options.ProcessAttributes {
|
||||
processAttributesMap[value] = targetProcessValue[idx]
|
||||
}
|
||||
processData["proc_perf"] = processAttributesMap
|
||||
|
||||
systemAttributesValue := systemInfo["System"].([]interface{})
|
||||
systemAttributesMap := make(map[string]int64)
|
||||
for idx, value := range c.options.SystemAttributes {
|
||||
systemAttributesMap[value] = convert2Int64(systemAttributesValue[idx])
|
||||
}
|
||||
processData["sys_perf"] = systemAttributesMap
|
||||
}
|
||||
|
||||
func (c *perfdSysmontap) parseSystemData(dataArray []interface{}) {
|
||||
timestamp := time.Now().Unix()
|
||||
var systemInfo map[string]interface{}
|
||||
data1 := dataArray[0].(map[string]interface{})
|
||||
data2 := dataArray[1].(map[string]interface{})
|
||||
if _, ok := data1["SystemCPUUsage"]; ok {
|
||||
systemInfo = data1
|
||||
} else {
|
||||
systemInfo = data2
|
||||
}
|
||||
|
||||
// systemInfo example:
|
||||
// map[
|
||||
// CPUCount:2
|
||||
// EnabledCPUs:2
|
||||
// PerCPUUsage:[
|
||||
// map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:3.9215686274509807 CPU_UserLoad:-1]
|
||||
// map[CPU_NiceLoad:0 CPU_SystemLoad:-1 CPU_TotalLoad:11.650485436893206 CPU_UserLoad:-1]]
|
||||
// ]
|
||||
// System:[704211 35486281728 6303789056 3001119 1001 11033 52668 1740 40022 2114 17310 126903 1835008 160323 107909856 95067 95808179]
|
||||
// SystemCPUUsage:map[
|
||||
// CPU_NiceLoad:0
|
||||
// CPU_SystemLoad:-1
|
||||
// CPU_TotalLoad:15.572054064344186
|
||||
// CPU_UserLoad:-1
|
||||
// ]
|
||||
// StartMachAbsTime:5339240248449
|
||||
// EndMachAbsTime:5339264441260
|
||||
// Type:41
|
||||
// ]
|
||||
|
||||
if c.options.SysCPU {
|
||||
sysCPUUsage := systemInfo["SystemCPUUsage"].(map[string]interface{})
|
||||
sysCPUInfo := SystemCPUData{
|
||||
PerfDataBase: PerfDataBase{
|
||||
Type: "sys_cpu",
|
||||
TimeStamp: timestamp,
|
||||
},
|
||||
NiceLoad: sysCPUUsage["CPU_NiceLoad"].(float64),
|
||||
SystemLoad: sysCPUUsage["CPU_SystemLoad"].(float64),
|
||||
TotalLoad: sysCPUUsage["CPU_TotalLoad"].(float64),
|
||||
UserLoad: sysCPUUsage["CPU_UserLoad"].(float64),
|
||||
}
|
||||
cpuBytes, _ := json.Marshal(sysCPUInfo)
|
||||
c.chanSysCPU <- cpuBytes
|
||||
}
|
||||
|
||||
systemAttributesValue := systemInfo["System"].([]interface{})
|
||||
systemAttributesMap := make(map[string]int64)
|
||||
for idx, value := range c.options.SystemAttributes {
|
||||
systemAttributesMap[value] = convert2Int64(systemAttributesValue[idx])
|
||||
}
|
||||
|
||||
if c.options.SysMem {
|
||||
kernelPageSize := int64(1) // why 16384 ?
|
||||
appMemory := (systemAttributesMap["vmIntPageCount"] - systemAttributesMap["vmPurgeableCount"]) * kernelPageSize
|
||||
cachedFiles := (systemAttributesMap["vmExtPageCount"] - systemAttributesMap["vmPurgeableCount"]) * kernelPageSize
|
||||
compressed := systemAttributesMap["vmCompressorPageCount"] * kernelPageSize
|
||||
usedMemory := (systemAttributesMap["vmUsedCount"] - systemAttributesMap["vmExtPageCount"]) * kernelPageSize
|
||||
wiredMemory := systemAttributesMap["vmWireCount"] * kernelPageSize
|
||||
swapUsed := systemAttributesMap["__vmSwapUsage"]
|
||||
freeMemory := systemAttributesMap["vmFreeCount"] * kernelPageSize
|
||||
|
||||
sysMemInfo := SystemMemData{
|
||||
PerfDataBase: PerfDataBase{
|
||||
Type: "sys_mem",
|
||||
TimeStamp: timestamp,
|
||||
},
|
||||
AppMemory: appMemory,
|
||||
UsedMemory: usedMemory,
|
||||
WiredMemory: wiredMemory,
|
||||
FreeMemory: freeMemory,
|
||||
CachedFiles: cachedFiles,
|
||||
Compressed: compressed,
|
||||
SwapUsed: swapUsed,
|
||||
}
|
||||
memBytes, _ := json.Marshal(sysMemInfo)
|
||||
c.chanSysMem <- memBytes
|
||||
}
|
||||
|
||||
if c.options.SysDisk {
|
||||
diskBytesRead := systemAttributesMap["diskBytesRead"]
|
||||
diskBytesWritten := systemAttributesMap["diskBytesWritten"]
|
||||
diskReadOps := systemAttributesMap["diskReadOps"]
|
||||
diskWriteOps := systemAttributesMap["diskWriteOps"]
|
||||
|
||||
sysDiskInfo := SystemDiskData{
|
||||
PerfDataBase: PerfDataBase{
|
||||
Type: "sys_disk",
|
||||
TimeStamp: timestamp,
|
||||
},
|
||||
DataRead: diskBytesRead,
|
||||
DataWritten: diskBytesWritten,
|
||||
ReadOps: diskReadOps,
|
||||
WriteOps: diskWriteOps,
|
||||
}
|
||||
diskBytes, _ := json.Marshal(sysDiskInfo)
|
||||
c.chanSysDisk <- diskBytes
|
||||
}
|
||||
|
||||
if c.options.SysNetwork {
|
||||
netBytesIn := systemAttributesMap["netBytesIn"]
|
||||
netBytesOut := systemAttributesMap["netBytesOut"]
|
||||
netPacketsIn := systemAttributesMap["netPacketsIn"]
|
||||
netPacketsOut := systemAttributesMap["netPacketsOut"]
|
||||
|
||||
sysNetworkInfo := SystemNetworkData{
|
||||
PerfDataBase: PerfDataBase{
|
||||
Type: "sys_network",
|
||||
TimeStamp: timestamp,
|
||||
},
|
||||
BytesIn: netBytesIn,
|
||||
BytesOut: netBytesOut,
|
||||
PacketsIn: netPacketsIn,
|
||||
PacketsOut: netPacketsOut,
|
||||
}
|
||||
networkBytes, _ := json.Marshal(sysNetworkInfo)
|
||||
c.chanSysNetwork <- networkBytes
|
||||
}
|
||||
}
|
||||
|
||||
type SystemCPUData struct {
|
||||
PerfDataBase // system cpu
|
||||
NiceLoad float64 `json:"nice_load"`
|
||||
SystemLoad float64 `json:"system_load"`
|
||||
TotalLoad float64 `json:"total_load"`
|
||||
UserLoad float64 `json:"user_load"`
|
||||
}
|
||||
|
||||
type SystemMemData struct {
|
||||
PerfDataBase // mem
|
||||
AppMemory int64 `json:"app_memory"`
|
||||
FreeMemory int64 `json:"free_memory"`
|
||||
UsedMemory int64 `json:"used_memory"`
|
||||
WiredMemory int64 `json:"wired_memory"`
|
||||
CachedFiles int64 `json:"cached_files"`
|
||||
Compressed int64 `json:"compressed"`
|
||||
SwapUsed int64 `json:"swap_used"`
|
||||
}
|
||||
|
||||
type SystemDiskData struct {
|
||||
PerfDataBase // disk
|
||||
DataRead int64 `json:"data_read"`
|
||||
DataWritten int64 `json:"data_written"`
|
||||
ReadOps int64 `json:"reads_in"`
|
||||
WriteOps int64 `json:"writes_out"`
|
||||
}
|
||||
|
||||
type SystemNetworkData struct {
|
||||
PerfDataBase // network
|
||||
BytesIn int64 `json:"bytes_in"`
|
||||
BytesOut int64 `json:"bytes_out"`
|
||||
PacketsIn int64 `json:"packets_in"`
|
||||
PacketsOut int64 `json:"packets_out"`
|
||||
}
|
||||
|
||||
func (d *device) newPerfdNetworking(options *PerfOptions) (*perfdNetworking, error) {
|
||||
instruments, err := d.newInstrumentsService()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &perfdNetworking{
|
||||
perfdClient: perfdClient{
|
||||
i: instruments,
|
||||
options: options,
|
||||
stop: make(chan struct{}),
|
||||
},
|
||||
chanNetwork: make(chan []byte, 10),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type perfdNetworking struct {
|
||||
perfdClient
|
||||
chanNetwork chan []byte // network channel
|
||||
}
|
||||
|
||||
func (c *perfdNetworking) Start() (data <-chan []byte, err error) {
|
||||
if _, err = c.i.call(
|
||||
instrumentsServiceNetworking,
|
||||
"replayLastRecordedSession",
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err = c.i.call(
|
||||
instrumentsServiceNetworking,
|
||||
"startMonitoring",
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
c.i.registerCallback("", func(m libimobiledevice.DTXMessageResult) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
c.i.call(instrumentsServiceNetworking, "stopMonitoring")
|
||||
return
|
||||
default:
|
||||
c.parseNetworking(m.Obj)
|
||||
}
|
||||
})
|
||||
c.cancel = cancel
|
||||
|
||||
outCh := make(chan []byte, 100)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-c.stop:
|
||||
c.cancel()
|
||||
return
|
||||
case networkBytes, ok := <-c.chanNetwork:
|
||||
if ok {
|
||||
outCh <- networkBytes
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return outCh, nil
|
||||
}
|
||||
|
||||
func (c *perfdNetworking) Stop() {
|
||||
close(c.stop)
|
||||
}
|
||||
|
||||
func (c *perfdNetworking) parseNetworking(data interface{}) {
|
||||
raw, ok := data.([]interface{})
|
||||
if !ok || len(raw) != 2 {
|
||||
fmt.Printf("invalid networking data: %v\n", data)
|
||||
return
|
||||
}
|
||||
|
||||
var netBytes []byte
|
||||
msgType := raw[0].(uint64)
|
||||
msgValue := raw[1].([]interface{})
|
||||
if msgType == 0 {
|
||||
// interface-detection
|
||||
// ['InterfaceIndex', "Name"]
|
||||
// e.g. [0, [14, 'en0']]
|
||||
netData := NetworkDataInterfaceDetection{
|
||||
PerfDataBase: PerfDataBase{
|
||||
Type: "network-interface-detection",
|
||||
TimeStamp: time.Now().Unix(),
|
||||
},
|
||||
InterfaceIndex: convert2Int64(msgValue[0]),
|
||||
Name: msgValue[1].(string),
|
||||
}
|
||||
netBytes, _ = json.Marshal(netData)
|
||||
} else if msgType == 1 {
|
||||
// connection-detected
|
||||
// ['LocalAddress', 'RemoteAddress', 'InterfaceIndex', 'Pid',
|
||||
// 'RecvBufferSize', 'RecvBufferUsed', 'SerialNumber', 'Kind']
|
||||
// e.g. [1 [[16 2 211 158 192 168 100 101 0 0 0 0 0 0 0 0]
|
||||
// [16 2 0 53 183 221 253 100 0 0 0 0 0 0 0 0]
|
||||
// 14 -2 786896 0 133 2]]
|
||||
|
||||
localAddr, err := parseSocketAddr(msgValue[0].([]byte))
|
||||
if err != nil {
|
||||
fmt.Printf("parse local socket address err: %v\n", err)
|
||||
}
|
||||
remoteAddr, err := parseSocketAddr(msgValue[1].([]byte))
|
||||
if err != nil {
|
||||
fmt.Printf("parse remote socket address err: %v\n", err)
|
||||
}
|
||||
netData := NetworkDataConnectionDetected{
|
||||
PerfDataBase: PerfDataBase{
|
||||
Type: "network-connection-detected",
|
||||
TimeStamp: time.Now().Unix(),
|
||||
},
|
||||
LocalAddress: localAddr,
|
||||
RemoteAddress: remoteAddr,
|
||||
InterfaceIndex: convert2Int64(msgValue[2]),
|
||||
Pid: convert2Int64(msgValue[3]),
|
||||
RecvBufferSize: convert2Int64(msgValue[4]),
|
||||
RecvBufferUsed: convert2Int64(msgValue[5]),
|
||||
SerialNumber: convert2Int64(msgValue[6]),
|
||||
Kind: convert2Int64(msgValue[7]),
|
||||
}
|
||||
netBytes, _ = json.Marshal(netData)
|
||||
} else if msgType == 2 {
|
||||
// connection-update
|
||||
// ['RxPackets', 'RxBytes', 'TxPackets', 'TxBytes',
|
||||
// 'RxDups', 'RxOOO', 'TxRetx', 'MinRTT', 'AvgRTT', 'ConnectionSerial']
|
||||
// e.g. [2, [21, 1708, 22, 14119, 309, 0, 5830, 0.076125, 0.076125, 54, -1]]
|
||||
netData := NetworkDataConnectionUpdate{
|
||||
PerfDataBase: PerfDataBase{
|
||||
Type: "network-connection-update",
|
||||
TimeStamp: time.Now().Unix(),
|
||||
},
|
||||
RxBytes: convert2Int64(msgValue[0]),
|
||||
RxPackets: convert2Int64(msgValue[1]),
|
||||
TxBytes: convert2Int64(msgValue[2]),
|
||||
TxPackets: convert2Int64(msgValue[3]),
|
||||
}
|
||||
if value, ok := msgValue[4].(uint64); ok {
|
||||
netData.RxDups = int64(value)
|
||||
}
|
||||
if value, ok := msgValue[5].(uint64); ok {
|
||||
netData.RxOOO = int64(value)
|
||||
}
|
||||
if value, ok := msgValue[6].(uint64); ok {
|
||||
netData.TxRetx = int64(value)
|
||||
}
|
||||
if value, ok := msgValue[7].(uint64); ok {
|
||||
netData.MinRTT = int64(value)
|
||||
}
|
||||
if value, ok := msgValue[8].(uint64); ok {
|
||||
netData.AvgRTT = int64(value)
|
||||
}
|
||||
if value, ok := msgValue[9].(uint64); ok {
|
||||
netData.ConnectionSerial = int64(value)
|
||||
}
|
||||
|
||||
netBytes, _ = json.Marshal(netData)
|
||||
}
|
||||
c.chanNetwork <- netBytes
|
||||
}
|
||||
|
||||
func parseSocketAddr(data []byte) (string, error) {
|
||||
len := data[0] // length of address
|
||||
_ = data[1] // family
|
||||
port := binary.BigEndian.Uint16(data[2:4]) // port
|
||||
|
||||
// network, data[4:4+len]
|
||||
if len == 0x10 {
|
||||
// IPv4, 4 bytes
|
||||
ip := net.IP(data[4:8])
|
||||
return fmt.Sprintf("%s:%d", ip, port), nil
|
||||
} else if len == 0x1c {
|
||||
// IPv6, 16 bytes
|
||||
ip := net.IP(data[4:20])
|
||||
return fmt.Sprintf("%s:%d", ip, port), nil
|
||||
}
|
||||
return "", fmt.Errorf("invalid socket address: %v", data)
|
||||
}
|
||||
|
||||
type PerfDataBase struct {
|
||||
Type string `json:"type"`
|
||||
TimeStamp int64 `json:"timestamp"`
|
||||
Msg string `json:"msg,omitempty"` // message for invalid data
|
||||
}
|
||||
|
||||
// network-interface-detection
|
||||
type NetworkDataInterfaceDetection struct {
|
||||
PerfDataBase
|
||||
InterfaceIndex int64 `json:"interface_index"` // 0
|
||||
Name string `json:"name"` // 1
|
||||
}
|
||||
|
||||
// network-connection-detected
|
||||
type NetworkDataConnectionDetected struct {
|
||||
PerfDataBase
|
||||
LocalAddress string `json:"local_address"` // 0
|
||||
RemoteAddress string `json:"remote_address"` // 1
|
||||
InterfaceIndex int64 `json:"interface_index"` // 2
|
||||
Pid int64 `json:"pid"` // 3
|
||||
RecvBufferSize int64 `json:"recv_buffer_size"` // 4
|
||||
RecvBufferUsed int64 `json:"recv_buffer_used"` // 5
|
||||
SerialNumber int64 `json:"serial_number"` // 6
|
||||
Kind int64 `json:"kind"` // 7
|
||||
}
|
||||
|
||||
// network-connection-update
|
||||
type NetworkDataConnectionUpdate struct {
|
||||
PerfDataBase
|
||||
RxBytes int64 `json:"rx_bytes"` // 0
|
||||
RxPackets int64 `json:"rx_packets"` // 1
|
||||
TxBytes int64 `json:"tx_bytes"` // 2
|
||||
TxPackets int64 `json:"tx_packets"` // 3
|
||||
RxDups int64 `json:"rx_dups,omitempty"` // 4
|
||||
RxOOO int64 `json:"rx_000,omitempty"` // 5
|
||||
TxRetx int64 `json:"tx_retx,omitempty"` // 6
|
||||
MinRTT int64 `json:"min_rtt,omitempty"` // 7
|
||||
AvgRTT int64 `json:"avg_rtt,omitempty"` // 8
|
||||
ConnectionSerial int64 `json:"connection_serial"` // 9
|
||||
}
|
||||
|
||||
func (d *device) newPerfdGraphicsOpengl(options *PerfOptions) (*perfdGraphicsOpengl, error) {
|
||||
instruments, err := d.newInstrumentsService()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &perfdGraphicsOpengl{
|
||||
perfdClient: perfdClient{
|
||||
i: instruments,
|
||||
options: options,
|
||||
stop: make(chan struct{}),
|
||||
},
|
||||
chanGPU: make(chan []byte, 10),
|
||||
chanFPS: make(chan []byte, 10),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type perfdGraphicsOpengl struct {
|
||||
perfdClient
|
||||
chanGPU chan []byte // gpu channel
|
||||
chanFPS chan []byte // fps channel
|
||||
}
|
||||
|
||||
func (c *perfdGraphicsOpengl) Start() (data <-chan []byte, err error) {
|
||||
if _, err = c.i.call(
|
||||
instrumentsServiceGraphicsOpengl,
|
||||
"setSamplingRate:",
|
||||
float64(c.options.OutputInterval)/100,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err = c.i.call(
|
||||
instrumentsServiceGraphicsOpengl,
|
||||
"startSamplingAtTimeInterval:",
|
||||
0,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
c.i.registerCallback("", func(m libimobiledevice.DTXMessageResult) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
c.i.call(instrumentsServiceGraphicsOpengl, "stopSampling")
|
||||
return
|
||||
default:
|
||||
c.parseData(m.Obj)
|
||||
}
|
||||
})
|
||||
c.cancel = cancel
|
||||
|
||||
outCh := make(chan []byte, 100)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-c.stop:
|
||||
c.cancel()
|
||||
return
|
||||
case gpuBytes, ok := <-c.chanGPU:
|
||||
if ok {
|
||||
outCh <- gpuBytes
|
||||
}
|
||||
case fpsBytes, ok := <-c.chanFPS:
|
||||
if ok {
|
||||
outCh <- fpsBytes
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return outCh, nil
|
||||
}
|
||||
|
||||
func (c *perfdGraphicsOpengl) Stop() {
|
||||
close(c.stop)
|
||||
}
|
||||
|
||||
func (c *perfdGraphicsOpengl) parseData(data interface{}) {
|
||||
// data example:
|
||||
// map[
|
||||
// Alloc system memory:50167808
|
||||
// Allocated PB Size:1179648
|
||||
// CoreAnimationFramesPerSecond:0 // fps from GPU
|
||||
// Device Utilization %:0 // device
|
||||
// IOGLBundleName:Built-In
|
||||
// In use system memory:10633216
|
||||
// Renderer Utilization %:0 // renderer
|
||||
// SplitSceneCount:0
|
||||
// TiledSceneBytes:0
|
||||
// Tiler Utilization %:0 // tiler
|
||||
// XRVideoCardRunTimeStamp:1010679
|
||||
// recoveryCount:0
|
||||
// ]
|
||||
|
||||
gpuInfo := GPUData{
|
||||
PerfDataBase: PerfDataBase{
|
||||
Type: "gpu",
|
||||
TimeStamp: time.Now().Unix(),
|
||||
},
|
||||
}
|
||||
fpsInfo := FPSData{
|
||||
PerfDataBase: PerfDataBase{
|
||||
Type: "fps",
|
||||
TimeStamp: time.Now().Unix(),
|
||||
},
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if c.options.gpu {
|
||||
gpuBytes, _ := json.Marshal(gpuInfo)
|
||||
c.chanGPU <- gpuBytes
|
||||
}
|
||||
if c.options.FPS {
|
||||
fpsBytes, _ := json.Marshal(fpsInfo)
|
||||
c.chanFPS <- fpsBytes
|
||||
}
|
||||
}()
|
||||
|
||||
raw, ok := data.(map[string]interface{})
|
||||
if !ok {
|
||||
gpuInfo.Msg = fmt.Sprintf("invalid graphics.opengl data: %v", data)
|
||||
return
|
||||
}
|
||||
|
||||
// gpu
|
||||
gpuInfo.DeviceUtilization = convert2Int64(raw["Device Utilization %"])
|
||||
gpuInfo.TilerUtilization = convert2Int64(raw["Tiler Utilization %"])
|
||||
gpuInfo.RendererUtilization = convert2Int64(raw["Renderer Utilization %"])
|
||||
|
||||
// fps
|
||||
fpsInfo.FPS = int(convert2Int64(raw["CoreAnimationFramesPerSecond"]))
|
||||
}
|
||||
|
||||
type GPUData struct {
|
||||
PerfDataBase // gpu
|
||||
TilerUtilization int64 `json:"tiler_utilization"` // 处理顶点的 GPU 时间占比
|
||||
DeviceUtilization int64 `json:"device_utilization"` // 设备利用率
|
||||
RendererUtilization int64 `json:"renderer_utilization"` // 渲染器利用率
|
||||
}
|
||||
|
||||
type FPSData struct {
|
||||
PerfDataBase // fps
|
||||
FPS int `json:"fps"`
|
||||
}
|
||||
|
||||
func convert2Int64(num interface{}) int64 {
|
||||
switch value := num.(type) {
|
||||
case int64:
|
||||
return value
|
||||
case uint64:
|
||||
return int64(value)
|
||||
case uint32:
|
||||
return int64(value)
|
||||
case uint16:
|
||||
return int64(value)
|
||||
case uint8:
|
||||
return int64(value)
|
||||
case uint:
|
||||
return int64(value)
|
||||
}
|
||||
fmt.Printf("convert2Int64 failed: %v, %T\n", num, num)
|
||||
return -1
|
||||
}
|
||||
|
||||
func containString(ss []string, s string) bool {
|
||||
for _, v := range ss {
|
||||
if s == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
180
hrp/pkg/gidevice/perfd_test.go
Normal file
180
hrp/pkg/gidevice/perfd_test.go
Normal file
@@ -0,0 +1,180 @@
|
||||
//go:build localtest
|
||||
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPerfSystemMonitor(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
data, err := dev.PerfStart(
|
||||
WithPerfSystemCPU(true),
|
||||
WithPerfSystemMem(true),
|
||||
WithPerfSystemDisk(true),
|
||||
WithPerfSystemNetwork(true),
|
||||
WithPerfOutputInterval(1000),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
timer := time.NewTimer(time.Duration(time.Second * 10))
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
dev.PerfStop()
|
||||
return
|
||||
case d := <-data:
|
||||
fmt.Println(string(d))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPerfProcessMonitor(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
data, err := dev.PerfStart(
|
||||
WithPerfProcessAttributes("cpuUsage", "memAnon"),
|
||||
WithPerfOutputInterval(1000),
|
||||
WithPerfPID(100),
|
||||
WithPerfBundleID("com.apple.mobilesafari"), // higher priority than pid
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
timer := time.NewTimer(time.Duration(time.Second * 10))
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
dev.PerfStop()
|
||||
return
|
||||
case d := <-data:
|
||||
fmt.Println(string(d))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPerfGPU(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
data, err := dev.PerfStart(
|
||||
WithPerfSystemCPU(false),
|
||||
WithPerfSystemMem(false),
|
||||
WithPerfGPU(true),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
timer := time.NewTimer(time.Duration(time.Second * 10))
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
dev.PerfStop()
|
||||
return
|
||||
case d := <-data:
|
||||
fmt.Println(string(d))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPerfFPS(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
data, err := dev.PerfStart(
|
||||
WithPerfSystemCPU(false),
|
||||
WithPerfSystemMem(false),
|
||||
WithPerfFPS(true),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
timer := time.NewTimer(time.Duration(time.Second * 10))
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
dev.PerfStop()
|
||||
return
|
||||
case d := <-data:
|
||||
fmt.Println(string(d))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPerfNetwork(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
data, err := dev.PerfStart(
|
||||
WithPerfSystemCPU(false),
|
||||
WithPerfSystemMem(false),
|
||||
WithPerfNetwork(true),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
timer := time.NewTimer(time.Duration(time.Second * 10))
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
dev.PerfStop()
|
||||
return
|
||||
case d := <-data:
|
||||
fmt.Println(string(d))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPerfAll(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
data, err := dev.PerfStart(
|
||||
WithPerfSystemCPU(true),
|
||||
WithPerfSystemMem(true),
|
||||
WithPerfSystemDisk(true),
|
||||
WithPerfSystemNetwork(true),
|
||||
WithPerfNetwork(true),
|
||||
WithPerfFPS(true),
|
||||
WithPerfGPU(true),
|
||||
WithPerfBundleID("com.apple.mobilesafari"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
timer := time.NewTimer(time.Duration(time.Second * 10))
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
dev.PerfStop()
|
||||
return
|
||||
case d := <-data:
|
||||
fmt.Println(string(d))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPcap(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
data, err := dev.Pcap()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
timer := time.NewTimer(time.Duration(time.Second * 10))
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
dev.PcapStop()
|
||||
return
|
||||
case d := <-data:
|
||||
fmt.Println(string(d))
|
||||
}
|
||||
}
|
||||
}
|
||||
53
hrp/pkg/gidevice/pkg/ipa/ipa.go
Normal file
53
hrp/pkg/gidevice/pkg/ipa/ipa.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package ipa
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
|
||||
"howett.net/plist"
|
||||
)
|
||||
|
||||
func Info(ipaPath string) (info map[string]interface{}, err error) {
|
||||
var reader *zip.ReadCloser
|
||||
if reader, err = zip.OpenReader(ipaPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = reader.Close()
|
||||
}()
|
||||
|
||||
for _, file := range reader.File {
|
||||
matched, _err := path.Match("Payload/*.app/Info.plist", file.Name)
|
||||
if _err != nil {
|
||||
err = _err
|
||||
continue
|
||||
}
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
|
||||
var rd io.ReadCloser
|
||||
if rd, _err = file.Open(); _err != nil {
|
||||
return nil, _err
|
||||
}
|
||||
data, _err := io.ReadAll(rd)
|
||||
if _err != nil {
|
||||
return nil, _err
|
||||
}
|
||||
|
||||
info = make(map[string]interface{})
|
||||
_, _err = plist.Unmarshal(data, &info)
|
||||
if _err != nil {
|
||||
return nil, _err
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil && len(info) == 0 {
|
||||
return nil, fmt.Errorf("find Info.plist: %w", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
23
hrp/pkg/gidevice/pkg/ipa/ipa_test.go
Normal file
23
hrp/pkg/gidevice/pkg/ipa/ipa_test.go
Normal file
@@ -0,0 +1,23 @@
|
||||
//go:build localtest
|
||||
|
||||
package ipa
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInfo(t *testing.T) {
|
||||
name := "/Users/hero/Documents/Workspace/GitHub/taobao-iphone-device/tests/testdata/WebDriverAgentRunner.ipa"
|
||||
name = "/private/tmp/derivedDataPath/Build/Products/Release-iphoneos/WebDriverAgentRunner-Runner.ipa"
|
||||
|
||||
info, err := Info(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for k, v := range info {
|
||||
t.Logf("%-50s\t%v", k, v)
|
||||
}
|
||||
|
||||
t.Log(info["CFBundleIdentifier"])
|
||||
}
|
||||
105
hrp/pkg/gidevice/pkg/libimobiledevice/afc.go
Normal file
105
hrp/pkg/gidevice/pkg/libimobiledevice/afc.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package libimobiledevice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const AfcServiceName = "com.apple.afc"
|
||||
|
||||
func NewAfcClient(innerConn InnerConn) *AfcClient {
|
||||
return &AfcClient{
|
||||
innerConn: innerConn,
|
||||
}
|
||||
}
|
||||
|
||||
type AfcClient struct {
|
||||
innerConn InnerConn
|
||||
packetNum uint64
|
||||
}
|
||||
|
||||
func (c *AfcClient) newPacket(operation uint64, data, payload []byte) Packet {
|
||||
c.packetNum++
|
||||
pkt := &afcPacket{
|
||||
operation: operation,
|
||||
packetNum: c.packetNum,
|
||||
entireLen: 40,
|
||||
thisLen: 40,
|
||||
}
|
||||
if data != nil {
|
||||
n := uint64(len(data))
|
||||
pkt.entireLen += n
|
||||
pkt.thisLen += n
|
||||
}
|
||||
if payload != nil {
|
||||
pkt.entireLen += uint64(len(payload))
|
||||
}
|
||||
return pkt
|
||||
}
|
||||
|
||||
func (c *AfcClient) Send(operation uint64, data, payload []byte) (err error) {
|
||||
pkt := c.newPacket(operation, data, payload)
|
||||
var raw []byte
|
||||
if raw, err = pkt.Pack(); err != nil {
|
||||
return fmt.Errorf("send packet (afc): %w", err)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
buf.Write(raw)
|
||||
if data != nil {
|
||||
debugLog(fmt.Sprintf("--> %s ...afc data...\n", pkt))
|
||||
buf.Write(data)
|
||||
} else {
|
||||
debugLog(fmt.Sprintf("--> %s\n", pkt))
|
||||
}
|
||||
|
||||
if err = c.innerConn.Write(buf.Bytes()); err != nil {
|
||||
return fmt.Errorf("send packet (afc): %w", err)
|
||||
}
|
||||
|
||||
if payload != nil {
|
||||
if err = c.innerConn.Write(payload); err != nil {
|
||||
return fmt.Errorf("send packet (afc): %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *AfcClient) Receive() (respMsg *AfcMessage, err error) {
|
||||
var bufHeader []byte
|
||||
if bufHeader, err = c.innerConn.Read(40); err != nil {
|
||||
return nil, fmt.Errorf("receive packet (afc): %w", err)
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
buffer.Write(bufHeader)
|
||||
var respPkt *afcPacket
|
||||
if respPkt, err = new(afcPacket).unpack(buffer); err != nil {
|
||||
return nil, fmt.Errorf("receive packet (afc): %w", err)
|
||||
}
|
||||
|
||||
respMsg = new(AfcMessage)
|
||||
respMsg.Operation = respPkt.operation
|
||||
|
||||
buffer.Reset()
|
||||
if respPkt.entireLen > 40 {
|
||||
length := int(respPkt.entireLen - 40)
|
||||
var bufDataAndPayload []byte
|
||||
if bufDataAndPayload, err = c.innerConn.Read(length); err != nil {
|
||||
return nil, fmt.Errorf("receive packet (afc): %w", err)
|
||||
}
|
||||
buffer.Write(bufDataAndPayload)
|
||||
}
|
||||
|
||||
bufData := make([]byte, respPkt.thisLen-40)
|
||||
if _, err = buffer.Read(bufData); err != nil {
|
||||
return nil, fmt.Errorf("receive packet (afc buffer): %w", err)
|
||||
}
|
||||
respMsg.Data = bufData
|
||||
respMsg.Payload = buffer.Bytes()
|
||||
|
||||
debugLog(fmt.Sprintf("<-- %s\n%s\n%s", respPkt, hex.Dump(respMsg.Data), hex.Dump(respMsg.Payload)))
|
||||
|
||||
return
|
||||
}
|
||||
187
hrp/pkg/gidevice/pkg/libimobiledevice/afcmessage.go
Normal file
187
hrp/pkg/gidevice/pkg/libimobiledevice/afcmessage.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package libimobiledevice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type AfcMessage struct {
|
||||
Operation uint64
|
||||
Data []byte
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
func (m *AfcMessage) Map() map[string]string {
|
||||
ret := make(map[string]string)
|
||||
ss := m.Strings()
|
||||
if ss != nil {
|
||||
for i := 0; i < len(ss); i += 2 {
|
||||
ret[ss[i]] = ss[i+1]
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (m *AfcMessage) Strings() []string {
|
||||
if m.Operation == AfcOperationData {
|
||||
bs := bytes.Split(m.Payload, []byte{0})
|
||||
ss := make([]string, len(bs)-1)
|
||||
for i := 0; i < len(ss); i++ {
|
||||
ss[i] = string(bs[i])
|
||||
}
|
||||
return ss
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AfcMessage) Uint64() uint64 {
|
||||
return binary.LittleEndian.Uint64(m.Data)
|
||||
}
|
||||
|
||||
func (m *AfcMessage) Err() error {
|
||||
if m.Operation == AfcOperationStatus {
|
||||
status := m.Uint64()
|
||||
if status != AfcErrSuccess {
|
||||
return toError(status)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func toError(status uint64) error {
|
||||
switch status {
|
||||
case AfcErrUnknownError:
|
||||
return errors.New("UnknownError")
|
||||
case AfcErrOperationHeaderInvalid:
|
||||
return errors.New("OperationHeaderInvalid")
|
||||
case AfcErrNoResources:
|
||||
return errors.New("NoResources")
|
||||
case AfcErrReadError:
|
||||
return errors.New("ReadError")
|
||||
case AfcErrWriteError:
|
||||
return errors.New("WriteError")
|
||||
case AfcErrUnknownPacketType:
|
||||
return errors.New("UnknownPacketType")
|
||||
case AfcErrInvalidArgument:
|
||||
return errors.New("InvalidArgument")
|
||||
case AfcErrObjectNotFound:
|
||||
return errors.New("ObjectNotFound")
|
||||
case AfcErrObjectIsDir:
|
||||
return errors.New("ObjectIsDir")
|
||||
case AfcErrPermDenied:
|
||||
return errors.New("PermDenied")
|
||||
case AfcErrServiceNotConnected:
|
||||
return errors.New("ServiceNotConnected")
|
||||
case AfcErrOperationTimeout:
|
||||
return errors.New("OperationTimeout")
|
||||
case AfcErrTooMuchData:
|
||||
return errors.New("TooMuchData")
|
||||
case AfcErrEndOfData:
|
||||
return errors.New("EndOfData")
|
||||
case AfcErrOperationNotSupported:
|
||||
return errors.New("OperationNotSupported")
|
||||
case AfcErrObjectExists:
|
||||
return errors.New("ObjectExists")
|
||||
case AfcErrObjectBusy:
|
||||
return errors.New("ObjectBusy")
|
||||
case AfcErrNoSpaceLeft:
|
||||
return errors.New("NoSpaceLeft")
|
||||
case AfcErrOperationWouldBlock:
|
||||
return errors.New("OperationWouldBlock")
|
||||
case AfcErrIoError:
|
||||
return errors.New("IoError")
|
||||
case AfcErrOperationInterrupted:
|
||||
return errors.New("OperationInterrupted")
|
||||
case AfcErrOperationInProgress:
|
||||
return errors.New("OperationInProgress")
|
||||
case AfcErrInternalError:
|
||||
return errors.New("InternalError")
|
||||
case AfcErrMuxError:
|
||||
return errors.New("MuxError")
|
||||
case AfcErrNoMemory:
|
||||
return errors.New("NoMemory")
|
||||
case AfcErrNotEnoughData:
|
||||
return errors.New("NotEnoughData")
|
||||
case AfcErrDirNotEmpty:
|
||||
return errors.New("DirNotEmpty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
AfcOperationInvalid = 0x00000000 /* Invalid */
|
||||
AfcOperationStatus = 0x00000001 /* Status */
|
||||
AfcOperationData = 0x00000002 /* Data */
|
||||
AfcOperationReadDir = 0x00000003 /* ReadDir */
|
||||
AfcOperationReadFile = 0x00000004 /* ReadFile */
|
||||
AfcOperationWriteFile = 0x00000005 /* WriteFile */
|
||||
AfcOperationWritePart = 0x00000006 /* WritePart */
|
||||
AfcOperationTruncateFile = 0x00000007 /* TruncateFile */
|
||||
AfcOperationRemovePath = 0x00000008 /* RemovePath */
|
||||
AfcOperationMakeDir = 0x00000009 /* MakeDir */
|
||||
AfcOperationGetFileInfo = 0x0000000A /* GetFileInfo */
|
||||
AfcOperationGetDeviceInfo = 0x0000000B /* GetDeviceInfo */
|
||||
AfcOperationWriteFileAtomic = 0x0000000C /* WriteFileAtomic (tmp file+rename) */
|
||||
AfcOperationFileOpen = 0x0000000D /* FileRefOpen */
|
||||
AfcOperationFileOpenResult = 0x0000000E /* FileRefOpenResult */
|
||||
AfcOperationFileRead = 0x0000000F /* FileRefRead */
|
||||
AfcOperationFileWrite = 0x00000010 /* FileRefWrite */
|
||||
AfcOperationFileSeek = 0x00000011 /* FileRefSeek */
|
||||
AfcOperationFileTell = 0x00000012 /* FileRefTell */
|
||||
AfcOperationFileTellResult = 0x00000013 /* FileRefTellResult */
|
||||
AfcOperationFileClose = 0x00000014 /* FileRefClose */
|
||||
AfcOperationFileSetSize = 0x00000015 /* FileRefSetFileSize (ftruncate) */
|
||||
AfcOperationGetConnectionInfo = 0x00000016 /* GetConnectionInfo */
|
||||
AfcOperationSetConnectionOptions = 0x00000017 /* SetConnectionOptions */
|
||||
AfcOperationRenamePath = 0x00000018 /* RenamePath */
|
||||
AfcOperationSetFSBlockSize = 0x00000019 /* SetFSBlockSize (0x800000) */
|
||||
AfcOperationSetSocketBlockSize = 0x0000001A /* SetSocketBlockSize (0x800000) */
|
||||
AfcOperationFileRefLock = 0x0000001B /* FileRefLock */
|
||||
AfcOperationMakeLink = 0x0000001C /* MakeLink */
|
||||
AfcOperationGetFileHash = 0x0000001D /* GetFileHash */
|
||||
AfcOperationSetFileModTime = 0x0000001E /* SetModTime */
|
||||
AfcOperationGetFileHashRange = 0x0000001F /* GetFileHashWithRange */
|
||||
/* iOS 6+ */
|
||||
AfcOperationFileSetImmutableHint = 0x00000020 /* FileRefSetImmutableHint */
|
||||
AfcOperationGetSizeOfPathContents = 0x00000021 /* GetSizeOfPathContents */
|
||||
AfcOperationRemovePathAndContents = 0x00000022 /* RemovePathAndContents */
|
||||
AfcOperationDirectoryEnumeratorRefOpen = 0x00000023 /* DirectoryEnumeratorRefOpen */
|
||||
AfcOperationDirectoryEnumeratorRefOpenResult = 0x00000024 /* DirectoryEnumeratorRefOpenResult */
|
||||
AfcOperationDirectoryEnumeratorRefRead = 0x00000025 /* DirectoryEnumeratorRefRead */
|
||||
AfcOperationDirectoryEnumeratorRefClose = 0x00000026 /* DirectoryEnumeratorRefClose */
|
||||
/* iOS 7+ */
|
||||
AfcOperationFileRefReadWithOffset = 0x00000027 /* FileRefReadWithOffset */
|
||||
AfcOperationFileRefWriteWithOffset = 0x00000028 /* FileRefWriteWithOffset */
|
||||
)
|
||||
|
||||
const (
|
||||
AfcErrSuccess = 0
|
||||
AfcErrUnknownError = 1
|
||||
AfcErrOperationHeaderInvalid = 2
|
||||
AfcErrNoResources = 3
|
||||
AfcErrReadError = 4
|
||||
AfcErrWriteError = 5
|
||||
AfcErrUnknownPacketType = 6
|
||||
AfcErrInvalidArgument = 7
|
||||
AfcErrObjectNotFound = 8
|
||||
AfcErrObjectIsDir = 9
|
||||
AfcErrPermDenied = 10
|
||||
AfcErrServiceNotConnected = 11
|
||||
AfcErrOperationTimeout = 12
|
||||
AfcErrTooMuchData = 13
|
||||
AfcErrEndOfData = 14
|
||||
AfcErrOperationNotSupported = 15
|
||||
AfcErrObjectExists = 16
|
||||
AfcErrObjectBusy = 17
|
||||
AfcErrNoSpaceLeft = 18
|
||||
AfcErrOperationWouldBlock = 19
|
||||
AfcErrIoError = 20
|
||||
AfcErrOperationInterrupted = 21
|
||||
AfcErrOperationInProgress = 22
|
||||
AfcErrInternalError = 23
|
||||
AfcErrMuxError = 30
|
||||
AfcErrNoMemory = 31
|
||||
AfcErrNotEnoughData = 32
|
||||
AfcErrDirNotEmpty = 33
|
||||
)
|
||||
138
hrp/pkg/gidevice/pkg/libimobiledevice/auxbuffer.go
Normal file
138
hrp/pkg/gidevice/pkg/libimobiledevice/auxbuffer.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package libimobiledevice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/nskeyedarchiver"
|
||||
)
|
||||
|
||||
type AuxBuffer struct {
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
func NewAuxBuffer() *AuxBuffer {
|
||||
return &AuxBuffer{
|
||||
buf: new(bytes.Buffer),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *AuxBuffer) AppendObject(obj interface{}) error {
|
||||
marshal, err := nskeyedarchiver.Marshal(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.AppendUInt32(10)
|
||||
m.AppendUInt32(2)
|
||||
m.AppendUInt32(uint32(len(marshal)))
|
||||
m.buf.Write(marshal)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AuxBuffer) AppendInt64(v int64) {
|
||||
m.AppendUInt32(10)
|
||||
m.AppendUInt32(4)
|
||||
m.AppendUInt64(uint64(v))
|
||||
}
|
||||
|
||||
func (m *AuxBuffer) AppendInt32(v int32) {
|
||||
m.AppendUInt32(10)
|
||||
m.AppendUInt32(3)
|
||||
m.AppendUInt32(uint32(v))
|
||||
}
|
||||
|
||||
func (m *AuxBuffer) AppendUInt32(v uint32) {
|
||||
_ = binary.Write(m.buf, binary.LittleEndian, v)
|
||||
}
|
||||
|
||||
func (m *AuxBuffer) AppendUInt64(v uint64) {
|
||||
_ = binary.Write(m.buf, binary.LittleEndian, v)
|
||||
}
|
||||
|
||||
func (m *AuxBuffer) AppendBytes(b []byte) {
|
||||
m.buf.Write(b)
|
||||
}
|
||||
|
||||
func (m *AuxBuffer) Len() int {
|
||||
return m.buf.Len()
|
||||
}
|
||||
|
||||
func (m *AuxBuffer) Bytes() []byte {
|
||||
dup := m.buf.Bytes()
|
||||
b := make([]byte, 16)
|
||||
binary.LittleEndian.PutUint64(b, 0x01f0)
|
||||
binary.LittleEndian.PutUint64(b[8:], uint64(m.Len()))
|
||||
return append(b, dup...)
|
||||
}
|
||||
|
||||
func UnmarshalAuxBuffer(b []byte) ([]interface{}, error) {
|
||||
reader := bytes.NewReader(b)
|
||||
var magic, pkgLen uint64
|
||||
if err := binary.Read(reader, binary.LittleEndian, &magic); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := binary.Read(reader, binary.LittleEndian, &pkgLen); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if magic != 0x1df0 {
|
||||
// TODO magic
|
||||
// return nil, errors.New("magic not equal 0x1df0")
|
||||
// }
|
||||
|
||||
if pkgLen > uint64(len(b)-16) {
|
||||
return nil, errors.New("package length not enough")
|
||||
}
|
||||
|
||||
var ret []interface{}
|
||||
|
||||
for reader.Len() > 0 {
|
||||
var flag, typ uint32
|
||||
if err := binary.Read(reader, binary.LittleEndian, &flag); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := binary.Read(reader, binary.LittleEndian, &typ); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch typ {
|
||||
case 2:
|
||||
var l uint32
|
||||
if err := binary.Read(reader, binary.LittleEndian, &l); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
plistBuf := make([]byte, l)
|
||||
if _, err := reader.Read(plistBuf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
archiver := NewNSKeyedArchiver()
|
||||
d, err := archiver.Unmarshal(plistBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret = append(ret, d)
|
||||
case 3, 5:
|
||||
var i int32
|
||||
if err := binary.Read(reader, binary.LittleEndian, &i); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret = append(ret, i)
|
||||
case 4, 6:
|
||||
var i int64
|
||||
if err := binary.Read(reader, binary.LittleEndian, &i); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret = append(ret, i)
|
||||
case 10:
|
||||
// TODO Dictionary key
|
||||
// fmt.Println("Dictionary key!")
|
||||
continue
|
||||
default:
|
||||
// fmt.Printf("unknown type %d\n", typ)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
405
hrp/pkg/gidevice/pkg/libimobiledevice/client_dtxmessage.go
Normal file
405
hrp/pkg/gidevice/pkg/libimobiledevice/client_dtxmessage.go
Normal file
@@ -0,0 +1,405 @@
|
||||
package libimobiledevice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/nskeyedarchiver"
|
||||
)
|
||||
|
||||
const (
|
||||
_unregistered = "_Golang-iDevice_Unregistered"
|
||||
_over = "_Golang-iDevice_Over"
|
||||
)
|
||||
|
||||
func newDtxMessageClient(innerConn InnerConn) *dtxMessageClient {
|
||||
c := &dtxMessageClient{
|
||||
innerConn: innerConn,
|
||||
msgID: 0,
|
||||
publishedChannels: make(map[string]int32),
|
||||
openedChannels: make(map[string]uint32),
|
||||
toReply: make(chan *dtxMessageHeaderPacket),
|
||||
|
||||
mu: sync.Mutex{},
|
||||
resultMap: make(map[interface{}]*DTXMessageResult),
|
||||
|
||||
callbackMap: make(map[string]func(m DTXMessageResult)),
|
||||
}
|
||||
c.RegisterCallback(_unregistered, func(m DTXMessageResult) {})
|
||||
c.RegisterCallback(_over, func(m DTXMessageResult) {})
|
||||
c.ctx, c.cancelFunc = context.WithCancel(context.Background())
|
||||
c.startReceive()
|
||||
c.startWaitingForReply()
|
||||
return c
|
||||
}
|
||||
|
||||
type dtxMessageClient struct {
|
||||
innerConn InnerConn
|
||||
msgID uint32
|
||||
|
||||
publishedChannels map[string]int32
|
||||
openedChannels map[string]uint32
|
||||
|
||||
toReply chan *dtxMessageHeaderPacket
|
||||
|
||||
mu sync.Mutex
|
||||
resultMap map[interface{}]*DTXMessageResult
|
||||
|
||||
callbackMap map[string]func(m DTXMessageResult)
|
||||
|
||||
ctx context.Context
|
||||
cancelFunc context.CancelFunc
|
||||
}
|
||||
|
||||
func (c *dtxMessageClient) SendDTXMessage(selector string, aux []byte, channelCode uint32, expectsReply bool) (msgID uint32, err error) {
|
||||
payload := new(dtxMessagePayloadPacket)
|
||||
header := &dtxMessageHeaderPacket{
|
||||
ExpectsReply: 1,
|
||||
}
|
||||
|
||||
flag := 0x1000
|
||||
if !expectsReply {
|
||||
flag = 0
|
||||
header.ExpectsReply = 0
|
||||
}
|
||||
|
||||
var sel []byte
|
||||
if sel, err = nskeyedarchiver.Marshal(selector); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if aux == nil {
|
||||
aux = make([]byte, 0)
|
||||
}
|
||||
|
||||
payload.Flags = uint32(0x2 | flag)
|
||||
payload.AuxiliaryLength = uint32(len(aux))
|
||||
payload.TotalLength = uint64(len(aux)) + uint64(len(sel))
|
||||
|
||||
header.Magic = 0x1F3D5B79
|
||||
header.CB = uint32(unsafe.Sizeof(*header))
|
||||
header.FragmentId = 0
|
||||
header.FragmentCount = 1
|
||||
header.Length = uint32(unsafe.Sizeof(*payload)) + uint32(payload.TotalLength)
|
||||
c.msgID++
|
||||
header.Identifier = c.msgID
|
||||
header.ConversationIndex = 0
|
||||
header.ChannelCode = channelCode
|
||||
|
||||
msgPkt := new(dtxMessagePacket)
|
||||
msgPkt.Header = header
|
||||
msgPkt.Payload = payload
|
||||
msgPkt.Aux = aux
|
||||
msgPkt.Sel = sel
|
||||
|
||||
raw, err := msgPkt.Pack()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
debugLog(fmt.Sprintf("--> %s\n", msgPkt))
|
||||
msgID = header.Identifier
|
||||
err = c.innerConn.Write(raw)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *dtxMessageClient) ReceiveDTXMessage() (result *DTXMessageResult, err error) {
|
||||
bufPayload := new(bytes.Buffer)
|
||||
|
||||
var header *dtxMessageHeaderPacket = nil
|
||||
var needToReply *dtxMessageHeaderPacket = nil
|
||||
|
||||
for {
|
||||
header = new(dtxMessageHeaderPacket)
|
||||
|
||||
lenHeader := int(unsafe.Sizeof(*header))
|
||||
var bufHeader []byte
|
||||
if bufHeader, err = c.innerConn.Read(lenHeader); err != nil {
|
||||
return nil, fmt.Errorf("receive: length of DTXMessageHeader: %w", err)
|
||||
}
|
||||
|
||||
if header, err = header.unpack(bytes.NewBuffer(bufHeader)); err != nil {
|
||||
return nil, fmt.Errorf("receive: DTXMessageHeader unpack: %w", err)
|
||||
}
|
||||
|
||||
if header.ExpectsReply == 1 {
|
||||
needToReply = header
|
||||
}
|
||||
|
||||
if header.Magic != 0x1F3D5B79 {
|
||||
return nil, fmt.Errorf("receive: bad magic %x", header.Magic)
|
||||
}
|
||||
|
||||
if header.ConversationIndex == 1 {
|
||||
if header.Identifier != c.msgID {
|
||||
return nil, fmt.Errorf("receive: except identifier %d new identifier %d", c.msgID, header.Identifier)
|
||||
}
|
||||
} else if header.ConversationIndex == 0 {
|
||||
if header.Identifier > c.msgID {
|
||||
c.msgID = header.Identifier
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("receive: invalid conversationIndex %d", header.ConversationIndex)
|
||||
}
|
||||
|
||||
if header.FragmentId == 0 && header.FragmentCount > 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
var data []byte
|
||||
if data, err = c.innerConn.Read(int(header.Length)); err != nil {
|
||||
return nil, fmt.Errorf("receive: length of DTXMessageHeader: %w", err)
|
||||
}
|
||||
bufPayload.Write(data)
|
||||
|
||||
if header.FragmentId == header.FragmentCount-1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
rawPayload := bufPayload.Bytes()
|
||||
payload := new(dtxMessagePayloadPacket)
|
||||
if payload, err = payload.unpack(bufPayload); err != nil {
|
||||
return nil, fmt.Errorf("receive: unpack DTXMessagePayload: %w", err)
|
||||
}
|
||||
|
||||
compress := (payload.Flags & 0xff000) >> 12
|
||||
if compress != 0 {
|
||||
return nil, fmt.Errorf("receive: message is compressed type %d", compress)
|
||||
}
|
||||
|
||||
payloadSize := uint32(unsafe.Sizeof(*payload))
|
||||
objOffset := uint64(payloadSize + payload.AuxiliaryLength)
|
||||
|
||||
var aux, obj []byte
|
||||
|
||||
// see https://github.com/electricbubble/gidevice/issues/28
|
||||
if r, l := payloadSize+payload.AuxiliaryLength, len(rawPayload); int(r) <= l {
|
||||
aux = rawPayload[payloadSize:r]
|
||||
} else {
|
||||
debugLog(fmt.Sprintf("<-- DTXMessage %s\n%s\n"+
|
||||
"[aux] bounds out of range [:%d] with capacity %d",
|
||||
header.String(), payload.String(),
|
||||
r, l,
|
||||
))
|
||||
}
|
||||
if r, l := objOffset+(payload.TotalLength-uint64(payload.AuxiliaryLength)), len(rawPayload); int(r) <= l {
|
||||
obj = rawPayload[objOffset:r]
|
||||
} else {
|
||||
debugLog(fmt.Sprintf("<-- DTXMessage %s\n%s\n"+
|
||||
"[obj] bounds out of range [:%d] with capacity %d",
|
||||
header.String(), payload.String(),
|
||||
r, l,
|
||||
))
|
||||
}
|
||||
|
||||
debugLog(fmt.Sprintf(
|
||||
"<-- DTXMessage %s\n%s\n"+
|
||||
"%s\n%s\n",
|
||||
header.String(), payload.String(),
|
||||
hex.Dump(aux), hex.Dump(obj),
|
||||
))
|
||||
|
||||
result = new(DTXMessageResult)
|
||||
|
||||
if len(aux) > 0 {
|
||||
if aux, err := UnmarshalAuxBuffer(aux); err != nil {
|
||||
return nil, fmt.Errorf("receive: unpack AUX: %w", err)
|
||||
} else {
|
||||
result.Aux = aux
|
||||
}
|
||||
}
|
||||
|
||||
if len(obj) > 0 {
|
||||
if obj, err := NewNSKeyedArchiver().Unmarshal(obj); err != nil {
|
||||
return nil, fmt.Errorf("receive: unpack NSKeyedArchiver: %w", err)
|
||||
} else {
|
||||
result.Obj = obj
|
||||
}
|
||||
}
|
||||
|
||||
sObj, ok := result.Obj.(string)
|
||||
if fn, do := c.callbackMap[sObj]; do {
|
||||
fn(*result)
|
||||
} else {
|
||||
c.callbackMap[_unregistered](*result)
|
||||
}
|
||||
|
||||
if needToReply != nil {
|
||||
go func() { c.toReply <- needToReply }()
|
||||
} else {
|
||||
var sk interface{} = header.Identifier
|
||||
|
||||
if ok && sObj == "_notifyOfPublishedCapabilities:" {
|
||||
sk = "_notifyOfPublishedCapabilities:"
|
||||
}
|
||||
c.mu.Lock()
|
||||
c.resultMap[sk] = result
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *dtxMessageClient) Connection() (publishedChannels map[string]int32, err error) {
|
||||
args := NewAuxBuffer()
|
||||
if err = args.AppendObject(map[string]interface{}{
|
||||
"com.apple.private.DTXBlockCompression": uint64(2),
|
||||
"com.apple.private.DTXConnection": uint64(1),
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("connection DTXMessage: %w", err)
|
||||
}
|
||||
|
||||
selector := "_notifyOfPublishedCapabilities:"
|
||||
if _, err = c.SendDTXMessage(selector, args.Bytes(), 0, false); err != nil {
|
||||
return nil, fmt.Errorf("connection send: %w", err)
|
||||
}
|
||||
|
||||
var result *DTXMessageResult
|
||||
if result, err = c.GetResult(selector); err != nil {
|
||||
return nil, fmt.Errorf("connection receive: %w", err)
|
||||
}
|
||||
|
||||
if result.Obj.(string) != "_notifyOfPublishedCapabilities:" {
|
||||
return nil, fmt.Errorf("connection: response mismatch: %s", result.Obj)
|
||||
}
|
||||
|
||||
aux := result.Aux[0].(map[string]interface{})
|
||||
for k, v := range aux {
|
||||
c.publishedChannels[k] = int32(v.(uint64))
|
||||
}
|
||||
|
||||
return c.publishedChannels, nil
|
||||
}
|
||||
|
||||
func (c *dtxMessageClient) MakeChannel(channel string) (id uint32, err error) {
|
||||
var ok bool
|
||||
if id, ok = c.openedChannels[channel]; ok {
|
||||
return id, nil
|
||||
}
|
||||
|
||||
id = uint32(len(c.openedChannels) + 1)
|
||||
args := NewAuxBuffer()
|
||||
args.AppendInt32(int32(id))
|
||||
if err = args.AppendObject(channel); err != nil {
|
||||
return 0, fmt.Errorf("make channel DTXMessage: %w", err)
|
||||
}
|
||||
|
||||
selector := "_requestChannelWithCode:identifier:"
|
||||
|
||||
var msgID uint32
|
||||
if msgID, err = c.SendDTXMessage(selector, args.Bytes(), 0, true); err != nil {
|
||||
return 0, fmt.Errorf("make channel send: %w", err)
|
||||
}
|
||||
|
||||
if _, err = c.GetResult(msgID); err != nil {
|
||||
return 0, fmt.Errorf("make channel receive: %w", err)
|
||||
}
|
||||
|
||||
c.openedChannels[channel] = id
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *dtxMessageClient) RegisterCallback(obj string, cb func(m DTXMessageResult)) {
|
||||
c.callbackMap[obj] = cb
|
||||
}
|
||||
|
||||
func (c *dtxMessageClient) GetResult(key interface{}) (*DTXMessageResult, error) {
|
||||
startTime := time.Now()
|
||||
for {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
c.mu.Lock()
|
||||
if v, ok := c.resultMap[key]; ok {
|
||||
delete(c.resultMap, key)
|
||||
c.mu.Unlock()
|
||||
return v, nil
|
||||
} else {
|
||||
c.mu.Unlock()
|
||||
}
|
||||
if elapsed := time.Since(startTime); elapsed > 30*time.Second {
|
||||
return nil, fmt.Errorf("dtx: get result: timeout after %v", elapsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *dtxMessageClient) Close() {
|
||||
c.cancelFunc()
|
||||
c.innerConn.Close()
|
||||
}
|
||||
|
||||
func (c *dtxMessageClient) startReceive() {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-c.ctx.Done():
|
||||
return
|
||||
default:
|
||||
if _, err := c.ReceiveDTXMessage(); err != nil {
|
||||
debugLog(fmt.Sprintf("dtx: receive: %s", err))
|
||||
if strings.Contains(err.Error(), io.EOF.Error()) {
|
||||
c.cancelFunc()
|
||||
c.callbackMap[_over](DTXMessageResult{})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (c *dtxMessageClient) startWaitingForReply() {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-c.ctx.Done():
|
||||
return
|
||||
case reqHeader := <-c.toReply:
|
||||
replyPayload := new(dtxMessagePayloadPacket)
|
||||
replyPayload.Flags = 0
|
||||
replyPayload.AuxiliaryLength = 0
|
||||
replyPayload.TotalLength = 0
|
||||
|
||||
replyHeader := new(dtxMessageHeaderPacket)
|
||||
replyHeader.Magic = 0x1F3D5B79
|
||||
replyHeader.CB = uint32(unsafe.Sizeof(*replyHeader))
|
||||
replyHeader.FragmentId = 0
|
||||
replyHeader.FragmentCount = 1
|
||||
replyHeader.Length = uint32(unsafe.Sizeof(*replyPayload)) + uint32(replyPayload.TotalLength)
|
||||
replyHeader.Identifier = reqHeader.Identifier
|
||||
replyHeader.ConversationIndex = reqHeader.ConversationIndex + 1
|
||||
replyHeader.ChannelCode = reqHeader.ChannelCode
|
||||
replyHeader.ExpectsReply = 0
|
||||
|
||||
replyPkt := new(dtxMessagePacket)
|
||||
replyPkt.Header = replyHeader
|
||||
replyPkt.Payload = replyPayload
|
||||
replyPkt.Aux = nil
|
||||
replyPkt.Sel = nil
|
||||
|
||||
raw, err := replyPkt.Pack()
|
||||
if err != nil {
|
||||
debugLog(fmt.Sprintf("pack: reply DTXMessage: %s", err))
|
||||
continue
|
||||
}
|
||||
|
||||
if err = c.innerConn.Write(raw); err != nil {
|
||||
debugLog(fmt.Sprintf("send: reply DTXMessage: %s", err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
type DTXMessageResult struct {
|
||||
Obj interface{}
|
||||
Aux []interface{}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package libimobiledevice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"howett.net/plist"
|
||||
)
|
||||
|
||||
func newServicePacketClient(innerConn InnerConn) *servicePacketClient {
|
||||
return &servicePacketClient{
|
||||
innerConn: innerConn,
|
||||
}
|
||||
}
|
||||
|
||||
type servicePacketClient struct {
|
||||
innerConn InnerConn
|
||||
}
|
||||
|
||||
func (c *servicePacketClient) NewXmlPacket(req interface{}) (Packet, error) {
|
||||
return c.newPacket(req, plist.XMLFormat)
|
||||
}
|
||||
|
||||
func (c *servicePacketClient) NewBinaryPacket(req interface{}) (Packet, error) {
|
||||
return c.newPacket(req, plist.BinaryFormat)
|
||||
}
|
||||
|
||||
func (c *servicePacketClient) newPacket(req interface{}, format int) (Packet, error) {
|
||||
pkt := new(servicePacket)
|
||||
if buf, err := plist.Marshal(req, format); err != nil {
|
||||
return nil, fmt.Errorf("plist packet marshal: %w", err)
|
||||
} else {
|
||||
pkt.body = buf
|
||||
}
|
||||
pkt.length = uint32(len(pkt.body))
|
||||
return pkt, nil
|
||||
}
|
||||
|
||||
func (c *servicePacketClient) SendPacket(pkt Packet) (err error) {
|
||||
var raw []byte
|
||||
if raw, err = pkt.Pack(); err != nil {
|
||||
return fmt.Errorf("send packet: %w", err)
|
||||
}
|
||||
debugLog(fmt.Sprintf("--> %s\n", pkt))
|
||||
return c.innerConn.Write(raw)
|
||||
}
|
||||
|
||||
func (c *servicePacketClient) ReceivePacket() (respPkt Packet, err error) {
|
||||
var bufLen []byte
|
||||
if bufLen, err = c.innerConn.Read(4); err != nil {
|
||||
return nil, fmt.Errorf("receive packet: %w", err)
|
||||
}
|
||||
lenPkg := binary.BigEndian.Uint32(bufLen)
|
||||
|
||||
buffer := bytes.NewBuffer([]byte{})
|
||||
buffer.Write(bufLen)
|
||||
|
||||
var buf []byte
|
||||
if buf, err = c.innerConn.Read(int(lenPkg)); err != nil {
|
||||
return nil, fmt.Errorf("receive packet: %w", err)
|
||||
}
|
||||
buffer.Write(buf)
|
||||
|
||||
if respPkt, err = new(servicePacket).Unpack(buffer); err != nil {
|
||||
return nil, fmt.Errorf("receive packet: %w", err)
|
||||
}
|
||||
|
||||
debugLog(fmt.Sprintf("<-- %s\n", respPkt))
|
||||
|
||||
var reply LockdownBasicResponse
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return nil, fmt.Errorf("receive packet: %w", err)
|
||||
}
|
||||
|
||||
if reply.Error != "" {
|
||||
return nil, fmt.Errorf("receive packet: %s", reply.Error)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
20
hrp/pkg/gidevice/pkg/libimobiledevice/crashreportmover.go
Normal file
20
hrp/pkg/gidevice/pkg/libimobiledevice/crashreportmover.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package libimobiledevice
|
||||
|
||||
const (
|
||||
CrashReportMoverServiceName = "com.apple.crashreportmover"
|
||||
CrashReportCopyMobileServiceName = "com.apple.crashreportcopymobile"
|
||||
)
|
||||
|
||||
func NewCrashReportMoverClient(innerConn InnerConn) *CrashReportMoverClient {
|
||||
return &CrashReportMoverClient{
|
||||
newServicePacketClient(innerConn),
|
||||
}
|
||||
}
|
||||
|
||||
type CrashReportMoverClient struct {
|
||||
client *servicePacketClient
|
||||
}
|
||||
|
||||
func (c *CrashReportMoverClient) InnerConn() InnerConn {
|
||||
return c.client.innerConn
|
||||
}
|
||||
39
hrp/pkg/gidevice/pkg/libimobiledevice/diagnosticsrelay.go
Normal file
39
hrp/pkg/gidevice/pkg/libimobiledevice/diagnosticsrelay.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package libimobiledevice
|
||||
|
||||
const (
|
||||
DiagnosticsRelayServiceName = "com.apple.mobile.diagnostics_relay"
|
||||
)
|
||||
|
||||
type DiagnosticsRelayBasicRequest struct {
|
||||
Request string `plist:"Request"`
|
||||
Label string `plist:"Label"`
|
||||
}
|
||||
|
||||
func NewDiagnosticsRelayClient(innerConn InnerConn) *DiagnosticsRelayClient {
|
||||
return &DiagnosticsRelayClient{
|
||||
newServicePacketClient(innerConn),
|
||||
}
|
||||
}
|
||||
|
||||
type DiagnosticsRelayClient struct {
|
||||
client *servicePacketClient
|
||||
}
|
||||
|
||||
func (c *DiagnosticsRelayClient) InnerConn() InnerConn {
|
||||
return c.client.innerConn
|
||||
}
|
||||
|
||||
func (c *DiagnosticsRelayClient) NewBasicRequest(relayType string) *DiagnosticsRelayBasicRequest {
|
||||
return &DiagnosticsRelayBasicRequest{
|
||||
Request: relayType,
|
||||
Label: BundleID,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DiagnosticsRelayClient) NewXmlPacket(req interface{}) (Packet, error) {
|
||||
return c.client.NewXmlPacket(req)
|
||||
}
|
||||
|
||||
func (c *DiagnosticsRelayClient) SendPacket(pkt Packet) (err error) {
|
||||
return c.client.SendPacket(pkt)
|
||||
}
|
||||
56
hrp/pkg/gidevice/pkg/libimobiledevice/housearrest.go
Normal file
56
hrp/pkg/gidevice/pkg/libimobiledevice/housearrest.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package libimobiledevice
|
||||
|
||||
const HouseArrestServiceName = "com.apple.mobile.house_arrest"
|
||||
|
||||
const (
|
||||
CommandTypeVendDocuments CommandType = "VendDocuments"
|
||||
CommandTypeVendContainer CommandType = "VendContainer"
|
||||
)
|
||||
|
||||
func NewHouseArrestClient(innerConn InnerConn) *HouseArrestClient {
|
||||
return &HouseArrestClient{
|
||||
newServicePacketClient(innerConn),
|
||||
}
|
||||
}
|
||||
|
||||
type HouseArrestClient struct {
|
||||
client *servicePacketClient
|
||||
}
|
||||
|
||||
func (c *HouseArrestClient) NewBasicRequest(cmdType CommandType, bundleID string) *HouseArrestBasicRequest {
|
||||
return &HouseArrestBasicRequest{
|
||||
Command: cmdType,
|
||||
Identifier: bundleID,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *HouseArrestClient) NewDocumentsRequest(bundleID string) *HouseArrestBasicRequest {
|
||||
return c.NewBasicRequest(CommandTypeVendDocuments, bundleID)
|
||||
}
|
||||
|
||||
func (c *HouseArrestClient) NewContainerRequest(bundleID string) *HouseArrestBasicRequest {
|
||||
return c.NewBasicRequest(CommandTypeVendContainer, bundleID)
|
||||
}
|
||||
|
||||
func (c *HouseArrestClient) NewXmlPacket(req interface{}) (Packet, error) {
|
||||
return c.client.NewXmlPacket(req)
|
||||
}
|
||||
|
||||
func (c *HouseArrestClient) SendPacket(pkt Packet) (err error) {
|
||||
return c.client.SendPacket(pkt)
|
||||
}
|
||||
|
||||
func (c *HouseArrestClient) ReceivePacket() (respPkt Packet, err error) {
|
||||
return c.client.ReceivePacket()
|
||||
}
|
||||
|
||||
func (c *HouseArrestClient) InnerConn() InnerConn {
|
||||
return c.client.innerConn
|
||||
}
|
||||
|
||||
type (
|
||||
HouseArrestBasicRequest struct {
|
||||
Command CommandType `plist:"Command"`
|
||||
Identifier string `plist:"Identifier"`
|
||||
}
|
||||
)
|
||||
107
hrp/pkg/gidevice/pkg/libimobiledevice/imagemounter.go
Normal file
107
hrp/pkg/gidevice/pkg/libimobiledevice/imagemounter.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package libimobiledevice
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const ImageMounterServiceName = "com.apple.mobile.mobile_image_mounter"
|
||||
|
||||
var ErrDeviceLocked = errors.New("device locked")
|
||||
|
||||
type CommandType string
|
||||
|
||||
const (
|
||||
CommandTypeLookupImage CommandType = "LookupImage"
|
||||
CommandTypeReceiveBytes CommandType = "ReceiveBytes"
|
||||
CommandTypeMountImage CommandType = "MountImage"
|
||||
)
|
||||
|
||||
func NewImageMounterClient(innerConn InnerConn) *ImageMounterClient {
|
||||
return &ImageMounterClient{
|
||||
client: newServicePacketClient(innerConn),
|
||||
}
|
||||
}
|
||||
|
||||
type ImageMounterClient struct {
|
||||
client *servicePacketClient
|
||||
}
|
||||
|
||||
func (c *ImageMounterClient) NewBasicRequest(cmdType CommandType, imgType string) *ImageMounterBasicRequest {
|
||||
return &ImageMounterBasicRequest{
|
||||
Command: cmdType,
|
||||
ImageType: imgType,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ImageMounterClient) NewReceiveBytesRequest(imgType string, imgSize uint32, imgSignature []byte) *ImageMounterReceiveBytesRequest {
|
||||
return &ImageMounterReceiveBytesRequest{
|
||||
ImageMounterBasicRequest: *c.NewBasicRequest(CommandTypeReceiveBytes, imgType),
|
||||
ImageSize: imgSize,
|
||||
ImageSignature: imgSignature,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ImageMounterClient) NewMountImageRequest(imgType, imgPath string, imgSignature []byte) *ImageMounterMountImageRequest {
|
||||
return &ImageMounterMountImageRequest{
|
||||
ImageMounterBasicRequest: *c.NewBasicRequest(CommandTypeMountImage, imgType),
|
||||
ImagePath: imgPath,
|
||||
ImageSignature: imgSignature,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ImageMounterClient) NewXmlPacket(req interface{}) (Packet, error) {
|
||||
return c.client.NewXmlPacket(req)
|
||||
}
|
||||
|
||||
func (c *ImageMounterClient) SendPacket(pkt Packet) (err error) {
|
||||
return c.client.SendPacket(pkt)
|
||||
}
|
||||
|
||||
func (c *ImageMounterClient) ReceivePacket() (respPkt Packet, err error) {
|
||||
respPkt, err = c.client.ReceivePacket()
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), io.EOF.Error()) {
|
||||
return nil, ErrDeviceLocked
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *ImageMounterClient) SendDmg(data []byte) (err error) {
|
||||
debugLog(fmt.Sprintf("--> ...DmgData...\n"))
|
||||
return c.client.innerConn.Write(data)
|
||||
}
|
||||
|
||||
type (
|
||||
ImageMounterBasicRequest struct {
|
||||
Command CommandType `plist:"Command"`
|
||||
ImageType string `plist:"ImageType"`
|
||||
}
|
||||
|
||||
ImageMounterReceiveBytesRequest struct {
|
||||
ImageMounterBasicRequest
|
||||
ImageSignature []byte `plist:"ImageSignature"`
|
||||
ImageSize uint32 `plist:"ImageSize"`
|
||||
}
|
||||
|
||||
ImageMounterMountImageRequest struct {
|
||||
ImageMounterBasicRequest
|
||||
ImagePath string `plist:"ImagePath"`
|
||||
ImageSignature []byte `plist:"ImageSignature"`
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
ImageMounterBasicResponse struct {
|
||||
LockdownBasicResponse
|
||||
Status string `plist:"Status"`
|
||||
}
|
||||
|
||||
ImageMounterLookupImageResponse struct {
|
||||
ImageMounterBasicResponse
|
||||
ImageSignature [][]byte `plist:"ImageSignature"`
|
||||
}
|
||||
)
|
||||
119
hrp/pkg/gidevice/pkg/libimobiledevice/installationproxy.go
Normal file
119
hrp/pkg/gidevice/pkg/libimobiledevice/installationproxy.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package libimobiledevice
|
||||
|
||||
const InstallationProxyServiceName = "com.apple.mobile.installation_proxy"
|
||||
|
||||
const (
|
||||
CommandTypeBrowse CommandType = "Browse"
|
||||
CommandTypeLookup CommandType = "Lookup"
|
||||
CommandTypeInstall CommandType = "Install"
|
||||
CommandTypeUninstall CommandType = "Uninstall"
|
||||
)
|
||||
|
||||
type ApplicationType string
|
||||
|
||||
const (
|
||||
ApplicationTypeSystem ApplicationType = "System"
|
||||
ApplicationTypeUser ApplicationType = "User"
|
||||
ApplicationTypeInternal ApplicationType = "internal"
|
||||
ApplicationTypeAny ApplicationType = "Any"
|
||||
)
|
||||
|
||||
func NewInstallationProxyClient(innerConn InnerConn) *InstallationProxyClient {
|
||||
return &InstallationProxyClient{
|
||||
client: newServicePacketClient(innerConn),
|
||||
}
|
||||
}
|
||||
|
||||
type InstallationProxyClient struct {
|
||||
client *servicePacketClient
|
||||
}
|
||||
|
||||
func (c *InstallationProxyClient) NewBasicRequest(cmdType CommandType, opt *InstallationProxyOption) *InstallationProxyBasicRequest {
|
||||
req := &InstallationProxyBasicRequest{Command: cmdType}
|
||||
if opt != nil {
|
||||
req.ClientOptions = opt
|
||||
}
|
||||
return req
|
||||
}
|
||||
|
||||
func (c *InstallationProxyClient) NewInstallRequest(bundleID, packagePath string) *InstallationProxyInstallRequest {
|
||||
opt := &InstallationProxyOption{
|
||||
BundleID: bundleID,
|
||||
}
|
||||
req := &InstallationProxyInstallRequest{
|
||||
Command: CommandTypeInstall,
|
||||
ClientOptions: opt,
|
||||
PackagePath: packagePath,
|
||||
}
|
||||
return req
|
||||
}
|
||||
|
||||
func (c *InstallationProxyClient) NewUninstallRequest(bundleID string) *InstallationProxyUninstallRequest {
|
||||
req := &InstallationProxyUninstallRequest{
|
||||
Command: CommandTypeUninstall,
|
||||
BundleID: bundleID,
|
||||
}
|
||||
return req
|
||||
}
|
||||
|
||||
func (c *InstallationProxyClient) NewXmlPacket(req interface{}) (Packet, error) {
|
||||
return c.client.NewXmlPacket(req)
|
||||
}
|
||||
|
||||
func (c *InstallationProxyClient) SendPacket(pkt Packet) (err error) {
|
||||
return c.client.SendPacket(pkt)
|
||||
}
|
||||
|
||||
func (c *InstallationProxyClient) ReceivePacket() (respPkt Packet, err error) {
|
||||
return c.client.ReceivePacket()
|
||||
}
|
||||
|
||||
type InstallationProxyOption struct {
|
||||
ApplicationType ApplicationType `plist:"ApplicationType,omitempty"`
|
||||
ReturnAttributes []string `plist:"ReturnAttributes,omitempty"`
|
||||
MetaData bool `plist:"com.apple.mobile_installation.metadata,omitempty"`
|
||||
BundleIDs []string `plist:"BundleIDs,omitempty"` // for Lookup
|
||||
BundleID string `plist:"CFBundleIdentifier,omitempty"` // for Install
|
||||
}
|
||||
|
||||
type (
|
||||
InstallationProxyBasicRequest struct {
|
||||
Command CommandType `plist:"Command"`
|
||||
ClientOptions *InstallationProxyOption `plist:"ClientOptions,omitempty"`
|
||||
}
|
||||
|
||||
InstallationProxyInstallRequest struct {
|
||||
Command CommandType `plist:"Command"`
|
||||
ClientOptions *InstallationProxyOption `plist:"ClientOptions"`
|
||||
PackagePath string `plist:"PackagePath"`
|
||||
}
|
||||
|
||||
InstallationProxyUninstallRequest struct {
|
||||
Command CommandType `plist:"Command"`
|
||||
BundleID string `plist:"ApplicationIdentifier"`
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
InstallationProxyBasicResponse struct {
|
||||
Status string `plist:"Status"`
|
||||
}
|
||||
|
||||
InstallationProxyLookupResponse struct {
|
||||
InstallationProxyBasicResponse
|
||||
LookupResult interface{} `plist:"LookupResult"`
|
||||
}
|
||||
|
||||
InstallationProxyBrowseResponse struct {
|
||||
InstallationProxyBasicResponse
|
||||
CurrentAmount int `plist:"CurrentAmount"`
|
||||
CurrentIndex int `plist:"CurrentIndex"`
|
||||
CurrentList []interface{} `plist:"CurrentList"`
|
||||
}
|
||||
|
||||
InstallationProxyInstallResponse struct {
|
||||
InstallationProxyBasicResponse
|
||||
Error string `plist:"Error"`
|
||||
ErrorDescription string `plist:"ErrorDescription"`
|
||||
}
|
||||
)
|
||||
41
hrp/pkg/gidevice/pkg/libimobiledevice/instruments.go
Normal file
41
hrp/pkg/gidevice/pkg/libimobiledevice/instruments.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package libimobiledevice
|
||||
|
||||
const (
|
||||
InstrumentsServiceName = "com.apple.instruments.remoteserver"
|
||||
InstrumentsSecureProxyServiceName = "com.apple.instruments.remoteserver.DVTSecureSocketProxy"
|
||||
)
|
||||
|
||||
func NewInstrumentsClient(innerConn InnerConn) *InstrumentsClient {
|
||||
return &InstrumentsClient{
|
||||
client: newDtxMessageClient(innerConn),
|
||||
}
|
||||
}
|
||||
|
||||
type InstrumentsClient struct {
|
||||
client *dtxMessageClient
|
||||
}
|
||||
|
||||
func (c *InstrumentsClient) NotifyOfPublishedCapabilities() (publishedChannels map[string]int32, err error) {
|
||||
return c.client.Connection()
|
||||
}
|
||||
|
||||
func (c *InstrumentsClient) RequestChannel(channel string) (id uint32, err error) {
|
||||
return c.client.MakeChannel(channel)
|
||||
}
|
||||
|
||||
func (c *InstrumentsClient) Invoke(selector string, args *AuxBuffer, channelCode uint32, expectsReply bool) (result *DTXMessageResult, err error) {
|
||||
var msgID uint32
|
||||
if msgID, err = c.client.SendDTXMessage(selector, args.Bytes(), channelCode, expectsReply); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if expectsReply {
|
||||
if result, err = c.client.GetResult(msgID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *InstrumentsClient) RegisterCallback(obj string, cb func(m DTXMessageResult)) {
|
||||
c.client.RegisterCallback(obj, cb)
|
||||
}
|
||||
260
hrp/pkg/gidevice/pkg/libimobiledevice/keyedarchiver.go
Normal file
260
hrp/pkg/gidevice/pkg/libimobiledevice/keyedarchiver.go
Normal file
@@ -0,0 +1,260 @@
|
||||
package libimobiledevice
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"howett.net/plist"
|
||||
)
|
||||
|
||||
const nsNull = "$null"
|
||||
|
||||
func newKeyedArchiver() *KeyedArchiver {
|
||||
return &KeyedArchiver{
|
||||
Archiver: "NSKeyedArchiver",
|
||||
Version: 100000,
|
||||
}
|
||||
}
|
||||
|
||||
type KeyedArchiver struct {
|
||||
Archiver string `plist:"$archiver"`
|
||||
Objects []interface{} `plist:"$objects"`
|
||||
Top ArchiverRoot `plist:"$top"`
|
||||
Version int `plist:"$version"`
|
||||
}
|
||||
|
||||
func (ka *KeyedArchiver) UID() plist.UID {
|
||||
return plist.UID(len(ka.Objects))
|
||||
}
|
||||
|
||||
type ArchiverRoot struct {
|
||||
Root plist.UID `plist:"root"`
|
||||
}
|
||||
|
||||
type ArchiverClasses struct {
|
||||
Classes []string `plist:"$classes"`
|
||||
ClassName string `plist:"$classname"`
|
||||
}
|
||||
|
||||
var (
|
||||
NSMutableDictionaryClass = &ArchiverClasses{
|
||||
Classes: []string{"NSMutableDictionary", "NSDictionary", "NSObject"},
|
||||
ClassName: "NSMutableDictionary",
|
||||
}
|
||||
NSDictionaryClass = &ArchiverClasses{
|
||||
Classes: []string{"NSDictionary", "NSObject"},
|
||||
ClassName: "NSDictionary",
|
||||
}
|
||||
NSMutableArrayClass = &ArchiverClasses{
|
||||
Classes: []string{"NSMutableArray", "NSArray", "NSObject"},
|
||||
ClassName: "NSMutableArray",
|
||||
}
|
||||
NSArrayClass = &ArchiverClasses{
|
||||
Classes: []string{"NSArray", "NSObject"},
|
||||
ClassName: "NSArray",
|
||||
}
|
||||
NSMutableDataClass = &ArchiverClasses{
|
||||
Classes: []string{"NSMutableArray", "NSArray", "NSObject"},
|
||||
ClassName: "NSMutableArray",
|
||||
}
|
||||
NSDataClass = &ArchiverClasses{
|
||||
Classes: []string{"NSData", "NSObject"},
|
||||
ClassName: "NSData",
|
||||
}
|
||||
NSDateClass = &ArchiverClasses{
|
||||
Classes: []string{"NSDate", "NSObject"},
|
||||
ClassName: "NSDate",
|
||||
}
|
||||
NSErrorClass = &ArchiverClasses{
|
||||
Classes: []string{"NSError", "NSObject"},
|
||||
ClassName: "NSError",
|
||||
}
|
||||
)
|
||||
|
||||
type NSObject struct {
|
||||
Class plist.UID `plist:"$class"`
|
||||
}
|
||||
|
||||
type NSArray struct {
|
||||
NSObject
|
||||
Values []plist.UID `plist:"NS.objects"`
|
||||
}
|
||||
|
||||
type NSDictionary struct {
|
||||
NSArray
|
||||
Keys []plist.UID `plist:"NS.keys"`
|
||||
}
|
||||
|
||||
type NSData struct {
|
||||
NSObject
|
||||
Data []byte `plist:"NS.data"`
|
||||
}
|
||||
|
||||
type NSError struct {
|
||||
NSCode int
|
||||
NSDomain string
|
||||
NSUserInfo interface{}
|
||||
}
|
||||
|
||||
type NSKeyedArchiver struct {
|
||||
objRefVal []interface{}
|
||||
objRef map[interface{}]plist.UID
|
||||
}
|
||||
|
||||
func NewNSKeyedArchiver() *NSKeyedArchiver {
|
||||
return &NSKeyedArchiver{
|
||||
objRef: make(map[interface{}]plist.UID),
|
||||
}
|
||||
}
|
||||
|
||||
func (ka *NSKeyedArchiver) id(v interface{}) plist.UID {
|
||||
var ref plist.UID
|
||||
if id, ok := ka.objRef[v]; !ok {
|
||||
ref = plist.UID(len(ka.objRef))
|
||||
ka.objRefVal = append(ka.objRefVal, v)
|
||||
ka.objRef[v] = ref
|
||||
} else {
|
||||
ref = id
|
||||
}
|
||||
return ref
|
||||
}
|
||||
|
||||
func (ka *NSKeyedArchiver) flushToStruct(root *KeyedArchiver) {
|
||||
for i := 0; i < len(ka.objRefVal); i++ {
|
||||
val := ka.objRefVal[i]
|
||||
vt := reflect.ValueOf(val)
|
||||
if vt.Kind() == reflect.Ptr {
|
||||
val = vt.Elem().Interface()
|
||||
}
|
||||
root.Objects = append(root.Objects, val)
|
||||
}
|
||||
}
|
||||
|
||||
func (ka *NSKeyedArchiver) clear() {
|
||||
ka.objRef = make(map[interface{}]plist.UID)
|
||||
ka.objRefVal = []interface{}{}
|
||||
}
|
||||
|
||||
type XCTestConfiguration struct {
|
||||
Contents map[string]interface{}
|
||||
}
|
||||
|
||||
func (ka *NSKeyedArchiver) Marshal(obj interface{}) ([]byte, error) {
|
||||
val := reflect.ValueOf(obj)
|
||||
typ := val.Type()
|
||||
|
||||
root := newKeyedArchiver()
|
||||
|
||||
var tmpTop plist.UID
|
||||
|
||||
ka.id(nsNull)
|
||||
|
||||
switch typ.Kind() {
|
||||
case reflect.Map:
|
||||
m := &NSDictionary{}
|
||||
m.Class = ka.id(NSDictionaryClass)
|
||||
keys := val.MapKeys()
|
||||
for _, v := range keys {
|
||||
m.Keys = append(m.Keys, ka.id(v.Interface()))
|
||||
m.Values = append(m.Values, ka.id(val.MapIndex(v).Interface()))
|
||||
}
|
||||
tmpTop = ka.id(m)
|
||||
case reflect.Slice, reflect.Array:
|
||||
if typ.Elem().Kind() == reflect.Uint8 {
|
||||
d := &NSData{}
|
||||
d.Class = ka.id(NSDataClass)
|
||||
var w []byte
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
w = append(w, uint8(val.Index(i).Uint()))
|
||||
}
|
||||
d.Data = w
|
||||
}
|
||||
a := &NSArray{}
|
||||
a.Class = ka.id(NSArrayClass)
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
a.Values = append(a.Values, ka.id(val.Index(i).Interface()))
|
||||
}
|
||||
tmpTop = ka.id(a)
|
||||
case reflect.String:
|
||||
tmpTop = ka.id(obj)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
tmpTop = ka.id(obj)
|
||||
}
|
||||
|
||||
root.Top.Root = tmpTop
|
||||
|
||||
ka.flushToStruct(root)
|
||||
|
||||
ka.clear()
|
||||
|
||||
return plist.Marshal(root, plist.BinaryFormat)
|
||||
}
|
||||
|
||||
func (ka *NSKeyedArchiver) convertValue(v interface{}) interface{} {
|
||||
if m, ok := v.(map[string]interface{}); ok {
|
||||
className := ka.objRefVal[m["$class"].(plist.UID)].(map[string]interface{})["$classname"]
|
||||
|
||||
switch className {
|
||||
case NSMutableDictionaryClass.Classes[0], NSDictionaryClass.Classes[0]:
|
||||
ret := make(map[string]interface{})
|
||||
keys := m["NS.keys"].([]interface{})
|
||||
values := m["NS.objects"].([]interface{})
|
||||
|
||||
for i := 0; i < len(keys); i++ {
|
||||
var keyValue string
|
||||
key := ka.objRefVal[keys[i].(plist.UID)]
|
||||
switch key.(type) {
|
||||
case uint64:
|
||||
keyValue = strconv.Itoa(int(key.(uint64)))
|
||||
break
|
||||
default:
|
||||
keyValue = key.(string)
|
||||
}
|
||||
val := ka.convertValue(ka.objRefVal[values[i].(plist.UID)])
|
||||
ret[keyValue] = val
|
||||
}
|
||||
return ret
|
||||
case NSMutableArrayClass.Classes[0], NSArrayClass.Classes[0]:
|
||||
ret := make([]interface{}, 0)
|
||||
values := m["NS.objects"].([]interface{})
|
||||
for i := 0; i < len(values); i++ {
|
||||
ret = append(ret, ka.convertValue(values[i]))
|
||||
}
|
||||
return ret
|
||||
case NSMutableDataClass.Classes[0], NSDataClass.Classes[0]:
|
||||
return m["NS.data"].([]byte)
|
||||
case NSDateClass.Classes[0]:
|
||||
return time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC).
|
||||
Add(time.Duration(m["NS.time"].(float64)) * time.Second)
|
||||
case NSErrorClass.Classes[0]:
|
||||
err := &NSError{}
|
||||
err.NSCode = int(m["NSCode"].(uint64))
|
||||
err.NSDomain = ka.objRefVal[m["NSDomain"].(plist.UID)].(string)
|
||||
err.NSUserInfo = ka.convertValue(ka.objRefVal[m["NSUserInfo"].(plist.UID)])
|
||||
return *err
|
||||
}
|
||||
} else if uid, ok := v.(plist.UID); ok {
|
||||
return ka.convertValue(ka.objRefVal[uid])
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (ka *NSKeyedArchiver) Unmarshal(b []byte) (interface{}, error) {
|
||||
archiver := new(KeyedArchiver)
|
||||
|
||||
if _, err := plist.Unmarshal(b, archiver); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, v := range archiver.Objects {
|
||||
ka.objRefVal = append(ka.objRefVal, v)
|
||||
}
|
||||
|
||||
ret := ka.convertValue(ka.objRefVal[archiver.Top.Root])
|
||||
|
||||
ka.clear()
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
28
hrp/pkg/gidevice/pkg/libimobiledevice/lib.go
Normal file
28
hrp/pkg/gidevice/pkg/libimobiledevice/lib.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package libimobiledevice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
)
|
||||
|
||||
type Packet interface {
|
||||
Pack() ([]byte, error)
|
||||
Unpack(buffer *bytes.Buffer) (Packet, error)
|
||||
Unmarshal(v interface{}) error
|
||||
|
||||
String() string
|
||||
}
|
||||
|
||||
var debugFlag = false
|
||||
|
||||
// SetDebug sets debug mode
|
||||
func SetDebug(debug bool) {
|
||||
debugFlag = debug
|
||||
}
|
||||
|
||||
func debugLog(msg string) {
|
||||
if !debugFlag {
|
||||
return
|
||||
}
|
||||
log.Printf("[%s-debug] %s\n", ProgramName, msg)
|
||||
}
|
||||
183
hrp/pkg/gidevice/pkg/libimobiledevice/lockdown.go
Normal file
183
hrp/pkg/gidevice/pkg/libimobiledevice/lockdown.go
Normal file
@@ -0,0 +1,183 @@
|
||||
package libimobiledevice
|
||||
|
||||
const ProtocolVersion = "2"
|
||||
|
||||
const LockdownPort = 62078
|
||||
|
||||
type RequestType string
|
||||
|
||||
const (
|
||||
RequestTypeQueryType RequestType = "QueryType"
|
||||
RequestTypeSetValue RequestType = "SetValue"
|
||||
RequestTypeGetValue RequestType = "GetValue"
|
||||
RequestTypePair RequestType = "Pair"
|
||||
RequestTypeEnterRecovery RequestType = "EnterRecovery"
|
||||
RequestTypeStartSession RequestType = "StartSession"
|
||||
RequestTypeStopSession RequestType = "StopSession"
|
||||
RequestTypeStartService RequestType = "StartService"
|
||||
)
|
||||
|
||||
type LockdownType struct {
|
||||
Type string `plist:"Type"`
|
||||
}
|
||||
|
||||
func NewLockdownClient(innerConn InnerConn) *LockdownClient {
|
||||
return &LockdownClient{
|
||||
client: newServicePacketClient(innerConn),
|
||||
}
|
||||
}
|
||||
|
||||
type LockdownClient struct {
|
||||
client *servicePacketClient
|
||||
}
|
||||
|
||||
func (c *LockdownClient) NewBasicRequest(reqType RequestType) *LockdownBasicRequest {
|
||||
return &LockdownBasicRequest{
|
||||
Label: BundleID,
|
||||
ProtocolVersion: ProtocolVersion,
|
||||
Request: reqType,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *LockdownClient) NewGetValueRequest(domain, key string) *LockdownValueRequest {
|
||||
return &LockdownValueRequest{
|
||||
LockdownBasicRequest: *c.NewBasicRequest(RequestTypeGetValue),
|
||||
Domain: domain,
|
||||
Key: key,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *LockdownClient) NewSetValueRequest(domain, key string, value interface{}) *LockdownValueRequest {
|
||||
return &LockdownValueRequest{
|
||||
LockdownBasicRequest: *c.NewBasicRequest(RequestTypeSetValue),
|
||||
Domain: domain,
|
||||
Key: key,
|
||||
Value: value,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *LockdownClient) NewEnterRecoveryRequest() *LockdownBasicRequest {
|
||||
return c.NewBasicRequest(RequestTypeEnterRecovery)
|
||||
}
|
||||
|
||||
func (c *LockdownClient) NewPairRequest(pairRecord *PairRecord) *LockdownPairRequest {
|
||||
return &LockdownPairRequest{
|
||||
LockdownBasicRequest: *c.NewBasicRequest(RequestTypePair),
|
||||
PairRecord: pairRecord,
|
||||
PairingOptions: map[string]interface{}{
|
||||
"ExtendedPairingErrors": true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *LockdownClient) NewStartSessionRequest(buid, hostID string) *LockdownStartSessionRequest {
|
||||
return &LockdownStartSessionRequest{
|
||||
LockdownBasicRequest: *c.NewBasicRequest(RequestTypeStartSession),
|
||||
SystemBUID: buid,
|
||||
HostID: hostID,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *LockdownClient) NewStopSessionRequest(sessionID string) *LockdownStopSessionRequest {
|
||||
return &LockdownStopSessionRequest{
|
||||
LockdownBasicRequest: *c.NewBasicRequest(RequestTypeStopSession),
|
||||
SessionID: sessionID,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *LockdownClient) NewStartServiceRequest(service string) *LockdownStartServiceRequest {
|
||||
return &LockdownStartServiceRequest{
|
||||
LockdownBasicRequest: *c.NewBasicRequest(RequestTypeStartService),
|
||||
Service: service,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *LockdownClient) NewXmlPacket(req interface{}) (Packet, error) {
|
||||
return c.client.NewXmlPacket(req)
|
||||
}
|
||||
|
||||
func (c *LockdownClient) SendPacket(pkt Packet) (err error) {
|
||||
return c.client.SendPacket(pkt)
|
||||
}
|
||||
|
||||
func (c *LockdownClient) ReceivePacket() (respPkt Packet, err error) {
|
||||
return c.client.ReceivePacket()
|
||||
}
|
||||
|
||||
func (c *LockdownClient) EnableSSL(version []int, pairRecord *PairRecord) (err error) {
|
||||
return c.client.innerConn.Handshake(version, pairRecord)
|
||||
}
|
||||
|
||||
type (
|
||||
LockdownBasicRequest struct {
|
||||
Label string `plist:"Label"`
|
||||
ProtocolVersion string `plist:"ProtocolVersion"`
|
||||
Request RequestType `plist:"Request"`
|
||||
}
|
||||
|
||||
LockdownValueRequest struct {
|
||||
LockdownBasicRequest
|
||||
Domain string `plist:"Domain,omitempty"`
|
||||
Key string `plist:"Key,omitempty"`
|
||||
Value interface{} `plist:"Value,omitempty"`
|
||||
}
|
||||
|
||||
LockdownPairRequest struct {
|
||||
LockdownBasicRequest
|
||||
PairRecord *PairRecord `plist:"PairRecord"`
|
||||
PairingOptions map[string]interface{} `plist:"PairingOptions"`
|
||||
}
|
||||
|
||||
LockdownStartSessionRequest struct {
|
||||
LockdownBasicRequest
|
||||
SystemBUID string `plist:"SystemBUID"`
|
||||
HostID string `plist:"HostID"`
|
||||
}
|
||||
|
||||
LockdownStopSessionRequest struct {
|
||||
LockdownBasicRequest
|
||||
SessionID string `plist:"SessionID"`
|
||||
}
|
||||
|
||||
LockdownStartServiceRequest struct {
|
||||
LockdownBasicRequest
|
||||
Service string `plist:"Service"`
|
||||
EscrowBag []byte `plist:"EscrowBag,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
LockdownBasicResponse struct {
|
||||
Request string `plist:"Request"`
|
||||
Error string `plist:"Error"`
|
||||
}
|
||||
|
||||
LockdownTypeResponse struct {
|
||||
LockdownBasicResponse
|
||||
Type string `plist:"Type"`
|
||||
}
|
||||
|
||||
LockdownValueResponse struct {
|
||||
LockdownBasicResponse
|
||||
Key string `plist:"Key"`
|
||||
Value interface{} `plist:"Value"`
|
||||
}
|
||||
|
||||
LockdownPairResponse struct {
|
||||
LockdownBasicResponse
|
||||
EscrowBag []byte `plist:"EscrowBag"`
|
||||
}
|
||||
|
||||
LockdownStartSessionResponse struct {
|
||||
LockdownBasicResponse
|
||||
EnableSessionSSL bool `plist:"EnableSessionSSL"`
|
||||
SessionID string `plist:"SessionID"`
|
||||
}
|
||||
|
||||
LockdownStartServiceResponse struct {
|
||||
LockdownBasicResponse
|
||||
EnableServiceSSL bool `plist:"EnableServiceSSL"`
|
||||
Port int `plist:"Port"`
|
||||
Service string `plist:"Service"`
|
||||
}
|
||||
)
|
||||
86
hrp/pkg/gidevice/pkg/libimobiledevice/packet_afc.go
Normal file
86
hrp/pkg/gidevice/pkg/libimobiledevice/packet_afc.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package libimobiledevice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var afcHeader = []byte{0x43, 0x46, 0x41, 0x36, 0x4C, 0x50, 0x41, 0x41}
|
||||
|
||||
var _ Packet = (*afcPacket)(nil)
|
||||
|
||||
type afcPacket struct {
|
||||
entireLen uint64
|
||||
thisLen uint64
|
||||
packetNum uint64
|
||||
operation uint64
|
||||
}
|
||||
|
||||
func (p *afcPacket) Pack() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.Write(afcHeader)
|
||||
|
||||
b := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(b, p.entireLen)
|
||||
buf.Write(b)
|
||||
binary.LittleEndian.PutUint64(b, p.thisLen)
|
||||
buf.Write(b)
|
||||
binary.LittleEndian.PutUint64(b, p.packetNum)
|
||||
buf.Write(b)
|
||||
binary.LittleEndian.PutUint64(b, p.operation)
|
||||
buf.Write(b)
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (p *afcPacket) Unpack(buffer *bytes.Buffer) (Packet, error) {
|
||||
return p.unpack(buffer)
|
||||
}
|
||||
|
||||
func (p *afcPacket) unpack(buffer *bytes.Buffer) (*afcPacket, error) {
|
||||
magic := make([]byte, 8)
|
||||
if _, err := buffer.Read(magic); err != nil {
|
||||
return nil, fmt.Errorf("afc packet unpack: %w", err)
|
||||
}
|
||||
if bytes.Compare(magic, afcHeader) != 0 {
|
||||
return nil, errors.New("afc packet unpack: header not match")
|
||||
}
|
||||
|
||||
respPkt := new(afcPacket)
|
||||
if err := binary.Read(buffer, binary.LittleEndian, &respPkt.entireLen); err != nil {
|
||||
return nil, fmt.Errorf("afc packet unpack: %w", err)
|
||||
}
|
||||
if err := binary.Read(buffer, binary.LittleEndian, &respPkt.thisLen); err != nil {
|
||||
return nil, fmt.Errorf("afc packet unpack: %w", err)
|
||||
}
|
||||
if err := binary.Read(buffer, binary.LittleEndian, &respPkt.packetNum); err != nil {
|
||||
return nil, fmt.Errorf("afc packet unpack: %w", err)
|
||||
}
|
||||
if err := binary.Read(buffer, binary.LittleEndian, &respPkt.operation); err != nil {
|
||||
return nil, fmt.Errorf("afc packet unpack: %w", err)
|
||||
}
|
||||
return respPkt, nil
|
||||
}
|
||||
|
||||
func (p *afcPacket) Unmarshal(v interface{}) error {
|
||||
// switch msg := v.(type) {
|
||||
// case *AfcMessage:
|
||||
// // msg.EntireLen = p.entireLen
|
||||
// // msg.ThisLen = p.thisLen
|
||||
// // msg.PacketNum = p.packetNum
|
||||
// msg.Operation = p.operation
|
||||
// default:
|
||||
// return errors.New("the type of the method parameter must be '*AfcMessage'")
|
||||
// }
|
||||
// return nil
|
||||
panic("never use (afcPacket)")
|
||||
}
|
||||
|
||||
func (p *afcPacket) String() string {
|
||||
return fmt.Sprintf(
|
||||
"EntireLen: %d, ThisLen: %d, PacketNum: %d, Operation: %X\n",
|
||||
p.entireLen, p.thisLen, p.packetNum, p.operation,
|
||||
)
|
||||
}
|
||||
203
hrp/pkg/gidevice/pkg/libimobiledevice/packet_dtxmessage.go
Normal file
203
hrp/pkg/gidevice/pkg/libimobiledevice/packet_dtxmessage.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package libimobiledevice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
_ Packet = (*dtxMessagePayloadPacket)(nil)
|
||||
_ Packet = (*dtxMessageHeaderPacket)(nil)
|
||||
_ Packet = (*dtxMessagePacket)(nil)
|
||||
)
|
||||
|
||||
type dtxMessagePayloadPacket struct {
|
||||
Flags uint32
|
||||
AuxiliaryLength uint32
|
||||
TotalLength uint64
|
||||
}
|
||||
|
||||
func (p *dtxMessagePayloadPacket) Pack() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
b := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(b, p.Flags)
|
||||
buf.Write(b)
|
||||
binary.LittleEndian.PutUint32(b, p.AuxiliaryLength)
|
||||
buf.Write(b)
|
||||
|
||||
b = make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(b, p.TotalLength)
|
||||
buf.Write(b)
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (p *dtxMessagePayloadPacket) Unpack(buffer *bytes.Buffer) (pkt Packet, err error) {
|
||||
return p.unpack(buffer)
|
||||
}
|
||||
|
||||
func (p *dtxMessagePayloadPacket) unpack(buffer *bytes.Buffer) (pkt *dtxMessagePayloadPacket, err error) {
|
||||
respPkt := new(dtxMessagePayloadPacket)
|
||||
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.Flags); err != nil {
|
||||
return nil, fmt.Errorf("packet (DTXMessagePayloadHeader) unpack: %w", err)
|
||||
}
|
||||
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.AuxiliaryLength); err != nil {
|
||||
return nil, fmt.Errorf("packet (DTXMessagePayloadHeader) unpack: %w", err)
|
||||
}
|
||||
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.TotalLength); err != nil {
|
||||
return nil, fmt.Errorf("packet (DTXMessagePayloadHeader) unpack: %w", err)
|
||||
}
|
||||
return respPkt, nil
|
||||
}
|
||||
|
||||
func (p *dtxMessagePayloadPacket) Unmarshal(v interface{}) error {
|
||||
panic("never use (dtxMessagePayloadHeader)")
|
||||
}
|
||||
|
||||
func (p *dtxMessagePayloadPacket) String() string {
|
||||
return fmt.Sprintf("DTXMessagePayloadHeader Flags: %d, AuxiliaryLength: %d, TotalLength: %d\n",
|
||||
p.Flags, p.AuxiliaryLength, p.TotalLength,
|
||||
)
|
||||
}
|
||||
|
||||
type dtxMessageHeaderPacket struct {
|
||||
Magic uint32
|
||||
CB uint32
|
||||
FragmentId uint16
|
||||
FragmentCount uint16
|
||||
Length uint32
|
||||
Identifier uint32
|
||||
ConversationIndex uint32
|
||||
ChannelCode uint32
|
||||
ExpectsReply uint32
|
||||
}
|
||||
|
||||
func (p *dtxMessageHeaderPacket) Pack() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
b := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(b, p.Magic)
|
||||
buf.Write(b)
|
||||
binary.LittleEndian.PutUint32(b, p.CB)
|
||||
buf.Write(b)
|
||||
|
||||
b = make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(b, p.FragmentId)
|
||||
buf.Write(b)
|
||||
binary.LittleEndian.PutUint16(b, p.FragmentCount)
|
||||
buf.Write(b)
|
||||
|
||||
b = make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(b, p.Length)
|
||||
buf.Write(b)
|
||||
binary.LittleEndian.PutUint32(b, p.Identifier)
|
||||
buf.Write(b)
|
||||
binary.LittleEndian.PutUint32(b, p.ConversationIndex)
|
||||
buf.Write(b)
|
||||
binary.LittleEndian.PutUint32(b, p.ChannelCode)
|
||||
buf.Write(b)
|
||||
binary.LittleEndian.PutUint32(b, p.ExpectsReply)
|
||||
buf.Write(b)
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (p *dtxMessageHeaderPacket) Unpack(buffer *bytes.Buffer) (pkt Packet, err error) {
|
||||
return p.unpack(buffer)
|
||||
}
|
||||
|
||||
func (p *dtxMessageHeaderPacket) unpack(buffer *bytes.Buffer) (pkt *dtxMessageHeaderPacket, err error) {
|
||||
respPkt := new(dtxMessageHeaderPacket)
|
||||
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.Magic); err != nil {
|
||||
return nil, fmt.Errorf("packet (DTXMessageHeader) unpack: %w", err)
|
||||
}
|
||||
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.CB); err != nil {
|
||||
return nil, fmt.Errorf("packet (DTXMessageHeader) unpack: %w", err)
|
||||
}
|
||||
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.FragmentId); err != nil {
|
||||
return nil, fmt.Errorf("packet (DTXMessageHeader) unpack: %w", err)
|
||||
}
|
||||
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.FragmentCount); err != nil {
|
||||
return nil, fmt.Errorf("packet (DTXMessageHeader) unpack: %w", err)
|
||||
}
|
||||
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.Length); err != nil {
|
||||
return nil, fmt.Errorf("packet (DTXMessageHeader) unpack: %w", err)
|
||||
}
|
||||
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.Identifier); err != nil {
|
||||
return nil, fmt.Errorf("packet (DTXMessageHeader) unpack: %w", err)
|
||||
}
|
||||
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.ConversationIndex); err != nil {
|
||||
return nil, fmt.Errorf("packet (DTXMessageHeader) unpack: %w", err)
|
||||
}
|
||||
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.ChannelCode); err != nil {
|
||||
return nil, fmt.Errorf("packet (DTXMessageHeader) unpack: %w", err)
|
||||
}
|
||||
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.ExpectsReply); err != nil {
|
||||
return nil, fmt.Errorf("packet (DTXMessageHeader) unpack: %w", err)
|
||||
}
|
||||
return respPkt, nil
|
||||
}
|
||||
|
||||
func (p *dtxMessageHeaderPacket) Unmarshal(v interface{}) error {
|
||||
panic("never use (DTXMessageHeader)")
|
||||
}
|
||||
|
||||
func (p *dtxMessageHeaderPacket) String() string {
|
||||
return fmt.Sprintf("DTXMessageHeader Magic: %d, CB: %d, FragmentId: %d, FragmentCount: %d\n"+
|
||||
"Length: %d, Identifier: %d, ConversationIndex: %d, ChannelCode: %d, ExpectsReply: %d\n",
|
||||
p.Magic, p.CB, p.FragmentId, p.FragmentCount,
|
||||
p.Length, p.Identifier, p.ConversationIndex, p.ChannelCode, p.ExpectsReply,
|
||||
)
|
||||
}
|
||||
|
||||
type dtxMessagePacket struct {
|
||||
Header *dtxMessageHeaderPacket
|
||||
Payload *dtxMessagePayloadPacket
|
||||
Aux []byte
|
||||
Sel []byte
|
||||
}
|
||||
|
||||
func (p *dtxMessagePacket) Pack() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
raw, err := p.Header.Pack()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("packet (DTXMessagePacket) pack: %w", err)
|
||||
}
|
||||
buf.Write(raw)
|
||||
|
||||
if raw, err = p.Payload.Pack(); err != nil {
|
||||
return nil, fmt.Errorf("packet (DTXMessagePacket) pack: %w", err)
|
||||
}
|
||||
buf.Write(raw)
|
||||
|
||||
if p.Aux != nil || len(p.Aux) != 0 {
|
||||
buf.Write(p.Aux)
|
||||
}
|
||||
if p.Sel != nil || len(p.Sel) != 0 {
|
||||
buf.Write(p.Sel)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (p *dtxMessagePacket) Unpack(buffer *bytes.Buffer) (Packet, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (p *dtxMessagePacket) Unmarshal(v interface{}) error {
|
||||
panic("never use (DTXMessagePacket)")
|
||||
}
|
||||
|
||||
func (p *dtxMessagePacket) String() string {
|
||||
return fmt.Sprintf(
|
||||
"DTXMessagePacket %s\n%s\n"+
|
||||
"%s\n%s\n",
|
||||
p.Header.String(), p.Payload.String(),
|
||||
// p.Aux, p.Sel,
|
||||
hex.Dump(p.Aux), hex.Dump(p.Sel),
|
||||
)
|
||||
}
|
||||
53
hrp/pkg/gidevice/pkg/libimobiledevice/packet_location.go
Normal file
53
hrp/pkg/gidevice/pkg/libimobiledevice/packet_location.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package libimobiledevice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var _ Packet = (*locationPacket)(nil)
|
||||
|
||||
type locationPacket struct {
|
||||
lon float64
|
||||
lat float64
|
||||
}
|
||||
|
||||
func (l *locationPacket) Pack() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
if err := binary.Write(buf, binary.BigEndian, uint32(0)); err != nil {
|
||||
return nil, fmt.Errorf("packet (location) pack: %w", err)
|
||||
}
|
||||
|
||||
latS := []byte(strconv.FormatFloat(l.lat, 'E', -1, 64))
|
||||
if err := binary.Write(buf, binary.BigEndian, uint32(len(latS))); err != nil {
|
||||
return nil, fmt.Errorf("packet (location) pack: %w", err)
|
||||
}
|
||||
if err := binary.Write(buf, binary.BigEndian, latS); err != nil {
|
||||
return nil, fmt.Errorf("packet (location) pack: %w", err)
|
||||
}
|
||||
|
||||
lonS := []byte(strconv.FormatFloat(l.lon, 'E', -1, 64))
|
||||
if err := binary.Write(buf, binary.BigEndian, uint32(len(lonS))); err != nil {
|
||||
return nil, fmt.Errorf("packet (location) pack: %w", err)
|
||||
}
|
||||
if err := binary.Write(buf, binary.BigEndian, lonS); err != nil {
|
||||
return nil, fmt.Errorf("packet (location) pack: %w", err)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (l *locationPacket) Unpack(buffer *bytes.Buffer) (Packet, error) {
|
||||
panic("never use (location)")
|
||||
}
|
||||
|
||||
func (l *locationPacket) Unmarshal(v interface{}) error {
|
||||
panic("never use (location)")
|
||||
}
|
||||
|
||||
func (l *locationPacket) String() string {
|
||||
return fmt.Sprintf("lon: %v, lat: %v\n", l.lon, l.lat)
|
||||
}
|
||||
47
hrp/pkg/gidevice/pkg/libimobiledevice/packet_service.go
Normal file
47
hrp/pkg/gidevice/pkg/libimobiledevice/packet_service.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package libimobiledevice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"howett.net/plist"
|
||||
)
|
||||
|
||||
var _ Packet = (*servicePacket)(nil)
|
||||
|
||||
type servicePacket struct {
|
||||
length uint32
|
||||
body []byte
|
||||
}
|
||||
|
||||
func (p *servicePacket) Pack() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
b := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(b, p.length)
|
||||
buf.Write(b)
|
||||
|
||||
buf.Write(p.body)
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (p *servicePacket) Unpack(buffer *bytes.Buffer) (pkt Packet, err error) {
|
||||
respPkt := new(servicePacket)
|
||||
if err = binary.Read(buffer, binary.BigEndian, &respPkt.length); err != nil {
|
||||
return nil, fmt.Errorf("packet (service) unpack: %w", err)
|
||||
}
|
||||
respPkt.body = buffer.Bytes()
|
||||
|
||||
return respPkt, nil
|
||||
}
|
||||
|
||||
func (p *servicePacket) Unmarshal(v interface{}) (err error) {
|
||||
_, err = plist.Unmarshal(p.body, v)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *servicePacket) String() string {
|
||||
return fmt.Sprintf("Length: %d\n%s", p.length, p.body)
|
||||
}
|
||||
112
hrp/pkg/gidevice/pkg/libimobiledevice/packet_usbmux.go
Normal file
112
hrp/pkg/gidevice/pkg/libimobiledevice/packet_usbmux.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package libimobiledevice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"howett.net/plist"
|
||||
)
|
||||
|
||||
var _ Packet = (*packet)(nil)
|
||||
|
||||
type packet struct {
|
||||
length uint32
|
||||
version ProtoVersion
|
||||
msgType ProtoMessageType
|
||||
tag uint32
|
||||
body []byte
|
||||
}
|
||||
|
||||
func (p *packet) Pack() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
b := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(b, p.length)
|
||||
buf.Write(b)
|
||||
binary.LittleEndian.PutUint32(b, uint32(p.version))
|
||||
buf.Write(b)
|
||||
binary.LittleEndian.PutUint32(b, uint32(p.msgType))
|
||||
buf.Write(b)
|
||||
binary.LittleEndian.PutUint32(b, p.tag)
|
||||
buf.Write(b)
|
||||
|
||||
buf.Write(p.body)
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (p *packet) Unpack(buffer *bytes.Buffer) (pkt Packet, err error) {
|
||||
respPkt := new(packet)
|
||||
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.length); err != nil {
|
||||
return nil, fmt.Errorf("packet unpack: %w", err)
|
||||
}
|
||||
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.version); err != nil {
|
||||
return nil, fmt.Errorf("packet unpack: %w", err)
|
||||
}
|
||||
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.msgType); err != nil {
|
||||
return nil, fmt.Errorf("packet unpack: %w", err)
|
||||
}
|
||||
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.tag); err != nil {
|
||||
return nil, fmt.Errorf("packet unpack: %w", err)
|
||||
}
|
||||
respPkt.body = buffer.Bytes()
|
||||
return respPkt, nil
|
||||
}
|
||||
|
||||
func (p *packet) Unmarshal(v interface{}) (err error) {
|
||||
_, err = plist.Unmarshal(p.body, v)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *packet) String() string {
|
||||
return fmt.Sprintf(
|
||||
"Length: %d, Version: %d, Type: %d, Tag: %d\n%s",
|
||||
p.length, p.version, p.msgType, p.tag, p.body,
|
||||
)
|
||||
}
|
||||
|
||||
type (
|
||||
BasicRequest struct {
|
||||
MessageType MessageType `plist:"MessageType"`
|
||||
BundleID string `plist:"BundleID,omitempty"`
|
||||
ProgramName string `plist:"ProgName,omitempty"`
|
||||
ClientVersionString string `plist:"ClientVersionString"`
|
||||
LibUSBMuxVersion uint `plist:"kLibUSBMuxVersion"`
|
||||
}
|
||||
|
||||
ConnectRequest struct {
|
||||
BasicRequest
|
||||
DeviceID int `plist:"DeviceID"`
|
||||
PortNumber int `plist:"PortNumber"`
|
||||
}
|
||||
|
||||
ReadPairRecordRequest struct {
|
||||
BasicRequest
|
||||
PairRecordID string `plist:"PairRecordID"`
|
||||
}
|
||||
|
||||
SavePairRecordRequest struct {
|
||||
BasicRequest
|
||||
PairRecordID string `plist:"PairRecordID"`
|
||||
PairRecordData []byte `plist:"PairRecordData"`
|
||||
DeviceID int `plist:"DeviceID"`
|
||||
}
|
||||
|
||||
DeletePairRecordRequest struct {
|
||||
BasicRequest
|
||||
PairRecordID string `plist:"PairRecordID"`
|
||||
}
|
||||
)
|
||||
|
||||
type PairRecord struct {
|
||||
DeviceCertificate []byte `plist:"DeviceCertificate"`
|
||||
EscrowBag []byte `plist:"EscrowBag,omitempty"`
|
||||
HostCertificate []byte `plist:"HostCertificate"`
|
||||
HostPrivateKey []byte `plist:"HostPrivateKey,omitempty"`
|
||||
HostID string `plist:"HostID"`
|
||||
RootCertificate []byte `plist:"RootCertificate"`
|
||||
RootPrivateKey []byte `plist:"RootPrivateKey,omitempty"`
|
||||
SystemBUID string `plist:"SystemBUID"`
|
||||
WiFiMACAddress string `plist:"WiFiMACAddress,omitempty"`
|
||||
}
|
||||
119
hrp/pkg/gidevice/pkg/libimobiledevice/pcapd.go
Normal file
119
hrp/pkg/gidevice/pkg/libimobiledevice/pcapd.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package libimobiledevice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/lunixbochs/struc"
|
||||
)
|
||||
|
||||
const PcapdServiceName = "com.apple.pcapd"
|
||||
|
||||
func NewPcapdClient(innerConn InnerConn) *PcapdClient {
|
||||
return &PcapdClient{
|
||||
client: newServicePacketClient(innerConn),
|
||||
}
|
||||
}
|
||||
|
||||
type PcapdClient struct {
|
||||
filter func(*IOSPacketHeader) bool
|
||||
client *servicePacketClient
|
||||
}
|
||||
|
||||
func (c *PcapdClient) ReceivePacket() (respPkt Packet, err error) {
|
||||
var bufLen []byte
|
||||
if bufLen, err = c.client.innerConn.Read(4); err != nil {
|
||||
return nil, fmt.Errorf("lockdown(Pcapd) receive: %w", err)
|
||||
}
|
||||
lenPkg := binary.BigEndian.Uint32(bufLen)
|
||||
|
||||
buffer := bytes.NewBuffer([]byte{})
|
||||
buffer.Write(bufLen)
|
||||
|
||||
var buf []byte
|
||||
if buf, err = c.client.innerConn.Read(int(lenPkg)); err != nil {
|
||||
return nil, fmt.Errorf("lockdown(Pcapd) receive: %w", err)
|
||||
}
|
||||
buffer.Write(buf)
|
||||
|
||||
if respPkt, err = new(servicePacket).Unpack(buffer); err != nil {
|
||||
return nil, fmt.Errorf("lockdown(Pcapd) receive: %w", err)
|
||||
}
|
||||
|
||||
debugLog(fmt.Sprintf("<-- %s\n", respPkt))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type IOSPacketHeader struct {
|
||||
HdrSize uint32 `struc:"uint32,big"`
|
||||
Version uint8 `struc:"uint8,big"`
|
||||
PacketSize uint32 `struc:"uint32,big"`
|
||||
Type uint8 `struc:"uint8,big"`
|
||||
Unit uint16 `struc:"uint16,big"`
|
||||
IO uint8 `struc:"uint8,big"`
|
||||
ProtocolFamily uint32 `struc:"uint32,big"`
|
||||
FramePreLength uint32 `struc:"uint32,big"`
|
||||
FramePstLength uint32 `struc:"uint32,big"`
|
||||
IFName string `struc:"[16]byte"`
|
||||
Pid int32 `struc:"int32,little"`
|
||||
ProcName string `struc:"[17]byte"`
|
||||
Unknown uint32 `struc:"uint32,little"`
|
||||
Pid2 int32 `struc:"int32,little"`
|
||||
ProcName2 string `struc:"[17]byte"`
|
||||
Unknown2 [8]byte `struc:"[8]byte"`
|
||||
}
|
||||
|
||||
func (c *PcapdClient) GetPacket(buf []byte) ([]byte, error) {
|
||||
iph := IOSPacketHeader{}
|
||||
preader := bytes.NewReader(buf)
|
||||
_ = struc.Unpack(preader, &iph)
|
||||
|
||||
if c.filter != nil {
|
||||
if !c.filter(&iph) {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
packet, err := ioutil.ReadAll(preader)
|
||||
if err != nil {
|
||||
return packet, err
|
||||
}
|
||||
if iph.FramePreLength == 0 {
|
||||
ext := []byte{0xbe, 0xfe, 0xbe, 0xfe, 0xbe, 0xfe, 0xbe, 0xfe, 0xbe, 0xfe, 0xbe, 0xfe, 0x08, 0x00}
|
||||
return append(ext, packet...), nil
|
||||
}
|
||||
return packet, nil
|
||||
}
|
||||
|
||||
type PcaprecHdrS struct {
|
||||
TsSec int `struc:"uint32,little"` /* timestamp seconds */
|
||||
TsUsec int `struc:"uint32,little"` /* timestamp microseconds */
|
||||
InclLen int `struc:"uint32,little"` /* number of octets of packet saved in file */
|
||||
OrigLen int `struc:"uint32,little"` /* actual length of packet */
|
||||
}
|
||||
|
||||
func (c *PcapdClient) CreatePacket(packet []byte) ([]byte, error) {
|
||||
now := time.Now()
|
||||
phs := &PcaprecHdrS{
|
||||
int(now.Unix()),
|
||||
int(now.UnixNano()/1e3 - now.Unix()*1e6),
|
||||
len(packet),
|
||||
len(packet),
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
err := struc.Pack(&buf, phs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf.Write(packet)
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (c *PcapdClient) Close() {
|
||||
c.client.innerConn.Close()
|
||||
}
|
||||
52
hrp/pkg/gidevice/pkg/libimobiledevice/screenshot.go
Normal file
52
hrp/pkg/gidevice/pkg/libimobiledevice/screenshot.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package libimobiledevice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const ScreenshotServiceName = "com.apple.mobile.screenshotr"
|
||||
|
||||
func NewScreenshotClient(innerConn InnerConn) *ScreenshotClient {
|
||||
return &ScreenshotClient{
|
||||
client: newServicePacketClient(innerConn),
|
||||
}
|
||||
}
|
||||
|
||||
type ScreenshotClient struct {
|
||||
client *servicePacketClient
|
||||
}
|
||||
|
||||
func (c *ScreenshotClient) NewBinaryPacket(req interface{}) (Packet, error) {
|
||||
return c.client.NewBinaryPacket(req)
|
||||
}
|
||||
|
||||
func (c *ScreenshotClient) SendPacket(pkt Packet) (err error) {
|
||||
return c.client.SendPacket(pkt)
|
||||
}
|
||||
|
||||
func (c *ScreenshotClient) ReceivePacket() (respPkt Packet, err error) {
|
||||
var bufLen []byte
|
||||
if bufLen, err = c.client.innerConn.Read(4); err != nil {
|
||||
return nil, fmt.Errorf("lockdown(Screenshot) receive: %w", err)
|
||||
}
|
||||
lenPkg := binary.BigEndian.Uint32(bufLen)
|
||||
|
||||
buffer := bytes.NewBuffer([]byte{})
|
||||
buffer.Write(bufLen)
|
||||
|
||||
var buf []byte
|
||||
if buf, err = c.client.innerConn.Read(int(lenPkg)); err != nil {
|
||||
return nil, fmt.Errorf("lockdown(Screenshot) receive: %w", err)
|
||||
}
|
||||
buffer.Write(buf)
|
||||
|
||||
if respPkt, err = new(servicePacket).Unpack(buffer); err != nil {
|
||||
return nil, fmt.Errorf("lockdown(Screenshot) receive: %w", err)
|
||||
}
|
||||
|
||||
debugLog(fmt.Sprintf("<-- %s\n", respPkt))
|
||||
|
||||
return
|
||||
}
|
||||
128
hrp/pkg/gidevice/pkg/libimobiledevice/simulatelocation.go
Normal file
128
hrp/pkg/gidevice/pkg/libimobiledevice/simulatelocation.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package libimobiledevice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const SimulateLocationServiceName = "com.apple.dt.simulatelocation"
|
||||
|
||||
type CoordinateSystem string
|
||||
|
||||
const (
|
||||
CoordinateSystemWGS84 CoordinateSystem = "WGS84"
|
||||
CoordinateSystemBD09 CoordinateSystem = "BD09"
|
||||
CoordinateSystemGCJ02 CoordinateSystem = "GCJ02"
|
||||
)
|
||||
|
||||
func NewSimulateLocationClient(innerConn InnerConn) *SimulateLocationClient {
|
||||
return &SimulateLocationClient{
|
||||
client: newServicePacketClient(innerConn),
|
||||
}
|
||||
}
|
||||
|
||||
type SimulateLocationClient struct {
|
||||
client *servicePacketClient
|
||||
}
|
||||
|
||||
func (c *SimulateLocationClient) NewLocationPacket(lon, lat float64, coordinateSystem CoordinateSystem) Packet {
|
||||
switch CoordinateSystem(strings.ToUpper(string(coordinateSystem))) {
|
||||
case CoordinateSystemGCJ02:
|
||||
lon, lat = gcj02ToWGS84(lon, lat)
|
||||
case CoordinateSystemBD09:
|
||||
lon, lat = bd09ToWGS84(lon, lat)
|
||||
case CoordinateSystemWGS84:
|
||||
_, _ = lon, lat
|
||||
default:
|
||||
_, _ = lon, lat
|
||||
}
|
||||
|
||||
pkt := new(locationPacket)
|
||||
pkt.lon = lon
|
||||
pkt.lat = lat
|
||||
return pkt
|
||||
}
|
||||
|
||||
func (c *SimulateLocationClient) SendPacket(pkt Packet) (err error) {
|
||||
return c.client.SendPacket(pkt)
|
||||
}
|
||||
|
||||
// Recover try to revert back
|
||||
func (c *SimulateLocationClient) Recover() error {
|
||||
data := []byte{0x00, 0x00, 0x00, 0x01}
|
||||
debugLog(fmt.Sprintf("--> %+v\n", data))
|
||||
return c.client.innerConn.Write(data)
|
||||
}
|
||||
|
||||
const (
|
||||
xPi = math.Pi * 3000.0 / 180.0
|
||||
offset = 0.00669342162296594323
|
||||
axis = 6378245.0
|
||||
)
|
||||
|
||||
func isOutOfChina(lon, lat float64) bool {
|
||||
return !(lon > 73.66 && lon < 135.05 && lat > 3.86 && lat < 53.55)
|
||||
}
|
||||
|
||||
func delta(lon, lat float64) (float64, float64) {
|
||||
dLat := transformLat(lon-105.0, lat-35.0)
|
||||
dLon := transformLng(lon-105.0, lat-35.0)
|
||||
|
||||
radLat := lat / 180.0 * math.Pi
|
||||
magic := math.Sin(radLat)
|
||||
magic = 1 - offset*magic*magic
|
||||
sqrtMagic := math.Sqrt(magic)
|
||||
|
||||
dLat = (dLat * 180.0) / ((axis * (1 - offset)) / (magic * sqrtMagic) * math.Pi)
|
||||
dLon = (dLon * 180.0) / (axis / sqrtMagic * math.Cos(radLat) * math.Pi)
|
||||
|
||||
mgLat := lat + dLat
|
||||
mgLon := lon + dLon
|
||||
|
||||
return mgLon, mgLat
|
||||
}
|
||||
|
||||
func transformLat(lon, lat float64) float64 {
|
||||
ret := -100.0 + 2.0*lon + 3.0*lat + 0.2*lat*lat + 0.1*lon*lat + 0.2*math.Sqrt(math.Abs(lon))
|
||||
ret += (20.0*math.Sin(6.0*lon*math.Pi) + 20.0*math.Sin(2.0*lon*math.Pi)) * 2.0 / 3.0
|
||||
ret += (20.0*math.Sin(lat*math.Pi) + 40.0*math.Sin(lat/3.0*math.Pi)) * 2.0 / 3.0
|
||||
ret += (160.0*math.Sin(lat/12.0*math.Pi) + 320*math.Sin(lat*math.Pi/30.0)) * 2.0 / 3.0
|
||||
return ret
|
||||
}
|
||||
|
||||
func transformLng(lon, lat float64) float64 {
|
||||
ret := 300.0 + lon + 2.0*lat + 0.1*lon*lon + 0.1*lon*lat + 0.1*math.Sqrt(math.Abs(lon))
|
||||
ret += (20.0*math.Sin(6.0*lon*math.Pi) + 20.0*math.Sin(2.0*lon*math.Pi)) * 2.0 / 3.0
|
||||
ret += (20.0*math.Sin(lon*math.Pi) + 40.0*math.Sin(lon/3.0*math.Pi)) * 2.0 / 3.0
|
||||
ret += (150.0*math.Sin(lon/12.0*math.Pi) + 300.0*math.Sin(lon/30.0*math.Pi)) * 2.0 / 3.0
|
||||
return ret
|
||||
}
|
||||
|
||||
func gcj02ToWGS84(lon, lat float64) (float64, float64) {
|
||||
if isOutOfChina(lon, lat) {
|
||||
return lon, lat
|
||||
}
|
||||
|
||||
mgLon, mgLat := delta(lon, lat)
|
||||
|
||||
return lon*2 - mgLon, lat*2 - mgLat
|
||||
}
|
||||
|
||||
func bd09ToGCJ02(lon, lat float64) (float64, float64) {
|
||||
x := lon - 0.0065
|
||||
y := lat - 0.006
|
||||
|
||||
z := math.Sqrt(x*x+y*y) - 0.00002*math.Sin(y*xPi)
|
||||
theta := math.Atan2(y, x) - 0.000003*math.Cos(x*xPi)
|
||||
|
||||
gLon := z * math.Cos(theta)
|
||||
gLat := z * math.Sin(theta)
|
||||
|
||||
return gLon, gLat
|
||||
}
|
||||
|
||||
func bd09ToWGS84(lon, lat float64) (float64, float64) {
|
||||
lon, lat = bd09ToGCJ02(lon, lat)
|
||||
return gcj02ToWGS84(lon, lat)
|
||||
}
|
||||
53
hrp/pkg/gidevice/pkg/libimobiledevice/springboard.go
Normal file
53
hrp/pkg/gidevice/pkg/libimobiledevice/springboard.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package libimobiledevice
|
||||
|
||||
type IconPNGDataResponse struct {
|
||||
PNGData []byte `plist:"pngData"`
|
||||
}
|
||||
|
||||
type InterfaceOrientationResponse struct {
|
||||
Orientation OrientationState `plist:"interfaceOrientation"`
|
||||
}
|
||||
|
||||
type OrientationState int64
|
||||
|
||||
const (
|
||||
Unknown OrientationState = iota
|
||||
Portrait
|
||||
PortraitUpsideDown
|
||||
LandscapeRight
|
||||
LandscapeLeft
|
||||
)
|
||||
|
||||
const (
|
||||
SpringBoardServiceName = "com.apple.springboardservices"
|
||||
)
|
||||
|
||||
func NewSpringBoardClient(innerConn InnerConn) *SpringBoardClient {
|
||||
return &SpringBoardClient{
|
||||
newServicePacketClient(innerConn),
|
||||
}
|
||||
}
|
||||
|
||||
type SpringBoardClient struct {
|
||||
client *servicePacketClient
|
||||
}
|
||||
|
||||
func (c *SpringBoardClient) InnerConn() InnerConn {
|
||||
return c.client.innerConn
|
||||
}
|
||||
|
||||
func (c *SpringBoardClient) NewXmlPacket(req interface{}) (Packet, error) {
|
||||
return c.client.NewXmlPacket(req)
|
||||
}
|
||||
|
||||
func (c *SpringBoardClient) SendPacket(pkt Packet) (err error) {
|
||||
return c.client.SendPacket(pkt)
|
||||
}
|
||||
|
||||
func (c *SpringBoardClient) ReceivePacket() (respPkt Packet, err error) {
|
||||
return c.client.ReceivePacket()
|
||||
}
|
||||
|
||||
func (c *SpringBoardClient) NewBinaryPacket(req interface{}) (Packet, error) {
|
||||
return c.client.NewBinaryPacket(req)
|
||||
}
|
||||
21
hrp/pkg/gidevice/pkg/libimobiledevice/syslogrelay.go
Normal file
21
hrp/pkg/gidevice/pkg/libimobiledevice/syslogrelay.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package libimobiledevice
|
||||
|
||||
const SyslogRelayServiceName = "com.apple.syslog_relay"
|
||||
|
||||
func NewSyslogRelayClient(innerConn InnerConn) *SyslogRelayClient {
|
||||
return &SyslogRelayClient{
|
||||
newServicePacketClient(innerConn),
|
||||
}
|
||||
}
|
||||
|
||||
type SyslogRelayClient struct {
|
||||
client *servicePacketClient
|
||||
}
|
||||
|
||||
func (c *SyslogRelayClient) InnerConn() InnerConn {
|
||||
return c.client.innerConn
|
||||
}
|
||||
|
||||
func (c *SyslogRelayClient) Close() {
|
||||
c.client.innerConn.Close()
|
||||
}
|
||||
45
hrp/pkg/gidevice/pkg/libimobiledevice/testmanagerd.go
Normal file
45
hrp/pkg/gidevice/pkg/libimobiledevice/testmanagerd.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package libimobiledevice
|
||||
|
||||
const (
|
||||
TestmanagerdSecureServiceName = "com.apple.testmanagerd.lockdown.secure"
|
||||
TestmanagerdServiceName = "com.apple.testmanagerd.lockdown"
|
||||
)
|
||||
|
||||
func NewTestmanagerdClient(innerConn InnerConn) *TestmanagerdClient {
|
||||
return &TestmanagerdClient{
|
||||
client: newDtxMessageClient(innerConn),
|
||||
}
|
||||
}
|
||||
|
||||
type TestmanagerdClient struct {
|
||||
client *dtxMessageClient
|
||||
}
|
||||
|
||||
func (t *TestmanagerdClient) Connection() (publishedChannels map[string]int32, err error) {
|
||||
return t.client.Connection()
|
||||
}
|
||||
|
||||
func (t *TestmanagerdClient) MakeChannel(channel string) (id uint32, err error) {
|
||||
return t.client.MakeChannel(channel)
|
||||
}
|
||||
|
||||
func (t *TestmanagerdClient) Invoke(selector string, args *AuxBuffer, channelCode uint32, expectsReply bool) (result *DTXMessageResult, err error) {
|
||||
var msgID uint32
|
||||
if msgID, err = t.client.SendDTXMessage(selector, args.Bytes(), channelCode, expectsReply); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if expectsReply {
|
||||
if result, err = t.client.GetResult(msgID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *TestmanagerdClient) RegisterCallback(obj string, cb func(m DTXMessageResult)) {
|
||||
t.client.RegisterCallback(obj, cb)
|
||||
}
|
||||
|
||||
func (t *TestmanagerdClient) Close() {
|
||||
t.client.Close()
|
||||
}
|
||||
414
hrp/pkg/gidevice/pkg/libimobiledevice/usbmux.go
Normal file
414
hrp/pkg/gidevice/pkg/libimobiledevice/usbmux.go
Normal file
@@ -0,0 +1,414 @@
|
||||
package libimobiledevice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"howett.net/plist"
|
||||
)
|
||||
|
||||
var DefaultDeadlineTimeout = 30 * time.Second
|
||||
|
||||
const (
|
||||
BundleID = "electricbubble.libimobiledevice"
|
||||
ProgramName = "libimobiledevice"
|
||||
ClientVersion = "libimobiledevice-beta"
|
||||
LibUSBMuxVersion = 3
|
||||
)
|
||||
|
||||
type ReplyCode uint64
|
||||
|
||||
const (
|
||||
ReplyCodeOK ReplyCode = iota
|
||||
ReplyCodeBadCommand
|
||||
ReplyCodeBadDevice
|
||||
ReplyCodeConnectionRefused
|
||||
_ // ignore `4`
|
||||
_ // ignore `5`
|
||||
ReplyCodeBadVersion
|
||||
)
|
||||
|
||||
func (rc ReplyCode) String() string {
|
||||
switch rc {
|
||||
case ReplyCodeOK:
|
||||
return "ok"
|
||||
case ReplyCodeBadCommand:
|
||||
return "bad command"
|
||||
case ReplyCodeBadDevice:
|
||||
return "bad device"
|
||||
case ReplyCodeConnectionRefused:
|
||||
return "connection refused"
|
||||
case ReplyCodeBadVersion:
|
||||
return "bad version"
|
||||
default:
|
||||
return "unknown reply code: " + strconv.Itoa(int(rc))
|
||||
}
|
||||
}
|
||||
|
||||
type ProtoVersion uint32
|
||||
|
||||
// proto_version == 1
|
||||
// construct message plist
|
||||
// else `0`? res == `RESULT_BADVERSION`
|
||||
// binary packet
|
||||
|
||||
const (
|
||||
ProtoVersionBinary ProtoVersion = iota
|
||||
ProtoVersionPlist
|
||||
)
|
||||
|
||||
type ProtoMessageType uint32
|
||||
|
||||
const (
|
||||
_ ProtoMessageType = iota
|
||||
ProtoMessageTypeResult
|
||||
ProtoMessageTypeConnect
|
||||
ProtoMessageTypeListen
|
||||
ProtoMessageTypeDeviceAdd
|
||||
ProtoMessageTypeDeviceRemove
|
||||
ProtoMessageTypeDevicePaired
|
||||
_ // `7`
|
||||
ProtoMessageTypePlist
|
||||
)
|
||||
|
||||
type MessageType string
|
||||
|
||||
const (
|
||||
MessageTypeResult MessageType = "Result"
|
||||
MessageTypeConnect MessageType = "Connect"
|
||||
MessageTypeListen MessageType = "Listen"
|
||||
MessageTypeDeviceAdd MessageType = "Attached"
|
||||
MessageTypeDeviceRemove MessageType = "Detached"
|
||||
MessageTypeReadBUID MessageType = "ReadBUID"
|
||||
MessageTypeReadPairRecord MessageType = "ReadPairRecord"
|
||||
MessageTypeSavePairRecord MessageType = "SavePairRecord"
|
||||
MessageTypeDeletePairRecord MessageType = "DeletePairRecord"
|
||||
MessageTypeDeviceList MessageType = "ListDevices"
|
||||
)
|
||||
|
||||
type BaseDevice struct {
|
||||
MessageType MessageType `plist:"MessageType"`
|
||||
DeviceID int `plist:"DeviceID"`
|
||||
Properties DeviceProperties `plist:"Properties"`
|
||||
}
|
||||
|
||||
type DeviceProperties struct {
|
||||
DeviceID int `plist:"DeviceID"`
|
||||
ConnectionType string `plist:"ConnectionType"`
|
||||
ConnectionSpeed int `plist:"ConnectionSpeed"`
|
||||
ProductID int `plist:"ProductID"`
|
||||
LocationID int `plist:"LocationID"`
|
||||
SerialNumber string `plist:"SerialNumber"`
|
||||
UDID string `plist:"UDID"`
|
||||
USBSerialNumber string `plist:"USBSerialNumber"`
|
||||
|
||||
EscapedFullServiceName string `plist:"EscapedFullServiceName"`
|
||||
InterfaceIndex int `plist:"InterfaceIndex"`
|
||||
NetworkAddress []byte `plist:"NetworkAddress"`
|
||||
}
|
||||
|
||||
func NewUsbmuxClient(timeout ...time.Duration) (c *UsbmuxClient, err error) {
|
||||
if len(timeout) == 0 {
|
||||
timeout = []time.Duration{DefaultDeadlineTimeout}
|
||||
}
|
||||
c = &UsbmuxClient{version: ProtoVersionPlist}
|
||||
var conn net.Conn
|
||||
if conn, err = rawDial(timeout[0]); err != nil {
|
||||
return nil, fmt.Errorf("usbmux connect: %w", err)
|
||||
}
|
||||
|
||||
c.innerConn = newInnerConn(conn, timeout[0])
|
||||
return
|
||||
}
|
||||
|
||||
type UsbmuxClient struct {
|
||||
innerConn InnerConn
|
||||
version ProtoVersion
|
||||
tag uint32
|
||||
}
|
||||
|
||||
func (c *UsbmuxClient) NewBasicRequest(msgType MessageType) *BasicRequest {
|
||||
return &BasicRequest{
|
||||
MessageType: msgType,
|
||||
BundleID: BundleID,
|
||||
ProgramName: ProgramName,
|
||||
ClientVersionString: ClientVersion,
|
||||
LibUSBMuxVersion: LibUSBMuxVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UsbmuxClient) NewConnectRequest(deviceID, port int) *ConnectRequest {
|
||||
return &ConnectRequest{
|
||||
BasicRequest: *c.NewBasicRequest(MessageTypeConnect),
|
||||
DeviceID: deviceID,
|
||||
PortNumber: ((port << 8) & 0xFF00) | (port >> 8),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UsbmuxClient) NewReadPairRecordRequest(udid string) *ReadPairRecordRequest {
|
||||
return &ReadPairRecordRequest{
|
||||
BasicRequest: *c.NewBasicRequest(MessageTypeReadPairRecord),
|
||||
PairRecordID: udid,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UsbmuxClient) NewSavePairRecordRequest(udid string, deviceID int, data []byte) *SavePairRecordRequest {
|
||||
return &SavePairRecordRequest{
|
||||
BasicRequest: *c.NewBasicRequest(MessageTypeSavePairRecord),
|
||||
PairRecordID: udid,
|
||||
PairRecordData: data,
|
||||
DeviceID: deviceID,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UsbmuxClient) NewDeletePairRecordRequest(udid string) *DeletePairRecordRequest {
|
||||
return &DeletePairRecordRequest{
|
||||
BasicRequest: *c.NewBasicRequest(MessageTypeDeletePairRecord),
|
||||
PairRecordID: udid,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UsbmuxClient) NewPacket(protoMsgType ProtoMessageType) Packet {
|
||||
return c.newPacket(protoMsgType)
|
||||
}
|
||||
|
||||
func (c *UsbmuxClient) newPacket(protoMsgType ProtoMessageType) *packet {
|
||||
c.tag++
|
||||
pkt := &packet{
|
||||
version: c.version,
|
||||
msgType: protoMsgType,
|
||||
tag: c.tag,
|
||||
}
|
||||
return pkt
|
||||
}
|
||||
|
||||
func (c *UsbmuxClient) NewPlistPacket(req interface{}) (Packet, error) {
|
||||
pkt := c.newPacket(ProtoMessageTypePlist)
|
||||
if buf, err := plist.Marshal(req, plist.XMLFormat); err != nil {
|
||||
return nil, fmt.Errorf("plist packet marshal: %w", err)
|
||||
} else {
|
||||
pkt.body = buf
|
||||
}
|
||||
pkt.length = uint32(len(pkt.body) + 4*4)
|
||||
return pkt, nil
|
||||
}
|
||||
|
||||
func (c *UsbmuxClient) SendPacket(pkt Packet) (err error) {
|
||||
var raw []byte
|
||||
if raw, err = pkt.Pack(); err != nil {
|
||||
return fmt.Errorf("usbmux send: %w", err)
|
||||
}
|
||||
// debugLog(fmt.Sprintf("--> Length: %d, Version: %d, Type: %d, Tag: %d\n%s\n", pkt.Length(), pkt.Version(), pkt.Type(), pkt.Tag(), pkt.Body()))
|
||||
debugLog(fmt.Sprintf("--> %s\n", pkt))
|
||||
return c.innerConn.Write(raw)
|
||||
}
|
||||
|
||||
func (c *UsbmuxClient) ReceivePacket() (respPkt Packet, err error) {
|
||||
var bufLen []byte
|
||||
if bufLen, err = c.innerConn.Read(4); err != nil {
|
||||
return nil, fmt.Errorf("usbmux receive: %w", err)
|
||||
}
|
||||
lenPkg := binary.LittleEndian.Uint32(bufLen)
|
||||
|
||||
buffer := bytes.NewBuffer([]byte{})
|
||||
buffer.Write(bufLen)
|
||||
|
||||
var buf []byte
|
||||
if buf, err = c.innerConn.Read(int(lenPkg - 4)); err != nil {
|
||||
return nil, fmt.Errorf("usbmux receive: %w", err)
|
||||
}
|
||||
buffer.Write(buf)
|
||||
|
||||
if respPkt, err = new(packet).Unpack(buffer); err != nil {
|
||||
return nil, fmt.Errorf("usbmux receive: %w", err)
|
||||
}
|
||||
|
||||
// debugLog(fmt.Sprintf("<-- Length: %d, Version: %d, Type: %d, Tag: %d\n%s\n", respPkt.Length(), respPkt.Version(), respPkt.Type(), respPkt.Tag(), respPkt.Body()))
|
||||
debugLog(fmt.Sprintf("<-- %s\n", respPkt))
|
||||
|
||||
reply := struct {
|
||||
MessageType string `plist:"MessageType"`
|
||||
Number ReplyCode `plist:"Number"`
|
||||
}{}
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return nil, fmt.Errorf("usbmux receive: %w", err)
|
||||
}
|
||||
|
||||
if reply.Number != ReplyCodeOK {
|
||||
return nil, fmt.Errorf("usbmux receive: %s", reply.Number.String())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *UsbmuxClient) Close() {
|
||||
c.innerConn.Close()
|
||||
}
|
||||
|
||||
func (c *UsbmuxClient) RawConn() net.Conn {
|
||||
return c.innerConn.RawConn()
|
||||
}
|
||||
|
||||
func (c *UsbmuxClient) InnerConn() InnerConn {
|
||||
return c.innerConn
|
||||
}
|
||||
|
||||
func rawDial(timeout time.Duration) (net.Conn, error) {
|
||||
dialer := net.Dialer{
|
||||
Timeout: timeout,
|
||||
}
|
||||
|
||||
var network, address string
|
||||
switch runtime.GOOS {
|
||||
case "darwin", "android", "linux":
|
||||
network, address = "unix", "/var/run/usbmuxd"
|
||||
case "windows":
|
||||
network, address = "tcp", "127.0.0.1:27015"
|
||||
default:
|
||||
return nil, fmt.Errorf("raw dial: unsupported system: %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
return dialer.Dial(network, address)
|
||||
}
|
||||
|
||||
type InnerConn interface {
|
||||
Write(data []byte) (err error)
|
||||
Read(length int) (data []byte, err error)
|
||||
Handshake(version []int, pairRecord *PairRecord) (err error)
|
||||
DismissSSL() (err error)
|
||||
Close()
|
||||
RawConn() net.Conn
|
||||
Timeout(time.Duration)
|
||||
}
|
||||
|
||||
func newInnerConn(conn net.Conn, timeout time.Duration) InnerConn {
|
||||
return &safeConn{
|
||||
conn: conn,
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
type safeConn struct {
|
||||
conn net.Conn
|
||||
sslConn *tls.Conn
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (c *safeConn) Write(data []byte) (err error) {
|
||||
conn := c.RawConn()
|
||||
if c.timeout <= 0 {
|
||||
err = conn.SetWriteDeadline(time.Time{})
|
||||
} else {
|
||||
err = conn.SetWriteDeadline(time.Now().Add(c.timeout))
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for totalSent := 0; totalSent < len(data); {
|
||||
var sent int
|
||||
if sent, err = conn.Write(data[totalSent:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if sent == 0 {
|
||||
return err
|
||||
}
|
||||
totalSent += sent
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *safeConn) Read(length int) (data []byte, err error) {
|
||||
conn := c.RawConn()
|
||||
if c.timeout <= 0 {
|
||||
err = conn.SetReadDeadline(time.Time{})
|
||||
} else {
|
||||
err = conn.SetReadDeadline(time.Now().Add(c.timeout))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data = make([]byte, 0, length)
|
||||
for len(data) < length {
|
||||
buf := make([]byte, length-len(data))
|
||||
_n, _err := 0, error(nil)
|
||||
if _n, _err = conn.Read(buf); _err != nil && _n == 0 {
|
||||
return nil, _err
|
||||
}
|
||||
data = append(data, buf[:_n]...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *safeConn) Handshake(version []int, pairRecord *PairRecord) (err error) {
|
||||
minVersion := uint16(tls.VersionTLS11)
|
||||
maxVersion := uint16(tls.VersionTLS11)
|
||||
|
||||
if version[0] > 10 {
|
||||
minVersion = tls.VersionTLS11
|
||||
maxVersion = tls.VersionTLS13
|
||||
}
|
||||
|
||||
cert, err := tls.X509KeyPair(pairRecord.RootCertificate, pairRecord.RootPrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
InsecureSkipVerify: true,
|
||||
MinVersion: minVersion,
|
||||
MaxVersion: maxVersion,
|
||||
}
|
||||
|
||||
c.sslConn = tls.Client(c.conn, config)
|
||||
|
||||
if err = c.sslConn.Handshake(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *safeConn) DismissSSL() (err error) {
|
||||
if c.sslConn != nil {
|
||||
// err = c.sslConn.CloseWrite()
|
||||
// if err = c.sslConn.CloseWrite(); err != nil {
|
||||
// return err
|
||||
// }
|
||||
c.sslConn = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *safeConn) Close() {
|
||||
if c.sslConn != nil {
|
||||
if err := c.sslConn.Close(); err != nil {
|
||||
debugLog(fmt.Sprintf("close: %s", err))
|
||||
}
|
||||
}
|
||||
if c.conn != nil {
|
||||
if err := c.conn.Close(); err != nil {
|
||||
debugLog(fmt.Sprintf("close: %s", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RawConn `sslConn` first
|
||||
func (c *safeConn) RawConn() net.Conn {
|
||||
if c.sslConn != nil {
|
||||
return c.sslConn
|
||||
}
|
||||
return c.conn
|
||||
}
|
||||
|
||||
func (c *safeConn) Timeout(duration time.Duration) {
|
||||
c.timeout = duration
|
||||
}
|
||||
37
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsarray.go
Normal file
37
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsarray.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package nskeyedarchiver
|
||||
|
||||
import "howett.net/plist"
|
||||
|
||||
type NSArray struct {
|
||||
internal []interface{}
|
||||
}
|
||||
|
||||
func NewNSArray(value []interface{}) *NSArray {
|
||||
return &NSArray{
|
||||
internal: value,
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *NSArray) archive(objects []interface{}) []interface{} {
|
||||
objs := make([]interface{}, 0, len(ns.internal))
|
||||
|
||||
info := map[string]interface{}{}
|
||||
objects = append(objects, info)
|
||||
|
||||
for _, v := range ns.internal {
|
||||
var uid plist.UID
|
||||
objects, uid = archive(objects, v)
|
||||
objs = append(objs, uid)
|
||||
}
|
||||
|
||||
info["NS.objects"] = objs
|
||||
info["$class"] = plist.UID(len(objects))
|
||||
|
||||
cls := map[string]interface{}{
|
||||
"$classname": "NSArray",
|
||||
"$classes": []interface{}{"NSArray", "NSObject"},
|
||||
}
|
||||
objects = append(objects, cls)
|
||||
|
||||
return objects
|
||||
}
|
||||
20
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsarray_test.go
Normal file
20
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsarray_test.go
Normal file
@@ -0,0 +1,20 @@
|
||||
//go:build localtest
|
||||
|
||||
package nskeyedarchiver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNSArray_archive(t *testing.T) {
|
||||
objs := make([]interface{}, 0, 1)
|
||||
value := []interface{}{
|
||||
"a", 1,
|
||||
"b", "2",
|
||||
"c", false,
|
||||
}
|
||||
array := NewNSArray(value)
|
||||
objects := array.archive(objs)
|
||||
fmt.Println(objects)
|
||||
}
|
||||
44
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsdictionary.go
Normal file
44
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsdictionary.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package nskeyedarchiver
|
||||
|
||||
import (
|
||||
"howett.net/plist"
|
||||
)
|
||||
|
||||
type NSDictionary struct {
|
||||
internal map[string]interface{}
|
||||
}
|
||||
|
||||
func NewNSDictionary(value map[string]interface{}) *NSDictionary {
|
||||
return &NSDictionary{
|
||||
internal: value,
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *NSDictionary) archive(objects []interface{}) []interface{} {
|
||||
keys := make([]interface{}, 0, len(ns.internal))
|
||||
objs := make([]interface{}, 0, len(ns.internal))
|
||||
|
||||
info := map[string]interface{}{}
|
||||
objects = append(objects, info)
|
||||
|
||||
for k, v := range ns.internal {
|
||||
uid := plist.UID(len(objects))
|
||||
keys = append(keys, uid)
|
||||
objects = append(objects, k)
|
||||
|
||||
objects, uid = archive(objects, v)
|
||||
objs = append(objs, uid)
|
||||
}
|
||||
|
||||
info["NS.keys"] = keys
|
||||
info["NS.objects"] = objs
|
||||
info["$class"] = plist.UID(len(objects))
|
||||
|
||||
cls := map[string]interface{}{
|
||||
"$classname": "NSDictionary",
|
||||
"$classes": []interface{}{"NSDictionary", "NSObject"},
|
||||
}
|
||||
objects = append(objects, cls)
|
||||
|
||||
return objects
|
||||
}
|
||||
20
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsdictionary_test.go
Normal file
20
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsdictionary_test.go
Normal file
@@ -0,0 +1,20 @@
|
||||
//go:build localtest
|
||||
|
||||
package nskeyedarchiver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNSDictionary_archive(t *testing.T) {
|
||||
objs := make([]interface{}, 0, 1)
|
||||
value := map[string]interface{}{
|
||||
"a": 1,
|
||||
"b": "2",
|
||||
"c": true,
|
||||
}
|
||||
dict := NewNSDictionary(value)
|
||||
objects := dict.archive(objs)
|
||||
fmt.Println(objects)
|
||||
}
|
||||
80
hrp/pkg/gidevice/pkg/nskeyedarchiver/nskeyedarchiver.go
Normal file
80
hrp/pkg/gidevice/pkg/nskeyedarchiver/nskeyedarchiver.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package nskeyedarchiver
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"howett.net/plist"
|
||||
)
|
||||
|
||||
func Marshal(obj interface{}) (raw []byte, err error) {
|
||||
objects := []interface{}{"$null"}
|
||||
objects, _ = archive(objects, obj)
|
||||
archiver := map[string]interface{}{
|
||||
"$version": 100000,
|
||||
"$archiver": "NSKeyedArchiver",
|
||||
"$top": map[string]interface{}{"root": plist.UID(1)},
|
||||
"$objects": objects,
|
||||
}
|
||||
// if len(format) == 0 {
|
||||
// format = []int{plist.BinaryFormat}
|
||||
// }
|
||||
// return plist.Marshal(archiver, format[0])
|
||||
return plist.Marshal(archiver, plist.BinaryFormat)
|
||||
}
|
||||
|
||||
func archive(_objects []interface{}, _value interface{}) (objects []interface{}, uid plist.UID) {
|
||||
val := reflect.ValueOf(_value)
|
||||
typ := val.Type()
|
||||
|
||||
switch typ.Kind() {
|
||||
case reflect.String,
|
||||
reflect.Bool,
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Uintptr:
|
||||
uid = plist.UID(len(_objects))
|
||||
objects = append(_objects, _value)
|
||||
return
|
||||
case reflect.Map:
|
||||
uid = plist.UID(len(_objects))
|
||||
vv := make(map[string]interface{})
|
||||
keys := val.MapKeys()
|
||||
for _, k := range keys {
|
||||
vv[k.String()] = val.MapIndex(k).Interface()
|
||||
}
|
||||
objects = NewNSDictionary(vv).archive(_objects)
|
||||
return
|
||||
case reflect.Slice, reflect.Array:
|
||||
uid = plist.UID(len(_objects))
|
||||
vv := make([]interface{}, val.Len())
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
vv[i] = val.Index(i).Interface()
|
||||
}
|
||||
objects = NewNSArray(vv).archive(_objects)
|
||||
return
|
||||
case reflect.Struct, reflect.Ptr:
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
switch typ.Name() {
|
||||
case "NSUUID":
|
||||
uid = plist.UID(len(_objects))
|
||||
objects = NewNSUUID(val.Field(0).Bytes()).archive(_objects)
|
||||
return
|
||||
case "NSURL":
|
||||
uid = plist.UID(len(_objects))
|
||||
objects = NewNSURL(val.Field(0).String()).archive(_objects)
|
||||
return
|
||||
case "XCTestConfiguration":
|
||||
uid = plist.UID(len(_objects))
|
||||
objects = newXCTestConfiguration(_value).archive(_objects)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TODO unarchive
|
||||
41
hrp/pkg/gidevice/pkg/nskeyedarchiver/nskeyedarchiver_test.go
Normal file
41
hrp/pkg/gidevice/pkg/nskeyedarchiver/nskeyedarchiver_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
//go:build localtest
|
||||
|
||||
package nskeyedarchiver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
uuid "github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
// value := map[string]interface{}{
|
||||
// "a": 1,
|
||||
// "b": "2",
|
||||
// "c": true,
|
||||
// }
|
||||
|
||||
// value := []interface{}{
|
||||
// "a", 1,
|
||||
// "b", "2",
|
||||
// "c", false,
|
||||
// }
|
||||
|
||||
// value := NewNSUUID(uuid.NewV4().Bytes())
|
||||
|
||||
// value := NewNSURL("/tmp")
|
||||
|
||||
value := NewXCTestConfiguration(NewNSUUID(uuid.NewV4().Bytes()), NewNSURL("/tmp"), "", "")
|
||||
|
||||
raw, err := Marshal(value)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, v := range raw {
|
||||
fmt.Printf("%x", v)
|
||||
}
|
||||
fmt.Println()
|
||||
// fmt.Println(raw)
|
||||
}
|
||||
27
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsnull.go
Normal file
27
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsnull.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package nskeyedarchiver
|
||||
|
||||
import (
|
||||
"howett.net/plist"
|
||||
)
|
||||
|
||||
type NSNull struct{}
|
||||
|
||||
func NewNSNull() *NSNull {
|
||||
return &NSNull{}
|
||||
}
|
||||
|
||||
func (ns *NSNull) archive(objects []interface{}) []interface{} {
|
||||
info := map[string]interface{}{}
|
||||
|
||||
objects = append(objects, info)
|
||||
|
||||
info["$class"] = plist.UID(len(objects))
|
||||
|
||||
cls := map[string]interface{}{
|
||||
"$classname": "NSNull",
|
||||
"$classes": []interface{}{"NSNull", "NSObject"},
|
||||
}
|
||||
objects = append(objects, cls)
|
||||
|
||||
return objects
|
||||
}
|
||||
38
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsurl.go
Normal file
38
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsurl.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package nskeyedarchiver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"howett.net/plist"
|
||||
)
|
||||
|
||||
type NSURL struct {
|
||||
internal string
|
||||
}
|
||||
|
||||
func NewNSURL(path string) *NSURL {
|
||||
return &NSURL{
|
||||
internal: path,
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *NSURL) archive(objects []interface{}) []interface{} {
|
||||
info := map[string]interface{}{}
|
||||
|
||||
objects = append(objects, info)
|
||||
|
||||
uid := plist.UID(0)
|
||||
info["NS.base"] = uid
|
||||
objects, uid = archive(objects, fmt.Sprintf("file://%s", ns.internal))
|
||||
info["NS.relative"] = uid
|
||||
|
||||
info["$class"] = plist.UID(len(objects))
|
||||
|
||||
cls := map[string]interface{}{
|
||||
"$classname": "NSURL",
|
||||
"$classes": []interface{}{"NSURL", "NSObject"},
|
||||
}
|
||||
objects = append(objects, cls)
|
||||
|
||||
return objects
|
||||
}
|
||||
15
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsurl_test.go
Normal file
15
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsurl_test.go
Normal file
@@ -0,0 +1,15 @@
|
||||
//go:build localtest
|
||||
|
||||
package nskeyedarchiver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNSURL_archive(t *testing.T) {
|
||||
objs := make([]interface{}, 0, 1)
|
||||
nsurl := NewNSURL("/tmp")
|
||||
objects := nsurl.archive(objs)
|
||||
fmt.Println(objects)
|
||||
}
|
||||
51
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsuuid.go
Normal file
51
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsuuid.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package nskeyedarchiver
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
|
||||
"howett.net/plist"
|
||||
)
|
||||
|
||||
type NSUUID struct {
|
||||
internal []byte
|
||||
}
|
||||
|
||||
func NewNSUUID(uuid []byte) *NSUUID {
|
||||
return &NSUUID{
|
||||
internal: uuid,
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *NSUUID) archive(objects []interface{}) []interface{} {
|
||||
info := map[string]interface{}{
|
||||
"NS.uuidbytes": ns.internal,
|
||||
}
|
||||
|
||||
objects = append(objects, info)
|
||||
|
||||
info["$class"] = plist.UID(len(objects))
|
||||
|
||||
cls := map[string]interface{}{
|
||||
"$classname": "NSUUID",
|
||||
"$classes": []interface{}{"NSUUID", "NSObject"},
|
||||
}
|
||||
objects = append(objects, cls)
|
||||
|
||||
return objects
|
||||
}
|
||||
|
||||
func (ns *NSUUID) String() string {
|
||||
buf := make([]byte, 36)
|
||||
|
||||
hex.Encode(buf[0:8], ns.internal[0:4])
|
||||
buf[8] = '-'
|
||||
hex.Encode(buf[9:13], ns.internal[4:6])
|
||||
buf[13] = '-'
|
||||
hex.Encode(buf[14:18], ns.internal[6:8])
|
||||
buf[18] = '-'
|
||||
hex.Encode(buf[19:23], ns.internal[8:10])
|
||||
buf[23] = '-'
|
||||
hex.Encode(buf[24:], ns.internal[10:])
|
||||
|
||||
return string(buf)
|
||||
}
|
||||
17
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsuuid_test.go
Normal file
17
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsuuid_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
//go:build localtest
|
||||
|
||||
package nskeyedarchiver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
uuid "github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
func TestNSUUID_archive(t *testing.T) {
|
||||
objs := make([]interface{}, 0, 1)
|
||||
nsuuid := NewNSUUID(uuid.NewV4().Bytes())
|
||||
objects := nsuuid.archive(objs)
|
||||
fmt.Println(objects)
|
||||
}
|
||||
10
hrp/pkg/gidevice/pkg/nskeyedarchiver/xctcapabilities.go
Normal file
10
hrp/pkg/gidevice/pkg/nskeyedarchiver/xctcapabilities.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package nskeyedarchiver
|
||||
|
||||
type XCTCapabilities struct {
|
||||
internal map[string]interface{}
|
||||
}
|
||||
|
||||
func (caps *XCTCapabilities) archive(objects []interface{}) []interface{} {
|
||||
// TODO caps
|
||||
return nil
|
||||
}
|
||||
89
hrp/pkg/gidevice/pkg/nskeyedarchiver/xctestconfiguration.go
Normal file
89
hrp/pkg/gidevice/pkg/nskeyedarchiver/xctestconfiguration.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package nskeyedarchiver
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"howett.net/plist"
|
||||
)
|
||||
|
||||
type XCTestConfiguration struct {
|
||||
internal map[string]interface{}
|
||||
}
|
||||
|
||||
func newXCTestConfiguration(cfg interface{}) *XCTestConfiguration {
|
||||
return cfg.(*XCTestConfiguration)
|
||||
}
|
||||
|
||||
func NewXCTestConfiguration(nsuuid *NSUUID, nsurl *NSURL, targetBundleID, targetAppPath string) *XCTestConfiguration {
|
||||
contents := map[string]interface{}{
|
||||
"aggregateStatisticsBeforeCrash": map[string]interface{}{
|
||||
"XCSuiteRecordsKey": map[string]interface{}{},
|
||||
},
|
||||
"automationFrameworkPath": "/Developer/Library/PrivateFrameworks/XCTAutomationSupport.framework",
|
||||
"baselineFileRelativePath": nil,
|
||||
"baselineFileURL": nil,
|
||||
"defaultTestExecutionTimeAllowance": nil,
|
||||
"disablePerformanceMetrics": false,
|
||||
"emitOSLogs": false,
|
||||
"formatVersion": 2,
|
||||
"gatherLocalizableStringsData": false,
|
||||
"initializeForUITesting": true,
|
||||
"maximumTestExecutionTimeAllowance": nil,
|
||||
"productModuleName": "WebDriverAgentRunner", // set to other value is also OK
|
||||
"randomExecutionOrderingSeed": nil,
|
||||
"reportActivities": true,
|
||||
"reportResultsToIDE": true,
|
||||
"systemAttachmentLifetime": 2,
|
||||
"targetApplicationArguments": []interface{}{}, // maybe useless
|
||||
"targetApplicationEnvironment": nil,
|
||||
"targetApplicationPath": targetAppPath,
|
||||
"testApplicationDependencies": map[string]interface{}{},
|
||||
"testApplicationUserOverrides": nil,
|
||||
"testBundleRelativePath": nil,
|
||||
"testExecutionOrdering": 0,
|
||||
"testTimeoutsEnabled": false,
|
||||
"testsDrivenByIDE": false,
|
||||
"testsMustRunOnMainThread": true,
|
||||
"testsToRun": nil,
|
||||
"testsToSkip": nil,
|
||||
"treatMissingBaselinesAsFailures": false,
|
||||
"userAttachmentLifetime": 1,
|
||||
"testBundleURL": nsurl,
|
||||
"sessionIdentifier": nsuuid,
|
||||
"targetApplicationBundleID": targetBundleID,
|
||||
// "targetApplicationBundleID": "",
|
||||
}
|
||||
return &XCTestConfiguration{internal: contents}
|
||||
}
|
||||
|
||||
func (cfg *XCTestConfiguration) archive(objects []interface{}) []interface{} {
|
||||
info := map[string]interface{}{}
|
||||
objects = append(objects, info)
|
||||
|
||||
info["$class"] = plist.UID(len(objects))
|
||||
|
||||
cls := map[string]interface{}{
|
||||
"$classname": "XCTestConfiguration",
|
||||
"$classes": []interface{}{"XCTestConfiguration", "NSObject"},
|
||||
}
|
||||
objects = append(objects, cls)
|
||||
|
||||
for k, v := range cfg.internal {
|
||||
val := reflect.ValueOf(v)
|
||||
if !val.IsValid() {
|
||||
info[k] = plist.UID(0)
|
||||
continue
|
||||
}
|
||||
|
||||
typ := val.Type()
|
||||
|
||||
if k != "formatVersion" && (typ.Kind() == reflect.Bool || typ.Kind() == reflect.Uintptr || typ.Kind() == reflect.Int) {
|
||||
info[k] = v
|
||||
} else {
|
||||
var uid plist.UID
|
||||
objects, uid = archive(objects, v)
|
||||
info[k] = uid
|
||||
}
|
||||
}
|
||||
return objects
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
//go:build localtest
|
||||
|
||||
package nskeyedarchiver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
uuid "github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
func TestXCTestConfiguration_archive(t *testing.T) {
|
||||
objs := make([]interface{}, 0, 1)
|
||||
xcTestConfiguration := NewXCTestConfiguration(NewNSUUID(uuid.NewV4().Bytes()), NewNSURL("/tmp"), "", "")
|
||||
objects := xcTestConfiguration.archive(objs)
|
||||
fmt.Println(objects)
|
||||
}
|
||||
119
hrp/pkg/gidevice/screenshot.go
Normal file
119
hrp/pkg/gidevice/screenshot.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/libimobiledevice"
|
||||
)
|
||||
|
||||
var _ Screenshot = (*screenshot)(nil)
|
||||
|
||||
func newScreenshot(client *libimobiledevice.ScreenshotClient) *screenshot {
|
||||
return &screenshot{
|
||||
client: client,
|
||||
exchanged: false,
|
||||
}
|
||||
}
|
||||
|
||||
type screenshot struct {
|
||||
client *libimobiledevice.ScreenshotClient
|
||||
exchanged bool
|
||||
}
|
||||
|
||||
func (s *screenshot) Take() (raw *bytes.Buffer, err error) {
|
||||
if err = s.exchange(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// link service
|
||||
req := []interface{}{
|
||||
"DLMessageProcessMessage",
|
||||
map[string]interface{}{
|
||||
"MessageType": "ScreenShotRequest",
|
||||
},
|
||||
}
|
||||
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = s.client.NewBinaryPacket(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = s.client.SendPacket(pkt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var respPkt libimobiledevice.Packet
|
||||
if respPkt, err = s.client.ReceivePacket(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp []interface{}
|
||||
if err = respPkt.Unmarshal(&resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp[0].(string) != "DLMessageProcessMessage" {
|
||||
return nil, fmt.Errorf("message device not ready %s %s", resp[3], resp[4])
|
||||
}
|
||||
|
||||
raw = new(bytes.Buffer)
|
||||
|
||||
screen := resp[1].(map[string]interface{})
|
||||
var data []byte
|
||||
ok := false
|
||||
if data, ok = screen["ScreenShotData"].([]byte); !ok {
|
||||
return nil, errors.New("`ScreenShotData` not ready")
|
||||
}
|
||||
if _, err = raw.Write(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *screenshot) exchange() (err error) {
|
||||
if s.exchanged {
|
||||
return
|
||||
}
|
||||
|
||||
var respPkt libimobiledevice.Packet
|
||||
if respPkt, err = s.client.ReceivePacket(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var resp []interface{}
|
||||
if err = respPkt.Unmarshal(&resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := []interface{}{
|
||||
"DLMessageVersionExchange",
|
||||
"DLVersionsOk",
|
||||
resp[1],
|
||||
}
|
||||
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = s.client.NewBinaryPacket(req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = s.client.SendPacket(pkt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if respPkt, err = s.client.ReceivePacket(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = respPkt.Unmarshal(&resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp[3].(string) != "DLMessageDeviceReady" {
|
||||
return fmt.Errorf("message device not ready %s", resp[3])
|
||||
}
|
||||
|
||||
s.exchanged = true
|
||||
return
|
||||
}
|
||||
58
hrp/pkg/gidevice/screenshot_test.go
Normal file
58
hrp/pkg/gidevice/screenshot_test.go
Normal file
@@ -0,0 +1,58 @@
|
||||
//go:build localtest
|
||||
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var screenshotSrv Screenshot
|
||||
|
||||
func setupScreenshotSrv(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
var err error
|
||||
if lockdownSrv, err = dev.lockdownService(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if screenshotSrv, err = lockdownSrv.ScreenshotService(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_screenshot_Take(t *testing.T) {
|
||||
setupScreenshotSrv(t)
|
||||
|
||||
// raw, err := dev.Screenshot()
|
||||
raw, err := screenshotSrv.Take()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = raw
|
||||
|
||||
img, format, err := image.Decode(raw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
userHomeDir, _ := os.UserHomeDir()
|
||||
file, err := os.Create(userHomeDir + "/Desktop/s1." + format)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() { _ = file.Close() }()
|
||||
switch format {
|
||||
case "png":
|
||||
err = png.Encode(file, img)
|
||||
case "jpeg":
|
||||
err = jpeg.Encode(file, img, nil)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(file.Name())
|
||||
}
|
||||
27
hrp/pkg/gidevice/simulatelocation.go
Normal file
27
hrp/pkg/gidevice/simulatelocation.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package gidevice
|
||||
|
||||
import "github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/libimobiledevice"
|
||||
|
||||
var _ SimulateLocation = (*simulateLocation)(nil)
|
||||
|
||||
func newSimulateLocation(client *libimobiledevice.SimulateLocationClient) *simulateLocation {
|
||||
return &simulateLocation{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
type simulateLocation struct {
|
||||
client *libimobiledevice.SimulateLocationClient
|
||||
}
|
||||
|
||||
func (s *simulateLocation) Update(longitude float64, latitude float64, coordinateSystem ...CoordinateSystem) (err error) {
|
||||
if len(coordinateSystem) == 0 {
|
||||
coordinateSystem = []CoordinateSystem{CoordinateSystemWGS84}
|
||||
}
|
||||
pkt := s.client.NewLocationPacket(longitude, latitude, coordinateSystem[0])
|
||||
return s.client.SendPacket(pkt)
|
||||
}
|
||||
|
||||
func (s *simulateLocation) Recover() (err error) {
|
||||
return s.client.Recover()
|
||||
}
|
||||
50
hrp/pkg/gidevice/simulatelocation_test.go
Normal file
50
hrp/pkg/gidevice/simulatelocation_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
//go:build localtest
|
||||
|
||||
package gidevice
|
||||
|
||||
import "testing"
|
||||
|
||||
var simulateLocationSrv SimulateLocation
|
||||
|
||||
func setupSimulateLocationSrv(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
var err error
|
||||
if lockdownSrv, err = dev.lockdownService(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if simulateLocationSrv, err = lockdownSrv.SimulateLocationService(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_simulateLocation_Update(t *testing.T) {
|
||||
setupSimulateLocationSrv(t)
|
||||
|
||||
// https://api.map.baidu.com/lbsapi/getpoint/index.html
|
||||
// if err := dev.SimulateLocationUpdate(116.024067, 40.362639, CoordinateSystemBD09); err != nil {
|
||||
if err := simulateLocationSrv.Update(116.024067, 40.362639, CoordinateSystemBD09); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// https://developer.amap.com/tools/picker
|
||||
// https://lbs.qq.com/tool/getpoint/index.html
|
||||
// if err := simulateLocationSrv.Update(120.116979,30.252876, CoordinateSystemGCJ02); err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
|
||||
if err := simulateLocationSrv.Update(121.499763, 31.239580); err != nil {
|
||||
// if err := simulateLocationSrv.Update(121.499763, 31.239580, CoordinateSystemWGS84); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_simulateLocation_Recover(t *testing.T) {
|
||||
setupSimulateLocationSrv(t)
|
||||
|
||||
// if err := dev.SimulateLocationRecover(); err != nil {
|
||||
if err := simulateLocationSrv.Recover(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
68
hrp/pkg/gidevice/springboard.go
Normal file
68
hrp/pkg/gidevice/springboard.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/libimobiledevice"
|
||||
)
|
||||
|
||||
func newSpringBoard(client *libimobiledevice.SpringBoardClient) *springboard {
|
||||
return &springboard{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
type springboard struct {
|
||||
client *libimobiledevice.SpringBoardClient
|
||||
}
|
||||
|
||||
func (s springboard) GetIconPNGData(bundleId string) (raw *bytes.Buffer, err error) {
|
||||
var pkt libimobiledevice.Packet
|
||||
req := map[string]interface{}{
|
||||
"command": "getIconPNGData",
|
||||
"bundleId": bundleId,
|
||||
}
|
||||
if pkt, err = s.client.NewBinaryPacket(req); err != nil {
|
||||
return
|
||||
}
|
||||
if err = s.client.SendPacket(pkt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var respPkt libimobiledevice.Packet
|
||||
if respPkt, err = s.client.ReceivePacket(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var reply libimobiledevice.IconPNGDataResponse
|
||||
raw = new(bytes.Buffer)
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return nil, fmt.Errorf("receive packet: %w", err)
|
||||
}
|
||||
if _, err = raw.Write(reply.PNGData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s springboard) GetInterfaceOrientation() (orientation libimobiledevice.OrientationState, err error) {
|
||||
var pkt libimobiledevice.Packet
|
||||
req := map[string]interface{}{
|
||||
"command": "getInterfaceOrientation",
|
||||
}
|
||||
if pkt, err = s.client.NewBinaryPacket(req); err != nil {
|
||||
return
|
||||
}
|
||||
if err = s.client.SendPacket(pkt); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var respPkt libimobiledevice.Packet
|
||||
if respPkt, err = s.client.ReceivePacket(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var reply libimobiledevice.InterfaceOrientationResponse
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return 0, fmt.Errorf("receive packet: %w", err)
|
||||
}
|
||||
orientation = reply.Orientation
|
||||
return
|
||||
}
|
||||
55
hrp/pkg/gidevice/springboard_test.go
Normal file
55
hrp/pkg/gidevice/springboard_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
//go:build localtest
|
||||
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var springBoardSrv SpringBoard
|
||||
|
||||
func setupSpringBoardSrv(t *testing.T) {
|
||||
setupLockdownSrv(t)
|
||||
|
||||
var err error
|
||||
if lockdownSrv, err = dev.lockdownService(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if springBoardSrv, err = lockdownSrv.SpringBoardService(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_springBoard_GetIcon(t *testing.T) {
|
||||
setupSpringBoardSrv(t)
|
||||
raw, _ := springBoardSrv.GetIconPNGData("com.tencent.xin")
|
||||
img, format, err := image.Decode(raw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
file, err := os.Create("./abc." + format)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() { _ = file.Close() }()
|
||||
switch format {
|
||||
case "png":
|
||||
err = png.Encode(file, img)
|
||||
case "jpeg":
|
||||
err = jpeg.Encode(file, img, nil)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_springBoard_GetOrient(t *testing.T) {
|
||||
setupSpringBoardSrv(t)
|
||||
fmt.Println(springBoardSrv.GetInterfaceOrientation())
|
||||
}
|
||||
85
hrp/pkg/gidevice/syslogrelay.go
Normal file
85
hrp/pkg/gidevice/syslogrelay.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/libimobiledevice"
|
||||
)
|
||||
|
||||
var _ SyslogRelay = (*syslogRelay)(nil)
|
||||
|
||||
func newSyslogRelay(client *libimobiledevice.SyslogRelayClient) *syslogRelay {
|
||||
r := &syslogRelay{
|
||||
client: client,
|
||||
stop: make(chan bool),
|
||||
isReading: false,
|
||||
}
|
||||
r.reader = bufio.NewReader(r.client.InnerConn().RawConn())
|
||||
return r
|
||||
}
|
||||
|
||||
type syslogRelay struct {
|
||||
client *libimobiledevice.SyslogRelayClient
|
||||
|
||||
reader *bufio.Reader
|
||||
stop chan bool
|
||||
isReading bool
|
||||
}
|
||||
|
||||
func (r *syslogRelay) Lines() <-chan string {
|
||||
out := make(chan string)
|
||||
r.isReading = true
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
close(out)
|
||||
r.isReading = false
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-r.stop:
|
||||
return
|
||||
default:
|
||||
bs, err := r.readLine()
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), io.EOF.Error()) {
|
||||
return
|
||||
}
|
||||
debugLog(fmt.Sprintf("syslog: %s", err))
|
||||
}
|
||||
if len(bs) > 1 && bs[0] == 0 {
|
||||
bs = bs[1:]
|
||||
}
|
||||
out <- string(bs)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return out
|
||||
}
|
||||
|
||||
func (r *syslogRelay) Stop() {
|
||||
if r.isReading {
|
||||
r.stop <- true
|
||||
}
|
||||
}
|
||||
|
||||
func (r *syslogRelay) readLine() ([]byte, error) {
|
||||
var line []byte
|
||||
for {
|
||||
l, more, err := r.reader.ReadLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if line == nil && !more {
|
||||
return l, nil
|
||||
}
|
||||
line = append(line, l...)
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
return line, nil
|
||||
}
|
||||
50
hrp/pkg/gidevice/testmanagerd.go
Normal file
50
hrp/pkg/gidevice/testmanagerd.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/libimobiledevice"
|
||||
)
|
||||
|
||||
var _ Testmanagerd = (*testmanagerd)(nil)
|
||||
|
||||
func newTestmanagerd(client *libimobiledevice.TestmanagerdClient, iOSVersion []int) *testmanagerd {
|
||||
return &testmanagerd{
|
||||
client: client,
|
||||
iOSVersion: iOSVersion,
|
||||
}
|
||||
}
|
||||
|
||||
type testmanagerd struct {
|
||||
client *libimobiledevice.TestmanagerdClient
|
||||
iOSVersion []int
|
||||
}
|
||||
|
||||
func (t *testmanagerd) notifyOfPublishedCapabilities() (err error) {
|
||||
_, err = t.client.Connection()
|
||||
return
|
||||
}
|
||||
|
||||
func (t *testmanagerd) requestChannel(channel string) (id uint32, err error) {
|
||||
return t.client.MakeChannel(channel)
|
||||
}
|
||||
|
||||
func (t *testmanagerd) newXCTestManagerDaemon() (xcTestManager XCTestManagerDaemon, err error) {
|
||||
var channelCode uint32
|
||||
if channelCode, err = t.requestChannel("dtxproxy:XCTestManager_IDEInterface:XCTestManager_DaemonConnectionInterface"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
xcTestManager = newXcTestManagerDaemon(t, channelCode)
|
||||
return
|
||||
}
|
||||
|
||||
func (t *testmanagerd) invoke(selector string, args *libimobiledevice.AuxBuffer, channelCode uint32, expectsReply bool) (
|
||||
result *libimobiledevice.DTXMessageResult, err error) {
|
||||
return t.client.Invoke(selector, args, channelCode, expectsReply)
|
||||
}
|
||||
|
||||
func (t *testmanagerd) registerCallback(obj string, cb func(m libimobiledevice.DTXMessageResult)) {
|
||||
t.client.RegisterCallback(obj, cb)
|
||||
}
|
||||
|
||||
func (t *testmanagerd) close() {
|
||||
t.client.Close()
|
||||
}
|
||||
153
hrp/pkg/gidevice/usbmux.go
Normal file
153
hrp/pkg/gidevice/usbmux.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/libimobiledevice"
|
||||
)
|
||||
|
||||
var _ Usbmux = (*usbmux)(nil)
|
||||
|
||||
func NewUsbmux() (Usbmux, error) {
|
||||
umClient, err := libimobiledevice.NewUsbmuxClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &usbmux{client: umClient}, nil
|
||||
}
|
||||
|
||||
func newUsbmux(client *libimobiledevice.UsbmuxClient) *usbmux {
|
||||
return &usbmux{client: client}
|
||||
}
|
||||
|
||||
type usbmux struct {
|
||||
client *libimobiledevice.UsbmuxClient
|
||||
}
|
||||
|
||||
func (um *usbmux) Devices() (devices []Device, err error) {
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = um.client.NewPlistPacket(
|
||||
um.client.NewBasicRequest(libimobiledevice.MessageTypeDeviceList),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = um.client.SendPacket(pkt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var respPkt libimobiledevice.Packet
|
||||
if respPkt, err = um.client.ReceivePacket(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reply := struct {
|
||||
DeviceList []libimobiledevice.BaseDevice `plist:"DeviceList"`
|
||||
}{}
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
devices = make([]Device, len(reply.DeviceList))
|
||||
for i := range reply.DeviceList {
|
||||
dev := reply.DeviceList[i]
|
||||
devices[i] = newDevice(um.client, dev.Properties)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (um *usbmux) ReadBUID() (buid string, err error) {
|
||||
var pktReadBUID libimobiledevice.Packet
|
||||
if pktReadBUID, err = um.client.NewPlistPacket(
|
||||
um.client.NewBasicRequest(libimobiledevice.MessageTypeReadBUID),
|
||||
); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = um.client.SendPacket(pktReadBUID); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
respPkt, err := um.client.ReceivePacket()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
reply := struct {
|
||||
BUID string `plist:"BUID"`
|
||||
}{}
|
||||
if err = respPkt.Unmarshal(&reply); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buid = reply.BUID
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (um *usbmux) Listen(devNotifier chan Device) (context.CancelFunc, error) {
|
||||
baseDevNotifier := make(chan libimobiledevice.BaseDevice)
|
||||
ctx, cancelFunc, err := um.listen(baseDevNotifier)
|
||||
go func(ctx context.Context) {
|
||||
defer close(devNotifier)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case baseDev := <-baseDevNotifier:
|
||||
if baseDev.MessageType != libimobiledevice.MessageTypeDeviceAdd {
|
||||
baseDev.Properties.DeviceID = baseDev.DeviceID
|
||||
}
|
||||
client, err := libimobiledevice.NewUsbmuxClient()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
devNotifier <- newDevice(client, baseDev.Properties)
|
||||
}
|
||||
}
|
||||
}(ctx)
|
||||
return cancelFunc, err
|
||||
}
|
||||
|
||||
func (um *usbmux) listen(devNotifier chan libimobiledevice.BaseDevice) (ctx context.Context, cancelFunc context.CancelFunc, err error) {
|
||||
var pkt libimobiledevice.Packet
|
||||
if pkt, err = um.client.NewPlistPacket(
|
||||
um.client.NewBasicRequest(libimobiledevice.MessageTypeListen),
|
||||
); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if err = um.client.SendPacket(pkt); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
ctx, cancelFunc = context.WithCancel(context.Background())
|
||||
|
||||
go func(ctx context.Context) {
|
||||
defer close(devNotifier)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
var respPkt libimobiledevice.Packet
|
||||
if respPkt, err = um.client.ReceivePacket(); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
var replyDevice libimobiledevice.BaseDevice
|
||||
if err = respPkt.Unmarshal(&replyDevice); err != nil {
|
||||
break
|
||||
}
|
||||
if replyDevice.MessageType == libimobiledevice.MessageTypeResult {
|
||||
break
|
||||
}
|
||||
|
||||
devNotifier <- replyDevice
|
||||
}
|
||||
}
|
||||
}(ctx)
|
||||
|
||||
return ctx, cancelFunc, nil
|
||||
}
|
||||
70
hrp/pkg/gidevice/usbmux_test.go
Normal file
70
hrp/pkg/gidevice/usbmux_test.go
Normal file
@@ -0,0 +1,70 @@
|
||||
//go:build localtest
|
||||
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/libimobiledevice"
|
||||
)
|
||||
|
||||
var um Usbmux
|
||||
|
||||
func setupUsbmux(t *testing.T) {
|
||||
var err error
|
||||
um, err = NewUsbmux()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_usbmux_Devices(t *testing.T) {
|
||||
setupUsbmux(t)
|
||||
|
||||
devices, err := um.Devices()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, dev := range devices {
|
||||
t.Log(dev.Properties().SerialNumber, dev.Properties().ProductID, dev.Properties().DeviceID)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_usbmux_ReadBUID(t *testing.T) {
|
||||
setupUsbmux(t)
|
||||
|
||||
buid, err := um.ReadBUID()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Log(buid)
|
||||
}
|
||||
|
||||
func Test_usbmux_Listen(t *testing.T) {
|
||||
setupUsbmux(t)
|
||||
|
||||
devNotifier := make(chan Device)
|
||||
cancelFunc, err := um.Listen(devNotifier)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(20 * time.Second)
|
||||
cancelFunc()
|
||||
}()
|
||||
|
||||
for dev := range devNotifier {
|
||||
if dev.Properties().ConnectionType != "" {
|
||||
t.Log(dev.Properties().SerialNumber, dev.Properties().ProductID, dev.Properties().DeviceID)
|
||||
} else {
|
||||
t.Log(libimobiledevice.MessageTypeDeviceRemove, dev.Properties().DeviceID)
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
t.Log("Done")
|
||||
}
|
||||
153
hrp/pkg/gidevice/xctestmanagerdaemon.go
Normal file
153
hrp/pkg/gidevice/xctestmanagerdaemon.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package gidevice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/libimobiledevice"
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/nskeyedarchiver"
|
||||
)
|
||||
|
||||
var _ XCTestManagerDaemon = (*xcTestManagerDaemon)(nil)
|
||||
|
||||
func newXcTestManagerDaemon(testmanagerd Testmanagerd, channelCode uint32) *xcTestManagerDaemon {
|
||||
return &xcTestManagerDaemon{
|
||||
testmanagerd: testmanagerd,
|
||||
channelCode: channelCode,
|
||||
}
|
||||
}
|
||||
|
||||
type xcTestManagerDaemon struct {
|
||||
testmanagerd Testmanagerd
|
||||
channelCode uint32
|
||||
}
|
||||
|
||||
func (d *xcTestManagerDaemon) initiateControlSession(XcodeVersion uint64) (err error) {
|
||||
args := libimobiledevice.NewAuxBuffer()
|
||||
if err = args.AppendObject(XcodeVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
selector := "_IDE_initiateControlSessionWithProtocolVersion:"
|
||||
|
||||
var ret *libimobiledevice.DTXMessageResult
|
||||
if ret, err = d.testmanagerd.invoke(selector, args, d.channelCode, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if nsErr, ok := ret.Obj.(libimobiledevice.NSError); ok {
|
||||
return fmt.Errorf("%s", nsErr.NSUserInfo.(map[string]interface{})["NSLocalizedDescription"])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *xcTestManagerDaemon) startExecutingTestPlan(XcodeVersion uint64) (err error) {
|
||||
args := libimobiledevice.NewAuxBuffer()
|
||||
if err = args.AppendObject(XcodeVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
selector := "_IDE_startExecutingTestPlanWithProtocolVersion:"
|
||||
|
||||
if _, err = d.testmanagerd.invoke(selector, args, 0xFFFFFFFF, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *xcTestManagerDaemon) initiateSession(XcodeVersion uint64, nsUUID *nskeyedarchiver.NSUUID) (err error) {
|
||||
args := libimobiledevice.NewAuxBuffer()
|
||||
if err = args.AppendObject(nsUUID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = args.AppendObject(nsUUID.String() + "-Go-iDevice"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = args.AppendObject("/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = args.AppendObject(XcodeVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
selector := "_IDE_initiateSessionWithIdentifier:forClient:atPath:protocolVersion:"
|
||||
|
||||
var ret *libimobiledevice.DTXMessageResult
|
||||
if ret, err = d.testmanagerd.invoke(selector, args, d.channelCode, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if nsErr, ok := ret.Obj.(libimobiledevice.NSError); ok {
|
||||
return fmt.Errorf("%s", nsErr.NSUserInfo.(map[string]interface{})["NSLocalizedDescription"])
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *xcTestManagerDaemon) authorizeTestSession(pid int) (err error) {
|
||||
args := libimobiledevice.NewAuxBuffer()
|
||||
if err = args.AppendObject(pid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
selector := "_IDE_authorizeTestSessionWithProcessID:"
|
||||
|
||||
var ret *libimobiledevice.DTXMessageResult
|
||||
if ret, err = d.testmanagerd.invoke(selector, args, d.channelCode, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if nsErr, ok := ret.Obj.(libimobiledevice.NSError); ok {
|
||||
return fmt.Errorf("%s", nsErr.NSUserInfo.(map[string]interface{})["NSLocalizedDescription"])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *xcTestManagerDaemon) initiateControlSessionForTestProcessID(pid int) (err error) {
|
||||
args := libimobiledevice.NewAuxBuffer()
|
||||
if err = args.AppendObject(pid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
selector := "_IDE_initiateControlSessionForTestProcessID:"
|
||||
|
||||
var ret *libimobiledevice.DTXMessageResult
|
||||
if ret, err = d.testmanagerd.invoke(selector, args, d.channelCode, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if nsErr, ok := ret.Obj.(libimobiledevice.NSError); ok {
|
||||
return fmt.Errorf("%s", nsErr.NSUserInfo.(map[string]interface{})["NSLocalizedDescription"])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *xcTestManagerDaemon) initiateControlSessionForTestProcessIDProtocolVersion(pid int, XcodeVersion uint64) (err error) {
|
||||
args := libimobiledevice.NewAuxBuffer()
|
||||
if err = args.AppendObject(pid); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = args.AppendObject(XcodeVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
selector := "_IDE_initiateControlSessionForTestProcessID:protocolVersion:"
|
||||
|
||||
var ret *libimobiledevice.DTXMessageResult
|
||||
if ret, err = d.testmanagerd.invoke(selector, args, d.channelCode, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if nsErr, ok := ret.Obj.(libimobiledevice.NSError); ok {
|
||||
return fmt.Errorf("%s", nsErr.NSUserInfo.(map[string]interface{})["NSLocalizedDescription"])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *xcTestManagerDaemon) registerCallback(obj string, cb func(m libimobiledevice.DTXMessageResult)) {
|
||||
d.testmanagerd.registerCallback(obj, cb)
|
||||
}
|
||||
|
||||
func (d *xcTestManagerDaemon) close() {
|
||||
d.testmanagerd.close()
|
||||
}
|
||||
@@ -9,13 +9,13 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/electricbubble/gadb"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/code"
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/json"
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/myexec"
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gadb"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -11,11 +11,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/electricbubble/gadb"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/code"
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gadb"
|
||||
)
|
||||
|
||||
var errDriverNotImplemented = errors.New("driver method not implemented")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user