refactor ios: replace gidevice with go-ios

This commit is contained in:
lilong.129
2024-11-23 14:12:07 +08:00
parent 8aac2181be
commit ef37d88e0b
118 changed files with 2202 additions and 11764 deletions

View File

@@ -1,446 +0,0 @@
# 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

View File

@@ -1,552 +0,0 @@
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
)

View File

@@ -1,102 +0,0 @@
//go:build localtest
package gidevice
import (
"fmt"
"io"
"log"
"os"
"testing"
)
var afcSrv Afc
func setupAfcSrv(t *testing.T) {
setupLockdownSrv(t)
var err error
if lockdownSrv, err = dev.lockdownService(); err != nil {
t.Fatal(err)
}
if afcSrv, err = lockdownSrv.AfcService(); err != nil {
t.Fatal(err)
}
}
func Test_afc_DiskInfo(t *testing.T) {
setupAfcSrv(t)
info, err := afcSrv.DiskInfo()
if err != nil {
t.Fatal(err)
}
log.Printf("%10s: %s\n", "Model", info.Model)
log.Printf("%10s: %d\n", "BlockSize", info.BlockSize/8)
log.Printf("%10s: %s\n", "FreeSpace", byteCountDecimal(int64(info.FreeBytes)))
log.Printf("%10s: %s\n", "UsedSpace", byteCountDecimal(int64(info.TotalBytes-info.FreeBytes)))
log.Printf("%10s: %s\n", "TotalSpace", byteCountDecimal(int64(info.TotalBytes)))
}
func byteCountDecimal(b int64) string {
const unit = 1000
if b < unit {
return fmt.Sprintf("%dB", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f%cB", float64(b)/float64(div), "kMGTPE"[exp])
}
func Test_afc_ReadDir(t *testing.T) {
setupAfcSrv(t)
names, err := afcSrv.ReadDir("Downloads")
if err != nil {
t.Fatal(err)
}
for _, name := range names {
t.Log(name)
}
}
func Test_afc_Stat(t *testing.T) {
setupAfcSrv(t)
fileInfo, err := afcSrv.Stat("Downloads/downloads.28.sqlitedb")
if err != nil {
t.Fatal(err)
}
t.Log(fileInfo.Name())
t.Log(fileInfo.IsDir())
t.Log(fileInfo.CreationTime())
t.Log(fileInfo.ModTime())
t.Log(fileInfo.Size())
t.Log(byteCountDecimal(fileInfo.Size()))
}
func Test_afc_Open(t *testing.T) {
setupAfcSrv(t)
afcFile, err := afcSrv.Open("DCIM/105APPLE/IMG_5977.JPEG", AfcFileModeRdOnly)
if err != nil {
t.Fatal(err)
}
defer func() {
_ = afcFile.Close()
}()
userHomeDir, _ := os.UserHomeDir()
file, err := os.Create(userHomeDir + "/Desktop/tmp.jpeg")
if err != nil {
t.Fatal(err)
}
if _, err = io.Copy(file, afcFile); err != nil {
t.Fatal(err)
}
}

View File

@@ -1,169 +0,0 @@
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
}

View File

@@ -1,43 +0,0 @@
//go:build localtest
package gidevice
import (
"fmt"
"os"
"testing"
)
var crashReportMoverSrv CrashReportMover
func setupCrashReportMoverSrv(t *testing.T) {
setupLockdownSrv(t)
var err error
if lockdownSrv, err = dev.lockdownService(); err != nil {
t.Fatal(err)
}
if crashReportMoverSrv, err = lockdownSrv.CrashReportMoverService(); err != nil {
t.Fatal(err)
}
}
func Test_crashReportMover_Move(t *testing.T) {
setupCrashReportMoverSrv(t)
SetDebug(true)
userHomeDir, _ := os.UserHomeDir()
// err := crashReportMoverSrv.Move(userHomeDir + "/Documents/temp/2021-04/out_gidevice")
// err := crashReportMoverSrv.Move(userHomeDir+"/Documents/temp/2021-04/out_gidevice",
err := crashReportMoverSrv.Move(userHomeDir+"/Documents/temp/2021-04/out_gidevice_extract",
WithKeepCrashReport(true),
WithExtractRawCrashReport(true),
WithWhenMoveIsDone(func(filename string) {
fmt.Println("Copy:", filename)
}),
)
if err != nil {
t.Fatal(err)
}
}

View File

@@ -1,959 +0,0 @@
package gidevice
import (
"bytes"
"context"
"errors"
"fmt"
"os"
"path"
"strings"
"time"
"github.com/rs/zerolog/log"
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) PcapStart(opts ...PcapOption) (lines <-chan []byte, err error) {
pcapOptions := &PcapOptions{}
for _, fn := range opts {
fn(pcapOptions)
}
log.Info().Interface("options", pcapOptions).Msg("pcap start")
// wait until get pid for bundle id
if pcapOptions.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(pcapOptions.BundleID)
if err != nil {
time.Sleep(1 * time.Second)
continue
}
pcapOptions.Pid = pid
break
}
}
if d.pcapd == nil {
if _, err = d.lockdownService(); err != nil {
return nil, err
}
if d.pcapd, err = d.lockdown.PcapdService(
pcapOptions.Pid, pcapOptions.ProcName); 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
}

View File

@@ -1,176 +0,0 @@
//go:build localtest
package gidevice
import (
"fmt"
"os"
"os/signal"
"testing"
"time"
)
var dev Device
func setupDevice(t *testing.T) {
setupUsbmux(t)
devices, err := um.Devices()
if err != nil {
t.Fatal(err)
}
if len(devices) == 0 {
t.Fatal("No Device")
}
dev = devices[0]
}
func Test_device_ReadPairRecord(t *testing.T) {
setupDevice(t)
pairRecord, err := dev.ReadPairRecord()
if err != nil {
t.Fatal(err)
}
t.Log(pairRecord.HostID, pairRecord.SystemBUID, pairRecord.WiFiMACAddress)
}
func Test_device_NewConnect(t *testing.T) {
setupDevice(t)
if _, err := dev.NewConnect(LockdownPort); err != nil {
t.Fatal(err)
}
}
func Test_device_DeletePairRecord(t *testing.T) {
setupDevice(t)
if err := dev.DeletePairRecord(); err != nil {
t.Fatal(err)
}
}
func Test_device_SavePairRecord(t *testing.T) {
setupLockdownSrv(t)
pairRecord, err := lockdownSrv.Pair()
if err != nil {
t.Fatal(err)
}
err = dev.SavePairRecord(pairRecord)
if err != nil {
t.Fatal(err)
}
}
func Test_device_XCTest(t *testing.T) {
setupLockdownSrv(t)
bundleID = "com.leixipaopao.WebDriverAgentRunner.xctrunner"
out, cancel, err := dev.XCTest(bundleID)
// out, cancel, err := dev.XCTest(bundleID, WithXCTestEnv(map[string]interface{}{"USE_PORT": 8222, "MJPEG_SERVER_PORT": 8333}))
if err != nil {
t.Fatal(err)
}
done := make(chan os.Signal, 1)
signal.Notify(done, os.Interrupt)
go func() {
for s := range out {
fmt.Print(s)
}
done <- os.Interrupt
}()
for {
select {
case <-done:
cancel()
fmt.Println()
t.Log("DONE")
return
}
}
}
func Test_device_AppInstall(t *testing.T) {
setupLockdownSrv(t)
ipaPath := "/private/tmp/derivedDataPath/Build/Products/Release-iphoneos/WebDriverAgentRunner-Runner.ipa"
err := dev.AppInstall(ipaPath)
if err != nil {
t.Fatal(err)
}
}
func Test_device_AppUninstall(t *testing.T) {
setupLockdownSrv(t)
bundleID = "com.leixipaopao.WebDriverAgentRunner.xctrunner"
err := dev.AppUninstall(bundleID)
if err != nil {
t.Fatal(err)
}
}
func Test_device_Syslog(t *testing.T) {
setupLockdownSrv(t)
dev.SyslogStop()
lines, err := dev.Syslog()
if err != nil {
t.Fatal(err)
}
done := make(chan os.Signal, 1)
go func() {
for line := range lines {
fmt.Println(line)
}
done <- os.Interrupt
t.Log("DONE!!!")
}()
signal.Notify(done, os.Interrupt, os.Kill)
// <-done
time.Sleep(3 * time.Second)
dev.SyslogStop()
time.Sleep(200 * time.Millisecond)
}
func Test_device_Reboot(t *testing.T) {
setupDevice(t)
dev.Reboot()
}
func Test_device_Shutdown(t *testing.T) {
setupDevice(t)
dev.Shutdown()
}
func Test_device_InstallationProxyBrowse(t *testing.T) {
setupDevice(t)
list, err := dev.InstallationProxyBrowse(
WithApplicationType(ApplicationTypeUser),
WithReturnAttributes("CFBundleDisplayName", "CFBundleIdentifier", "SequenceNumber", "SequenceNumber"),
)
// list, err := dev.InstallationProxyBrowse()
if err != nil {
t.Fatal(err)
}
t.Log(len(list))
for _, l := range list {
t.Logf("%#v", l)
}
}

View File

@@ -1,39 +0,0 @@
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
}

View File

@@ -1,57 +0,0 @@
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
}

View File

@@ -1,60 +0,0 @@
//go:build localtest
package gidevice
import (
"testing"
)
var houseArrestSrv HouseArrest
func setupHouseArrestSrv(t *testing.T) {
setupLockdownSrv(t)
var err error
if lockdownSrv, err = dev.lockdownService(); err != nil {
t.Fatal(err)
}
if houseArrestSrv, err = lockdownSrv.HouseArrestService(); err != nil {
t.Fatal(err)
}
}
func Test_houseArrest_Documents(t *testing.T) {
setupHouseArrestSrv(t)
bundleID = "com.apple.iMovie"
appAfc, err := houseArrestSrv.Documents(bundleID)
if err != nil {
t.Fatal(err)
}
names, err := appAfc.ReadDir("Documents")
if err != nil {
t.Fatal(err)
}
for _, name := range names {
t.Log(name)
}
}
func Test_houseArrest_Container(t *testing.T) {
setupHouseArrestSrv(t)
bundleID = "com.apple.iMovie"
appAfc, err := houseArrestSrv.Documents(bundleID)
if err != nil {
t.Fatal(err)
}
names, err := appAfc.ReadDir("Documents")
if err != nil {
t.Fatal(err)
}
for _, name := range names {
t.Log(name)
}
}

View File

@@ -1,481 +0,0 @@
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()
PcapStart(opts ...PcapOption) (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)
}

View File

@@ -1,142 +0,0 @@
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
}

View File

@@ -1,51 +0,0 @@
//go:build localtest
package gidevice
import (
"encoding/base64"
"testing"
)
var imageMounterSrv ImageMounter
func setupImageMounterSrv(t *testing.T) {
setupLockdownSrv(t)
var err error
if lockdownSrv, err = dev.lockdownService(); err != nil {
t.Fatal(err)
}
// Once
// dev.Images()
if imageMounterSrv, err = lockdownSrv.ImageMounterService(); err != nil {
t.Fatal(err)
}
}
func Test_imageMounter_Images(t *testing.T) {
setupImageMounterSrv(t)
// imageSignatures, err := dev.Images()
imageSignatures, err := imageMounterSrv.Images("Developer")
if err != nil {
t.Fatal(err)
}
for i, imgSign := range imageSignatures {
t.Logf("%2d, %s", i+1, base64.StdEncoding.EncodeToString(imgSign))
}
}
func Test_imageMounter_UploadImageAndMount(t *testing.T) {
setupImageMounterSrv(t)
devImgPath := "/private/var/mobile/Media/PublicStaging/staging.dimage"
dmgPath := "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/14.4/DeveloperDiskImage.dmg"
signaturePath := "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/14.4/DeveloperDiskImage.dmg.signature"
if err := imageMounterSrv.UploadImageAndMount("Developer", devImgPath, dmgPath, signaturePath); err != nil {
t.Fatal(err)
}
}

View File

@@ -1,167 +0,0 @@
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
}

View File

@@ -1,73 +0,0 @@
//go:build localtest
package gidevice
import (
"testing"
)
var installationProxySrv InstallationProxy
func setupInstallationProxySrv(t *testing.T) {
setupLockdownSrv(t)
var err error
if lockdownSrv, err = dev.lockdownService(); err != nil {
t.Fatal(err)
}
if installationProxySrv, err = lockdownSrv.InstallationProxyService(); err != nil {
t.Fatal(err)
}
}
func Test_installationProxy_Browse(t *testing.T) {
setupInstallationProxySrv(t)
// currentList, err := installationProxySrv.Browse(WithMetaData(true))
// currentList, err := installationProxySrv.Browse(WithReturnAttributes("CFBundleIdentifier", "SequenceNumber", "SequenceNumber"))
// currentList, err := installationProxySrv.Browse(WithApplicationType(ApplicationTypeSystem))
// currentList, err := installationProxySrv.Browse(WithApplicationType(ApplicationTypeSystem), WithReturnAttributes("ApplicationType", "ApplicationType"))
// currentList, err := dev.InstallationProxyBrowse()
currentList, err := installationProxySrv.Browse()
// currentList, err := installationProxySrv.Browse(WithBundleIDs("com.apple.MusicUIService"), WithBundleIDs("com.apple.Home.HomeControlService"))
if err != nil {
t.Fatal(err)
}
t.Log(len(currentList))
for _, cl := range currentList {
app, ok := cl.(map[string]interface{})
if ok {
t.Log(app)
} else {
t.Log(cl)
}
}
}
func Test_installationProxy_Lookup(t *testing.T) {
setupInstallationProxySrv(t)
// lookupResult, err := installationProxySrv.Lookup()
// lookupResult, err := dev.InstallationProxyLookup(
lookupResult, err := installationProxySrv.Lookup(
// WithApplicationType(ApplicationTypeUser),
// WithApplicationType(ApplicationTypeSystem),
// WithReturnAttributes("CFBundleDevelopmentRegion"),
// WithReturnAttributes("CFBundleDisplayName", "CFBundleIdentifier"),
// WithBundleIDs("com.apple.mobilephone"),
WithBundleIDs("com.leixipaopao.WebDriverAgentRunner.xctrunner"),
)
if err != nil {
t.Fatal(err)
}
ret := lookupResult.(map[string]interface{})
t.Log(len(ret))
for k, v := range ret {
t.Log(k, "-->", v)
}
}

View File

@@ -1,348 +0,0 @@
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"`
}

View File

@@ -1,99 +0,0 @@
//go:build localtest
package gidevice
import (
"testing"
)
var (
instrumentsSrv Instruments
bundleID = "com.apple.Preferences"
)
func setupInstrumentsSrv(t *testing.T) {
setupLockdownSrv(t)
var err error
if lockdownSrv, err = dev.lockdownService(); err != nil {
t.Fatal(err)
}
if instrumentsSrv, err = lockdownSrv.InstrumentsService(); err != nil {
t.Fatal(err)
}
}
func Test_instruments_AppLaunch(t *testing.T) {
setupInstrumentsSrv(t)
// bundleID = "com.leixipaopao.WebDriverAgentRunner.xctrunner"
// pid, err := dev.AppLaunch(bundleID)
pid, err := instrumentsSrv.AppLaunch(bundleID)
// pid, err := instrumentsSrv.AppLaunch(bundleID, WithKillExisting(true))
// pid, err := instrumentsSrv.AppLaunch(bundleID, WithKillExisting(true), WithArguments([]interface{}{"-AppleLanguages", "(Russian)"}))
if err != nil {
t.Fatal(err)
}
t.Log(pid)
}
func Test_instruments_AppKill(t *testing.T) {
setupInstrumentsSrv(t)
pid, err := instrumentsSrv.AppLaunch(bundleID)
if err != nil {
t.Fatal(err)
}
t.Log(pid)
// if err = dev.AppKill(pid); err != nil {
if err = instrumentsSrv.AppKill(pid); err != nil {
t.Fatal(err)
}
}
func Test_instruments_AppRunningProcesses(t *testing.T) {
setupInstrumentsSrv(t)
// processes, err := dev.AppRunningProcesses()
processes, err := instrumentsSrv.AppRunningProcesses()
if err != nil {
t.Fatal(err)
}
for _, p := range processes {
t.Log(p.IsApplication, "\t", p.Pid, "\t", p.Name, "\t", p.RealAppName, "\t", p.StartDate)
}
}
func Test_instruments_AppList(t *testing.T) {
setupInstrumentsSrv(t)
// apps, err := dev.AppList()
apps, err := instrumentsSrv.AppList()
if err != nil {
t.Fatal(err)
}
for _, app := range apps {
t.Logf("%v\t%v\t%v\t%v\t%v\n", app.Type, app.DisplayName, app.ExecutableName, app.AppExtensionUUIDs, app.BundlePath)
}
}
func Test_instruments_DeviceInfo(t *testing.T) {
setupInstrumentsSrv(t)
devInfo, err := instrumentsSrv.DeviceInfo()
if err != nil {
t.Fatal(err)
}
t.Log(devInfo.Description)
t.Log(devInfo.DisplayName)
t.Log(devInfo.Identifier)
t.Log(devInfo.Version)
t.Log(devInfo.ProductType)
t.Log(devInfo.ProductVersion)
t.Log(devInfo.XRDeviceClassName)
}

View File

@@ -1,684 +0,0 @@
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(targetPID int, targetProcName string) (pcapd Pcapd, err error) {
var innerConn InnerConn
if innerConn, err = c._startService(libimobiledevice.PcapdServiceName, nil); err != nil {
return nil, err
}
pcapdClient := libimobiledevice.NewPcapdClient(innerConn, targetPID, targetProcName)
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
}

View File

@@ -1,108 +0,0 @@
//go:build localtest
package gidevice
import (
"fmt"
"os"
"os/signal"
"path"
"testing"
"time"
)
var lockdownSrv Lockdown
func setupLockdownSrv(t *testing.T) {
setupDevice(t)
var err error
if lockdownSrv, err = dev.lockdownService(); err != nil {
t.Fatal(err)
}
}
func Test_lockdown_QueryType(t *testing.T) {
setupLockdownSrv(t)
lockdownType, err := lockdownSrv.QueryType()
if err != nil {
t.Fatal(err)
}
t.Log(lockdownType.Type)
}
func Test_lockdown_GetValue(t *testing.T) {
setupLockdownSrv(t)
// v, err := dev.GetValue("com.apple.mobile.iTunes", "")
// v, err := dev.GetValue("com.apple.mobile.internal", "")
v, err := dev.GetValue("com.apple.mobile.battery", "")
// v, err := lockdownSrv.GetValue("", "ProductVersion")
// v, err := lockdownSrv.GetValue("", "DeviceName")
// v, err := lockdownSrv.GetValue("com.apple.mobile.iTunes", "")
// v, err := lockdownSrv.GetValue("com.apple.mobile.battery", "")
// v, err := lockdownSrv.GetValue("com.apple.disk_usage", "")
if err != nil {
t.Fatal(err)
}
t.Log(v)
}
func Test_lockdown_SyslogRelayService(t *testing.T) {
setupLockdownSrv(t)
syslogRelaySrv, err := lockdownSrv.SyslogRelayService()
if err != nil {
t.Fatal(err)
}
syslogRelaySrv.Stop()
lines := syslogRelaySrv.Lines()
done := make(chan os.Signal, 1)
go func() {
for line := range lines {
fmt.Println(line)
}
done <- os.Interrupt
fmt.Println("DONE!!!")
}()
signal.Notify(done, os.Interrupt, os.Kill)
<-done
syslogRelaySrv.Stop()
time.Sleep(time.Second)
}
func Test_lockdown_CrashReportMoverService(t *testing.T) {
setupLockdownSrv(t)
crashReportMoverSrv, err := lockdownSrv.CrashReportMoverService()
if err != nil {
t.Fatal(err)
}
filenames := make([]string, 0, 36)
fn := func(cwd string, info *AfcFileInfo) {
if cwd == "." {
cwd = ""
}
filenames = append(filenames, path.Join(cwd, info.Name()))
// fmt.Println(path.Join(cwd, name))
}
err = crashReportMoverSrv.walkDir(".", fn)
if err != nil {
t.Fatal(err)
}
for _, n := range filenames {
fmt.Println(n)
}
t.Log(len(filenames))
}

View File

@@ -1,97 +0,0 @@
package gidevice
import (
"log"
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/libimobiledevice"
)
type PcapOptions struct {
All bool // capture all packets
Pid int // capture packets from specific PID
ProcName string // capture packets from specific process name
BundleID string // convert to PID first, then capture packets from the PID
}
type PcapOption func(*PcapOptions)
func WithPcapAll(all bool) PcapOption {
return func(opt *PcapOptions) {
opt.All = all
}
}
func WithPcapProcName(procName string) PcapOption {
return func(opt *PcapOptions) {
opt.ProcName = procName
}
}
func WithPcapPID(pid int) PcapOption {
return func(opt *PcapOptions) {
opt.Pid = pid
}
}
func WithPcapBundleID(bundleID string) PcapOption {
return func(opt *PcapOptions) {
opt.BundleID = bundleID
}
}
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
}
if raw == nil {
// filtered packet
continue
}
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()
}

View File

@@ -1,66 +0,0 @@
//go:build localtest
package gidevice
import (
"fmt"
"testing"
"time"
)
func TestPcapWithPID(t *testing.T) {
setupLockdownSrv(t)
data, err := dev.PcapStart(WithPcapPID(1234))
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))
}
}
}
func TestPcapWithProcName(t *testing.T) {
setupLockdownSrv(t)
data, err := dev.PcapStart(WithPcapProcName("Awe"))
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))
}
}
}
func TestPcapWithBundleID(t *testing.T) {
setupLockdownSrv(t)
data, err := dev.PcapStart(WithPcapBundleID("com.ss.iphone.ugc.Aweme"))
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))
}
}
}

View File

@@ -1,893 +0,0 @@
package gidevice
import (
"context"
"encoding/binary"
"encoding/json"
"fmt"
"log"
"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: false,
SysMem: false,
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
interval := time.Millisecond * time.Duration(c.options.OutputInterval)
log.Printf("set sysmontap sample interval: %dms\n", c.options.OutputInterval)
config := map[string]interface{}{
"bm": 0,
"cpuUsage": true,
"sampleInterval": interval, // time.Duration
"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, // FIXME: unable to set sampling rate, always 1.0
); 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
}

View File

@@ -1,161 +0,0 @@
//go:build localtest
package gidevice
import (
"fmt"
"testing"
"time"
)
func TestPerfSystemMonitor(t *testing.T) {
setupLockdownSrv(t)
data, err := dev.PerfStart(
WithPerfSystemCPU(true),
WithPerfSystemMem(true),
WithPerfSystemDisk(true),
WithPerfSystemNetwork(true),
WithPerfOutputInterval(1000),
)
if err != nil {
t.Fatal(err)
}
timer := time.NewTimer(time.Duration(time.Second * 10))
for {
select {
case <-timer.C:
dev.PerfStop()
return
case d := <-data:
fmt.Println(string(d))
}
}
}
func TestPerfProcessMonitor(t *testing.T) {
setupLockdownSrv(t)
data, err := dev.PerfStart(
WithPerfProcessAttributes("cpuUsage", "memAnon"),
WithPerfOutputInterval(1000),
WithPerfPID(100),
WithPerfBundleID("com.apple.mobilesafari"), // higher priority than pid
)
if err != nil {
t.Fatal(err)
}
timer := time.NewTimer(time.Duration(time.Second * 10))
for {
select {
case <-timer.C:
dev.PerfStop()
return
case d := <-data:
fmt.Println(string(d))
}
}
}
func TestPerfGPU(t *testing.T) {
setupLockdownSrv(t)
data, err := dev.PerfStart(
WithPerfSystemCPU(false),
WithPerfSystemMem(false),
WithPerfGPU(true),
)
if err != nil {
t.Fatal(err)
}
timer := time.NewTimer(time.Duration(time.Second * 10))
for {
select {
case <-timer.C:
dev.PerfStop()
return
case d := <-data:
fmt.Println(string(d))
}
}
}
func TestPerfFPS(t *testing.T) {
setupLockdownSrv(t)
data, err := dev.PerfStart(
WithPerfSystemCPU(false),
WithPerfSystemMem(false),
WithPerfFPS(true),
)
if err != nil {
t.Fatal(err)
}
timer := time.NewTimer(time.Duration(time.Second * 10))
for {
select {
case <-timer.C:
dev.PerfStop()
return
case d := <-data:
fmt.Println(string(d))
}
}
}
func TestPerfNetwork(t *testing.T) {
setupLockdownSrv(t)
data, err := dev.PerfStart(
WithPerfSystemCPU(false),
WithPerfSystemMem(false),
WithPerfNetwork(true),
)
if err != nil {
t.Fatal(err)
}
timer := time.NewTimer(time.Duration(time.Second * 10))
for {
select {
case <-timer.C:
dev.PerfStop()
return
case d := <-data:
fmt.Println(string(d))
}
}
}
func TestPerfAll(t *testing.T) {
setupLockdownSrv(t)
data, err := dev.PerfStart(
WithPerfSystemCPU(true),
WithPerfSystemMem(true),
WithPerfSystemDisk(true),
WithPerfSystemNetwork(true),
WithPerfNetwork(true),
WithPerfFPS(true),
WithPerfGPU(true),
WithPerfBundleID("com.apple.mobilesafari"),
)
if err != nil {
t.Fatal(err)
}
timer := time.NewTimer(time.Duration(time.Second * 10))
for {
select {
case <-timer.C:
dev.PerfStop()
return
case d := <-data:
fmt.Println(string(d))
}
}
}

View File

@@ -1,53 +0,0 @@
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
}

View File

@@ -1,23 +0,0 @@
//go:build localtest
package ipa
import (
"testing"
)
func TestInfo(t *testing.T) {
name := "/Users/hero/Documents/Workspace/GitHub/taobao-iphone-device/tests/testdata/WebDriverAgentRunner.ipa"
name = "/private/tmp/derivedDataPath/Build/Products/Release-iphoneos/WebDriverAgentRunner-Runner.ipa"
info, err := Info(name)
if err != nil {
t.Fatal(err)
}
for k, v := range info {
t.Logf("%-50s\t%v", k, v)
}
t.Log(info["CFBundleIdentifier"])
}

View File

@@ -1,105 +0,0 @@
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
}

View File

@@ -1,187 +0,0 @@
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
)

View File

@@ -1,138 +0,0 @@
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
}

View File

@@ -1,405 +0,0 @@
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{}
}

View File

@@ -1,81 +0,0 @@
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
}

View File

@@ -1,20 +0,0 @@
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
}

View File

@@ -1,39 +0,0 @@
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)
}

View File

@@ -1,56 +0,0 @@
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"`
}
)

View File

@@ -1,107 +0,0 @@
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"`
}
)

View File

@@ -1,119 +0,0 @@
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"`
}
)

View File

@@ -1,41 +0,0 @@
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)
}

View File

@@ -1,260 +0,0 @@
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
}

View File

@@ -1,28 +0,0 @@
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)
}

View File

@@ -1,183 +0,0 @@
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"`
}
)

View File

@@ -1,86 +0,0 @@
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,
)
}

View File

@@ -1,203 +0,0 @@
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),
)
}

View File

@@ -1,53 +0,0 @@
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)
}

View File

@@ -1,47 +0,0 @@
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)
}

View File

@@ -1,112 +0,0 @@
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"`
}

View File

@@ -1,152 +0,0 @@
package libimobiledevice
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"strings"
"time"
"github.com/lunixbochs/struc"
)
const PcapdServiceName = "com.apple.pcapd"
func filterPacket(pid int, procName string) func(*IOSPacketHeader) bool {
return func(iph *IOSPacketHeader) bool {
if pid > 0 {
return iph.Pid == int32(pid) ||
iph.Pid2 == int32(pid)
}
if procName != "" {
return strings.HasPrefix(iph.ProcName, procName) ||
strings.HasPrefix(iph.ProcName2, procName)
}
return true
}
}
func NewPcapdClient(innerConn InnerConn, targetPID int, targetProcName string) *PcapdClient {
return &PcapdClient{
filter: filterPacket(targetPID, targetProcName),
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
}
const (
PacketHeaderSize = uint32(95)
)
// ref: https://github.com/danielpaulus/go-ios/blob/fc943b9d236571f9775f5c593e3d49bb5bd67afd/ios/pcap/pcap.go#L27
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
}
}
// support ios 15 beta4
if iph.HdrSize > PacketHeaderSize {
buf := make([]byte, iph.HdrSize-PacketHeaderSize)
_, err := io.ReadFull(preader, buf)
if err != nil {
return []byte{}, err
}
}
packet, err := io.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()
}

View File

@@ -1,52 +0,0 @@
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
}

View File

@@ -1,128 +0,0 @@
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)
}

View File

@@ -1,53 +0,0 @@
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)
}

View File

@@ -1,21 +0,0 @@
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()
}

View File

@@ -1,45 +0,0 @@
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()
}

View File

@@ -1,414 +0,0 @@
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
}

View File

@@ -1,37 +0,0 @@
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
}

View File

@@ -1,20 +0,0 @@
//go:build localtest
package nskeyedarchiver
import (
"fmt"
"testing"
)
func TestNSArray_archive(t *testing.T) {
objs := make([]interface{}, 0, 1)
value := []interface{}{
"a", 1,
"b", "2",
"c", false,
}
array := NewNSArray(value)
objects := array.archive(objs)
fmt.Println(objects)
}

View File

@@ -1,44 +0,0 @@
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
}

View File

@@ -1,20 +0,0 @@
//go:build localtest
package nskeyedarchiver
import (
"fmt"
"testing"
)
func TestNSDictionary_archive(t *testing.T) {
objs := make([]interface{}, 0, 1)
value := map[string]interface{}{
"a": 1,
"b": "2",
"c": true,
}
dict := NewNSDictionary(value)
objects := dict.archive(objs)
fmt.Println(objects)
}

View File

@@ -1,80 +0,0 @@
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

View File

@@ -1,41 +0,0 @@
//go:build localtest
package nskeyedarchiver
import (
"fmt"
"testing"
uuid "github.com/satori/go.uuid"
)
func TestMarshal(t *testing.T) {
// value := map[string]interface{}{
// "a": 1,
// "b": "2",
// "c": true,
// }
// value := []interface{}{
// "a", 1,
// "b", "2",
// "c", false,
// }
// value := NewNSUUID(uuid.NewV4().Bytes())
// value := NewNSURL("/tmp")
value := NewXCTestConfiguration(NewNSUUID(uuid.NewV4().Bytes()), NewNSURL("/tmp"), "", "")
raw, err := Marshal(value)
if err != nil {
t.Fatal(err)
}
for _, v := range raw {
fmt.Printf("%x", v)
}
fmt.Println()
// fmt.Println(raw)
}

View File

@@ -1,27 +0,0 @@
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
}

View File

@@ -1,38 +0,0 @@
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
}

View File

@@ -1,15 +0,0 @@
//go:build localtest
package nskeyedarchiver
import (
"fmt"
"testing"
)
func TestNSURL_archive(t *testing.T) {
objs := make([]interface{}, 0, 1)
nsurl := NewNSURL("/tmp")
objects := nsurl.archive(objs)
fmt.Println(objects)
}

View File

@@ -1,51 +0,0 @@
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)
}

View File

@@ -1,17 +0,0 @@
//go:build localtest
package nskeyedarchiver
import (
"fmt"
"testing"
uuid "github.com/satori/go.uuid"
)
func TestNSUUID_archive(t *testing.T) {
objs := make([]interface{}, 0, 1)
nsuuid := NewNSUUID(uuid.NewV4().Bytes())
objects := nsuuid.archive(objs)
fmt.Println(objects)
}

View File

@@ -1,10 +0,0 @@
package nskeyedarchiver
type XCTCapabilities struct {
internal map[string]interface{}
}
func (caps *XCTCapabilities) archive(objects []interface{}) []interface{} {
// TODO caps
return nil
}

View File

@@ -1,89 +0,0 @@
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
}

View File

@@ -1,17 +0,0 @@
//go:build localtest
package nskeyedarchiver
import (
"fmt"
"testing"
uuid "github.com/satori/go.uuid"
)
func TestXCTestConfiguration_archive(t *testing.T) {
objs := make([]interface{}, 0, 1)
xcTestConfiguration := NewXCTestConfiguration(NewNSUUID(uuid.NewV4().Bytes()), NewNSURL("/tmp"), "", "")
objects := xcTestConfiguration.archive(objs)
fmt.Println(objects)
}

View File

@@ -1,119 +0,0 @@
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
}

View File

@@ -1,58 +0,0 @@
//go:build localtest
package gidevice
import (
"image"
"image/jpeg"
"image/png"
"os"
"testing"
)
var screenshotSrv Screenshot
func setupScreenshotSrv(t *testing.T) {
setupLockdownSrv(t)
var err error
if lockdownSrv, err = dev.lockdownService(); err != nil {
t.Fatal(err)
}
if screenshotSrv, err = lockdownSrv.ScreenshotService(); err != nil {
t.Fatal(err)
}
}
func Test_screenshot_Take(t *testing.T) {
setupScreenshotSrv(t)
// raw, err := dev.Screenshot()
raw, err := screenshotSrv.Take()
if err != nil {
t.Fatal(err)
}
_ = raw
img, format, err := image.Decode(raw)
if err != nil {
t.Fatal(err)
}
userHomeDir, _ := os.UserHomeDir()
file, err := os.Create(userHomeDir + "/Desktop/s1." + format)
if err != nil {
t.Fatal(err)
}
defer func() { _ = file.Close() }()
switch format {
case "png":
err = png.Encode(file, img)
case "jpeg":
err = jpeg.Encode(file, img, nil)
}
if err != nil {
t.Fatal(err)
}
t.Log(file.Name())
}

View File

@@ -1,27 +0,0 @@
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()
}

View File

@@ -1,50 +0,0 @@
//go:build localtest
package gidevice
import "testing"
var simulateLocationSrv SimulateLocation
func setupSimulateLocationSrv(t *testing.T) {
setupLockdownSrv(t)
var err error
if lockdownSrv, err = dev.lockdownService(); err != nil {
t.Fatal(err)
}
if simulateLocationSrv, err = lockdownSrv.SimulateLocationService(); err != nil {
t.Fatal(err)
}
}
func Test_simulateLocation_Update(t *testing.T) {
setupSimulateLocationSrv(t)
// https://api.map.baidu.com/lbsapi/getpoint/index.html
// if err := dev.SimulateLocationUpdate(116.024067, 40.362639, CoordinateSystemBD09); err != nil {
if err := simulateLocationSrv.Update(116.024067, 40.362639, CoordinateSystemBD09); err != nil {
t.Fatal(err)
}
// https://developer.amap.com/tools/picker
// https://lbs.qq.com/tool/getpoint/index.html
// if err := simulateLocationSrv.Update(120.116979,30.252876, CoordinateSystemGCJ02); err != nil {
// t.Fatal(err)
// }
if err := simulateLocationSrv.Update(121.499763, 31.239580); err != nil {
// if err := simulateLocationSrv.Update(121.499763, 31.239580, CoordinateSystemWGS84); err != nil {
t.Fatal(err)
}
}
func Test_simulateLocation_Recover(t *testing.T) {
setupSimulateLocationSrv(t)
// if err := dev.SimulateLocationRecover(); err != nil {
if err := simulateLocationSrv.Recover(); err != nil {
t.Fatal(err)
}
}

View File

@@ -1,68 +0,0 @@
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
}

View File

@@ -1,55 +0,0 @@
//go:build localtest
package gidevice
import (
"fmt"
"image"
"image/jpeg"
"image/png"
"os"
"testing"
)
var springBoardSrv SpringBoard
func setupSpringBoardSrv(t *testing.T) {
setupLockdownSrv(t)
var err error
if lockdownSrv, err = dev.lockdownService(); err != nil {
t.Fatal(err)
}
if springBoardSrv, err = lockdownSrv.SpringBoardService(); err != nil {
t.Fatal(err)
}
}
func Test_springBoard_GetIcon(t *testing.T) {
setupSpringBoardSrv(t)
raw, _ := springBoardSrv.GetIconPNGData("com.ss.iphone.ugc.Aweme")
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())
}

View File

@@ -1,85 +0,0 @@
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
}

View File

@@ -1,50 +0,0 @@
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()
}

View File

@@ -1,153 +0,0 @@
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
}

View File

@@ -1,70 +0,0 @@
//go:build localtest
package gidevice
import (
"testing"
"time"
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/libimobiledevice"
)
var um Usbmux
func setupUsbmux(t *testing.T) {
var err error
um, err = NewUsbmux()
if err != nil {
t.Fatal(err)
}
}
func Test_usbmux_Devices(t *testing.T) {
setupUsbmux(t)
devices, err := um.Devices()
if err != nil {
t.Fatal(err)
}
for _, dev := range devices {
t.Log(dev.Properties().SerialNumber, dev.Properties().ProductID, dev.Properties().DeviceID)
}
}
func Test_usbmux_ReadBUID(t *testing.T) {
setupUsbmux(t)
buid, err := um.ReadBUID()
if err != nil {
t.Fatal(err)
}
t.Log(buid)
}
func Test_usbmux_Listen(t *testing.T) {
setupUsbmux(t)
devNotifier := make(chan Device)
cancelFunc, err := um.Listen(devNotifier)
if err != nil {
t.Fatal(err)
}
go func() {
time.Sleep(20 * time.Second)
cancelFunc()
}()
for dev := range devNotifier {
if dev.Properties().ConnectionType != "" {
t.Log(dev.Properties().SerialNumber, dev.Properties().ProductID, dev.Properties().DeviceID)
} else {
t.Log(libimobiledevice.MessageTypeDeviceRemove, dev.Properties().DeviceID)
}
}
time.Sleep(5 * time.Second)
t.Log("Done")
}

View File

@@ -1,153 +0,0 @@
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()
}

View File

@@ -54,6 +54,7 @@ type LoginRequest struct {
PackageName string `json:"packageName"`
PhoneNumber string `json:"phoneNumber"`
Captcha string `json:"captcha"`
Password string `json:"password"`
}
type LogoutRequest struct {

View File

@@ -105,7 +105,7 @@ func loginHandler(c *gin.Context) {
return
}
err = dExt.Driver.LoginNoneUI(loginReq.PackageName, loginReq.PhoneNumber, loginReq.Captcha)
info, err := dExt.Driver.LoginNoneUI(loginReq.PackageName, loginReq.PhoneNumber, loginReq.Captcha, loginReq.Password)
if err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to login", c.HandlerName()))
c.JSON(http.StatusInternalServerError,
@@ -117,7 +117,7 @@ func loginHandler(c *gin.Context) {
c.Abort()
return
}
c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"})
c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success", Result: info})
}
func logoutHandler(c *gin.Context) {

View File

@@ -15,24 +15,25 @@ import (
type ActionMethod string
const (
ACTION_LOG ActionMethod = "log"
ACTION_AppInstall ActionMethod = "install"
ACTION_AppUninstall ActionMethod = "uninstall"
ACTION_AppClear ActionMethod = "app_clear"
ACTION_AppStart ActionMethod = "app_start"
ACTION_AppLaunch ActionMethod = "app_launch" // 启动 app 并堵塞等待 app 首屏加载完成
ACTION_AppTerminate ActionMethod = "app_terminate"
ACTION_AppStop ActionMethod = "app_stop"
ACTION_ScreenShot ActionMethod = "screenshot"
ACTION_Sleep ActionMethod = "sleep"
ACTION_SleepMS ActionMethod = "sleep_ms"
ACTION_SleepRandom ActionMethod = "sleep_random"
ACTION_StartCamera ActionMethod = "camera_start" // alias for app_launch camera
ACTION_StopCamera ActionMethod = "camera_stop" // alias for app_terminate camera
ACTION_SetClipboard ActionMethod = "set_clipboard"
ACTION_GetClipboard ActionMethod = "get_clipboard"
ACTION_SetIme ActionMethod = "set_ime"
ACTION_GetSource ActionMethod = "get_source"
ACTION_LOG ActionMethod = "log"
ACTION_AppInstall ActionMethod = "install"
ACTION_AppUninstall ActionMethod = "uninstall"
ACTION_AppClear ActionMethod = "app_clear"
ACTION_AppStart ActionMethod = "app_start"
ACTION_AppLaunch ActionMethod = "app_launch" // 启动 app 并堵塞等待 app 首屏加载完成
ACTION_AppTerminate ActionMethod = "app_terminate"
ACTION_AppStop ActionMethod = "app_stop"
ACTION_ScreenShot ActionMethod = "screenshot"
ACTION_Sleep ActionMethod = "sleep"
ACTION_SleepMS ActionMethod = "sleep_ms"
ACTION_SleepRandom ActionMethod = "sleep_random"
ACTION_StartCamera ActionMethod = "camera_start" // alias for app_launch camera
ACTION_StopCamera ActionMethod = "camera_stop" // alias for app_terminate camera
ACTION_SetClipboard ActionMethod = "set_clipboard"
ACTION_GetClipboard ActionMethod = "get_clipboard"
ACTION_SetIme ActionMethod = "set_ime"
ACTION_GetSource ActionMethod = "get_source"
ACTION_GetForegroundApp ActionMethod = "get_foreground_app"
// UI handling
ACTION_Home ActionMethod = "home"
@@ -46,6 +47,7 @@ const (
ACTION_Swipe ActionMethod = "swipe"
ACTION_Input ActionMethod = "input"
ACTION_Back ActionMethod = "back"
ACTION_KeyCode ActionMethod = "keycode"
// custom actions
ACTION_SwipeToTapApp ActionMethod = "swipe_to_tap_app" // swipe left & right to find app and tap
@@ -109,7 +111,8 @@ type ActionOptions struct {
MaxRetryTimes int `json:"max_retry_times,omitempty" yaml:"max_retry_times,omitempty"` // max retry times
IgnoreNotFoundError bool `json:"ignore_NotFoundError,omitempty" yaml:"ignore_NotFoundError,omitempty"` // ignore error if target element not found
Interval float64 `json:"interval,omitempty" yaml:"interval,omitempty"` // interval between retries in seconds
PressDuration float64 `json:"duration,omitempty" yaml:"duration,omitempty"` // used to set duration of ios swipe action
Duration float64 `json:"duration,omitempty" yaml:"duration,omitempty"` // used to set duration of ios swipe action
PressDuration float64 `json:"press_duration,omitempty" yaml:"press_duration,omitempty"` // used to set duration of ios swipe action
Steps int `json:"steps,omitempty" yaml:"steps,omitempty"` // used to set steps of android swipe action
Direction interface{} `json:"direction,omitempty" yaml:"direction,omitempty"` // used by swipe to tap text or app
Timeout int `json:"timeout,omitempty" yaml:"timeout,omitempty"` // TODO: wait timeout in seconds for mobile action
@@ -159,6 +162,9 @@ func (o *ActionOptions) Options() []ActionOption {
if o.Interval != 0 {
options = append(options, WithInterval(o.Interval))
}
if o.Duration != 0 {
options = append(options, WithDuration(o.Duration))
}
if o.PressDuration != 0 {
options = append(options, WithPressDuration(o.PressDuration))
}
@@ -309,8 +315,8 @@ func (o *ActionOptions) updateData(data map[string]interface{}) {
data["steps"] = 12 // default steps
}
if o.PressDuration > 0 {
data["duration"] = o.PressDuration
if o.Duration > 0 {
data["duration"] = o.Duration
}
if _, ok := data["duration"]; !ok {
data["duration"] = 0 // default duration
@@ -380,9 +386,15 @@ func WithInterval(sec float64) ActionOption {
}
}
func WithPressDuration(duration float64) ActionOption {
func WithDuration(duration float64) ActionOption {
return func(o *ActionOptions) {
o.PressDuration = duration
o.Duration = duration
}
}
func WithPressDuration(pressDuration float64) ActionOption {
return func(o *ActionOptions) {
o.PressDuration = pressDuration
}
}

View File

@@ -6,12 +6,15 @@ import (
"encoding/json"
"encoding/xml"
"fmt"
"io"
"io/fs"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"syscall"
"time"
"github.com/httprunner/funplugin/myexec"
@@ -245,6 +248,34 @@ func (ad *adbDriver) Unlock() (err error) {
return ad.PressKeyCodes(KCMenu, KMEmpty)
}
func (ad *adbDriver) Backspace(count int, options ...ActionOption) (err error) {
if count == 0 {
return nil
}
if count == 1 {
return ad.PressKeyCode(67)
}
keyArray := make([]KeyCode, count)
for i := range keyArray {
keyArray[i] = KeyCode(67)
}
return ad.combinationKey(keyArray)
}
func (ad *adbDriver) combinationKey(keyCodes []KeyCode) (err error) {
if len(keyCodes) == 1 {
return ad.PressKeyCode(keyCodes[0])
}
strKeyCodes := make([]string, len(keyCodes))
for i, keycode := range keyCodes {
strKeyCodes[i] = fmt.Sprintf("%d", keycode)
}
_, err = ad.adbClient.RunShellCommand(
"input", append([]string{"keycombination"}, strKeyCodes...)...)
return
}
func (ad *adbDriver) PressKeyCode(keyCode KeyCode) (err error) {
return ad.PressKeyCodes(keyCode, KMEmpty)
}
@@ -309,15 +340,11 @@ func (ad *adbDriver) TapFloat(x, y float64, options ...ActionOption) (err error)
return nil
}
func (ad *adbDriver) DoubleTap(x, y int, options ...ActionOption) error {
return ad.DoubleTapFloat(float64(x), float64(y), options...)
}
func (ad *adbDriver) DoubleTapFloat(x, y float64, options ...ActionOption) (err error) {
func (ad *adbDriver) DoubleTap(x, y float64, options ...ActionOption) error {
// adb shell input tap x y
xStr := fmt.Sprintf("%.1f", x)
yStr := fmt.Sprintf("%.1f", y)
_, err = ad.adbClient.RunShellCommand(
_, err := ad.adbClient.RunShellCommand(
"input", "tap", xStr, yStr)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("tap <%s, %s> failed", xStr, yStr))
@@ -331,22 +358,64 @@ func (ad *adbDriver) DoubleTapFloat(x, y float64, options ...ActionOption) (err
return nil
}
func (ad *adbDriver) TouchAndHold(x, y int, second ...float64) (err error) {
return ad.TouchAndHoldFloat(float64(x), float64(y), second...)
func (ad *adbDriver) TouchAndHold(x, y float64, options ...ActionOption) (err error) {
actionOptions := NewActionOptions(options...)
if len(actionOptions.Offset) == 2 {
x += float64(actionOptions.Offset[0])
y += float64(actionOptions.Offset[1])
}
x += actionOptions.getRandomOffset()
y += actionOptions.getRandomOffset()
duration := 1000.0
if actionOptions.Duration > 0 {
duration = actionOptions.Duration * 1000
}
// adb shell input swipe fromX fromY toX toY
_, err = ad.adbClient.RunShellCommand(
"input", "swipe",
fmt.Sprintf("%.1f", x), fmt.Sprintf("%.1f", y),
fmt.Sprintf("%.1f", x), fmt.Sprintf("%.1f", y),
fmt.Sprintf("%d", int(duration)),
)
if err != nil {
return errors.Wrap(err, "long press failed")
}
return nil
}
func (ad *adbDriver) TouchAndHoldFloat(x, y float64, second ...float64) (err error) {
err = errDriverNotImplemented
return
}
func (ad *adbDriver) Drag(fromX, fromY, toX, toY float64, options ...ActionOption) (err error) {
actionOptions := NewActionOptions(options...)
func (ad *adbDriver) Drag(fromX, fromY, toX, toY int, options ...ActionOption) error {
return ad.DragFloat(float64(fromX), float64(fromY), float64(toX), float64(toY), options...)
}
func (ad *adbDriver) DragFloat(fromX, fromY, toX, toY float64, options ...ActionOption) (err error) {
err = errDriverNotImplemented
return
if len(actionOptions.Offset) == 4 {
fromX += float64(actionOptions.Offset[0])
fromY += float64(actionOptions.Offset[1])
toX += float64(actionOptions.Offset[2])
toY += float64(actionOptions.Offset[3])
}
fromX += actionOptions.getRandomOffset()
fromY += actionOptions.getRandomOffset()
toX += actionOptions.getRandomOffset()
toY += actionOptions.getRandomOffset()
duration := 200.0
if actionOptions.Duration > 0 {
duration = actionOptions.Duration * 1000
}
command := "swipe"
if actionOptions.PressDuration > 0 {
command = "draganddrop"
}
// adb shell input swipe fromX fromY toX toY
_, err = ad.adbClient.RunShellCommand(
"input", command,
fmt.Sprintf("%.1f", fromX), fmt.Sprintf("%.1f", fromY),
fmt.Sprintf("%.1f", toX), fmt.Sprintf("%.1f", toY),
fmt.Sprintf("%d", int(duration)),
)
if err != nil {
return errors.Wrap(err, "drag failed")
}
return nil
}
func (ad *adbDriver) Swipe(fromX, fromY, toX, toY int, options ...ActionOption) error {
@@ -557,8 +626,8 @@ func (ad *adbDriver) Source(srcOpt ...SourceOption) (source string, err error) {
return
}
func (ad *adbDriver) LoginNoneUI(packageName, phoneNumber string, captcha string) error {
return errDriverNotImplemented
func (ad *adbDriver) LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error) {
return info, errDriverNotImplemented
}
func (ad *adbDriver) LogoutNoneUI(packageName string) error {
@@ -750,6 +819,10 @@ func (ad *adbDriver) GetSession() *DriverSession {
return &ad.Driver.session
}
func (ad *adbDriver) GetDriverResults() []*DriverResult {
return nil
}
func (ad *adbDriver) GetForegroundApp() (app AppInfo, err error) {
packageInfo, err := ad.adbClient.RunShellCommand(
"CLASSPATH=/data/local/tmp/evalite", "app_process", "/",
@@ -916,3 +989,63 @@ var androidActivities = map[string]map[string][]string{
},
// TODO: SPH, XHS
}
func (ad *adbDriver) RecordScreen(folderPath string, duration time.Duration) (videoPath string, err error) {
// 获取当前时间戳
timestamp := time.Now().Format("20060102_150405") + fmt.Sprintf("_%03d", time.Now().UnixNano()/1e6%1000)
// 创建文件名
fileName := fmt.Sprintf("%s/%s.mp4", folderPath, timestamp)
err = os.MkdirAll(folderPath, os.ModePerm)
if err != nil {
log.Error().Err(err).Msg("Error creating directory")
}
// 创建一个文件
file, err := os.Create(fileName)
if err != nil {
log.Error().Err(err)
return "", err
}
defer func() {
_ = file.Close()
}()
// scrcpy -s 7d21bb91 --record=file.mp4 -N
cmd := exec.Command(
"scrcpy",
"-s", ad.adbClient.Serial(),
fmt.Sprintf("--record=%s", fileName),
"-N",
)
cmd.Stdout = io.Discard
cmd.Stderr = io.Discard
// 启动命令
if err := cmd.Start(); err != nil {
log.Error().Err(err)
return "", err
}
timer := time.After(duration)
done := make(chan error)
go func() {
// 等待 ffmpeg 命令执行完毕
done <- cmd.Wait()
}()
select {
case <-timer:
// 超时,停止 scrcpy 进程
if err := cmd.Process.Signal(syscall.SIGINT); err != nil {
log.Error().Err(err)
}
case err := <-done:
// ffmpeg 正常结束
if err != nil {
log.Error().Err(err)
return "", err
}
}
return filepath.Abs(fileName)
}
func (ad *adbDriver) TearDown() {
}

View File

@@ -24,6 +24,12 @@ type stubAndroidDriver struct {
const StubSocketName = "com.bytest.device"
type AppLoginInfo struct {
Did string `json:"did,omitempty" yaml:"did,omitempty"`
Uid string `json:"uid,omitempty" yaml:"uid,omitempty"`
IsLogin bool `json:"is_login,omitempty" yaml:"is_login,omitempty"`
}
// newStubAndroidDriver
// 创建stub Driver address为forward后的端口格式127.0.0.1:${port}
func newStubAndroidDriver(address string, urlPrefix string, readTimeout ...time.Duration) (*stubAndroidDriver, error) {
@@ -174,52 +180,37 @@ func (sad *stubAndroidDriver) Source(srcOpt ...SourceOption) (source string, err
return res.(string), nil
}
func (sad *stubAndroidDriver) LoginNoneUIBak(packageName, phoneNumber, captcha string) error {
_, err := sad.adbClient.RunShellCommand(
"am", "broadcast",
"-a", fmt.Sprintf("%s.util.crony.action_login", packageName),
"-e", "phone", phoneNumber,
"-e", "code", captcha)
if err != nil {
return err
}
time.Sleep(10 * time.Second)
login, err := sad.isLogin(packageName)
if err != nil || !login {
log.Err(err).Msg("failed to login")
return fmt.Errorf("failed to login")
}
return err
}
func (sad *stubAndroidDriver) LoginNoneUI(packageName, phoneNumber, captcha string) error {
func (sad *stubAndroidDriver) LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error) {
params := map[string]interface{}{
"phone": phoneNumber,
"code": captcha,
}
if captcha != "" {
params["captcha"] = captcha
} else if password != "" {
params["password"] = password
} else {
return info, fmt.Errorf("password and capcha is empty")
}
resp, err := sad.httpPOST(params, "/host", "/login", "account")
if err != nil {
return err
return info, err
}
res, err := resp.valueConvertToJsonObject()
if err != nil {
return err
return info, err
}
log.Info().Msgf("%v", res)
if res["isSuccess"] != true {
err = fmt.Errorf("failed to login %s", res["data"])
err = fmt.Errorf("falied to login %s", res["data"])
log.Err(err).Msgf("%v", res)
return err
return info, err
}
time.Sleep(10 * time.Second)
login, err := sad.isLogin(packageName)
if err != nil {
return err
time.Sleep(20 * time.Second)
info, err = sad.getLoginAppInfo(packageName)
if err != nil || !info.IsLogin {
return info, fmt.Errorf("falied to login %v", info)
}
if !login {
return fmt.Errorf("failed to login")
}
return nil
return info, nil
}
func (sad *stubAndroidDriver) LogoutNoneUI(packageName string) error {
@@ -233,11 +224,15 @@ func (sad *stubAndroidDriver) LogoutNoneUI(packageName string) error {
}
log.Info().Msgf("%v", res)
if res["isSuccess"] != true {
err = fmt.Errorf("failed to logout %s", res["data"])
err = fmt.Errorf("falied to logout %s", res["data"])
log.Err(err).Msgf("%v", res)
return err
}
log.Info().Interface("resp", resp).Msg("logout success")
fmt.Printf("%v", resp)
if err != nil {
return err
}
time.Sleep(3 * time.Second)
return nil
}
@@ -256,21 +251,44 @@ func (sad *stubAndroidDriver) LoginNoneUIDynamic(packageName, phoneNumber string
return nil
}
func (sad *stubAndroidDriver) isLogin(packageName string) (login bool, err error) {
resp, err := sad.httpGET("/host", "/login", "/check")
func (sad *stubAndroidDriver) SetHDTStatus(status bool) error {
_, err := sad.adbClient.RunShellCommand("settings", "put", "global", "feedbacker_sso_bypass_token", "default_sso_bypass_token")
if err != nil {
return false, err
log.Warn().Msg(fmt.Sprintf("failed to disable sso, error: %v", err))
}
params := map[string]interface{}{
"ClassName": "com.bytedance.ies.stark.framework.HybridDevTool",
"Method": "setEnabled",
"RetType": "",
"Args": []bool{status},
}
res, err := sad.sendCommand("com.ss.android.ugc.aweme", "CallStaticMethod", params)
if err != nil {
return fmt.Errorf("failed to set hds status %v, error: %v", status, err)
}
log.Info().Msg(fmt.Sprintf("set hdt status result: %s", res))
return nil
}
func (sad *stubAndroidDriver) getLoginAppInfo(packageName string) (info AppLoginInfo, err error) {
resp, err := sad.httpGET("/host", "/app", "/info")
if err != nil {
return info, err
}
res, err := resp.valueConvertToJsonObject()
if err != nil {
return false, err
return info, err
}
log.Info().Msgf("%v", res)
if res["isSuccess"] != true {
err = fmt.Errorf("failed to check login %s", res["data"])
err = fmt.Errorf("falied to get app info %s", res["data"])
log.Err(err).Msgf("%v", res)
return false, err
return info, err
}
log.Info().Interface("resp", resp).Msg("check login success")
return true, nil
err = json.Unmarshal([]byte(res["data"].(string)), &info)
if err != nil {
err = fmt.Errorf("falied to parse app info %s", res["data"])
return
}
return info, nil
}

View File

@@ -1,6 +1,10 @@
package uixt
import "testing"
import (
"fmt"
"os"
"testing"
)
var androidStubDriver *stubAndroidDriver
@@ -30,21 +34,13 @@ func TestSource(t *testing.T) {
t.Log(source)
}
func TestIsLogin(t *testing.T) {
setupStubDriver(t)
res, err := androidStubDriver.isLogin("com.ss.android.ugc.aweme")
if err != nil {
t.Fatal(err)
}
t.Log(res)
}
func TestLogin(t *testing.T) {
setupStubDriver(t)
err := androidStubDriver.LoginNoneUI("com.ss.android.ugc.aweme", "12342316231", "8517")
info, err := androidStubDriver.LoginNoneUI("com.ss.android.ugc.aweme", "12342316231", "8517", "")
if err != nil {
t.Fatal(err)
}
t.Log(info)
}
func TestLogout(t *testing.T) {
@@ -54,3 +50,92 @@ func TestLogout(t *testing.T) {
t.Fatal(err)
}
}
func TestSwipe(t *testing.T) {
setupStubDriver(t)
err := androidStubDriver.Swipe(878, 2375, 672, 2375)
if err != nil {
t.Fatal(err)
}
}
func TestTap(t *testing.T) {
setupStubDriver(t)
err := androidStubDriver.Tap(900, 400)
if err != nil {
t.Fatal(err)
}
}
func TestDoubleTap(t *testing.T) {
setupStubDriver(t)
err := androidStubDriver.DoubleTap(500, 500)
if err != nil {
t.Fatal(err)
}
}
func TestLongPress(t *testing.T) {
setupStubDriver(t)
err := androidStubDriver.Swipe(1036, 1076, 1036, 1076, WithDuration(3))
if err != nil {
t.Fatal(err)
}
}
func TestInput(t *testing.T) {
setupStubDriver(t)
err := androidStubDriver.Input("\"哈哈\"")
if err != nil {
t.Fatal(err)
}
}
func TestSave(t *testing.T) {
setupStubDriver(t)
raw, err := androidStubDriver.Screenshot()
if err != nil {
t.Fatal(err)
}
source, err := androidStubDriver.Source()
if err != nil {
t.Fatal(err)
}
step := 14
file, err := os.Create(fmt.Sprintf("/Users/bytedance/workcode/wings_algorithm/testcases/data/cases/0/%d.jpg", step))
if err != nil {
t.Fatal(err)
}
file.Write(raw.Bytes())
file, err = os.Create(fmt.Sprintf("/Users/bytedance/workcode/wings_algorithm/testcases/data/cases/0/%d.json", step))
if err != nil {
t.Fatal(err)
}
file.Write([]byte(source))
}
func TestAppLaunch(t *testing.T) {
setupStubDriver(t)
err := androidStubDriver.AppLaunch("com.ss.android.ugc.aweme")
if err != nil {
t.Fatal(err)
}
}
func TestAppTerminal(t *testing.T) {
setupStubDriver(t)
_, err := androidStubDriver.AppTerminate("com.ss.android.ugc.aweme")
if err != nil {
t.Fatal(err)
}
}
func TestAppInfo(t *testing.T) {
setupStubDriver(t)
info, err := androidStubDriver.getLoginAppInfo("com.ss.android.ugc.aweme")
if err != nil {
t.Fatal(err)
}
t.Log(info)
}

View File

@@ -262,7 +262,7 @@ func TestDriver_Drag(t *testing.T) {
}
time.Sleep(time.Millisecond * 200)
err = driver.DragFloat(400, 501.5, 400, 261.5)
err = driver.Drag(400, 501.5, 400, 261.5)
if err != nil {
t.Fatal(err)
}
@@ -502,3 +502,21 @@ func TestTapTexts(t *testing.T) {
t.Fatal(err)
}
}
func TestRecordVideo(t *testing.T) {
setupAndroidAdbDriver(t)
path, err := driverExt.Driver.(*adbDriver).RecordScreen("", 5*time.Second)
if err != nil {
t.Fatal(err)
}
println(path)
}
func Test_Android_Backspace(t *testing.T) {
setupAndroidAdbDriver(t)
err := driverExt.Driver.Backspace(1)
if err != nil {
t.Fatal(err)
}
}

View File

@@ -294,8 +294,8 @@ func (ud *uiaDriver) Orientation() (orientation Orientation, err error) {
return
}
func (ud *uiaDriver) DoubleTap(x, y int, options ...ActionOption) error {
return ud.DoubleFloatTap(float64(x), float64(y))
func (ud *uiaDriver) DoubleTap(x, y float64, options ...ActionOption) error {
return ud.DoubleFloatTap(x, y)
}
func (ud *uiaDriver) DoubleFloatTap(x, y float64) error {
@@ -362,20 +362,18 @@ func (ud *uiaDriver) TapFloat(x, y float64, options ...ActionOption) (err error)
return err
}
func (ud *uiaDriver) TouchAndHold(x, y int, second ...float64) (err error) {
return ud.TouchAndHoldFloat(float64(x), float64(y), second...)
}
func (ud *uiaDriver) TouchAndHoldFloat(x, y float64, second ...float64) (err error) {
if len(second) == 0 {
second = []float64{1.0}
func (ud *uiaDriver) TouchAndHold(x, y float64, options ...ActionOption) (err error) {
opts := NewActionOptions(options...)
duration := opts.Duration
if duration == 0 {
duration = 1.0
}
// register(postHandler, new TouchLongClick("/wd/hub/session/:sessionId/touch/longclick"))
data := map[string]interface{}{
"params": map[string]interface{}{
"x": x,
"y": y,
"duration": int(second[0] * 1000),
"duration": int(duration * 1000),
},
}
_, err = ud.httpPOST(data, "/session", ud.session.ID, "touch/longclick")
@@ -386,11 +384,7 @@ func (ud *uiaDriver) TouchAndHoldFloat(x, y float64, second ...float64) (err err
// the smoothness and speed of the swipe by specifying the number of steps.
// Each step execution is throttled to 5 milliseconds per step, so for a 100
// steps, the swipe will take around 0.5 seconds to complete.
func (ud *uiaDriver) Drag(fromX, fromY, toX, toY int, options ...ActionOption) error {
return ud.DragFloat(float64(fromX), float64(fromY), float64(toX), float64(toY), options...)
}
func (ud *uiaDriver) DragFloat(fromX, fromY, toX, toY float64, options ...ActionOption) (err error) {
func (ud *uiaDriver) Drag(fromX, fromY, toX, toY float64, options ...ActionOption) (err error) {
actionOptions := NewActionOptions(options...)
if len(actionOptions.Offset) == 4 {
fromX += float64(actionOptions.Offset[0])
@@ -662,3 +656,10 @@ func (ud *uiaDriver) TapByTexts(actions ...TapTextAction) error {
}
return nil
}
func (ud *uiaDriver) GetDriverResults() []*DriverResult {
defer func() {
ud.Driver.driverResults = nil
}()
return ud.Driver.driverResults
}

View File

@@ -62,8 +62,9 @@ type Driver struct {
client *http.Client
// cache to avoid repeated query
scale float64
windowSize *Size
scale float64
windowSize *Size
driverResults []*DriverResult
// cache session data
session DriverSession

View File

@@ -1,27 +1,42 @@
package uixt
func (dExt *DriverExt) Drag(pathname string, toX, toY int, pressForDuration ...float64) (err error) {
return dExt.DragFloat(pathname, float64(toX), float64(toY), pressForDuration...)
import (
"fmt"
"github.com/rs/zerolog/log"
)
func (dExt *DriverExt) Drag(fromX, fromY, toX, toY float64, options ...ActionOption) (err error) {
return dExt.Driver.Drag(fromX, fromY, toX, toY, options...)
}
func (dExt *DriverExt) DragFloat(pathname string, toX, toY float64, pressForDuration ...float64) (err error) {
return dExt.DragOffsetFloat(pathname, toX, toY, 0, 0, pressForDuration...)
}
func (dExt *DriverExt) DragOffset(pathname string, toX, toY int, xOffset, yOffset float64, pressForDuration ...float64) (err error) {
return dExt.DragOffsetFloat(pathname, float64(toX), float64(toY), xOffset, yOffset, pressForDuration...)
}
func (dExt *DriverExt) DragOffsetFloat(pathname string, toX, toY, xOffset, yOffset float64, pressForDuration ...float64) (err error) {
if len(pressForDuration) == 0 {
pressForDuration = []float64{1.0}
}
point, err := dExt.FindUIRectInUIKit(pathname)
func (dExt *DriverExt) DragRelative(fromX, fromY, toX, toY float64, options ...ActionOption) (err error) {
width := dExt.windowSize.Width
height := dExt.windowSize.Height
orientation, err := dExt.Driver.Orientation()
if err != nil {
return err
log.Warn().Err(err).Msgf("drag from (%v, %v) to (%v, %v) get orientation failed, use default orientation",
fromX, fromY, toX, toY)
orientation = OrientationPortrait
}
return dExt.Driver.DragFloat(point.X+xOffset, point.Y+yOffset, toX, toY,
WithPressDuration(pressForDuration[0]))
if !assertRelative(fromX) || !assertRelative(fromY) ||
!assertRelative(toX) || !assertRelative(toY) {
return fmt.Errorf("fromX(%f), fromY(%f), toX(%f), toY(%f) must be less than 1",
fromX, fromY, toX, toY)
}
// 左转和右转都是"LANDSCAPE"
if orientation == OrientationPortrait {
fromX = float64(width) * fromX
fromY = float64(height) * fromY
toX = float64(width) * toX
toY = float64(height) * toY
} else {
fromX = float64(height) * fromX
fromY = float64(width) * fromY
toX = float64(height) * toX
toY = float64(width) * toY
}
return dExt.Driver.Drag(fromX, fromY, toX, toY, options...)
}

View File

@@ -20,6 +20,7 @@ type DriverExt struct {
Driver IWebDriver
ImageService IImageService // used to extract image data
windowSize Size
// funplugin
plugin funplugin.IPlugin
}

View File

@@ -145,22 +145,6 @@ func (dev *HarmonyDevice) NewUSBDriver(options ...DriverOption) (driver IWebDriv
return harmonyDriver, nil
}
func (dev *HarmonyDevice) StartPerf() error {
return nil
}
func (dev *HarmonyDevice) StopPerf() string {
return ""
}
func (dev *HarmonyDevice) StartPcap() error {
return nil
}
func (dev *HarmonyDevice) StopPcap() string {
return ""
}
func (dev *HarmonyDevice) Install(appPath string, options ...InstallOption) error {
return nil
}

View File

@@ -175,27 +175,15 @@ func (hd *hdcDriver) TapFloat(x, y float64, options ...ActionOption) error {
return hd.uiDriver.InjectGesture(ghdc.NewGesture().Start(ghdc.Point{X: int(x), Y: int(y)}).Pause(100))
}
func (hd *hdcDriver) DoubleTap(x, y int, options ...ActionOption) error {
func (hd *hdcDriver) DoubleTap(x, y float64, options ...ActionOption) error {
return errDriverNotImplemented
}
func (hd *hdcDriver) DoubleTapFloat(x, y float64, options ...ActionOption) error {
func (hd *hdcDriver) TouchAndHold(x, y float64, options ...ActionOption) (err error) {
return errDriverNotImplemented
}
func (hd *hdcDriver) TouchAndHold(x, y int, second ...float64) error {
return errDriverNotImplemented
}
func (hd *hdcDriver) TouchAndHoldFloat(x, y float64, second ...float64) error {
return errDriverNotImplemented
}
func (hd *hdcDriver) Drag(fromX, fromY, toX, toY int, options ...ActionOption) error {
return errDriverNotImplemented
}
func (hd *hdcDriver) DragFloat(fromX, fromY, toX, toY float64, options ...ActionOption) error {
func (hd *hdcDriver) Drag(fromX, fromY, toX, toY float64, options ...ActionOption) error {
return errDriverNotImplemented
}
@@ -260,6 +248,10 @@ func (hd *hdcDriver) PressBack(options ...ActionOption) error {
return hd.uiDriver.PressBack()
}
func (hd *hdcDriver) Backspace(count int, options ...ActionOption) (err error) {
return nil
}
func (hd *hdcDriver) PressKeyCode(keyCode KeyCode) (err error) {
return errDriverNotImplemented
}
@@ -292,8 +284,9 @@ func (hd *hdcDriver) Source(srcOpt ...SourceOption) (string, error) {
return "", nil
}
func (hd *hdcDriver) LoginNoneUI(packageName, phoneNumber string, captcha string) error {
return errDriverNotImplemented
func (hd *hdcDriver) LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error) {
err = errDriverNotImplemented
return
}
func (hd *hdcDriver) LogoutNoneUI(packageName string) error {
@@ -336,3 +329,14 @@ func (hd *hdcDriver) StopCaptureLog() (result interface{}, err error) {
// defer clear(hd.points)
return hd.points, nil
}
func (hd *hdcDriver) GetDriverResults() []*DriverResult {
return nil
}
func (hd *hdcDriver) RecordScreen(folderPath string, duration time.Duration) (videoPath string, err error) {
return "", nil
}
func (hd *hdcDriver) TearDown() {
}

View File

@@ -41,7 +41,7 @@ func TestHarmonyTap(t *testing.T) {
}
}
func TestSwipe(t *testing.T) {
func TestHarmonySwipe(t *testing.T) {
setupHarmonyDevice(t)
err := harmonyDriverExt.SwipeLeft()
if err != nil {
@@ -49,7 +49,7 @@ func TestSwipe(t *testing.T) {
}
}
func TestInput(t *testing.T) {
func TestHarmonyInput(t *testing.T) {
setupHarmonyDevice(t)
err := harmonyDriverExt.Input("test")
if err != nil {

View File

@@ -446,12 +446,14 @@ type DriverOptions struct {
plugin funplugin.IPlugin
withImageService bool
withResultFolder bool
withUIAction bool
}
func NewDriverOptions() *DriverOptions {
return &DriverOptions{
withImageService: true,
withResultFolder: true,
withUIAction: true,
}
}
@@ -475,6 +477,12 @@ func WithDriverResultFolder(withResultFolder bool) DriverOption {
}
}
func WithUIAction(withUIAction bool) DriverOption {
return func(options *DriverOptions) {
options.withUIAction = withUIAction
}
}
func WithDriverPlugin(plugin funplugin.IPlugin) DriverOption {
return func(options *DriverOptions) {
options.plugin = plugin
@@ -490,17 +498,13 @@ type IDevice interface {
// TODO: add ctx to NewDriver
NewDriver(...DriverOption) (driverExt *DriverExt, err error)
StartPerf() error
StopPerf() string
StartPcap() error
StopPcap() string
Install(appPath string, options ...InstallOption) error
Uninstall(packageName string) error
GetPackageInfo(packageName string) (AppInfo, error)
GetCurrentWindow() (windowInfo WindowInfo, err error)
// Teardown() error
}
type ForegroundApp struct {
@@ -577,18 +581,15 @@ type IWebDriver interface {
TapFloat(x, y float64, options ...ActionOption) error
// DoubleTap Sends a double tap event at the coordinate.
DoubleTap(x, y int, options ...ActionOption) error
DoubleTapFloat(x, y float64, options ...ActionOption) error
DoubleTap(x, y float64, options ...ActionOption) error
// TouchAndHold Initiates a long-press gesture at the coordinate, holding for the specified duration.
// second: The default value is 1
TouchAndHold(x, y int, second ...float64) error
TouchAndHoldFloat(x, y float64, second ...float64) error
TouchAndHold(x, y float64, options ...ActionOption) error
// Drag Initiates a press-and-hold gesture at the coordinate, then drags to another coordinate.
// WithPressDurationOption option can be used to set pressForDuration (default to 1 second).
Drag(fromX, fromY, toX, toY int, options ...ActionOption) error
DragFloat(fromX, fromY, toX, toY float64, options ...ActionOption) error
Drag(fromX, fromY, toX, toY float64, options ...ActionOption) error
// Swipe works like Drag, but `pressForDuration` value is 0
Swipe(fromX, fromY, toX, toY int, options ...ActionOption) error
@@ -620,12 +621,14 @@ type IWebDriver interface {
PressKeyCode(keyCode KeyCode) (err error)
Backspace(count int, options ...ActionOption) (err error)
Screenshot() (*bytes.Buffer, error)
// Source Return application elements tree
Source(srcOpt ...SourceOption) (string, error)
LoginNoneUI(packageName, phoneNumber string, captcha string) error
LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error)
LogoutNoneUI(packageName string) error
TapByText(text string, options ...ActionOption) error
@@ -647,4 +650,9 @@ type IWebDriver interface {
// triggers the log capture and returns the log entries
StartCaptureLog(identifier ...string) (err error)
StopCaptureLog() (result interface{}, err error)
GetDriverResults() []*DriverResult
RecordScreen(folderPath string, duration time.Duration) (videoPath string, err error)
TearDown()
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,11 +5,17 @@ import (
"encoding/base64"
builtinJSON "encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"syscall"
"time"
"github.com/pkg/errors"
@@ -18,35 +24,32 @@ import (
"github.com/httprunner/httprunner/v4/hrp/code"
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
"github.com/httprunner/httprunner/v4/hrp/internal/json"
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
)
type wdaDriver struct {
Driver
// default port
defaultConn gidevice.InnerConn
// mjpeg port
mjpegUSBConn gidevice.InnerConn // via USB
mjpegHTTPConn net.Conn // via HTTP
udid string
device *IOSDevice
mjpegHTTPConn net.Conn // via HTTP
mjpegClient *http.Client
mjpegUrl string
}
func (wd *wdaDriver) resetSession() error {
capabilities := NewCapabilities()
capabilities.WithDefaultAlertAction(AlertActionAccept)
_, err := wd.NewSession(capabilities)
sessionInfo, err := wd.NewSession(capabilities)
if err != nil {
return err
}
wd.session.ID = sessionInfo.SessionId
return nil
}
func (wd *wdaDriver) httpRequest(method string, rawURL string, rawBody []byte, disableRetry ...bool) (rawResp rawResponse, err error) {
disableRetryBool := len(disableRetry) > 0 && disableRetry[0]
for retryCount := 1; retryCount <= 5; retryCount++ {
for retryCount := 1; retryCount <= 2; retryCount++ {
rawResp, err = wd.Driver.httpRequest(method, rawURL, rawBody)
if err == nil || disableRetryBool {
return
@@ -108,19 +111,10 @@ func (wd *wdaDriver) NewSession(capabilities Capabilities) (sessionInfo SessionI
if sessionInfo, err = rawResp.valueConvertToSessionInfo(); err != nil {
return SessionInfo{}, err
}
wd.Driver.session.Reset()
wd.Driver.session.ID = sessionInfo.SessionId
return
}
func (wd *wdaDriver) DeleteSession() (err error) {
if wd.defaultConn != nil {
wd.defaultConn.Close()
}
if wd.mjpegUSBConn != nil {
wd.mjpegUSBConn.Close()
}
if wd.mjpegClient != nil {
wd.mjpegClient.CloseIdleConnections()
}
@@ -399,6 +393,9 @@ func (wd *wdaDriver) AppLaunch(bundleId string) (err error) {
// [[FBRoute POST:@"/wda/apps/launch"] respondWithTarget:self action:@selector(handleSessionAppLaunch:)]
data := make(map[string]interface{})
data["bundleId"] = bundleId
data["environment"] = map[string]interface{}{
"SHOW_EXPLORER": "NO",
}
_, err = wd.httpPOST(data, "/session", wd.session.ID, "/wda/apps/launch")
if err != nil {
return errors.Wrap(code.MobileUILaunchAppError,
@@ -448,20 +445,30 @@ func (wd *wdaDriver) AppDeactivate(second float64) (err error) {
return
}
func (wd *wdaDriver) GetForegroundApp() (app AppInfo, err error) {
// appInfo, err := wd.ActiveAppInfo()
// if err != nil {
// return AppInfo{}, err
// }
// app = AppInfo{
// AppBaseInfo: AppBaseInfo{
// PackageName: appInfo.BundleId,
// Activity: "",
// },
// }
return AppInfo{}, errors.Wrap(errDriverNotImplemented,
"GetForegroundApp not implemented for ios")
func (wd *wdaDriver) GetForegroundApp() (appInfo AppInfo, err error) {
activeAppInfo, err := wd.ActiveAppInfo()
appInfo.BundleId = activeAppInfo.BundleId
if err != nil {
return appInfo, err
}
apps, err := wd.device.ListApps(ApplicationTypeAny)
if err != nil {
return appInfo, err
}
for _, app := range apps {
if app.CFBundleIdentifier == activeAppInfo.BundleId {
appInfo.BundleId = app.CFBundleIdentifier
appInfo.AppName = app.CFBundleName
appInfo.VersionName = app.CFBundleShortVersionString
appInfo.PackageName = app.CFBundleIdentifier
versionCode, err := strconv.Atoi(app.CFBundleVersion)
if err == nil {
appInfo.VersionCode = versionCode
}
return appInfo, err
}
}
return appInfo, err
}
func (wd *wdaDriver) AssertForegroundApp(bundleId string, viewControllerType ...string) error {
@@ -499,43 +506,35 @@ func (wd *wdaDriver) TapFloat(x, y float64, options ...ActionOption) (err error)
return
}
func (wd *wdaDriver) DoubleTap(x, y int, options ...ActionOption) error {
return wd.DoubleTapFloat(float64(x), float64(y), options...)
}
func (wd *wdaDriver) DoubleTapFloat(x, y float64, options ...ActionOption) (err error) {
func (wd *wdaDriver) DoubleTap(x, y float64, options ...ActionOption) (err error) {
// [[FBRoute POST:@"/wda/doubleTap"] respondWithTarget:self action:@selector(handleDoubleTapCoordinate:)]
actionOptions := NewActionOptions(options...)
x = wd.toScale(x)
y = wd.toScale(y)
if len(actionOptions.Offset) == 2 {
x += float64(actionOptions.Offset[0])
y += float64(actionOptions.Offset[1])
}
x += actionOptions.getRandomOffset()
y += actionOptions.getRandomOffset()
data := map[string]interface{}{
"x": wd.toScale(x),
"y": wd.toScale(y),
"x": x,
"y": y,
}
_, err = wd.httpPOST(data, "/session", wd.session.ID, "/wda/doubleTap")
return
}
func (wd *wdaDriver) TouchAndHold(x, y int, second ...float64) error {
return wd.TouchAndHoldFloat(float64(x), float64(y), second...)
}
func (wd *wdaDriver) TouchAndHoldFloat(x, y float64, second ...float64) (err error) {
// [[FBRoute POST:@"/wda/touchAndHold"] respondWithTarget:self action:@selector(handleTouchAndHoldCoordinate:)]
data := map[string]interface{}{
"x": wd.toScale(x),
"y": wd.toScale(y),
func (wd *wdaDriver) TouchAndHold(x, y float64, options ...ActionOption) (err error) {
actionOptions := NewActionOptions(options...)
if actionOptions.Duration == 0 {
options = append(options, WithDuration(1))
}
if len(second) == 0 || second[0] <= 0 {
second = []float64{1.0}
}
data["duration"] = second[0]
_, err = wd.httpPOST(data, "/session", wd.session.ID, "/wda/touchAndHold")
return
return wd.TapFloat(x, y, options...)
}
func (wd *wdaDriver) Drag(fromX, fromY, toX, toY int, options ...ActionOption) error {
return wd.DragFloat(float64(fromX), float64(fromY), float64(toX), float64(toY), options...)
}
func (wd *wdaDriver) DragFloat(fromX, fromY, toX, toY float64, options ...ActionOption) (err error) {
func (wd *wdaDriver) Drag(fromX, fromY, toX, toY float64, options ...ActionOption) (err error) {
// [[FBRoute POST:@"/wda/dragfromtoforduration"] respondWithTarget:self action:@selector(handleDragCoordinate:)]
actionOptions := NewActionOptions(options...)
@@ -560,11 +559,15 @@ func (wd *wdaDriver) DragFloat(fromX, fromY, toX, toY float64, options ...Action
"toX": toX,
"toY": toY,
}
if actionOptions.PressDuration > 0 {
data["pressDuration"] = actionOptions.PressDuration
}
// update data options in post data for extra WDA configurations
actionOptions.updateData(data)
// wda 43 version
_, err = wd.httpPOST(data, "/session", wd.session.ID, "/wda/dragfromtoforduration")
// _, err = wd.httpPOST(data, "/session", wd.session.ID, "/wda/drag")
return
}
@@ -573,7 +576,7 @@ func (wd *wdaDriver) Swipe(fromX, fromY, toX, toY int, options ...ActionOption)
}
func (wd *wdaDriver) SwipeFloat(fromX, fromY, toX, toY float64, options ...ActionOption) error {
return wd.DragFloat(fromX, fromY, toX, toY, options...)
return wd.Drag(fromX, fromY, toX, toY, options...)
}
func (wd *wdaDriver) SetPasteboard(contentType PasteboardType, content string) (err error) {
@@ -619,6 +622,20 @@ func (wd *wdaDriver) SendKeys(text string, options ...ActionOption) (err error)
return
}
func (wd *wdaDriver) Backspace(count int, options ...ActionOption) (err error) {
if count == 0 {
return nil
}
actionOptions := NewActionOptions(options...)
data := map[string]interface{}{"count": count}
// new data options in post data for extra WDA configurations
actionOptions.updateData(data)
_, err = wd.httpPOST(data, "/gtf/interaction/input/backspace")
return
}
func (wd *wdaDriver) Input(text string, options ...ActionOption) (err error) {
return wd.SendKeys(text, options...)
}
@@ -671,8 +688,8 @@ func (wd *wdaDriver) PressButton(devBtn DeviceButton) (err error) {
return
}
func (wd *wdaDriver) LoginNoneUI(packageName, phoneNumber string, captcha string) error {
return errDriverNotImplemented
func (wd *wdaDriver) LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error) {
return info, errDriverNotImplemented
}
func (wd *wdaDriver) LogoutNoneUI(packageName string) error {
@@ -742,11 +759,13 @@ func (wd *wdaDriver) Screenshot() (raw *bytes.Buffer, err error) {
// [[FBRoute GET:@"/screenshot"].withoutSession respondWithTarget:self action:@selector(handleGetScreenshot:)]
var rawResp rawResponse
if rawResp, err = wd.httpGET("/session", wd.session.ID, "/screenshot"); err != nil {
return nil, errors.Wrap(err, "get WDA screenshot data failed")
return nil, errors.Wrap(code.DeviceScreenShotError,
fmt.Sprintf("get WDA screenshot data failed: %v", err))
}
if raw, err = rawResp.valueDecodeAsBase64(); err != nil {
return nil, errors.Wrap(err, "decode WDA screenshot data failed")
return nil, errors.Wrap(code.DeviceScreenShotError,
fmt.Sprintf("decode WDA screenshot data failed: %v", err))
}
return
}
@@ -864,6 +883,70 @@ func (wd *wdaDriver) triggerWDALog(data map[string]interface{}) (rawResp []byte,
return wd.httpPOST(data, "/gtf/automation/log")
}
func (wd *wdaDriver) RecordScreen(folderPath string, duration time.Duration) (videoPath string, err error) {
// 获取当前时间戳
timestamp := time.Now().Format("20060102_150405") + fmt.Sprintf("_%03d", time.Now().UnixNano()/1e6%1000)
// 创建文件名
fileName := fmt.Sprintf("%s/%s.mp4", folderPath, timestamp)
err = os.MkdirAll(folderPath, os.ModePerm)
if err != nil {
log.Error().Err(err).Msg("Error creating directory")
}
// 创建一个文件
file, err := os.Create(fileName)
if err != nil {
fmt.Println("Error creating file:", err)
return "", err
}
defer func() {
// 确保文件在程序结束时被删除
_ = file.Close()
}()
// ffmpeg 命令
cmd := exec.Command(
"ffmpeg",
"-use_wallclock_as_timestamps", "1",
"-f", "mjpeg",
"-y",
"-r", "10",
"-i", "http://"+wd.mjpegUrl,
"-c:v", "libx264",
"-vf", "pad=width=ceil(iw/2)*2:height=ceil(ih/2)*2",
fileName,
)
cmd.Stdout = io.Discard
cmd.Stderr = io.Discard
// 启动命令
if err := cmd.Start(); err != nil {
fmt.Println("Error starting ffmpeg command:", err)
return "", err
}
timer := time.After(duration)
done := make(chan error)
go func() {
// 等待 ffmpeg 命令执行完毕
done <- cmd.Wait()
}()
select {
case <-timer:
// 超时,停止 ffmpeg 进程
fmt.Println("Time is up, stopping ffmpeg command...")
if err := cmd.Process.Signal(syscall.SIGINT); err != nil {
fmt.Println("Error killing ffmpeg process:", err)
}
case err := <-done:
// ffmpeg 正常结束
if err != nil {
fmt.Println("FFmpeg finished with error:", err)
} else {
fmt.Println("FFmpeg finished successfully")
}
}
return filepath.Abs(fileName)
}
func (wd *wdaDriver) StartCaptureLog(identifier ...string) error {
log.Info().Msg("start WDA log recording")
if identifier == nil {
@@ -903,8 +986,20 @@ func (wd *wdaDriver) StopCaptureLog() (result interface{}, err error) {
return reply.Value, nil
}
func (ud *wdaDriver) GetSession() *DriverSession {
return &ud.Driver.session
func (wd *wdaDriver) GetSession() *DriverSession {
return &wd.Driver.session
}
func (wd *wdaDriver) GetDriverResults() []*DriverResult {
defer func() {
wd.Driver.driverResults = nil
}()
return wd.Driver.driverResults
}
func (wd *wdaDriver) TearDown() {
wd.mjpegClient.CloseIdleConnections()
wd.client.CloseIdleConnections()
}
type rawResponse []byte

View File

@@ -0,0 +1,569 @@
package uixt
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/rs/zerolog/log"
)
type stubIOSDriver struct {
bightInsightPrefix string
serverPrefix string
timeout time.Duration
Driver
*wdaDriver
device *IOSDevice
}
func newStubIOSDriver(bightInsightAddr, serverAddr string, dev *IOSDevice, readTimeout ...time.Duration) (*stubIOSDriver, error) {
timeout := 10 * time.Second
if len(readTimeout) > 0 {
timeout = readTimeout[0]
}
driver := new(stubIOSDriver)
driver.device = dev
driver.bightInsightPrefix = bightInsightAddr
driver.serverPrefix = serverAddr
driver.timeout = timeout
driver.Driver.client = &http.Client{
Timeout: time.Second * 20, // 设置超时时间为 10 秒
}
return driver, nil
}
func (s *stubIOSDriver) setUpWda() (err error) {
if s.wdaDriver == nil {
capabilities := NewCapabilities()
capabilities.WithDefaultAlertAction(AlertActionAccept)
driver, err := s.device.NewHTTPDriver(capabilities)
if err != nil {
log.Error().Err(err).Msg("stub driver failed to init wda driver")
return err
}
s.wdaDriver = driver.(*wdaDriver)
}
return nil
}
// NewSession starts a new session and returns the SessionInfo.
func (s *stubIOSDriver) NewSession(capabilities Capabilities) (SessionInfo, error) {
err := s.setUpWda()
if err != nil {
return SessionInfo{}, err
}
return s.wdaDriver.NewSession(capabilities)
}
// DeleteSession Kills application associated with that session and removes session
// 1. alertsMonitor disable
// 2. testedApplicationBundleId terminate
func (s *stubIOSDriver) DeleteSession() error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.DeleteSession()
}
func (s *stubIOSDriver) Status() (DeviceStatus, error) {
err := s.setUpWda()
if err != nil {
return DeviceStatus{}, err
}
return s.wdaDriver.Status()
}
func (s *stubIOSDriver) DeviceInfo() (DeviceInfo, error) {
err := s.setUpWda()
if err != nil {
return DeviceInfo{}, err
}
return s.wdaDriver.DeviceInfo()
}
func (s *stubIOSDriver) Location() (Location, error) {
err := s.setUpWda()
if err != nil {
return Location{}, err
}
return s.wdaDriver.Location()
}
func (s *stubIOSDriver) BatteryInfo() (BatteryInfo, error) {
err := s.setUpWda()
if err != nil {
return BatteryInfo{}, err
}
return s.wdaDriver.BatteryInfo()
}
// WindowSize Return the width and height in portrait mode.
// when getting the window size in wda/ui2/adb, if the device is in landscape mode,
// the width and height will be reversed.
func (s *stubIOSDriver) WindowSize() (Size, error) {
err := s.setUpWda()
if err != nil {
return Size{}, err
}
return s.wdaDriver.WindowSize()
}
func (s *stubIOSDriver) Screen() (Screen, error) {
err := s.setUpWda()
if err != nil {
return Screen{}, err
}
return s.wdaDriver.Screen()
}
func (s *stubIOSDriver) Scale() (float64, error) {
err := s.setUpWda()
if err != nil {
return 0, err
}
return s.wdaDriver.Scale()
}
// GetTimestamp returns the timestamp of the mobile device
func (s *stubIOSDriver) GetTimestamp() (timestamp int64, err error) {
err = s.setUpWda()
if err != nil {
return 0, err
}
return s.wdaDriver.GetTimestamp()
}
// Homescreen Forces the device under test to switch to the home screen
func (s *stubIOSDriver) Homescreen() error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.Homescreen()
}
func (s *stubIOSDriver) Unlock() (err error) {
err = s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.Unlock()
}
// AppLaunch Launch an application with given bundle identifier in scope of current session.
// !This method is only available since Xcode9 SDK
func (s *stubIOSDriver) AppLaunch(packageName string) error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.AppLaunch(packageName)
}
// AppTerminate Terminate an application with the given package name.
// Either `true` if the app has been successfully terminated or `false` if it was not running
func (s *stubIOSDriver) AppTerminate(packageName string) (bool, error) {
err := s.setUpWda()
if err != nil {
return false, err
}
return s.wdaDriver.AppTerminate(packageName)
}
// GetForegroundApp returns current foreground app package name and activity name
func (s *stubIOSDriver) GetForegroundApp() (app AppInfo, err error) {
err = s.setUpWda()
if err != nil {
return AppInfo{}, err
}
return s.wdaDriver.GetForegroundApp()
}
// AssertForegroundApp returns nil if the given package and activity are in foreground
func (s *stubIOSDriver) AssertForegroundApp(packageName string, activityType ...string) error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.AssertForegroundApp(packageName, activityType...)
}
// StartCamera Starts a new camera for recording
func (s *stubIOSDriver) StartCamera() error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.StartCamera()
}
// StopCamera Stops the camera for recording
func (s *stubIOSDriver) StopCamera() error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.StopCamera()
}
func (s *stubIOSDriver) Orientation() (orientation Orientation, err error) {
err = s.setUpWda()
if err != nil {
return OrientationPortrait, err
}
return s.wdaDriver.Orientation()
}
// Tap Sends a tap event at the coordinate.
func (s *stubIOSDriver) Tap(x, y int, options ...ActionOption) error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.Tap(x, y, options...)
}
func (s *stubIOSDriver) TapFloat(x, y float64, options ...ActionOption) error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.TapFloat(x, y, options...)
}
// DoubleTap Sends a double tap event at the coordinate.
func (s *stubIOSDriver) DoubleTap(x, y float64, options ...ActionOption) error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.DoubleTap(x, y, options...)
}
// TouchAndHold Initiates a long-press gesture at the coordinate, holding for the specified duration.
//
// second: The default value is 1
func (s *stubIOSDriver) TouchAndHold(x, y float64, options ...ActionOption) error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.TouchAndHold(x, y, options...)
}
// Drag Initiates a press-and-hold gesture at the coordinate, then drags to another coordinate.
// WithPressDurationOption option can be used to set pressForDuration (default to 1 second).
func (s *stubIOSDriver) Drag(fromX, fromY, toX, toY float64, options ...ActionOption) error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.Drag(fromX, fromY, toX, toY, options...)
}
// Swipe works like Drag, but `pressForDuration` value is 0
func (s *stubIOSDriver) Swipe(fromX, fromY, toX, toY int, options ...ActionOption) error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.Swipe(fromX, fromY, toX, toY, options...)
}
func (s *stubIOSDriver) SwipeFloat(fromX, fromY, toX, toY float64, options ...ActionOption) error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.SwipeFloat(fromX, fromY, toX, toY, options...)
}
// SetPasteboard Sets data to the general pasteboard
func (s *stubIOSDriver) SetPasteboard(contentType PasteboardType, content string) error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.SetPasteboard(contentType, content)
}
// GetPasteboard Gets the data contained in the general pasteboard.
//
// It worked when `WDA` was foreground. https://github.com/appium/WebDriverAgent/issues/330
func (s *stubIOSDriver) GetPasteboard(contentType PasteboardType) (raw *bytes.Buffer, err error) {
err = s.setUpWda()
if err != nil {
return nil, err
}
return s.wdaDriver.GetPasteboard(contentType)
}
func (s *stubIOSDriver) SetIme(ime string) error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.SetIme(ime)
}
// SendKeys Types a string into active element. There must be element with keyboard focus,
// otherwise an error is raised.
// WithFrequency option can be used to set frequency of typing (letters per sec). The default value is 60
func (s *stubIOSDriver) SendKeys(text string, options ...ActionOption) error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.SendKeys(text, options...)
}
// Input works like SendKeys
func (s *stubIOSDriver) Input(text string, options ...ActionOption) error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.Input(text, options...)
}
func (s *stubIOSDriver) Clear(packageName string) error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.Clear(packageName)
}
// PressButton Presses the corresponding hardware button on the device
func (s *stubIOSDriver) PressButton(devBtn DeviceButton) error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.PressButton(devBtn)
}
// PressBack Presses the back button
func (s *stubIOSDriver) PressBack(options ...ActionOption) error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.PressBack(options...)
}
func (s *stubIOSDriver) PressKeyCode(keyCode KeyCode) (err error) {
err = s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.PressKeyCode(keyCode)
}
func (s *stubIOSDriver) Screenshot() (*bytes.Buffer, error) {
err := s.setUpWda()
if err != nil {
return nil, err
}
return s.wdaDriver.Screenshot()
//screenshotService, err := instruments.NewScreenshotService(s.device.d)
//if err != nil {
// log.Error().Err(err).Msg("Starting screenshot service failed")
// return nil, err
//}
//defer screenshotService.Close()
//
//imageBytes, err := screenshotService.TakeScreenshot()
//if err != nil {
// log.Error().Err(err).Msg("failed to task screenshot")
// return nil, err
//}
//return bytes.NewBuffer(imageBytes), nil
}
func (s *stubIOSDriver) TapByText(text string, options ...ActionOption) error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.TapByText(text, options...)
}
func (s *stubIOSDriver) TapByTexts(actions ...TapTextAction) error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.TapByTexts(actions...)
}
// AccessibleSource Return application elements accessibility tree
func (s *stubIOSDriver) AccessibleSource() (string, error) {
err := s.setUpWda()
if err != nil {
return "", err
}
return s.wdaDriver.AccessibleSource()
}
// HealthCheck Health check might modify simulator state so it should only be called in-between testing sessions
//
// Checks health of XCTest by:
// 1) Querying application for some elements,
// 2) Triggering some device events.
func (s *stubIOSDriver) HealthCheck() error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.HealthCheck()
}
func (s *stubIOSDriver) GetAppiumSettings() (map[string]interface{}, error) {
err := s.setUpWda()
if err != nil {
return nil, err
}
return s.wdaDriver.GetAppiumSettings()
}
func (s *stubIOSDriver) SetAppiumSettings(settings map[string]interface{}) (map[string]interface{}, error) {
err := s.setUpWda()
if err != nil {
return nil, err
}
return s.wdaDriver.SetAppiumSettings(settings)
}
func (s *stubIOSDriver) IsHealthy() (bool, error) {
err := s.setUpWda()
if err != nil {
return false, err
}
return s.wdaDriver.IsHealthy()
}
// triggers the log capture and returns the log entries
func (s *stubIOSDriver) StartCaptureLog(identifier ...string) (err error) {
err = s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.StartCaptureLog(identifier...)
}
func (s *stubIOSDriver) StopCaptureLog() (result interface{}, err error) {
err = s.setUpWda()
if err != nil {
return nil, err
}
return s.wdaDriver.StopCaptureLog()
}
func (s *stubIOSDriver) GetDriverResults() []*DriverResult {
err := s.setUpWda()
if err != nil {
return nil
}
return s.wdaDriver.GetDriverResults()
}
func (s *stubIOSDriver) Source(srcOpt ...SourceOption) (string, error) {
resp, err := s.Driver.httpRequest(http.MethodGet, fmt.Sprintf("%s/source?format=json&onlyWeb=false", s.bightInsightPrefix), []byte{})
if err != nil {
return "", err
}
return string(resp), nil
}
func (s *stubIOSDriver) LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error) {
params := map[string]interface{}{
"phone": phoneNumber,
}
if captcha != "" {
params["captcha"] = captcha
} else if password != "" {
params["password"] = password
} else {
return info, fmt.Errorf("password and capcha is empty")
}
bsJSON, err := json.Marshal(params)
if err != nil {
return info, err
}
resp, err := s.Driver.httpRequest(http.MethodPost, fmt.Sprintf("%s/host/login/account/", s.serverPrefix), bsJSON)
if err != nil {
return info, err
}
res, err := resp.valueConvertToJsonObject()
if err != nil {
return info, err
}
log.Info().Msgf("%v", res)
// {'isSuccess': True, 'data': '登录成功', 'code': 0}
if res["isSuccess"] != true {
err = fmt.Errorf("falied to logout %s", res["data"])
log.Err(err).Msgf("%v", res)
return info, err
}
time.Sleep(20 * time.Second)
info, err = s.getLoginAppInfo(packageName)
if err != nil || !info.IsLogin {
return info, fmt.Errorf("falied to login %v", info)
}
return info, nil
}
func (s *stubIOSDriver) LogoutNoneUI(packageName string) error {
resp, err := s.Driver.httpRequest(http.MethodGet, fmt.Sprintf("%s/host/loginout/", s.serverPrefix), []byte{})
if err != nil {
return err
}
res, err := resp.valueConvertToJsonObject()
if err != nil {
return err
}
log.Info().Msgf("%v", res)
if res["isSuccess"] != true {
err = fmt.Errorf("falied to logout %s", res["data"])
log.Err(err).Msgf("%v", res)
return err
}
time.Sleep(10 * time.Second)
return nil
}
func (s *stubIOSDriver) TearDown() {
s.Driver.client.CloseIdleConnections()
return
}
func (s *stubIOSDriver) getLoginAppInfo(packageName string) (info AppLoginInfo, err error) {
resp, err := s.Driver.httpRequest(http.MethodGet, fmt.Sprintf("%s/host/app/info/", s.serverPrefix), []byte{})
if err != nil {
return info, err
}
res, err := resp.valueConvertToJsonObject()
if err != nil {
return info, err
}
log.Info().Msgf("%v", res)
if res["isSuccess"] != true {
err = fmt.Errorf("falied to get is login %s", res["data"])
log.Err(err).Msgf("%v", res)
return info, err
}
err = json.Unmarshal([]byte(res["data"].(string)), &info)
if err != nil {
return info, err
}
return info, nil
}
func (s *stubIOSDriver) GetSession() *DriverSession {
return &s.Driver.session
}

View File

@@ -0,0 +1,105 @@
package uixt
import (
"fmt"
"net"
"os"
"testing"
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
)
var (
iOSStubDriver IWebDriver
iOSDevice *IOSDevice
)
func setupiOSStubDriver(t *testing.T) {
var err error
iOSDevice, err = NewIOSDevice(WithWDAPort(8700), WithWDAMjpegPort(8800), WithIOSStub(false))
checkErr(t, err)
iOSStubDriver, err = iOSDevice.NewStubDriver()
checkErr(t, err)
}
func TestIOSLogin(t *testing.T) {
setupiOSStubDriver(t)
info, err := iOSStubDriver.LoginNoneUI("", "12342316231", "8517", "")
checkErr(t, err)
t.Log(info)
}
func TestIOSLogout(t *testing.T) {
setupiOSStubDriver(t)
err := iOSStubDriver.LogoutNoneUI("")
checkErr(t, err)
}
func TestIOSIsLogin(t *testing.T) {
setupiOSStubDriver(t)
err := iOSStubDriver.LogoutNoneUI("")
checkErr(t, err)
}
func TestIOSSource(t *testing.T) {
setupiOSStubDriver(t)
source, err := iOSStubDriver.Source()
checkErr(t, err)
t.Log(source)
}
func TestIOSForeground(t *testing.T) {
setupiOSStubDriver(t)
app, err := iOSStubDriver.GetForegroundApp()
checkErr(t, err)
t.Log(app)
}
func TestIOSSwipe(t *testing.T) {
setupiOSStubDriver(t)
iOSStubDriver.Swipe(540, 0, 540, 1000)
}
func TestIOSSave(t *testing.T) {
setupiOSStubDriver(t)
raw, err := iOSStubDriver.Screenshot()
if err != nil {
t.Fatal(err)
}
source, err := iOSStubDriver.Source()
if err != nil {
t.Fatal(err)
}
step := 7
file, err := os.Create(fmt.Sprintf("/Users/bytedance/workcode/wings_algorithm/testcases/data/cases/ios/4159417_cvcn02okg4g0/%d.jpg", step))
if err != nil {
t.Fatal(err)
}
file.Write(raw.Bytes())
file, err = os.Create(fmt.Sprintf("/Users/bytedance/workcode/wings_algorithm/testcases/data/cases/ios/4159417_cvcn02okg4g0/%d.json", step))
if err != nil {
t.Fatal(err)
}
file.Write([]byte(source))
}
func TestListen(t *testing.T) {
setupiOSStubDriver(t)
localPort, err := builtin.GetFreePort()
if err != nil {
t.Fatal(err)
}
err = iOSDevice.forward(localPort, 8800)
if err != nil {
t.Fatal(err)
}
addr := fmt.Sprintf("0.0.0.0:%d", localPort)
l, err := net.Listen("tcp", addr)
if err == nil {
l.Close() // 端口成功绑定后立即释放,返回该端口号
} else {
t.Fatal(err)
}
}

View File

@@ -24,7 +24,7 @@ func setup(t *testing.T) {
}
capabilities := NewCapabilities()
capabilities.WithDefaultAlertAction(AlertActionAccept)
driver, err = device.NewUSBDriver(capabilities)
driver, err = device.NewHTTPDriver(capabilities)
if err != nil {
t.Fatal(err)
}
@@ -49,7 +49,7 @@ func TestInstall(t *testing.T) {
}
func TestNewIOSDevice(t *testing.T) {
device, _ := NewIOSDevice()
device, _ := NewIOSDevice(WithWDAPort(8700), WithWDAMjpegPort(8800))
if device != nil {
t.Log(device)
}
@@ -70,8 +70,16 @@ func TestNewIOSDevice(t *testing.T) {
}
}
func TestIOSDevice_GetPackageInfo(t *testing.T) {
device, err := NewIOSDevice(WithWDAPort(8700))
checkErr(t, err)
appInfo, err := device.GetPackageInfo("com.apple.Preferences")
checkErr(t, err)
t.Log(appInfo)
}
func TestNewWDAHTTPDriver(t *testing.T) {
device, _ := NewIOSDevice(WithWDAPort(8700), WithWDAMjpegPort(8800))
device, _ := NewIOSDevice()
var err error
_, err = device.NewHTTPDriver(nil)
if err != nil {
@@ -85,14 +93,6 @@ func TestNewUSBDriver(t *testing.T) {
// t.Log(driver.IsWdaHealthy())
}
func TestIOSDevice_GetPackageInfo(t *testing.T) {
device, err := NewIOSDevice(WithWDAPort(8700))
checkErr(t, err)
appInfo, err := device.GetPackageInfo("com.apple.Preferences")
checkErr(t, err)
t.Log(appInfo)
}
func TestDriver_DeviceScaleRatio(t *testing.T) {
setup(t)
@@ -276,7 +276,7 @@ func Test_remoteWD_TouchAndHold(t *testing.T) {
setup(t)
// err := driver.TouchAndHold(200, 300)
err := driver.TouchAndHold(200, 300, -1)
err := driver.TouchAndHold(200, 300)
if err != nil {
t.Fatal(err)
}
@@ -286,7 +286,7 @@ func Test_remoteWD_Drag(t *testing.T) {
setup(t)
// err := driver.Drag(200, 300, 200, 500, WithDataPressDuration(0.5))
err := driver.Swipe(200, 300, 200, 500)
err := driver.Drag(200, 300, 200, 500, WithPressDuration(2), WithDuration(3))
if err != nil {
t.Fatal(err)
}
@@ -308,7 +308,7 @@ func Test_remoteWD_SetPasteboard(t *testing.T) {
// err := driver.SetPasteboard(PasteboardTypePlaintext, "gwda")
err := driver.SetPasteboard(PasteboardTypeUrl, "Clock-stopwatch://")
// userHomeDir, _ := os.UserHomeDir()
// bytesImg, _ := os.ReadFile(userHomeDir + "/Pictures/IMG_0806.jpg")
// bytesImg, _ := ioutil.ReadFile(userHomeDir + "/Pictures/IMG_0806.jpg")
// err := driver.SetPasteboard(PasteboardTypeImage, string(bytesImg))
if err != nil {
t.Fatal(err)
@@ -333,21 +333,21 @@ func Test_remoteWD_GetPasteboard(t *testing.T) {
// t.Fatal(err)
// }
// userHomeDir, _ := os.UserHomeDir()
// if err = os.WriteFile(userHomeDir+"/Desktop/p1.png", buffer.Bytes(), 0600); err != nil {
// if err = ioutil.WriteFile(userHomeDir+"/Desktop/p1.png", buffer.Bytes(), 0600); err != nil {
// t.Error(err)
// }
}
func Test_remoteWD_SendKeys(t *testing.T) {
setup(t)
driver.StartCaptureLog("hrp_wda_log")
err := driver.SendKeys("", WithIdentifier("test"))
result, _ := driver.StopCaptureLog()
// driver.StartCaptureLog("hrp_wda_log")
err := driver.SendKeys("test", WithIdentifier("test"))
// result, _ := driver.StopCaptureLog()
// err := driver.SendKeys("App Store", WithFrequency(3))
if err != nil {
t.Fatal(err)
}
t.Log(result)
// t.Log(result)
}
func Test_remoteWD_PressButton(t *testing.T) {
@@ -445,3 +445,21 @@ func Test_remoteWD_AccessibleSource(t *testing.T) {
_ = source
fmt.Println(source)
}
func TestRecord(t *testing.T) {
setup(t)
path, err := driver.(*wdaDriver).RecordScreen("", 5*time.Second)
if err != nil {
t.Fatal(err)
}
println(path)
}
// func Test_Backspace(t *testing.T) {
// setup(t)
// err := driver.Backspace(3)
// if err != nil {
// t.Fatal(err)
// }
// }

View File

@@ -93,7 +93,7 @@ func (dExt *DriverExt) DoubleTapXY(x, y float64, options ...ActionOption) error
}
x = x * float64(windowSize.Width)
y = y * float64(windowSize.Height)
err = dExt.Driver.DoubleTapFloat(x, y, options...)
err = dExt.Driver.DoubleTap(x, y, options...)
if err != nil {
return errors.Wrap(code.MobileUITapError, err.Error())
}
@@ -110,7 +110,7 @@ func (dExt *DriverExt) DoubleTapOffset(param string, xOffset, yOffset float64, o
return err
}
err = dExt.Driver.DoubleTapFloat(point.X+xOffset, point.Y+yOffset, options...)
err = dExt.Driver.DoubleTap(point.X+xOffset, point.Y+yOffset, options...)
if err != nil {
return errors.Wrap(code.MobileUITapError, err.Error())
}