mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-11 10:49:43 +08:00
refactor: move gidevice to hrp pkg
This commit is contained in:
@@ -1,12 +1,23 @@
|
|||||||
# Release History
|
# Release History
|
||||||
|
|
||||||
## v4.3.0 (2022-10-21)
|
## v4.3.0 (2022-10-23)
|
||||||
|
|
||||||
- feat: support iOS UI automation with [WebDriverAgent]
|
Release hrp sub package `uixt` to support iOS/Android UI automation 🎉
|
||||||
- feat support Android UI automation with [uiautomator2]
|
|
||||||
- feat: integrage ios device management with [gidevice]
|
- feat: support iOS UI automation with [WebDriverAgent] and [gwda]
|
||||||
- feat: add specified exit code for different exceptions
|
- feat: support Android UI automation with [uiautomator2] and [guia2]
|
||||||
- refactor: make boomer/uixt/httpstat as sub package
|
- feat: support UI recognition with [OCR service] and [gwda-ext-opencv]
|
||||||
|
|
||||||
|
For iOS/Android device management:
|
||||||
|
|
||||||
|
- feat: integrage ios device management with [gidevice],
|
||||||
|
- 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)
|
## v4.2.1 (2022-09-01)
|
||||||
|
|
||||||
@@ -681,3 +692,8 @@ reference: [v2-changelog]
|
|||||||
[WebDriverAgent]: https://github.com/appium/WebDriverAgent
|
[WebDriverAgent]: https://github.com/appium/WebDriverAgent
|
||||||
[uiautomator2]: https://github.com/appium/appium-uiautomator2-server
|
[uiautomator2]: https://github.com/appium/appium-uiautomator2-server
|
||||||
[gidevice]: https://github.com/electricbubble/gidevice
|
[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
|
||||||
|
|||||||
8
go.mod
8
go.mod
@@ -5,7 +5,6 @@ go 1.18
|
|||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.0.4
|
github.com/andybalholm/brotli v1.0.4
|
||||||
github.com/denisbrodbeck/machineid v1.0.1
|
github.com/denisbrodbeck/machineid v1.0.1
|
||||||
github.com/electricbubble/gidevice v0.6.2
|
|
||||||
github.com/electricbubble/opencv-helper v0.0.3
|
github.com/electricbubble/opencv-helper v0.0.3
|
||||||
github.com/fatih/color v1.13.0
|
github.com/fatih/color v1.13.0
|
||||||
github.com/getsentry/sentry-go v0.13.0
|
github.com/getsentry/sentry-go v0.13.0
|
||||||
@@ -18,6 +17,7 @@ require (
|
|||||||
github.com/jinzhu/copier v0.3.5
|
github.com/jinzhu/copier v0.3.5
|
||||||
github.com/jmespath/go-jmespath v0.4.0
|
github.com/jmespath/go-jmespath v0.4.0
|
||||||
github.com/json-iterator/go v1.1.12
|
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/maja42/goval v1.2.1
|
||||||
github.com/miekg/dns v1.1.50
|
github.com/miekg/dns v1.1.50
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
@@ -25,6 +25,7 @@ require (
|
|||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/prometheus/client_golang v1.13.0
|
github.com/prometheus/client_golang v1.13.0
|
||||||
github.com/rs/zerolog v1.28.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/shirou/gopsutil v3.21.11+incompatible
|
||||||
github.com/spf13/cobra v1.5.0
|
github.com/spf13/cobra v1.5.0
|
||||||
github.com/stretchr/testify v1.8.0
|
github.com/stretchr/testify v1.8.0
|
||||||
@@ -33,6 +34,7 @@ require (
|
|||||||
google.golang.org/grpc v1.49.0
|
google.golang.org/grpc v1.49.0
|
||||||
google.golang.org/protobuf v1.28.1
|
google.golang.org/protobuf v1.28.1
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
howett.net/plist v1.0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -52,7 +54,6 @@ require (
|
|||||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // 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/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||||
@@ -68,7 +69,6 @@ require (
|
|||||||
github.com/prometheus/procfs v0.8.0 // indirect
|
github.com/prometheus/procfs v0.8.0 // indirect
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.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/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
||||||
github.com/tklauser/numcpus v0.5.0 // indirect
|
github.com/tklauser/numcpus v0.5.0 // indirect
|
||||||
@@ -83,8 +83,6 @@ require (
|
|||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51 // indirect
|
google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // 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/httprunner/funplugin => ../funplugin
|
||||||
replace github.com/electricbubble/gidevice => github.com/debugtalk/gidevice v0.6.3-0.20221012071407-9b59e12ecc77
|
|
||||||
|
|||||||
3
go.sum
3
go.sum
@@ -96,8 +96,6 @@ 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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
|
||||||
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
||||||
github.com/electricbubble/opencv-helper v0.0.3 h1:p0sHTUPPPm8GqzVUtYH+wQbJoguzotUXVRAS7Ibk7nI=
|
github.com/electricbubble/opencv-helper v0.0.3 h1:p0sHTUPPPm8GqzVUtYH+wQbJoguzotUXVRAS7Ibk7nI=
|
||||||
@@ -918,7 +916,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-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.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/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 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
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=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ package ios
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
giDevice "github.com/electricbubble/gidevice"
|
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Application struct {
|
type Application struct {
|
||||||
@@ -24,21 +25,21 @@ var listAppsCmd = &cobra.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var applicationType giDevice.ApplicationType
|
var applicationType gidevice.ApplicationType
|
||||||
switch appType {
|
switch appType {
|
||||||
case "user":
|
case "user":
|
||||||
applicationType = giDevice.ApplicationTypeUser
|
applicationType = gidevice.ApplicationTypeUser
|
||||||
case "system":
|
case "system":
|
||||||
applicationType = giDevice.ApplicationTypeSystem
|
applicationType = gidevice.ApplicationTypeSystem
|
||||||
case "internal":
|
case "internal":
|
||||||
applicationType = giDevice.ApplicationTypeInternal
|
applicationType = gidevice.ApplicationTypeInternal
|
||||||
case "all":
|
case "all":
|
||||||
applicationType = giDevice.ApplicationTypeAny
|
applicationType = gidevice.ApplicationTypeAny
|
||||||
}
|
}
|
||||||
|
|
||||||
result, errList := device.InstallationProxyBrowse(
|
result, errList := device.InstallationProxyBrowse(
|
||||||
giDevice.WithApplicationType(applicationType),
|
gidevice.WithApplicationType(applicationType),
|
||||||
giDevice.WithReturnAttributes("CFBundleVersion", "CFBundleDisplayName", "CFBundleIdentifier"))
|
gidevice.WithReturnAttributes("CFBundleVersion", "CFBundleDisplayName", "CFBundleIdentifier"))
|
||||||
if errList != nil {
|
if errList != nil {
|
||||||
return fmt.Errorf("get app list failed")
|
return fmt.Errorf("get app list failed")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,15 +5,15 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
giDevice "github.com/electricbubble/gidevice"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Device struct {
|
type Device struct {
|
||||||
d giDevice.Device
|
d gidevice.Device
|
||||||
UDID string `json:"UDID"`
|
UDID string `json:"UDID"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
ConnectionType string `json:"connectionType"`
|
ConnectionType string `json:"connectionType"`
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
giDevice "github.com/electricbubble/gidevice"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ var iosRootCmd = &cobra.Command{
|
|||||||
Short: "simple utils for ios device management",
|
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)
|
devices, err := uixt.IOSDevices(udid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
1
hrp/pkg/gidevice/.gitignore
vendored
Normal file
1
hrp/pkg/gidevice/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/.idea
|
||||||
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
|
||||||
|
)
|
||||||
100
hrp/pkg/gidevice/afc_test.go
Normal file
100
hrp/pkg/gidevice/afc_test.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
41
hrp/pkg/gidevice/crashreportmover_test.go
Normal file
41
hrp/pkg/gidevice/crashreportmover_test.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
174
hrp/pkg/gidevice/device_test.go
Normal file
174
hrp/pkg/gidevice/device_test.go
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
58
hrp/pkg/gidevice/housearrest_test.go
Normal file
58
hrp/pkg/gidevice/housearrest_test.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
49
hrp/pkg/gidevice/imagemounter_test.go
Normal file
49
hrp/pkg/gidevice/imagemounter_test.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
71
hrp/pkg/gidevice/installationproxy_test.go
Normal file
71
hrp/pkg/gidevice/installationproxy_test.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
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"`
|
||||||
|
}
|
||||||
97
hrp/pkg/gidevice/instruments_test.go
Normal file
97
hrp/pkg/gidevice/instruments_test.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
106
hrp/pkg/gidevice/lockdown_test.go
Normal file
106
hrp/pkg/gidevice/lockdown_test.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
178
hrp/pkg/gidevice/perfd_test.go
Normal file
178
hrp/pkg/gidevice/perfd_test.go
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
21
hrp/pkg/gidevice/pkg/ipa/ipa_test.go
Normal file
21
hrp/pkg/gidevice/pkg/ipa/ipa_test.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
18
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsarray_test.go
Normal file
18
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsarray_test.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
18
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsdictionary_test.go
Normal file
18
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsdictionary_test.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
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
|
||||||
39
hrp/pkg/gidevice/pkg/nskeyedarchiver/nskeyedarchiver_test.go
Normal file
39
hrp/pkg/gidevice/pkg/nskeyedarchiver/nskeyedarchiver_test.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
13
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsurl_test.go
Normal file
13
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsurl_test.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
15
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsuuid_test.go
Normal file
15
hrp/pkg/gidevice/pkg/nskeyedarchiver/nsuuid_test.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
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,15 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
56
hrp/pkg/gidevice/screenshot_test.go
Normal file
56
hrp/pkg/gidevice/screenshot_test.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
48
hrp/pkg/gidevice/simulatelocation_test.go
Normal file
48
hrp/pkg/gidevice/simulatelocation_test.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
53
hrp/pkg/gidevice/springboard_test.go
Normal file
53
hrp/pkg/gidevice/springboard_test.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
68
hrp/pkg/gidevice/usbmux_test.go
Normal file
68
hrp/pkg/gidevice/usbmux_test.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
@@ -17,13 +17,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
giDevice "github.com/electricbubble/gidevice"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/code"
|
"github.com/httprunner/httprunner/v4/hrp/internal/code"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/env"
|
"github.com/httprunner/httprunner/v4/hrp/internal/env"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/json"
|
"github.com/httprunner/httprunner/v4/hrp/internal/json"
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -96,18 +96,18 @@ func WithDismissAlertButtonSelector(selector string) IOSDeviceOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithPerfOptions(options ...giDevice.PerfOption) IOSDeviceOption {
|
func WithPerfOptions(options ...gidevice.PerfOption) IOSDeviceOption {
|
||||||
return func(device *IOSDevice) {
|
return func(device *IOSDevice) {
|
||||||
device.PerfOptions = &giDevice.PerfOptions{}
|
device.PerfOptions = &gidevice.PerfOptions{}
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(device.PerfOptions)
|
option(device.PerfOptions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func IOSDevices(udid ...string) (devices []giDevice.Device, err error) {
|
func IOSDevices(udid ...string) (devices []gidevice.Device, err error) {
|
||||||
var usbmux giDevice.Usbmux
|
var usbmux gidevice.Usbmux
|
||||||
if usbmux, err = giDevice.NewUsbmux(); err != nil {
|
if usbmux, err = gidevice.NewUsbmux(); err != nil {
|
||||||
return nil, errors.Wrap(code.IOSDeviceConnectionError,
|
return nil, errors.Wrap(code.IOSDeviceConnectionError,
|
||||||
fmt.Sprintf("init usbmux failed: %v", err))
|
fmt.Sprintf("init usbmux failed: %v", err))
|
||||||
}
|
}
|
||||||
@@ -118,7 +118,7 @@ func IOSDevices(udid ...string) (devices []giDevice.Device, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// filter by udid
|
// filter by udid
|
||||||
var deviceList []giDevice.Device
|
var deviceList []gidevice.Device
|
||||||
for _, d := range devices {
|
for _, d := range devices {
|
||||||
for _, u := range udid {
|
for _, u := range udid {
|
||||||
if u != "" && u != d.Properties().SerialNumber {
|
if u != "" && u != d.Properties().SerialNumber {
|
||||||
@@ -194,8 +194,8 @@ func NewIOSDevice(options ...IOSDeviceOption) (device *IOSDevice, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type IOSDevice struct {
|
type IOSDevice struct {
|
||||||
d giDevice.Device
|
d gidevice.Device
|
||||||
PerfOptions *giDevice.PerfOptions `json:"perf_options,omitempty" yaml:"perf_options,omitempty"`
|
PerfOptions *gidevice.PerfOptions `json:"perf_options,omitempty" yaml:"perf_options,omitempty"`
|
||||||
UDID string `json:"udid,omitempty" yaml:"udid,omitempty"`
|
UDID string `json:"udid,omitempty" yaml:"udid,omitempty"`
|
||||||
Port int `json:"port,omitempty" yaml:"port,omitempty"` // WDA remote port
|
Port int `json:"port,omitempty" yaml:"port,omitempty"` // WDA remote port
|
||||||
MjpegPort int `json:"mjpeg_port,omitempty" yaml:"mjpeg_port,omitempty"` // WDA remote MJPEG port
|
MjpegPort int `json:"mjpeg_port,omitempty" yaml:"mjpeg_port,omitempty"` // WDA remote MJPEG port
|
||||||
@@ -297,7 +297,7 @@ func (dev *IOSDevice) forward(localPort, remotePort int) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
go func(listener net.Listener, device giDevice.Device) {
|
go func(listener net.Listener, device gidevice.Device) {
|
||||||
for {
|
for {
|
||||||
accept, err := listener.Accept()
|
accept, err := listener.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -332,53 +332,53 @@ func (dev *IOSDevice) forward(localPort, remotePort int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dev *IOSDevice) perfOpitons() (perfOptions []giDevice.PerfOption) {
|
func (dev *IOSDevice) perfOpitons() (perfOptions []gidevice.PerfOption) {
|
||||||
if dev.PerfOptions == nil {
|
if dev.PerfOptions == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// system
|
// system
|
||||||
if dev.PerfOptions.SysCPU {
|
if dev.PerfOptions.SysCPU {
|
||||||
perfOptions = append(perfOptions, giDevice.WithPerfSystemCPU(true))
|
perfOptions = append(perfOptions, gidevice.WithPerfSystemCPU(true))
|
||||||
}
|
}
|
||||||
if dev.PerfOptions.SysMem {
|
if dev.PerfOptions.SysMem {
|
||||||
perfOptions = append(perfOptions, giDevice.WithPerfSystemMem(true))
|
perfOptions = append(perfOptions, gidevice.WithPerfSystemMem(true))
|
||||||
}
|
}
|
||||||
if dev.PerfOptions.SysDisk {
|
if dev.PerfOptions.SysDisk {
|
||||||
perfOptions = append(perfOptions, giDevice.WithPerfSystemDisk(true))
|
perfOptions = append(perfOptions, gidevice.WithPerfSystemDisk(true))
|
||||||
}
|
}
|
||||||
if dev.PerfOptions.SysNetwork {
|
if dev.PerfOptions.SysNetwork {
|
||||||
perfOptions = append(perfOptions, giDevice.WithPerfSystemNetwork(true))
|
perfOptions = append(perfOptions, gidevice.WithPerfSystemNetwork(true))
|
||||||
}
|
}
|
||||||
if dev.PerfOptions.FPS {
|
if dev.PerfOptions.FPS {
|
||||||
perfOptions = append(perfOptions, giDevice.WithPerfFPS(true))
|
perfOptions = append(perfOptions, gidevice.WithPerfFPS(true))
|
||||||
}
|
}
|
||||||
if dev.PerfOptions.Network {
|
if dev.PerfOptions.Network {
|
||||||
perfOptions = append(perfOptions, giDevice.WithPerfNetwork(true))
|
perfOptions = append(perfOptions, gidevice.WithPerfNetwork(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
// process
|
// process
|
||||||
if dev.PerfOptions.BundleID != "" {
|
if dev.PerfOptions.BundleID != "" {
|
||||||
perfOptions = append(perfOptions,
|
perfOptions = append(perfOptions,
|
||||||
giDevice.WithPerfBundleID(dev.PerfOptions.BundleID))
|
gidevice.WithPerfBundleID(dev.PerfOptions.BundleID))
|
||||||
}
|
}
|
||||||
if dev.PerfOptions.Pid != 0 {
|
if dev.PerfOptions.Pid != 0 {
|
||||||
perfOptions = append(perfOptions,
|
perfOptions = append(perfOptions,
|
||||||
giDevice.WithPerfPID(dev.PerfOptions.Pid))
|
gidevice.WithPerfPID(dev.PerfOptions.Pid))
|
||||||
}
|
}
|
||||||
|
|
||||||
// config
|
// config
|
||||||
if dev.PerfOptions.OutputInterval != 0 {
|
if dev.PerfOptions.OutputInterval != 0 {
|
||||||
perfOptions = append(perfOptions,
|
perfOptions = append(perfOptions,
|
||||||
giDevice.WithPerfOutputInterval(dev.PerfOptions.OutputInterval))
|
gidevice.WithPerfOutputInterval(dev.PerfOptions.OutputInterval))
|
||||||
}
|
}
|
||||||
if dev.PerfOptions.SystemAttributes != nil {
|
if dev.PerfOptions.SystemAttributes != nil {
|
||||||
perfOptions = append(perfOptions,
|
perfOptions = append(perfOptions,
|
||||||
giDevice.WithPerfSystemAttributes(dev.PerfOptions.SystemAttributes...))
|
gidevice.WithPerfSystemAttributes(dev.PerfOptions.SystemAttributes...))
|
||||||
}
|
}
|
||||||
if dev.PerfOptions.ProcessAttributes != nil {
|
if dev.PerfOptions.ProcessAttributes != nil {
|
||||||
perfOptions = append(perfOptions,
|
perfOptions = append(perfOptions,
|
||||||
giDevice.WithPerfProcessAttributes(dev.PerfOptions.ProcessAttributes...))
|
gidevice.WithPerfProcessAttributes(dev.PerfOptions.ProcessAttributes...))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,22 +11,22 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
giDevice "github.com/electricbubble/gidevice"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/code"
|
"github.com/httprunner/httprunner/v4/hrp/internal/code"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/json"
|
"github.com/httprunner/httprunner/v4/hrp/internal/json"
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
||||||
)
|
)
|
||||||
|
|
||||||
type wdaDriver struct {
|
type wdaDriver struct {
|
||||||
Driver
|
Driver
|
||||||
|
|
||||||
// default port
|
// default port
|
||||||
defaultConn giDevice.InnerConn
|
defaultConn gidevice.InnerConn
|
||||||
|
|
||||||
// mjpeg port
|
// mjpeg port
|
||||||
mjpegUSBConn giDevice.InnerConn // via USB
|
mjpegUSBConn gidevice.InnerConn // via USB
|
||||||
mjpegHTTPConn net.Conn // via HTTP
|
mjpegHTTPConn net.Conn // via HTTP
|
||||||
mjpegClient *http.Client
|
mjpegClient *http.Client
|
||||||
}
|
}
|
||||||
|
|||||||
27
hrp/step.go
27
hrp/step.go
@@ -1,8 +1,7 @@
|
|||||||
package hrp
|
package hrp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
giDevice "github.com/electricbubble/gidevice"
|
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,18 +35,18 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
WithPerfSystemCPU = giDevice.WithPerfSystemCPU
|
WithPerfSystemCPU = gidevice.WithPerfSystemCPU
|
||||||
WithPerfSystemMem = giDevice.WithPerfSystemMem
|
WithPerfSystemMem = gidevice.WithPerfSystemMem
|
||||||
WithPerfSystemDisk = giDevice.WithPerfSystemDisk
|
WithPerfSystemDisk = gidevice.WithPerfSystemDisk
|
||||||
WithPerfSystemNetwork = giDevice.WithPerfSystemNetwork
|
WithPerfSystemNetwork = gidevice.WithPerfSystemNetwork
|
||||||
WithPerfGPU = giDevice.WithPerfGPU
|
WithPerfGPU = gidevice.WithPerfGPU
|
||||||
WithPerfFPS = giDevice.WithPerfFPS
|
WithPerfFPS = gidevice.WithPerfFPS
|
||||||
WithPerfNetwork = giDevice.WithPerfNetwork
|
WithPerfNetwork = gidevice.WithPerfNetwork
|
||||||
WithPerfBundleID = giDevice.WithPerfBundleID
|
WithPerfBundleID = gidevice.WithPerfBundleID
|
||||||
WithPerfPID = giDevice.WithPerfPID
|
WithPerfPID = gidevice.WithPerfPID
|
||||||
WithPerfOutputInterval = giDevice.WithPerfOutputInterval
|
WithPerfOutputInterval = gidevice.WithPerfOutputInterval
|
||||||
WithPerfProcessAttributes = giDevice.WithPerfProcessAttributes
|
WithPerfProcessAttributes = gidevice.WithPerfProcessAttributes
|
||||||
WithPerfSystemAttributes = giDevice.WithPerfSystemAttributes
|
WithPerfSystemAttributes = gidevice.WithPerfSystemAttributes
|
||||||
)
|
)
|
||||||
|
|
||||||
type StepResult struct {
|
type StepResult struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user