refactor: move gidevice to hrp pkg

This commit is contained in:
debugtalk
2022-10-23 22:25:14 +08:00
parent d9422f3771
commit 863ffdf798
85 changed files with 10476 additions and 65 deletions

View File

@@ -0,0 +1,53 @@
package ipa
import (
"archive/zip"
"fmt"
"io"
"path"
"howett.net/plist"
)
func Info(ipaPath string) (info map[string]interface{}, err error) {
var reader *zip.ReadCloser
if reader, err = zip.OpenReader(ipaPath); err != nil {
return nil, err
}
defer func() {
err = reader.Close()
}()
for _, file := range reader.File {
matched, _err := path.Match("Payload/*.app/Info.plist", file.Name)
if _err != nil {
err = _err
continue
}
if !matched {
continue
}
var rd io.ReadCloser
if rd, _err = file.Open(); _err != nil {
return nil, _err
}
data, _err := io.ReadAll(rd)
if _err != nil {
return nil, _err
}
info = make(map[string]interface{})
_, _err = plist.Unmarshal(data, &info)
if _err != nil {
return nil, _err
}
}
if err != nil && len(info) == 0 {
return nil, fmt.Errorf("find Info.plist: %w", err)
}
return
}

View File

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

View File

@@ -0,0 +1,105 @@
package libimobiledevice
import (
"bytes"
"encoding/hex"
"fmt"
)
const AfcServiceName = "com.apple.afc"
func NewAfcClient(innerConn InnerConn) *AfcClient {
return &AfcClient{
innerConn: innerConn,
}
}
type AfcClient struct {
innerConn InnerConn
packetNum uint64
}
func (c *AfcClient) newPacket(operation uint64, data, payload []byte) Packet {
c.packetNum++
pkt := &afcPacket{
operation: operation,
packetNum: c.packetNum,
entireLen: 40,
thisLen: 40,
}
if data != nil {
n := uint64(len(data))
pkt.entireLen += n
pkt.thisLen += n
}
if payload != nil {
pkt.entireLen += uint64(len(payload))
}
return pkt
}
func (c *AfcClient) Send(operation uint64, data, payload []byte) (err error) {
pkt := c.newPacket(operation, data, payload)
var raw []byte
if raw, err = pkt.Pack(); err != nil {
return fmt.Errorf("send packet (afc): %w", err)
}
buf := new(bytes.Buffer)
buf.Write(raw)
if data != nil {
debugLog(fmt.Sprintf("--> %s ...afc data...\n", pkt))
buf.Write(data)
} else {
debugLog(fmt.Sprintf("--> %s\n", pkt))
}
if err = c.innerConn.Write(buf.Bytes()); err != nil {
return fmt.Errorf("send packet (afc): %w", err)
}
if payload != nil {
if err = c.innerConn.Write(payload); err != nil {
return fmt.Errorf("send packet (afc): %w", err)
}
}
return
}
func (c *AfcClient) Receive() (respMsg *AfcMessage, err error) {
var bufHeader []byte
if bufHeader, err = c.innerConn.Read(40); err != nil {
return nil, fmt.Errorf("receive packet (afc): %w", err)
}
buffer := new(bytes.Buffer)
buffer.Write(bufHeader)
var respPkt *afcPacket
if respPkt, err = new(afcPacket).unpack(buffer); err != nil {
return nil, fmt.Errorf("receive packet (afc): %w", err)
}
respMsg = new(AfcMessage)
respMsg.Operation = respPkt.operation
buffer.Reset()
if respPkt.entireLen > 40 {
length := int(respPkt.entireLen - 40)
var bufDataAndPayload []byte
if bufDataAndPayload, err = c.innerConn.Read(length); err != nil {
return nil, fmt.Errorf("receive packet (afc): %w", err)
}
buffer.Write(bufDataAndPayload)
}
bufData := make([]byte, respPkt.thisLen-40)
if _, err = buffer.Read(bufData); err != nil {
return nil, fmt.Errorf("receive packet (afc buffer): %w", err)
}
respMsg.Data = bufData
respMsg.Payload = buffer.Bytes()
debugLog(fmt.Sprintf("<-- %s\n%s\n%s", respPkt, hex.Dump(respMsg.Data), hex.Dump(respMsg.Payload)))
return
}

View File

@@ -0,0 +1,187 @@
package libimobiledevice
import (
"bytes"
"encoding/binary"
"errors"
)
type AfcMessage struct {
Operation uint64
Data []byte
Payload []byte
}
func (m *AfcMessage) Map() map[string]string {
ret := make(map[string]string)
ss := m.Strings()
if ss != nil {
for i := 0; i < len(ss); i += 2 {
ret[ss[i]] = ss[i+1]
}
}
return ret
}
func (m *AfcMessage) Strings() []string {
if m.Operation == AfcOperationData {
bs := bytes.Split(m.Payload, []byte{0})
ss := make([]string, len(bs)-1)
for i := 0; i < len(ss); i++ {
ss[i] = string(bs[i])
}
return ss
}
return nil
}
func (m *AfcMessage) Uint64() uint64 {
return binary.LittleEndian.Uint64(m.Data)
}
func (m *AfcMessage) Err() error {
if m.Operation == AfcOperationStatus {
status := m.Uint64()
if status != AfcErrSuccess {
return toError(status)
}
}
return nil
}
func toError(status uint64) error {
switch status {
case AfcErrUnknownError:
return errors.New("UnknownError")
case AfcErrOperationHeaderInvalid:
return errors.New("OperationHeaderInvalid")
case AfcErrNoResources:
return errors.New("NoResources")
case AfcErrReadError:
return errors.New("ReadError")
case AfcErrWriteError:
return errors.New("WriteError")
case AfcErrUnknownPacketType:
return errors.New("UnknownPacketType")
case AfcErrInvalidArgument:
return errors.New("InvalidArgument")
case AfcErrObjectNotFound:
return errors.New("ObjectNotFound")
case AfcErrObjectIsDir:
return errors.New("ObjectIsDir")
case AfcErrPermDenied:
return errors.New("PermDenied")
case AfcErrServiceNotConnected:
return errors.New("ServiceNotConnected")
case AfcErrOperationTimeout:
return errors.New("OperationTimeout")
case AfcErrTooMuchData:
return errors.New("TooMuchData")
case AfcErrEndOfData:
return errors.New("EndOfData")
case AfcErrOperationNotSupported:
return errors.New("OperationNotSupported")
case AfcErrObjectExists:
return errors.New("ObjectExists")
case AfcErrObjectBusy:
return errors.New("ObjectBusy")
case AfcErrNoSpaceLeft:
return errors.New("NoSpaceLeft")
case AfcErrOperationWouldBlock:
return errors.New("OperationWouldBlock")
case AfcErrIoError:
return errors.New("IoError")
case AfcErrOperationInterrupted:
return errors.New("OperationInterrupted")
case AfcErrOperationInProgress:
return errors.New("OperationInProgress")
case AfcErrInternalError:
return errors.New("InternalError")
case AfcErrMuxError:
return errors.New("MuxError")
case AfcErrNoMemory:
return errors.New("NoMemory")
case AfcErrNotEnoughData:
return errors.New("NotEnoughData")
case AfcErrDirNotEmpty:
return errors.New("DirNotEmpty")
}
return nil
}
const (
AfcOperationInvalid = 0x00000000 /* Invalid */
AfcOperationStatus = 0x00000001 /* Status */
AfcOperationData = 0x00000002 /* Data */
AfcOperationReadDir = 0x00000003 /* ReadDir */
AfcOperationReadFile = 0x00000004 /* ReadFile */
AfcOperationWriteFile = 0x00000005 /* WriteFile */
AfcOperationWritePart = 0x00000006 /* WritePart */
AfcOperationTruncateFile = 0x00000007 /* TruncateFile */
AfcOperationRemovePath = 0x00000008 /* RemovePath */
AfcOperationMakeDir = 0x00000009 /* MakeDir */
AfcOperationGetFileInfo = 0x0000000A /* GetFileInfo */
AfcOperationGetDeviceInfo = 0x0000000B /* GetDeviceInfo */
AfcOperationWriteFileAtomic = 0x0000000C /* WriteFileAtomic (tmp file+rename) */
AfcOperationFileOpen = 0x0000000D /* FileRefOpen */
AfcOperationFileOpenResult = 0x0000000E /* FileRefOpenResult */
AfcOperationFileRead = 0x0000000F /* FileRefRead */
AfcOperationFileWrite = 0x00000010 /* FileRefWrite */
AfcOperationFileSeek = 0x00000011 /* FileRefSeek */
AfcOperationFileTell = 0x00000012 /* FileRefTell */
AfcOperationFileTellResult = 0x00000013 /* FileRefTellResult */
AfcOperationFileClose = 0x00000014 /* FileRefClose */
AfcOperationFileSetSize = 0x00000015 /* FileRefSetFileSize (ftruncate) */
AfcOperationGetConnectionInfo = 0x00000016 /* GetConnectionInfo */
AfcOperationSetConnectionOptions = 0x00000017 /* SetConnectionOptions */
AfcOperationRenamePath = 0x00000018 /* RenamePath */
AfcOperationSetFSBlockSize = 0x00000019 /* SetFSBlockSize (0x800000) */
AfcOperationSetSocketBlockSize = 0x0000001A /* SetSocketBlockSize (0x800000) */
AfcOperationFileRefLock = 0x0000001B /* FileRefLock */
AfcOperationMakeLink = 0x0000001C /* MakeLink */
AfcOperationGetFileHash = 0x0000001D /* GetFileHash */
AfcOperationSetFileModTime = 0x0000001E /* SetModTime */
AfcOperationGetFileHashRange = 0x0000001F /* GetFileHashWithRange */
/* iOS 6+ */
AfcOperationFileSetImmutableHint = 0x00000020 /* FileRefSetImmutableHint */
AfcOperationGetSizeOfPathContents = 0x00000021 /* GetSizeOfPathContents */
AfcOperationRemovePathAndContents = 0x00000022 /* RemovePathAndContents */
AfcOperationDirectoryEnumeratorRefOpen = 0x00000023 /* DirectoryEnumeratorRefOpen */
AfcOperationDirectoryEnumeratorRefOpenResult = 0x00000024 /* DirectoryEnumeratorRefOpenResult */
AfcOperationDirectoryEnumeratorRefRead = 0x00000025 /* DirectoryEnumeratorRefRead */
AfcOperationDirectoryEnumeratorRefClose = 0x00000026 /* DirectoryEnumeratorRefClose */
/* iOS 7+ */
AfcOperationFileRefReadWithOffset = 0x00000027 /* FileRefReadWithOffset */
AfcOperationFileRefWriteWithOffset = 0x00000028 /* FileRefWriteWithOffset */
)
const (
AfcErrSuccess = 0
AfcErrUnknownError = 1
AfcErrOperationHeaderInvalid = 2
AfcErrNoResources = 3
AfcErrReadError = 4
AfcErrWriteError = 5
AfcErrUnknownPacketType = 6
AfcErrInvalidArgument = 7
AfcErrObjectNotFound = 8
AfcErrObjectIsDir = 9
AfcErrPermDenied = 10
AfcErrServiceNotConnected = 11
AfcErrOperationTimeout = 12
AfcErrTooMuchData = 13
AfcErrEndOfData = 14
AfcErrOperationNotSupported = 15
AfcErrObjectExists = 16
AfcErrObjectBusy = 17
AfcErrNoSpaceLeft = 18
AfcErrOperationWouldBlock = 19
AfcErrIoError = 20
AfcErrOperationInterrupted = 21
AfcErrOperationInProgress = 22
AfcErrInternalError = 23
AfcErrMuxError = 30
AfcErrNoMemory = 31
AfcErrNotEnoughData = 32
AfcErrDirNotEmpty = 33
)

View File

@@ -0,0 +1,138 @@
package libimobiledevice
import (
"bytes"
"encoding/binary"
"errors"
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/nskeyedarchiver"
)
type AuxBuffer struct {
buf *bytes.Buffer
}
func NewAuxBuffer() *AuxBuffer {
return &AuxBuffer{
buf: new(bytes.Buffer),
}
}
func (m *AuxBuffer) AppendObject(obj interface{}) error {
marshal, err := nskeyedarchiver.Marshal(obj)
if err != nil {
return err
}
m.AppendUInt32(10)
m.AppendUInt32(2)
m.AppendUInt32(uint32(len(marshal)))
m.buf.Write(marshal)
return nil
}
func (m *AuxBuffer) AppendInt64(v int64) {
m.AppendUInt32(10)
m.AppendUInt32(4)
m.AppendUInt64(uint64(v))
}
func (m *AuxBuffer) AppendInt32(v int32) {
m.AppendUInt32(10)
m.AppendUInt32(3)
m.AppendUInt32(uint32(v))
}
func (m *AuxBuffer) AppendUInt32(v uint32) {
_ = binary.Write(m.buf, binary.LittleEndian, v)
}
func (m *AuxBuffer) AppendUInt64(v uint64) {
_ = binary.Write(m.buf, binary.LittleEndian, v)
}
func (m *AuxBuffer) AppendBytes(b []byte) {
m.buf.Write(b)
}
func (m *AuxBuffer) Len() int {
return m.buf.Len()
}
func (m *AuxBuffer) Bytes() []byte {
dup := m.buf.Bytes()
b := make([]byte, 16)
binary.LittleEndian.PutUint64(b, 0x01f0)
binary.LittleEndian.PutUint64(b[8:], uint64(m.Len()))
return append(b, dup...)
}
func UnmarshalAuxBuffer(b []byte) ([]interface{}, error) {
reader := bytes.NewReader(b)
var magic, pkgLen uint64
if err := binary.Read(reader, binary.LittleEndian, &magic); err != nil {
return nil, err
}
if err := binary.Read(reader, binary.LittleEndian, &pkgLen); err != nil {
return nil, err
}
// if magic != 0x1df0 {
// TODO magic
// return nil, errors.New("magic not equal 0x1df0")
// }
if pkgLen > uint64(len(b)-16) {
return nil, errors.New("package length not enough")
}
var ret []interface{}
for reader.Len() > 0 {
var flag, typ uint32
if err := binary.Read(reader, binary.LittleEndian, &flag); err != nil {
return nil, err
}
if err := binary.Read(reader, binary.LittleEndian, &typ); err != nil {
return nil, err
}
switch typ {
case 2:
var l uint32
if err := binary.Read(reader, binary.LittleEndian, &l); err != nil {
return nil, err
}
plistBuf := make([]byte, l)
if _, err := reader.Read(plistBuf); err != nil {
return nil, err
}
archiver := NewNSKeyedArchiver()
d, err := archiver.Unmarshal(plistBuf)
if err != nil {
return nil, err
}
ret = append(ret, d)
case 3, 5:
var i int32
if err := binary.Read(reader, binary.LittleEndian, &i); err != nil {
return nil, err
}
ret = append(ret, i)
case 4, 6:
var i int64
if err := binary.Read(reader, binary.LittleEndian, &i); err != nil {
return nil, err
}
ret = append(ret, i)
case 10:
// TODO Dictionary key
// fmt.Println("Dictionary key!")
continue
default:
// fmt.Printf("unknown type %d\n", typ)
break
}
}
return ret, nil
}

View File

@@ -0,0 +1,405 @@
package libimobiledevice
import (
"bytes"
"context"
"encoding/hex"
"fmt"
"io"
"strings"
"sync"
"time"
"unsafe"
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice/pkg/nskeyedarchiver"
)
const (
_unregistered = "_Golang-iDevice_Unregistered"
_over = "_Golang-iDevice_Over"
)
func newDtxMessageClient(innerConn InnerConn) *dtxMessageClient {
c := &dtxMessageClient{
innerConn: innerConn,
msgID: 0,
publishedChannels: make(map[string]int32),
openedChannels: make(map[string]uint32),
toReply: make(chan *dtxMessageHeaderPacket),
mu: sync.Mutex{},
resultMap: make(map[interface{}]*DTXMessageResult),
callbackMap: make(map[string]func(m DTXMessageResult)),
}
c.RegisterCallback(_unregistered, func(m DTXMessageResult) {})
c.RegisterCallback(_over, func(m DTXMessageResult) {})
c.ctx, c.cancelFunc = context.WithCancel(context.Background())
c.startReceive()
c.startWaitingForReply()
return c
}
type dtxMessageClient struct {
innerConn InnerConn
msgID uint32
publishedChannels map[string]int32
openedChannels map[string]uint32
toReply chan *dtxMessageHeaderPacket
mu sync.Mutex
resultMap map[interface{}]*DTXMessageResult
callbackMap map[string]func(m DTXMessageResult)
ctx context.Context
cancelFunc context.CancelFunc
}
func (c *dtxMessageClient) SendDTXMessage(selector string, aux []byte, channelCode uint32, expectsReply bool) (msgID uint32, err error) {
payload := new(dtxMessagePayloadPacket)
header := &dtxMessageHeaderPacket{
ExpectsReply: 1,
}
flag := 0x1000
if !expectsReply {
flag = 0
header.ExpectsReply = 0
}
var sel []byte
if sel, err = nskeyedarchiver.Marshal(selector); err != nil {
return 0, err
}
if aux == nil {
aux = make([]byte, 0)
}
payload.Flags = uint32(0x2 | flag)
payload.AuxiliaryLength = uint32(len(aux))
payload.TotalLength = uint64(len(aux)) + uint64(len(sel))
header.Magic = 0x1F3D5B79
header.CB = uint32(unsafe.Sizeof(*header))
header.FragmentId = 0
header.FragmentCount = 1
header.Length = uint32(unsafe.Sizeof(*payload)) + uint32(payload.TotalLength)
c.msgID++
header.Identifier = c.msgID
header.ConversationIndex = 0
header.ChannelCode = channelCode
msgPkt := new(dtxMessagePacket)
msgPkt.Header = header
msgPkt.Payload = payload
msgPkt.Aux = aux
msgPkt.Sel = sel
raw, err := msgPkt.Pack()
if err != nil {
return 0, err
}
debugLog(fmt.Sprintf("--> %s\n", msgPkt))
msgID = header.Identifier
err = c.innerConn.Write(raw)
return
}
func (c *dtxMessageClient) ReceiveDTXMessage() (result *DTXMessageResult, err error) {
bufPayload := new(bytes.Buffer)
var header *dtxMessageHeaderPacket = nil
var needToReply *dtxMessageHeaderPacket = nil
for {
header = new(dtxMessageHeaderPacket)
lenHeader := int(unsafe.Sizeof(*header))
var bufHeader []byte
if bufHeader, err = c.innerConn.Read(lenHeader); err != nil {
return nil, fmt.Errorf("receive: length of DTXMessageHeader: %w", err)
}
if header, err = header.unpack(bytes.NewBuffer(bufHeader)); err != nil {
return nil, fmt.Errorf("receive: DTXMessageHeader unpack: %w", err)
}
if header.ExpectsReply == 1 {
needToReply = header
}
if header.Magic != 0x1F3D5B79 {
return nil, fmt.Errorf("receive: bad magic %x", header.Magic)
}
if header.ConversationIndex == 1 {
if header.Identifier != c.msgID {
return nil, fmt.Errorf("receive: except identifier %d new identifier %d", c.msgID, header.Identifier)
}
} else if header.ConversationIndex == 0 {
if header.Identifier > c.msgID {
c.msgID = header.Identifier
}
} else {
return nil, fmt.Errorf("receive: invalid conversationIndex %d", header.ConversationIndex)
}
if header.FragmentId == 0 && header.FragmentCount > 1 {
continue
}
var data []byte
if data, err = c.innerConn.Read(int(header.Length)); err != nil {
return nil, fmt.Errorf("receive: length of DTXMessageHeader: %w", err)
}
bufPayload.Write(data)
if header.FragmentId == header.FragmentCount-1 {
break
}
}
rawPayload := bufPayload.Bytes()
payload := new(dtxMessagePayloadPacket)
if payload, err = payload.unpack(bufPayload); err != nil {
return nil, fmt.Errorf("receive: unpack DTXMessagePayload: %w", err)
}
compress := (payload.Flags & 0xff000) >> 12
if compress != 0 {
return nil, fmt.Errorf("receive: message is compressed type %d", compress)
}
payloadSize := uint32(unsafe.Sizeof(*payload))
objOffset := uint64(payloadSize + payload.AuxiliaryLength)
var aux, obj []byte
// see https://github.com/electricbubble/gidevice/issues/28
if r, l := payloadSize+payload.AuxiliaryLength, len(rawPayload); int(r) <= l {
aux = rawPayload[payloadSize:r]
} else {
debugLog(fmt.Sprintf("<-- DTXMessage %s\n%s\n"+
"[aux] bounds out of range [:%d] with capacity %d",
header.String(), payload.String(),
r, l,
))
}
if r, l := objOffset+(payload.TotalLength-uint64(payload.AuxiliaryLength)), len(rawPayload); int(r) <= l {
obj = rawPayload[objOffset:r]
} else {
debugLog(fmt.Sprintf("<-- DTXMessage %s\n%s\n"+
"[obj] bounds out of range [:%d] with capacity %d",
header.String(), payload.String(),
r, l,
))
}
debugLog(fmt.Sprintf(
"<-- DTXMessage %s\n%s\n"+
"%s\n%s\n",
header.String(), payload.String(),
hex.Dump(aux), hex.Dump(obj),
))
result = new(DTXMessageResult)
if len(aux) > 0 {
if aux, err := UnmarshalAuxBuffer(aux); err != nil {
return nil, fmt.Errorf("receive: unpack AUX: %w", err)
} else {
result.Aux = aux
}
}
if len(obj) > 0 {
if obj, err := NewNSKeyedArchiver().Unmarshal(obj); err != nil {
return nil, fmt.Errorf("receive: unpack NSKeyedArchiver: %w", err)
} else {
result.Obj = obj
}
}
sObj, ok := result.Obj.(string)
if fn, do := c.callbackMap[sObj]; do {
fn(*result)
} else {
c.callbackMap[_unregistered](*result)
}
if needToReply != nil {
go func() { c.toReply <- needToReply }()
} else {
var sk interface{} = header.Identifier
if ok && sObj == "_notifyOfPublishedCapabilities:" {
sk = "_notifyOfPublishedCapabilities:"
}
c.mu.Lock()
c.resultMap[sk] = result
c.mu.Unlock()
}
return
}
func (c *dtxMessageClient) Connection() (publishedChannels map[string]int32, err error) {
args := NewAuxBuffer()
if err = args.AppendObject(map[string]interface{}{
"com.apple.private.DTXBlockCompression": uint64(2),
"com.apple.private.DTXConnection": uint64(1),
}); err != nil {
return nil, fmt.Errorf("connection DTXMessage: %w", err)
}
selector := "_notifyOfPublishedCapabilities:"
if _, err = c.SendDTXMessage(selector, args.Bytes(), 0, false); err != nil {
return nil, fmt.Errorf("connection send: %w", err)
}
var result *DTXMessageResult
if result, err = c.GetResult(selector); err != nil {
return nil, fmt.Errorf("connection receive: %w", err)
}
if result.Obj.(string) != "_notifyOfPublishedCapabilities:" {
return nil, fmt.Errorf("connection: response mismatch: %s", result.Obj)
}
aux := result.Aux[0].(map[string]interface{})
for k, v := range aux {
c.publishedChannels[k] = int32(v.(uint64))
}
return c.publishedChannels, nil
}
func (c *dtxMessageClient) MakeChannel(channel string) (id uint32, err error) {
var ok bool
if id, ok = c.openedChannels[channel]; ok {
return id, nil
}
id = uint32(len(c.openedChannels) + 1)
args := NewAuxBuffer()
args.AppendInt32(int32(id))
if err = args.AppendObject(channel); err != nil {
return 0, fmt.Errorf("make channel DTXMessage: %w", err)
}
selector := "_requestChannelWithCode:identifier:"
var msgID uint32
if msgID, err = c.SendDTXMessage(selector, args.Bytes(), 0, true); err != nil {
return 0, fmt.Errorf("make channel send: %w", err)
}
if _, err = c.GetResult(msgID); err != nil {
return 0, fmt.Errorf("make channel receive: %w", err)
}
c.openedChannels[channel] = id
return
}
func (c *dtxMessageClient) RegisterCallback(obj string, cb func(m DTXMessageResult)) {
c.callbackMap[obj] = cb
}
func (c *dtxMessageClient) GetResult(key interface{}) (*DTXMessageResult, error) {
startTime := time.Now()
for {
time.Sleep(100 * time.Millisecond)
c.mu.Lock()
if v, ok := c.resultMap[key]; ok {
delete(c.resultMap, key)
c.mu.Unlock()
return v, nil
} else {
c.mu.Unlock()
}
if elapsed := time.Since(startTime); elapsed > 30*time.Second {
return nil, fmt.Errorf("dtx: get result: timeout after %v", elapsed)
}
}
}
func (c *dtxMessageClient) Close() {
c.cancelFunc()
c.innerConn.Close()
}
func (c *dtxMessageClient) startReceive() {
go func() {
for {
select {
case <-c.ctx.Done():
return
default:
if _, err := c.ReceiveDTXMessage(); err != nil {
debugLog(fmt.Sprintf("dtx: receive: %s", err))
if strings.Contains(err.Error(), io.EOF.Error()) {
c.cancelFunc()
c.callbackMap[_over](DTXMessageResult{})
break
}
}
}
}
}()
}
func (c *dtxMessageClient) startWaitingForReply() {
go func() {
for {
select {
case <-c.ctx.Done():
return
case reqHeader := <-c.toReply:
replyPayload := new(dtxMessagePayloadPacket)
replyPayload.Flags = 0
replyPayload.AuxiliaryLength = 0
replyPayload.TotalLength = 0
replyHeader := new(dtxMessageHeaderPacket)
replyHeader.Magic = 0x1F3D5B79
replyHeader.CB = uint32(unsafe.Sizeof(*replyHeader))
replyHeader.FragmentId = 0
replyHeader.FragmentCount = 1
replyHeader.Length = uint32(unsafe.Sizeof(*replyPayload)) + uint32(replyPayload.TotalLength)
replyHeader.Identifier = reqHeader.Identifier
replyHeader.ConversationIndex = reqHeader.ConversationIndex + 1
replyHeader.ChannelCode = reqHeader.ChannelCode
replyHeader.ExpectsReply = 0
replyPkt := new(dtxMessagePacket)
replyPkt.Header = replyHeader
replyPkt.Payload = replyPayload
replyPkt.Aux = nil
replyPkt.Sel = nil
raw, err := replyPkt.Pack()
if err != nil {
debugLog(fmt.Sprintf("pack: reply DTXMessage: %s", err))
continue
}
if err = c.innerConn.Write(raw); err != nil {
debugLog(fmt.Sprintf("send: reply DTXMessage: %s", err))
continue
}
}
}
}()
}
type DTXMessageResult struct {
Obj interface{}
Aux []interface{}
}

View File

@@ -0,0 +1,81 @@
package libimobiledevice
import (
"bytes"
"encoding/binary"
"fmt"
"howett.net/plist"
)
func newServicePacketClient(innerConn InnerConn) *servicePacketClient {
return &servicePacketClient{
innerConn: innerConn,
}
}
type servicePacketClient struct {
innerConn InnerConn
}
func (c *servicePacketClient) NewXmlPacket(req interface{}) (Packet, error) {
return c.newPacket(req, plist.XMLFormat)
}
func (c *servicePacketClient) NewBinaryPacket(req interface{}) (Packet, error) {
return c.newPacket(req, plist.BinaryFormat)
}
func (c *servicePacketClient) newPacket(req interface{}, format int) (Packet, error) {
pkt := new(servicePacket)
if buf, err := plist.Marshal(req, format); err != nil {
return nil, fmt.Errorf("plist packet marshal: %w", err)
} else {
pkt.body = buf
}
pkt.length = uint32(len(pkt.body))
return pkt, nil
}
func (c *servicePacketClient) SendPacket(pkt Packet) (err error) {
var raw []byte
if raw, err = pkt.Pack(); err != nil {
return fmt.Errorf("send packet: %w", err)
}
debugLog(fmt.Sprintf("--> %s\n", pkt))
return c.innerConn.Write(raw)
}
func (c *servicePacketClient) ReceivePacket() (respPkt Packet, err error) {
var bufLen []byte
if bufLen, err = c.innerConn.Read(4); err != nil {
return nil, fmt.Errorf("receive packet: %w", err)
}
lenPkg := binary.BigEndian.Uint32(bufLen)
buffer := bytes.NewBuffer([]byte{})
buffer.Write(bufLen)
var buf []byte
if buf, err = c.innerConn.Read(int(lenPkg)); err != nil {
return nil, fmt.Errorf("receive packet: %w", err)
}
buffer.Write(buf)
if respPkt, err = new(servicePacket).Unpack(buffer); err != nil {
return nil, fmt.Errorf("receive packet: %w", err)
}
debugLog(fmt.Sprintf("<-- %s\n", respPkt))
var reply LockdownBasicResponse
if err = respPkt.Unmarshal(&reply); err != nil {
return nil, fmt.Errorf("receive packet: %w", err)
}
if reply.Error != "" {
return nil, fmt.Errorf("receive packet: %s", reply.Error)
}
return
}

View File

@@ -0,0 +1,20 @@
package libimobiledevice
const (
CrashReportMoverServiceName = "com.apple.crashreportmover"
CrashReportCopyMobileServiceName = "com.apple.crashreportcopymobile"
)
func NewCrashReportMoverClient(innerConn InnerConn) *CrashReportMoverClient {
return &CrashReportMoverClient{
newServicePacketClient(innerConn),
}
}
type CrashReportMoverClient struct {
client *servicePacketClient
}
func (c *CrashReportMoverClient) InnerConn() InnerConn {
return c.client.innerConn
}

View File

@@ -0,0 +1,39 @@
package libimobiledevice
const (
DiagnosticsRelayServiceName = "com.apple.mobile.diagnostics_relay"
)
type DiagnosticsRelayBasicRequest struct {
Request string `plist:"Request"`
Label string `plist:"Label"`
}
func NewDiagnosticsRelayClient(innerConn InnerConn) *DiagnosticsRelayClient {
return &DiagnosticsRelayClient{
newServicePacketClient(innerConn),
}
}
type DiagnosticsRelayClient struct {
client *servicePacketClient
}
func (c *DiagnosticsRelayClient) InnerConn() InnerConn {
return c.client.innerConn
}
func (c *DiagnosticsRelayClient) NewBasicRequest(relayType string) *DiagnosticsRelayBasicRequest {
return &DiagnosticsRelayBasicRequest{
Request: relayType,
Label: BundleID,
}
}
func (c *DiagnosticsRelayClient) NewXmlPacket(req interface{}) (Packet, error) {
return c.client.NewXmlPacket(req)
}
func (c *DiagnosticsRelayClient) SendPacket(pkt Packet) (err error) {
return c.client.SendPacket(pkt)
}

View File

@@ -0,0 +1,56 @@
package libimobiledevice
const HouseArrestServiceName = "com.apple.mobile.house_arrest"
const (
CommandTypeVendDocuments CommandType = "VendDocuments"
CommandTypeVendContainer CommandType = "VendContainer"
)
func NewHouseArrestClient(innerConn InnerConn) *HouseArrestClient {
return &HouseArrestClient{
newServicePacketClient(innerConn),
}
}
type HouseArrestClient struct {
client *servicePacketClient
}
func (c *HouseArrestClient) NewBasicRequest(cmdType CommandType, bundleID string) *HouseArrestBasicRequest {
return &HouseArrestBasicRequest{
Command: cmdType,
Identifier: bundleID,
}
}
func (c *HouseArrestClient) NewDocumentsRequest(bundleID string) *HouseArrestBasicRequest {
return c.NewBasicRequest(CommandTypeVendDocuments, bundleID)
}
func (c *HouseArrestClient) NewContainerRequest(bundleID string) *HouseArrestBasicRequest {
return c.NewBasicRequest(CommandTypeVendContainer, bundleID)
}
func (c *HouseArrestClient) NewXmlPacket(req interface{}) (Packet, error) {
return c.client.NewXmlPacket(req)
}
func (c *HouseArrestClient) SendPacket(pkt Packet) (err error) {
return c.client.SendPacket(pkt)
}
func (c *HouseArrestClient) ReceivePacket() (respPkt Packet, err error) {
return c.client.ReceivePacket()
}
func (c *HouseArrestClient) InnerConn() InnerConn {
return c.client.innerConn
}
type (
HouseArrestBasicRequest struct {
Command CommandType `plist:"Command"`
Identifier string `plist:"Identifier"`
}
)

View File

@@ -0,0 +1,107 @@
package libimobiledevice
import (
"errors"
"fmt"
"io"
"strings"
)
const ImageMounterServiceName = "com.apple.mobile.mobile_image_mounter"
var ErrDeviceLocked = errors.New("device locked")
type CommandType string
const (
CommandTypeLookupImage CommandType = "LookupImage"
CommandTypeReceiveBytes CommandType = "ReceiveBytes"
CommandTypeMountImage CommandType = "MountImage"
)
func NewImageMounterClient(innerConn InnerConn) *ImageMounterClient {
return &ImageMounterClient{
client: newServicePacketClient(innerConn),
}
}
type ImageMounterClient struct {
client *servicePacketClient
}
func (c *ImageMounterClient) NewBasicRequest(cmdType CommandType, imgType string) *ImageMounterBasicRequest {
return &ImageMounterBasicRequest{
Command: cmdType,
ImageType: imgType,
}
}
func (c *ImageMounterClient) NewReceiveBytesRequest(imgType string, imgSize uint32, imgSignature []byte) *ImageMounterReceiveBytesRequest {
return &ImageMounterReceiveBytesRequest{
ImageMounterBasicRequest: *c.NewBasicRequest(CommandTypeReceiveBytes, imgType),
ImageSize: imgSize,
ImageSignature: imgSignature,
}
}
func (c *ImageMounterClient) NewMountImageRequest(imgType, imgPath string, imgSignature []byte) *ImageMounterMountImageRequest {
return &ImageMounterMountImageRequest{
ImageMounterBasicRequest: *c.NewBasicRequest(CommandTypeMountImage, imgType),
ImagePath: imgPath,
ImageSignature: imgSignature,
}
}
func (c *ImageMounterClient) NewXmlPacket(req interface{}) (Packet, error) {
return c.client.NewXmlPacket(req)
}
func (c *ImageMounterClient) SendPacket(pkt Packet) (err error) {
return c.client.SendPacket(pkt)
}
func (c *ImageMounterClient) ReceivePacket() (respPkt Packet, err error) {
respPkt, err = c.client.ReceivePacket()
if err != nil {
if strings.Contains(err.Error(), io.EOF.Error()) {
return nil, ErrDeviceLocked
}
}
return
}
func (c *ImageMounterClient) SendDmg(data []byte) (err error) {
debugLog(fmt.Sprintf("--> ...DmgData...\n"))
return c.client.innerConn.Write(data)
}
type (
ImageMounterBasicRequest struct {
Command CommandType `plist:"Command"`
ImageType string `plist:"ImageType"`
}
ImageMounterReceiveBytesRequest struct {
ImageMounterBasicRequest
ImageSignature []byte `plist:"ImageSignature"`
ImageSize uint32 `plist:"ImageSize"`
}
ImageMounterMountImageRequest struct {
ImageMounterBasicRequest
ImagePath string `plist:"ImagePath"`
ImageSignature []byte `plist:"ImageSignature"`
}
)
type (
ImageMounterBasicResponse struct {
LockdownBasicResponse
Status string `plist:"Status"`
}
ImageMounterLookupImageResponse struct {
ImageMounterBasicResponse
ImageSignature [][]byte `plist:"ImageSignature"`
}
)

View File

@@ -0,0 +1,119 @@
package libimobiledevice
const InstallationProxyServiceName = "com.apple.mobile.installation_proxy"
const (
CommandTypeBrowse CommandType = "Browse"
CommandTypeLookup CommandType = "Lookup"
CommandTypeInstall CommandType = "Install"
CommandTypeUninstall CommandType = "Uninstall"
)
type ApplicationType string
const (
ApplicationTypeSystem ApplicationType = "System"
ApplicationTypeUser ApplicationType = "User"
ApplicationTypeInternal ApplicationType = "internal"
ApplicationTypeAny ApplicationType = "Any"
)
func NewInstallationProxyClient(innerConn InnerConn) *InstallationProxyClient {
return &InstallationProxyClient{
client: newServicePacketClient(innerConn),
}
}
type InstallationProxyClient struct {
client *servicePacketClient
}
func (c *InstallationProxyClient) NewBasicRequest(cmdType CommandType, opt *InstallationProxyOption) *InstallationProxyBasicRequest {
req := &InstallationProxyBasicRequest{Command: cmdType}
if opt != nil {
req.ClientOptions = opt
}
return req
}
func (c *InstallationProxyClient) NewInstallRequest(bundleID, packagePath string) *InstallationProxyInstallRequest {
opt := &InstallationProxyOption{
BundleID: bundleID,
}
req := &InstallationProxyInstallRequest{
Command: CommandTypeInstall,
ClientOptions: opt,
PackagePath: packagePath,
}
return req
}
func (c *InstallationProxyClient) NewUninstallRequest(bundleID string) *InstallationProxyUninstallRequest {
req := &InstallationProxyUninstallRequest{
Command: CommandTypeUninstall,
BundleID: bundleID,
}
return req
}
func (c *InstallationProxyClient) NewXmlPacket(req interface{}) (Packet, error) {
return c.client.NewXmlPacket(req)
}
func (c *InstallationProxyClient) SendPacket(pkt Packet) (err error) {
return c.client.SendPacket(pkt)
}
func (c *InstallationProxyClient) ReceivePacket() (respPkt Packet, err error) {
return c.client.ReceivePacket()
}
type InstallationProxyOption struct {
ApplicationType ApplicationType `plist:"ApplicationType,omitempty"`
ReturnAttributes []string `plist:"ReturnAttributes,omitempty"`
MetaData bool `plist:"com.apple.mobile_installation.metadata,omitempty"`
BundleIDs []string `plist:"BundleIDs,omitempty"` // for Lookup
BundleID string `plist:"CFBundleIdentifier,omitempty"` // for Install
}
type (
InstallationProxyBasicRequest struct {
Command CommandType `plist:"Command"`
ClientOptions *InstallationProxyOption `plist:"ClientOptions,omitempty"`
}
InstallationProxyInstallRequest struct {
Command CommandType `plist:"Command"`
ClientOptions *InstallationProxyOption `plist:"ClientOptions"`
PackagePath string `plist:"PackagePath"`
}
InstallationProxyUninstallRequest struct {
Command CommandType `plist:"Command"`
BundleID string `plist:"ApplicationIdentifier"`
}
)
type (
InstallationProxyBasicResponse struct {
Status string `plist:"Status"`
}
InstallationProxyLookupResponse struct {
InstallationProxyBasicResponse
LookupResult interface{} `plist:"LookupResult"`
}
InstallationProxyBrowseResponse struct {
InstallationProxyBasicResponse
CurrentAmount int `plist:"CurrentAmount"`
CurrentIndex int `plist:"CurrentIndex"`
CurrentList []interface{} `plist:"CurrentList"`
}
InstallationProxyInstallResponse struct {
InstallationProxyBasicResponse
Error string `plist:"Error"`
ErrorDescription string `plist:"ErrorDescription"`
}
)

View File

@@ -0,0 +1,41 @@
package libimobiledevice
const (
InstrumentsServiceName = "com.apple.instruments.remoteserver"
InstrumentsSecureProxyServiceName = "com.apple.instruments.remoteserver.DVTSecureSocketProxy"
)
func NewInstrumentsClient(innerConn InnerConn) *InstrumentsClient {
return &InstrumentsClient{
client: newDtxMessageClient(innerConn),
}
}
type InstrumentsClient struct {
client *dtxMessageClient
}
func (c *InstrumentsClient) NotifyOfPublishedCapabilities() (publishedChannels map[string]int32, err error) {
return c.client.Connection()
}
func (c *InstrumentsClient) RequestChannel(channel string) (id uint32, err error) {
return c.client.MakeChannel(channel)
}
func (c *InstrumentsClient) Invoke(selector string, args *AuxBuffer, channelCode uint32, expectsReply bool) (result *DTXMessageResult, err error) {
var msgID uint32
if msgID, err = c.client.SendDTXMessage(selector, args.Bytes(), channelCode, expectsReply); err != nil {
return nil, err
}
if expectsReply {
if result, err = c.client.GetResult(msgID); err != nil {
return nil, err
}
}
return
}
func (c *InstrumentsClient) RegisterCallback(obj string, cb func(m DTXMessageResult)) {
c.client.RegisterCallback(obj, cb)
}

View File

@@ -0,0 +1,260 @@
package libimobiledevice
import (
"reflect"
"strconv"
"time"
"howett.net/plist"
)
const nsNull = "$null"
func newKeyedArchiver() *KeyedArchiver {
return &KeyedArchiver{
Archiver: "NSKeyedArchiver",
Version: 100000,
}
}
type KeyedArchiver struct {
Archiver string `plist:"$archiver"`
Objects []interface{} `plist:"$objects"`
Top ArchiverRoot `plist:"$top"`
Version int `plist:"$version"`
}
func (ka *KeyedArchiver) UID() plist.UID {
return plist.UID(len(ka.Objects))
}
type ArchiverRoot struct {
Root plist.UID `plist:"root"`
}
type ArchiverClasses struct {
Classes []string `plist:"$classes"`
ClassName string `plist:"$classname"`
}
var (
NSMutableDictionaryClass = &ArchiverClasses{
Classes: []string{"NSMutableDictionary", "NSDictionary", "NSObject"},
ClassName: "NSMutableDictionary",
}
NSDictionaryClass = &ArchiverClasses{
Classes: []string{"NSDictionary", "NSObject"},
ClassName: "NSDictionary",
}
NSMutableArrayClass = &ArchiverClasses{
Classes: []string{"NSMutableArray", "NSArray", "NSObject"},
ClassName: "NSMutableArray",
}
NSArrayClass = &ArchiverClasses{
Classes: []string{"NSArray", "NSObject"},
ClassName: "NSArray",
}
NSMutableDataClass = &ArchiverClasses{
Classes: []string{"NSMutableArray", "NSArray", "NSObject"},
ClassName: "NSMutableArray",
}
NSDataClass = &ArchiverClasses{
Classes: []string{"NSData", "NSObject"},
ClassName: "NSData",
}
NSDateClass = &ArchiverClasses{
Classes: []string{"NSDate", "NSObject"},
ClassName: "NSDate",
}
NSErrorClass = &ArchiverClasses{
Classes: []string{"NSError", "NSObject"},
ClassName: "NSError",
}
)
type NSObject struct {
Class plist.UID `plist:"$class"`
}
type NSArray struct {
NSObject
Values []plist.UID `plist:"NS.objects"`
}
type NSDictionary struct {
NSArray
Keys []plist.UID `plist:"NS.keys"`
}
type NSData struct {
NSObject
Data []byte `plist:"NS.data"`
}
type NSError struct {
NSCode int
NSDomain string
NSUserInfo interface{}
}
type NSKeyedArchiver struct {
objRefVal []interface{}
objRef map[interface{}]plist.UID
}
func NewNSKeyedArchiver() *NSKeyedArchiver {
return &NSKeyedArchiver{
objRef: make(map[interface{}]plist.UID),
}
}
func (ka *NSKeyedArchiver) id(v interface{}) plist.UID {
var ref plist.UID
if id, ok := ka.objRef[v]; !ok {
ref = plist.UID(len(ka.objRef))
ka.objRefVal = append(ka.objRefVal, v)
ka.objRef[v] = ref
} else {
ref = id
}
return ref
}
func (ka *NSKeyedArchiver) flushToStruct(root *KeyedArchiver) {
for i := 0; i < len(ka.objRefVal); i++ {
val := ka.objRefVal[i]
vt := reflect.ValueOf(val)
if vt.Kind() == reflect.Ptr {
val = vt.Elem().Interface()
}
root.Objects = append(root.Objects, val)
}
}
func (ka *NSKeyedArchiver) clear() {
ka.objRef = make(map[interface{}]plist.UID)
ka.objRefVal = []interface{}{}
}
type XCTestConfiguration struct {
Contents map[string]interface{}
}
func (ka *NSKeyedArchiver) Marshal(obj interface{}) ([]byte, error) {
val := reflect.ValueOf(obj)
typ := val.Type()
root := newKeyedArchiver()
var tmpTop plist.UID
ka.id(nsNull)
switch typ.Kind() {
case reflect.Map:
m := &NSDictionary{}
m.Class = ka.id(NSDictionaryClass)
keys := val.MapKeys()
for _, v := range keys {
m.Keys = append(m.Keys, ka.id(v.Interface()))
m.Values = append(m.Values, ka.id(val.MapIndex(v).Interface()))
}
tmpTop = ka.id(m)
case reflect.Slice, reflect.Array:
if typ.Elem().Kind() == reflect.Uint8 {
d := &NSData{}
d.Class = ka.id(NSDataClass)
var w []byte
for i := 0; i < val.Len(); i++ {
w = append(w, uint8(val.Index(i).Uint()))
}
d.Data = w
}
a := &NSArray{}
a.Class = ka.id(NSArrayClass)
for i := 0; i < val.Len(); i++ {
a.Values = append(a.Values, ka.id(val.Index(i).Interface()))
}
tmpTop = ka.id(a)
case reflect.String:
tmpTop = ka.id(obj)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
tmpTop = ka.id(obj)
}
root.Top.Root = tmpTop
ka.flushToStruct(root)
ka.clear()
return plist.Marshal(root, plist.BinaryFormat)
}
func (ka *NSKeyedArchiver) convertValue(v interface{}) interface{} {
if m, ok := v.(map[string]interface{}); ok {
className := ka.objRefVal[m["$class"].(plist.UID)].(map[string]interface{})["$classname"]
switch className {
case NSMutableDictionaryClass.Classes[0], NSDictionaryClass.Classes[0]:
ret := make(map[string]interface{})
keys := m["NS.keys"].([]interface{})
values := m["NS.objects"].([]interface{})
for i := 0; i < len(keys); i++ {
var keyValue string
key := ka.objRefVal[keys[i].(plist.UID)]
switch key.(type) {
case uint64:
keyValue = strconv.Itoa(int(key.(uint64)))
break
default:
keyValue = key.(string)
}
val := ka.convertValue(ka.objRefVal[values[i].(plist.UID)])
ret[keyValue] = val
}
return ret
case NSMutableArrayClass.Classes[0], NSArrayClass.Classes[0]:
ret := make([]interface{}, 0)
values := m["NS.objects"].([]interface{})
for i := 0; i < len(values); i++ {
ret = append(ret, ka.convertValue(values[i]))
}
return ret
case NSMutableDataClass.Classes[0], NSDataClass.Classes[0]:
return m["NS.data"].([]byte)
case NSDateClass.Classes[0]:
return time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC).
Add(time.Duration(m["NS.time"].(float64)) * time.Second)
case NSErrorClass.Classes[0]:
err := &NSError{}
err.NSCode = int(m["NSCode"].(uint64))
err.NSDomain = ka.objRefVal[m["NSDomain"].(plist.UID)].(string)
err.NSUserInfo = ka.convertValue(ka.objRefVal[m["NSUserInfo"].(plist.UID)])
return *err
}
} else if uid, ok := v.(plist.UID); ok {
return ka.convertValue(ka.objRefVal[uid])
}
return v
}
func (ka *NSKeyedArchiver) Unmarshal(b []byte) (interface{}, error) {
archiver := new(KeyedArchiver)
if _, err := plist.Unmarshal(b, archiver); err != nil {
return nil, err
}
for _, v := range archiver.Objects {
ka.objRefVal = append(ka.objRefVal, v)
}
ret := ka.convertValue(ka.objRefVal[archiver.Top.Root])
ka.clear()
return ret, nil
}

View File

@@ -0,0 +1,28 @@
package libimobiledevice
import (
"bytes"
"log"
)
type Packet interface {
Pack() ([]byte, error)
Unpack(buffer *bytes.Buffer) (Packet, error)
Unmarshal(v interface{}) error
String() string
}
var debugFlag = false
// SetDebug sets debug mode
func SetDebug(debug bool) {
debugFlag = debug
}
func debugLog(msg string) {
if !debugFlag {
return
}
log.Printf("[%s-debug] %s\n", ProgramName, msg)
}

View File

@@ -0,0 +1,183 @@
package libimobiledevice
const ProtocolVersion = "2"
const LockdownPort = 62078
type RequestType string
const (
RequestTypeQueryType RequestType = "QueryType"
RequestTypeSetValue RequestType = "SetValue"
RequestTypeGetValue RequestType = "GetValue"
RequestTypePair RequestType = "Pair"
RequestTypeEnterRecovery RequestType = "EnterRecovery"
RequestTypeStartSession RequestType = "StartSession"
RequestTypeStopSession RequestType = "StopSession"
RequestTypeStartService RequestType = "StartService"
)
type LockdownType struct {
Type string `plist:"Type"`
}
func NewLockdownClient(innerConn InnerConn) *LockdownClient {
return &LockdownClient{
client: newServicePacketClient(innerConn),
}
}
type LockdownClient struct {
client *servicePacketClient
}
func (c *LockdownClient) NewBasicRequest(reqType RequestType) *LockdownBasicRequest {
return &LockdownBasicRequest{
Label: BundleID,
ProtocolVersion: ProtocolVersion,
Request: reqType,
}
}
func (c *LockdownClient) NewGetValueRequest(domain, key string) *LockdownValueRequest {
return &LockdownValueRequest{
LockdownBasicRequest: *c.NewBasicRequest(RequestTypeGetValue),
Domain: domain,
Key: key,
}
}
func (c *LockdownClient) NewSetValueRequest(domain, key string, value interface{}) *LockdownValueRequest {
return &LockdownValueRequest{
LockdownBasicRequest: *c.NewBasicRequest(RequestTypeSetValue),
Domain: domain,
Key: key,
Value: value,
}
}
func (c *LockdownClient) NewEnterRecoveryRequest() *LockdownBasicRequest {
return c.NewBasicRequest(RequestTypeEnterRecovery)
}
func (c *LockdownClient) NewPairRequest(pairRecord *PairRecord) *LockdownPairRequest {
return &LockdownPairRequest{
LockdownBasicRequest: *c.NewBasicRequest(RequestTypePair),
PairRecord: pairRecord,
PairingOptions: map[string]interface{}{
"ExtendedPairingErrors": true,
},
}
}
func (c *LockdownClient) NewStartSessionRequest(buid, hostID string) *LockdownStartSessionRequest {
return &LockdownStartSessionRequest{
LockdownBasicRequest: *c.NewBasicRequest(RequestTypeStartSession),
SystemBUID: buid,
HostID: hostID,
}
}
func (c *LockdownClient) NewStopSessionRequest(sessionID string) *LockdownStopSessionRequest {
return &LockdownStopSessionRequest{
LockdownBasicRequest: *c.NewBasicRequest(RequestTypeStopSession),
SessionID: sessionID,
}
}
func (c *LockdownClient) NewStartServiceRequest(service string) *LockdownStartServiceRequest {
return &LockdownStartServiceRequest{
LockdownBasicRequest: *c.NewBasicRequest(RequestTypeStartService),
Service: service,
}
}
func (c *LockdownClient) NewXmlPacket(req interface{}) (Packet, error) {
return c.client.NewXmlPacket(req)
}
func (c *LockdownClient) SendPacket(pkt Packet) (err error) {
return c.client.SendPacket(pkt)
}
func (c *LockdownClient) ReceivePacket() (respPkt Packet, err error) {
return c.client.ReceivePacket()
}
func (c *LockdownClient) EnableSSL(version []int, pairRecord *PairRecord) (err error) {
return c.client.innerConn.Handshake(version, pairRecord)
}
type (
LockdownBasicRequest struct {
Label string `plist:"Label"`
ProtocolVersion string `plist:"ProtocolVersion"`
Request RequestType `plist:"Request"`
}
LockdownValueRequest struct {
LockdownBasicRequest
Domain string `plist:"Domain,omitempty"`
Key string `plist:"Key,omitempty"`
Value interface{} `plist:"Value,omitempty"`
}
LockdownPairRequest struct {
LockdownBasicRequest
PairRecord *PairRecord `plist:"PairRecord"`
PairingOptions map[string]interface{} `plist:"PairingOptions"`
}
LockdownStartSessionRequest struct {
LockdownBasicRequest
SystemBUID string `plist:"SystemBUID"`
HostID string `plist:"HostID"`
}
LockdownStopSessionRequest struct {
LockdownBasicRequest
SessionID string `plist:"SessionID"`
}
LockdownStartServiceRequest struct {
LockdownBasicRequest
Service string `plist:"Service"`
EscrowBag []byte `plist:"EscrowBag,omitempty"`
}
)
type (
LockdownBasicResponse struct {
Request string `plist:"Request"`
Error string `plist:"Error"`
}
LockdownTypeResponse struct {
LockdownBasicResponse
Type string `plist:"Type"`
}
LockdownValueResponse struct {
LockdownBasicResponse
Key string `plist:"Key"`
Value interface{} `plist:"Value"`
}
LockdownPairResponse struct {
LockdownBasicResponse
EscrowBag []byte `plist:"EscrowBag"`
}
LockdownStartSessionResponse struct {
LockdownBasicResponse
EnableSessionSSL bool `plist:"EnableSessionSSL"`
SessionID string `plist:"SessionID"`
}
LockdownStartServiceResponse struct {
LockdownBasicResponse
EnableServiceSSL bool `plist:"EnableServiceSSL"`
Port int `plist:"Port"`
Service string `plist:"Service"`
}
)

View File

@@ -0,0 +1,86 @@
package libimobiledevice
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
)
var afcHeader = []byte{0x43, 0x46, 0x41, 0x36, 0x4C, 0x50, 0x41, 0x41}
var _ Packet = (*afcPacket)(nil)
type afcPacket struct {
entireLen uint64
thisLen uint64
packetNum uint64
operation uint64
}
func (p *afcPacket) Pack() ([]byte, error) {
buf := new(bytes.Buffer)
buf.Write(afcHeader)
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, p.entireLen)
buf.Write(b)
binary.LittleEndian.PutUint64(b, p.thisLen)
buf.Write(b)
binary.LittleEndian.PutUint64(b, p.packetNum)
buf.Write(b)
binary.LittleEndian.PutUint64(b, p.operation)
buf.Write(b)
return buf.Bytes(), nil
}
func (p *afcPacket) Unpack(buffer *bytes.Buffer) (Packet, error) {
return p.unpack(buffer)
}
func (p *afcPacket) unpack(buffer *bytes.Buffer) (*afcPacket, error) {
magic := make([]byte, 8)
if _, err := buffer.Read(magic); err != nil {
return nil, fmt.Errorf("afc packet unpack: %w", err)
}
if bytes.Compare(magic, afcHeader) != 0 {
return nil, errors.New("afc packet unpack: header not match")
}
respPkt := new(afcPacket)
if err := binary.Read(buffer, binary.LittleEndian, &respPkt.entireLen); err != nil {
return nil, fmt.Errorf("afc packet unpack: %w", err)
}
if err := binary.Read(buffer, binary.LittleEndian, &respPkt.thisLen); err != nil {
return nil, fmt.Errorf("afc packet unpack: %w", err)
}
if err := binary.Read(buffer, binary.LittleEndian, &respPkt.packetNum); err != nil {
return nil, fmt.Errorf("afc packet unpack: %w", err)
}
if err := binary.Read(buffer, binary.LittleEndian, &respPkt.operation); err != nil {
return nil, fmt.Errorf("afc packet unpack: %w", err)
}
return respPkt, nil
}
func (p *afcPacket) Unmarshal(v interface{}) error {
// switch msg := v.(type) {
// case *AfcMessage:
// // msg.EntireLen = p.entireLen
// // msg.ThisLen = p.thisLen
// // msg.PacketNum = p.packetNum
// msg.Operation = p.operation
// default:
// return errors.New("the type of the method parameter must be '*AfcMessage'")
// }
// return nil
panic("never use (afcPacket)")
}
func (p *afcPacket) String() string {
return fmt.Sprintf(
"EntireLen: %d, ThisLen: %d, PacketNum: %d, Operation: %X\n",
p.entireLen, p.thisLen, p.packetNum, p.operation,
)
}

View File

@@ -0,0 +1,203 @@
package libimobiledevice
import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
)
var (
_ Packet = (*dtxMessagePayloadPacket)(nil)
_ Packet = (*dtxMessageHeaderPacket)(nil)
_ Packet = (*dtxMessagePacket)(nil)
)
type dtxMessagePayloadPacket struct {
Flags uint32
AuxiliaryLength uint32
TotalLength uint64
}
func (p *dtxMessagePayloadPacket) Pack() ([]byte, error) {
buf := new(bytes.Buffer)
b := make([]byte, 4)
binary.LittleEndian.PutUint32(b, p.Flags)
buf.Write(b)
binary.LittleEndian.PutUint32(b, p.AuxiliaryLength)
buf.Write(b)
b = make([]byte, 8)
binary.LittleEndian.PutUint64(b, p.TotalLength)
buf.Write(b)
return buf.Bytes(), nil
}
func (p *dtxMessagePayloadPacket) Unpack(buffer *bytes.Buffer) (pkt Packet, err error) {
return p.unpack(buffer)
}
func (p *dtxMessagePayloadPacket) unpack(buffer *bytes.Buffer) (pkt *dtxMessagePayloadPacket, err error) {
respPkt := new(dtxMessagePayloadPacket)
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.Flags); err != nil {
return nil, fmt.Errorf("packet (DTXMessagePayloadHeader) unpack: %w", err)
}
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.AuxiliaryLength); err != nil {
return nil, fmt.Errorf("packet (DTXMessagePayloadHeader) unpack: %w", err)
}
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.TotalLength); err != nil {
return nil, fmt.Errorf("packet (DTXMessagePayloadHeader) unpack: %w", err)
}
return respPkt, nil
}
func (p *dtxMessagePayloadPacket) Unmarshal(v interface{}) error {
panic("never use (dtxMessagePayloadHeader)")
}
func (p *dtxMessagePayloadPacket) String() string {
return fmt.Sprintf("DTXMessagePayloadHeader Flags: %d, AuxiliaryLength: %d, TotalLength: %d\n",
p.Flags, p.AuxiliaryLength, p.TotalLength,
)
}
type dtxMessageHeaderPacket struct {
Magic uint32
CB uint32
FragmentId uint16
FragmentCount uint16
Length uint32
Identifier uint32
ConversationIndex uint32
ChannelCode uint32
ExpectsReply uint32
}
func (p *dtxMessageHeaderPacket) Pack() ([]byte, error) {
buf := new(bytes.Buffer)
b := make([]byte, 4)
binary.LittleEndian.PutUint32(b, p.Magic)
buf.Write(b)
binary.LittleEndian.PutUint32(b, p.CB)
buf.Write(b)
b = make([]byte, 2)
binary.LittleEndian.PutUint16(b, p.FragmentId)
buf.Write(b)
binary.LittleEndian.PutUint16(b, p.FragmentCount)
buf.Write(b)
b = make([]byte, 4)
binary.LittleEndian.PutUint32(b, p.Length)
buf.Write(b)
binary.LittleEndian.PutUint32(b, p.Identifier)
buf.Write(b)
binary.LittleEndian.PutUint32(b, p.ConversationIndex)
buf.Write(b)
binary.LittleEndian.PutUint32(b, p.ChannelCode)
buf.Write(b)
binary.LittleEndian.PutUint32(b, p.ExpectsReply)
buf.Write(b)
return buf.Bytes(), nil
}
func (p *dtxMessageHeaderPacket) Unpack(buffer *bytes.Buffer) (pkt Packet, err error) {
return p.unpack(buffer)
}
func (p *dtxMessageHeaderPacket) unpack(buffer *bytes.Buffer) (pkt *dtxMessageHeaderPacket, err error) {
respPkt := new(dtxMessageHeaderPacket)
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.Magic); err != nil {
return nil, fmt.Errorf("packet (DTXMessageHeader) unpack: %w", err)
}
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.CB); err != nil {
return nil, fmt.Errorf("packet (DTXMessageHeader) unpack: %w", err)
}
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.FragmentId); err != nil {
return nil, fmt.Errorf("packet (DTXMessageHeader) unpack: %w", err)
}
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.FragmentCount); err != nil {
return nil, fmt.Errorf("packet (DTXMessageHeader) unpack: %w", err)
}
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.Length); err != nil {
return nil, fmt.Errorf("packet (DTXMessageHeader) unpack: %w", err)
}
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.Identifier); err != nil {
return nil, fmt.Errorf("packet (DTXMessageHeader) unpack: %w", err)
}
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.ConversationIndex); err != nil {
return nil, fmt.Errorf("packet (DTXMessageHeader) unpack: %w", err)
}
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.ChannelCode); err != nil {
return nil, fmt.Errorf("packet (DTXMessageHeader) unpack: %w", err)
}
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.ExpectsReply); err != nil {
return nil, fmt.Errorf("packet (DTXMessageHeader) unpack: %w", err)
}
return respPkt, nil
}
func (p *dtxMessageHeaderPacket) Unmarshal(v interface{}) error {
panic("never use (DTXMessageHeader)")
}
func (p *dtxMessageHeaderPacket) String() string {
return fmt.Sprintf("DTXMessageHeader Magic: %d, CB: %d, FragmentId: %d, FragmentCount: %d\n"+
"Length: %d, Identifier: %d, ConversationIndex: %d, ChannelCode: %d, ExpectsReply: %d\n",
p.Magic, p.CB, p.FragmentId, p.FragmentCount,
p.Length, p.Identifier, p.ConversationIndex, p.ChannelCode, p.ExpectsReply,
)
}
type dtxMessagePacket struct {
Header *dtxMessageHeaderPacket
Payload *dtxMessagePayloadPacket
Aux []byte
Sel []byte
}
func (p *dtxMessagePacket) Pack() ([]byte, error) {
buf := new(bytes.Buffer)
raw, err := p.Header.Pack()
if err != nil {
return nil, fmt.Errorf("packet (DTXMessagePacket) pack: %w", err)
}
buf.Write(raw)
if raw, err = p.Payload.Pack(); err != nil {
return nil, fmt.Errorf("packet (DTXMessagePacket) pack: %w", err)
}
buf.Write(raw)
if p.Aux != nil || len(p.Aux) != 0 {
buf.Write(p.Aux)
}
if p.Sel != nil || len(p.Sel) != 0 {
buf.Write(p.Sel)
}
return buf.Bytes(), nil
}
func (p *dtxMessagePacket) Unpack(buffer *bytes.Buffer) (Packet, error) {
panic("implement me")
}
func (p *dtxMessagePacket) Unmarshal(v interface{}) error {
panic("never use (DTXMessagePacket)")
}
func (p *dtxMessagePacket) String() string {
return fmt.Sprintf(
"DTXMessagePacket %s\n%s\n"+
"%s\n%s\n",
p.Header.String(), p.Payload.String(),
// p.Aux, p.Sel,
hex.Dump(p.Aux), hex.Dump(p.Sel),
)
}

View File

@@ -0,0 +1,53 @@
package libimobiledevice
import (
"bytes"
"encoding/binary"
"fmt"
"strconv"
)
var _ Packet = (*locationPacket)(nil)
type locationPacket struct {
lon float64
lat float64
}
func (l *locationPacket) Pack() ([]byte, error) {
buf := new(bytes.Buffer)
if err := binary.Write(buf, binary.BigEndian, uint32(0)); err != nil {
return nil, fmt.Errorf("packet (location) pack: %w", err)
}
latS := []byte(strconv.FormatFloat(l.lat, 'E', -1, 64))
if err := binary.Write(buf, binary.BigEndian, uint32(len(latS))); err != nil {
return nil, fmt.Errorf("packet (location) pack: %w", err)
}
if err := binary.Write(buf, binary.BigEndian, latS); err != nil {
return nil, fmt.Errorf("packet (location) pack: %w", err)
}
lonS := []byte(strconv.FormatFloat(l.lon, 'E', -1, 64))
if err := binary.Write(buf, binary.BigEndian, uint32(len(lonS))); err != nil {
return nil, fmt.Errorf("packet (location) pack: %w", err)
}
if err := binary.Write(buf, binary.BigEndian, lonS); err != nil {
return nil, fmt.Errorf("packet (location) pack: %w", err)
}
return buf.Bytes(), nil
}
func (l *locationPacket) Unpack(buffer *bytes.Buffer) (Packet, error) {
panic("never use (location)")
}
func (l *locationPacket) Unmarshal(v interface{}) error {
panic("never use (location)")
}
func (l *locationPacket) String() string {
return fmt.Sprintf("lon: %v, lat: %v\n", l.lon, l.lat)
}

View File

@@ -0,0 +1,47 @@
package libimobiledevice
import (
"bytes"
"encoding/binary"
"fmt"
"howett.net/plist"
)
var _ Packet = (*servicePacket)(nil)
type servicePacket struct {
length uint32
body []byte
}
func (p *servicePacket) Pack() ([]byte, error) {
buf := new(bytes.Buffer)
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, p.length)
buf.Write(b)
buf.Write(p.body)
return buf.Bytes(), nil
}
func (p *servicePacket) Unpack(buffer *bytes.Buffer) (pkt Packet, err error) {
respPkt := new(servicePacket)
if err = binary.Read(buffer, binary.BigEndian, &respPkt.length); err != nil {
return nil, fmt.Errorf("packet (service) unpack: %w", err)
}
respPkt.body = buffer.Bytes()
return respPkt, nil
}
func (p *servicePacket) Unmarshal(v interface{}) (err error) {
_, err = plist.Unmarshal(p.body, v)
return
}
func (p *servicePacket) String() string {
return fmt.Sprintf("Length: %d\n%s", p.length, p.body)
}

View File

@@ -0,0 +1,112 @@
package libimobiledevice
import (
"bytes"
"encoding/binary"
"fmt"
"howett.net/plist"
)
var _ Packet = (*packet)(nil)
type packet struct {
length uint32
version ProtoVersion
msgType ProtoMessageType
tag uint32
body []byte
}
func (p *packet) Pack() ([]byte, error) {
buf := new(bytes.Buffer)
b := make([]byte, 4)
binary.LittleEndian.PutUint32(b, p.length)
buf.Write(b)
binary.LittleEndian.PutUint32(b, uint32(p.version))
buf.Write(b)
binary.LittleEndian.PutUint32(b, uint32(p.msgType))
buf.Write(b)
binary.LittleEndian.PutUint32(b, p.tag)
buf.Write(b)
buf.Write(p.body)
return buf.Bytes(), nil
}
func (p *packet) Unpack(buffer *bytes.Buffer) (pkt Packet, err error) {
respPkt := new(packet)
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.length); err != nil {
return nil, fmt.Errorf("packet unpack: %w", err)
}
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.version); err != nil {
return nil, fmt.Errorf("packet unpack: %w", err)
}
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.msgType); err != nil {
return nil, fmt.Errorf("packet unpack: %w", err)
}
if err = binary.Read(buffer, binary.LittleEndian, &respPkt.tag); err != nil {
return nil, fmt.Errorf("packet unpack: %w", err)
}
respPkt.body = buffer.Bytes()
return respPkt, nil
}
func (p *packet) Unmarshal(v interface{}) (err error) {
_, err = plist.Unmarshal(p.body, v)
return
}
func (p *packet) String() string {
return fmt.Sprintf(
"Length: %d, Version: %d, Type: %d, Tag: %d\n%s",
p.length, p.version, p.msgType, p.tag, p.body,
)
}
type (
BasicRequest struct {
MessageType MessageType `plist:"MessageType"`
BundleID string `plist:"BundleID,omitempty"`
ProgramName string `plist:"ProgName,omitempty"`
ClientVersionString string `plist:"ClientVersionString"`
LibUSBMuxVersion uint `plist:"kLibUSBMuxVersion"`
}
ConnectRequest struct {
BasicRequest
DeviceID int `plist:"DeviceID"`
PortNumber int `plist:"PortNumber"`
}
ReadPairRecordRequest struct {
BasicRequest
PairRecordID string `plist:"PairRecordID"`
}
SavePairRecordRequest struct {
BasicRequest
PairRecordID string `plist:"PairRecordID"`
PairRecordData []byte `plist:"PairRecordData"`
DeviceID int `plist:"DeviceID"`
}
DeletePairRecordRequest struct {
BasicRequest
PairRecordID string `plist:"PairRecordID"`
}
)
type PairRecord struct {
DeviceCertificate []byte `plist:"DeviceCertificate"`
EscrowBag []byte `plist:"EscrowBag,omitempty"`
HostCertificate []byte `plist:"HostCertificate"`
HostPrivateKey []byte `plist:"HostPrivateKey,omitempty"`
HostID string `plist:"HostID"`
RootCertificate []byte `plist:"RootCertificate"`
RootPrivateKey []byte `plist:"RootPrivateKey,omitempty"`
SystemBUID string `plist:"SystemBUID"`
WiFiMACAddress string `plist:"WiFiMACAddress,omitempty"`
}

View File

@@ -0,0 +1,119 @@
package libimobiledevice
import (
"bytes"
"encoding/binary"
"fmt"
"io/ioutil"
"time"
"github.com/lunixbochs/struc"
)
const PcapdServiceName = "com.apple.pcapd"
func NewPcapdClient(innerConn InnerConn) *PcapdClient {
return &PcapdClient{
client: newServicePacketClient(innerConn),
}
}
type PcapdClient struct {
filter func(*IOSPacketHeader) bool
client *servicePacketClient
}
func (c *PcapdClient) ReceivePacket() (respPkt Packet, err error) {
var bufLen []byte
if bufLen, err = c.client.innerConn.Read(4); err != nil {
return nil, fmt.Errorf("lockdown(Pcapd) receive: %w", err)
}
lenPkg := binary.BigEndian.Uint32(bufLen)
buffer := bytes.NewBuffer([]byte{})
buffer.Write(bufLen)
var buf []byte
if buf, err = c.client.innerConn.Read(int(lenPkg)); err != nil {
return nil, fmt.Errorf("lockdown(Pcapd) receive: %w", err)
}
buffer.Write(buf)
if respPkt, err = new(servicePacket).Unpack(buffer); err != nil {
return nil, fmt.Errorf("lockdown(Pcapd) receive: %w", err)
}
debugLog(fmt.Sprintf("<-- %s\n", respPkt))
return
}
type IOSPacketHeader struct {
HdrSize uint32 `struc:"uint32,big"`
Version uint8 `struc:"uint8,big"`
PacketSize uint32 `struc:"uint32,big"`
Type uint8 `struc:"uint8,big"`
Unit uint16 `struc:"uint16,big"`
IO uint8 `struc:"uint8,big"`
ProtocolFamily uint32 `struc:"uint32,big"`
FramePreLength uint32 `struc:"uint32,big"`
FramePstLength uint32 `struc:"uint32,big"`
IFName string `struc:"[16]byte"`
Pid int32 `struc:"int32,little"`
ProcName string `struc:"[17]byte"`
Unknown uint32 `struc:"uint32,little"`
Pid2 int32 `struc:"int32,little"`
ProcName2 string `struc:"[17]byte"`
Unknown2 [8]byte `struc:"[8]byte"`
}
func (c *PcapdClient) GetPacket(buf []byte) ([]byte, error) {
iph := IOSPacketHeader{}
preader := bytes.NewReader(buf)
_ = struc.Unpack(preader, &iph)
if c.filter != nil {
if !c.filter(&iph) {
return nil, nil
}
}
packet, err := ioutil.ReadAll(preader)
if err != nil {
return packet, err
}
if iph.FramePreLength == 0 {
ext := []byte{0xbe, 0xfe, 0xbe, 0xfe, 0xbe, 0xfe, 0xbe, 0xfe, 0xbe, 0xfe, 0xbe, 0xfe, 0x08, 0x00}
return append(ext, packet...), nil
}
return packet, nil
}
type PcaprecHdrS struct {
TsSec int `struc:"uint32,little"` /* timestamp seconds */
TsUsec int `struc:"uint32,little"` /* timestamp microseconds */
InclLen int `struc:"uint32,little"` /* number of octets of packet saved in file */
OrigLen int `struc:"uint32,little"` /* actual length of packet */
}
func (c *PcapdClient) CreatePacket(packet []byte) ([]byte, error) {
now := time.Now()
phs := &PcaprecHdrS{
int(now.Unix()),
int(now.UnixNano()/1e3 - now.Unix()*1e6),
len(packet),
len(packet),
}
var buf bytes.Buffer
err := struc.Pack(&buf, phs)
if err != nil {
return nil, err
}
buf.Write(packet)
return buf.Bytes(), nil
}
func (c *PcapdClient) Close() {
c.client.innerConn.Close()
}

View File

@@ -0,0 +1,52 @@
package libimobiledevice
import (
"bytes"
"encoding/binary"
"fmt"
)
const ScreenshotServiceName = "com.apple.mobile.screenshotr"
func NewScreenshotClient(innerConn InnerConn) *ScreenshotClient {
return &ScreenshotClient{
client: newServicePacketClient(innerConn),
}
}
type ScreenshotClient struct {
client *servicePacketClient
}
func (c *ScreenshotClient) NewBinaryPacket(req interface{}) (Packet, error) {
return c.client.NewBinaryPacket(req)
}
func (c *ScreenshotClient) SendPacket(pkt Packet) (err error) {
return c.client.SendPacket(pkt)
}
func (c *ScreenshotClient) ReceivePacket() (respPkt Packet, err error) {
var bufLen []byte
if bufLen, err = c.client.innerConn.Read(4); err != nil {
return nil, fmt.Errorf("lockdown(Screenshot) receive: %w", err)
}
lenPkg := binary.BigEndian.Uint32(bufLen)
buffer := bytes.NewBuffer([]byte{})
buffer.Write(bufLen)
var buf []byte
if buf, err = c.client.innerConn.Read(int(lenPkg)); err != nil {
return nil, fmt.Errorf("lockdown(Screenshot) receive: %w", err)
}
buffer.Write(buf)
if respPkt, err = new(servicePacket).Unpack(buffer); err != nil {
return nil, fmt.Errorf("lockdown(Screenshot) receive: %w", err)
}
debugLog(fmt.Sprintf("<-- %s\n", respPkt))
return
}

View File

@@ -0,0 +1,128 @@
package libimobiledevice
import (
"fmt"
"math"
"strings"
)
const SimulateLocationServiceName = "com.apple.dt.simulatelocation"
type CoordinateSystem string
const (
CoordinateSystemWGS84 CoordinateSystem = "WGS84"
CoordinateSystemBD09 CoordinateSystem = "BD09"
CoordinateSystemGCJ02 CoordinateSystem = "GCJ02"
)
func NewSimulateLocationClient(innerConn InnerConn) *SimulateLocationClient {
return &SimulateLocationClient{
client: newServicePacketClient(innerConn),
}
}
type SimulateLocationClient struct {
client *servicePacketClient
}
func (c *SimulateLocationClient) NewLocationPacket(lon, lat float64, coordinateSystem CoordinateSystem) Packet {
switch CoordinateSystem(strings.ToUpper(string(coordinateSystem))) {
case CoordinateSystemGCJ02:
lon, lat = gcj02ToWGS84(lon, lat)
case CoordinateSystemBD09:
lon, lat = bd09ToWGS84(lon, lat)
case CoordinateSystemWGS84:
_, _ = lon, lat
default:
_, _ = lon, lat
}
pkt := new(locationPacket)
pkt.lon = lon
pkt.lat = lat
return pkt
}
func (c *SimulateLocationClient) SendPacket(pkt Packet) (err error) {
return c.client.SendPacket(pkt)
}
// Recover try to revert back
func (c *SimulateLocationClient) Recover() error {
data := []byte{0x00, 0x00, 0x00, 0x01}
debugLog(fmt.Sprintf("--> %+v\n", data))
return c.client.innerConn.Write(data)
}
const (
xPi = math.Pi * 3000.0 / 180.0
offset = 0.00669342162296594323
axis = 6378245.0
)
func isOutOfChina(lon, lat float64) bool {
return !(lon > 73.66 && lon < 135.05 && lat > 3.86 && lat < 53.55)
}
func delta(lon, lat float64) (float64, float64) {
dLat := transformLat(lon-105.0, lat-35.0)
dLon := transformLng(lon-105.0, lat-35.0)
radLat := lat / 180.0 * math.Pi
magic := math.Sin(radLat)
magic = 1 - offset*magic*magic
sqrtMagic := math.Sqrt(magic)
dLat = (dLat * 180.0) / ((axis * (1 - offset)) / (magic * sqrtMagic) * math.Pi)
dLon = (dLon * 180.0) / (axis / sqrtMagic * math.Cos(radLat) * math.Pi)
mgLat := lat + dLat
mgLon := lon + dLon
return mgLon, mgLat
}
func transformLat(lon, lat float64) float64 {
ret := -100.0 + 2.0*lon + 3.0*lat + 0.2*lat*lat + 0.1*lon*lat + 0.2*math.Sqrt(math.Abs(lon))
ret += (20.0*math.Sin(6.0*lon*math.Pi) + 20.0*math.Sin(2.0*lon*math.Pi)) * 2.0 / 3.0
ret += (20.0*math.Sin(lat*math.Pi) + 40.0*math.Sin(lat/3.0*math.Pi)) * 2.0 / 3.0
ret += (160.0*math.Sin(lat/12.0*math.Pi) + 320*math.Sin(lat*math.Pi/30.0)) * 2.0 / 3.0
return ret
}
func transformLng(lon, lat float64) float64 {
ret := 300.0 + lon + 2.0*lat + 0.1*lon*lon + 0.1*lon*lat + 0.1*math.Sqrt(math.Abs(lon))
ret += (20.0*math.Sin(6.0*lon*math.Pi) + 20.0*math.Sin(2.0*lon*math.Pi)) * 2.0 / 3.0
ret += (20.0*math.Sin(lon*math.Pi) + 40.0*math.Sin(lon/3.0*math.Pi)) * 2.0 / 3.0
ret += (150.0*math.Sin(lon/12.0*math.Pi) + 300.0*math.Sin(lon/30.0*math.Pi)) * 2.0 / 3.0
return ret
}
func gcj02ToWGS84(lon, lat float64) (float64, float64) {
if isOutOfChina(lon, lat) {
return lon, lat
}
mgLon, mgLat := delta(lon, lat)
return lon*2 - mgLon, lat*2 - mgLat
}
func bd09ToGCJ02(lon, lat float64) (float64, float64) {
x := lon - 0.0065
y := lat - 0.006
z := math.Sqrt(x*x+y*y) - 0.00002*math.Sin(y*xPi)
theta := math.Atan2(y, x) - 0.000003*math.Cos(x*xPi)
gLon := z * math.Cos(theta)
gLat := z * math.Sin(theta)
return gLon, gLat
}
func bd09ToWGS84(lon, lat float64) (float64, float64) {
lon, lat = bd09ToGCJ02(lon, lat)
return gcj02ToWGS84(lon, lat)
}

View File

@@ -0,0 +1,53 @@
package libimobiledevice
type IconPNGDataResponse struct {
PNGData []byte `plist:"pngData"`
}
type InterfaceOrientationResponse struct {
Orientation OrientationState `plist:"interfaceOrientation"`
}
type OrientationState int64
const (
Unknown OrientationState = iota
Portrait
PortraitUpsideDown
LandscapeRight
LandscapeLeft
)
const (
SpringBoardServiceName = "com.apple.springboardservices"
)
func NewSpringBoardClient(innerConn InnerConn) *SpringBoardClient {
return &SpringBoardClient{
newServicePacketClient(innerConn),
}
}
type SpringBoardClient struct {
client *servicePacketClient
}
func (c *SpringBoardClient) InnerConn() InnerConn {
return c.client.innerConn
}
func (c *SpringBoardClient) NewXmlPacket(req interface{}) (Packet, error) {
return c.client.NewXmlPacket(req)
}
func (c *SpringBoardClient) SendPacket(pkt Packet) (err error) {
return c.client.SendPacket(pkt)
}
func (c *SpringBoardClient) ReceivePacket() (respPkt Packet, err error) {
return c.client.ReceivePacket()
}
func (c *SpringBoardClient) NewBinaryPacket(req interface{}) (Packet, error) {
return c.client.NewBinaryPacket(req)
}

View File

@@ -0,0 +1,21 @@
package libimobiledevice
const SyslogRelayServiceName = "com.apple.syslog_relay"
func NewSyslogRelayClient(innerConn InnerConn) *SyslogRelayClient {
return &SyslogRelayClient{
newServicePacketClient(innerConn),
}
}
type SyslogRelayClient struct {
client *servicePacketClient
}
func (c *SyslogRelayClient) InnerConn() InnerConn {
return c.client.innerConn
}
func (c *SyslogRelayClient) Close() {
c.client.innerConn.Close()
}

View File

@@ -0,0 +1,45 @@
package libimobiledevice
const (
TestmanagerdSecureServiceName = "com.apple.testmanagerd.lockdown.secure"
TestmanagerdServiceName = "com.apple.testmanagerd.lockdown"
)
func NewTestmanagerdClient(innerConn InnerConn) *TestmanagerdClient {
return &TestmanagerdClient{
client: newDtxMessageClient(innerConn),
}
}
type TestmanagerdClient struct {
client *dtxMessageClient
}
func (t *TestmanagerdClient) Connection() (publishedChannels map[string]int32, err error) {
return t.client.Connection()
}
func (t *TestmanagerdClient) MakeChannel(channel string) (id uint32, err error) {
return t.client.MakeChannel(channel)
}
func (t *TestmanagerdClient) Invoke(selector string, args *AuxBuffer, channelCode uint32, expectsReply bool) (result *DTXMessageResult, err error) {
var msgID uint32
if msgID, err = t.client.SendDTXMessage(selector, args.Bytes(), channelCode, expectsReply); err != nil {
return nil, err
}
if expectsReply {
if result, err = t.client.GetResult(msgID); err != nil {
return nil, err
}
}
return
}
func (t *TestmanagerdClient) RegisterCallback(obj string, cb func(m DTXMessageResult)) {
t.client.RegisterCallback(obj, cb)
}
func (t *TestmanagerdClient) Close() {
t.client.Close()
}

View File

@@ -0,0 +1,414 @@
package libimobiledevice
import (
"bytes"
"crypto/tls"
"encoding/binary"
"fmt"
"net"
"runtime"
"strconv"
"time"
"howett.net/plist"
)
var DefaultDeadlineTimeout = 30 * time.Second
const (
BundleID = "electricbubble.libimobiledevice"
ProgramName = "libimobiledevice"
ClientVersion = "libimobiledevice-beta"
LibUSBMuxVersion = 3
)
type ReplyCode uint64
const (
ReplyCodeOK ReplyCode = iota
ReplyCodeBadCommand
ReplyCodeBadDevice
ReplyCodeConnectionRefused
_ // ignore `4`
_ // ignore `5`
ReplyCodeBadVersion
)
func (rc ReplyCode) String() string {
switch rc {
case ReplyCodeOK:
return "ok"
case ReplyCodeBadCommand:
return "bad command"
case ReplyCodeBadDevice:
return "bad device"
case ReplyCodeConnectionRefused:
return "connection refused"
case ReplyCodeBadVersion:
return "bad version"
default:
return "unknown reply code: " + strconv.Itoa(int(rc))
}
}
type ProtoVersion uint32
// proto_version == 1
// construct message plist
// else `0`? res == `RESULT_BADVERSION`
// binary packet
const (
ProtoVersionBinary ProtoVersion = iota
ProtoVersionPlist
)
type ProtoMessageType uint32
const (
_ ProtoMessageType = iota
ProtoMessageTypeResult
ProtoMessageTypeConnect
ProtoMessageTypeListen
ProtoMessageTypeDeviceAdd
ProtoMessageTypeDeviceRemove
ProtoMessageTypeDevicePaired
_ // `7`
ProtoMessageTypePlist
)
type MessageType string
const (
MessageTypeResult MessageType = "Result"
MessageTypeConnect MessageType = "Connect"
MessageTypeListen MessageType = "Listen"
MessageTypeDeviceAdd MessageType = "Attached"
MessageTypeDeviceRemove MessageType = "Detached"
MessageTypeReadBUID MessageType = "ReadBUID"
MessageTypeReadPairRecord MessageType = "ReadPairRecord"
MessageTypeSavePairRecord MessageType = "SavePairRecord"
MessageTypeDeletePairRecord MessageType = "DeletePairRecord"
MessageTypeDeviceList MessageType = "ListDevices"
)
type BaseDevice struct {
MessageType MessageType `plist:"MessageType"`
DeviceID int `plist:"DeviceID"`
Properties DeviceProperties `plist:"Properties"`
}
type DeviceProperties struct {
DeviceID int `plist:"DeviceID"`
ConnectionType string `plist:"ConnectionType"`
ConnectionSpeed int `plist:"ConnectionSpeed"`
ProductID int `plist:"ProductID"`
LocationID int `plist:"LocationID"`
SerialNumber string `plist:"SerialNumber"`
UDID string `plist:"UDID"`
USBSerialNumber string `plist:"USBSerialNumber"`
EscapedFullServiceName string `plist:"EscapedFullServiceName"`
InterfaceIndex int `plist:"InterfaceIndex"`
NetworkAddress []byte `plist:"NetworkAddress"`
}
func NewUsbmuxClient(timeout ...time.Duration) (c *UsbmuxClient, err error) {
if len(timeout) == 0 {
timeout = []time.Duration{DefaultDeadlineTimeout}
}
c = &UsbmuxClient{version: ProtoVersionPlist}
var conn net.Conn
if conn, err = rawDial(timeout[0]); err != nil {
return nil, fmt.Errorf("usbmux connect: %w", err)
}
c.innerConn = newInnerConn(conn, timeout[0])
return
}
type UsbmuxClient struct {
innerConn InnerConn
version ProtoVersion
tag uint32
}
func (c *UsbmuxClient) NewBasicRequest(msgType MessageType) *BasicRequest {
return &BasicRequest{
MessageType: msgType,
BundleID: BundleID,
ProgramName: ProgramName,
ClientVersionString: ClientVersion,
LibUSBMuxVersion: LibUSBMuxVersion,
}
}
func (c *UsbmuxClient) NewConnectRequest(deviceID, port int) *ConnectRequest {
return &ConnectRequest{
BasicRequest: *c.NewBasicRequest(MessageTypeConnect),
DeviceID: deviceID,
PortNumber: ((port << 8) & 0xFF00) | (port >> 8),
}
}
func (c *UsbmuxClient) NewReadPairRecordRequest(udid string) *ReadPairRecordRequest {
return &ReadPairRecordRequest{
BasicRequest: *c.NewBasicRequest(MessageTypeReadPairRecord),
PairRecordID: udid,
}
}
func (c *UsbmuxClient) NewSavePairRecordRequest(udid string, deviceID int, data []byte) *SavePairRecordRequest {
return &SavePairRecordRequest{
BasicRequest: *c.NewBasicRequest(MessageTypeSavePairRecord),
PairRecordID: udid,
PairRecordData: data,
DeviceID: deviceID,
}
}
func (c *UsbmuxClient) NewDeletePairRecordRequest(udid string) *DeletePairRecordRequest {
return &DeletePairRecordRequest{
BasicRequest: *c.NewBasicRequest(MessageTypeDeletePairRecord),
PairRecordID: udid,
}
}
func (c *UsbmuxClient) NewPacket(protoMsgType ProtoMessageType) Packet {
return c.newPacket(protoMsgType)
}
func (c *UsbmuxClient) newPacket(protoMsgType ProtoMessageType) *packet {
c.tag++
pkt := &packet{
version: c.version,
msgType: protoMsgType,
tag: c.tag,
}
return pkt
}
func (c *UsbmuxClient) NewPlistPacket(req interface{}) (Packet, error) {
pkt := c.newPacket(ProtoMessageTypePlist)
if buf, err := plist.Marshal(req, plist.XMLFormat); err != nil {
return nil, fmt.Errorf("plist packet marshal: %w", err)
} else {
pkt.body = buf
}
pkt.length = uint32(len(pkt.body) + 4*4)
return pkt, nil
}
func (c *UsbmuxClient) SendPacket(pkt Packet) (err error) {
var raw []byte
if raw, err = pkt.Pack(); err != nil {
return fmt.Errorf("usbmux send: %w", err)
}
// debugLog(fmt.Sprintf("--> Length: %d, Version: %d, Type: %d, Tag: %d\n%s\n", pkt.Length(), pkt.Version(), pkt.Type(), pkt.Tag(), pkt.Body()))
debugLog(fmt.Sprintf("--> %s\n", pkt))
return c.innerConn.Write(raw)
}
func (c *UsbmuxClient) ReceivePacket() (respPkt Packet, err error) {
var bufLen []byte
if bufLen, err = c.innerConn.Read(4); err != nil {
return nil, fmt.Errorf("usbmux receive: %w", err)
}
lenPkg := binary.LittleEndian.Uint32(bufLen)
buffer := bytes.NewBuffer([]byte{})
buffer.Write(bufLen)
var buf []byte
if buf, err = c.innerConn.Read(int(lenPkg - 4)); err != nil {
return nil, fmt.Errorf("usbmux receive: %w", err)
}
buffer.Write(buf)
if respPkt, err = new(packet).Unpack(buffer); err != nil {
return nil, fmt.Errorf("usbmux receive: %w", err)
}
// debugLog(fmt.Sprintf("<-- Length: %d, Version: %d, Type: %d, Tag: %d\n%s\n", respPkt.Length(), respPkt.Version(), respPkt.Type(), respPkt.Tag(), respPkt.Body()))
debugLog(fmt.Sprintf("<-- %s\n", respPkt))
reply := struct {
MessageType string `plist:"MessageType"`
Number ReplyCode `plist:"Number"`
}{}
if err = respPkt.Unmarshal(&reply); err != nil {
return nil, fmt.Errorf("usbmux receive: %w", err)
}
if reply.Number != ReplyCodeOK {
return nil, fmt.Errorf("usbmux receive: %s", reply.Number.String())
}
return
}
func (c *UsbmuxClient) Close() {
c.innerConn.Close()
}
func (c *UsbmuxClient) RawConn() net.Conn {
return c.innerConn.RawConn()
}
func (c *UsbmuxClient) InnerConn() InnerConn {
return c.innerConn
}
func rawDial(timeout time.Duration) (net.Conn, error) {
dialer := net.Dialer{
Timeout: timeout,
}
var network, address string
switch runtime.GOOS {
case "darwin", "android", "linux":
network, address = "unix", "/var/run/usbmuxd"
case "windows":
network, address = "tcp", "127.0.0.1:27015"
default:
return nil, fmt.Errorf("raw dial: unsupported system: %s", runtime.GOOS)
}
return dialer.Dial(network, address)
}
type InnerConn interface {
Write(data []byte) (err error)
Read(length int) (data []byte, err error)
Handshake(version []int, pairRecord *PairRecord) (err error)
DismissSSL() (err error)
Close()
RawConn() net.Conn
Timeout(time.Duration)
}
func newInnerConn(conn net.Conn, timeout time.Duration) InnerConn {
return &safeConn{
conn: conn,
timeout: timeout,
}
}
type safeConn struct {
conn net.Conn
sslConn *tls.Conn
timeout time.Duration
}
func (c *safeConn) Write(data []byte) (err error) {
conn := c.RawConn()
if c.timeout <= 0 {
err = conn.SetWriteDeadline(time.Time{})
} else {
err = conn.SetWriteDeadline(time.Now().Add(c.timeout))
}
if err != nil {
return err
}
for totalSent := 0; totalSent < len(data); {
var sent int
if sent, err = conn.Write(data[totalSent:]); err != nil {
return err
}
if sent == 0 {
return err
}
totalSent += sent
}
return
}
func (c *safeConn) Read(length int) (data []byte, err error) {
conn := c.RawConn()
if c.timeout <= 0 {
err = conn.SetReadDeadline(time.Time{})
} else {
err = conn.SetReadDeadline(time.Now().Add(c.timeout))
}
if err != nil {
return nil, err
}
data = make([]byte, 0, length)
for len(data) < length {
buf := make([]byte, length-len(data))
_n, _err := 0, error(nil)
if _n, _err = conn.Read(buf); _err != nil && _n == 0 {
return nil, _err
}
data = append(data, buf[:_n]...)
}
return
}
func (c *safeConn) Handshake(version []int, pairRecord *PairRecord) (err error) {
minVersion := uint16(tls.VersionTLS11)
maxVersion := uint16(tls.VersionTLS11)
if version[0] > 10 {
minVersion = tls.VersionTLS11
maxVersion = tls.VersionTLS13
}
cert, err := tls.X509KeyPair(pairRecord.RootCertificate, pairRecord.RootPrivateKey)
if err != nil {
return err
}
config := &tls.Config{
Certificates: []tls.Certificate{cert},
InsecureSkipVerify: true,
MinVersion: minVersion,
MaxVersion: maxVersion,
}
c.sslConn = tls.Client(c.conn, config)
if err = c.sslConn.Handshake(); err != nil {
return err
}
return
}
func (c *safeConn) DismissSSL() (err error) {
if c.sslConn != nil {
// err = c.sslConn.CloseWrite()
// if err = c.sslConn.CloseWrite(); err != nil {
// return err
// }
c.sslConn = nil
}
return
}
func (c *safeConn) Close() {
if c.sslConn != nil {
if err := c.sslConn.Close(); err != nil {
debugLog(fmt.Sprintf("close: %s", err))
}
}
if c.conn != nil {
if err := c.conn.Close(); err != nil {
debugLog(fmt.Sprintf("close: %s", err))
}
}
}
// RawConn `sslConn` first
func (c *safeConn) RawConn() net.Conn {
if c.sslConn != nil {
return c.sslConn
}
return c.conn
}
func (c *safeConn) Timeout(duration time.Duration) {
c.timeout = duration
}

View File

@@ -0,0 +1,37 @@
package nskeyedarchiver
import "howett.net/plist"
type NSArray struct {
internal []interface{}
}
func NewNSArray(value []interface{}) *NSArray {
return &NSArray{
internal: value,
}
}
func (ns *NSArray) archive(objects []interface{}) []interface{} {
objs := make([]interface{}, 0, len(ns.internal))
info := map[string]interface{}{}
objects = append(objects, info)
for _, v := range ns.internal {
var uid plist.UID
objects, uid = archive(objects, v)
objs = append(objs, uid)
}
info["NS.objects"] = objs
info["$class"] = plist.UID(len(objects))
cls := map[string]interface{}{
"$classname": "NSArray",
"$classes": []interface{}{"NSArray", "NSObject"},
}
objects = append(objects, cls)
return objects
}

View File

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

View File

@@ -0,0 +1,44 @@
package nskeyedarchiver
import (
"howett.net/plist"
)
type NSDictionary struct {
internal map[string]interface{}
}
func NewNSDictionary(value map[string]interface{}) *NSDictionary {
return &NSDictionary{
internal: value,
}
}
func (ns *NSDictionary) archive(objects []interface{}) []interface{} {
keys := make([]interface{}, 0, len(ns.internal))
objs := make([]interface{}, 0, len(ns.internal))
info := map[string]interface{}{}
objects = append(objects, info)
for k, v := range ns.internal {
uid := plist.UID(len(objects))
keys = append(keys, uid)
objects = append(objects, k)
objects, uid = archive(objects, v)
objs = append(objs, uid)
}
info["NS.keys"] = keys
info["NS.objects"] = objs
info["$class"] = plist.UID(len(objects))
cls := map[string]interface{}{
"$classname": "NSDictionary",
"$classes": []interface{}{"NSDictionary", "NSObject"},
}
objects = append(objects, cls)
return objects
}

View File

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

View File

@@ -0,0 +1,80 @@
package nskeyedarchiver
import (
"reflect"
"howett.net/plist"
)
func Marshal(obj interface{}) (raw []byte, err error) {
objects := []interface{}{"$null"}
objects, _ = archive(objects, obj)
archiver := map[string]interface{}{
"$version": 100000,
"$archiver": "NSKeyedArchiver",
"$top": map[string]interface{}{"root": plist.UID(1)},
"$objects": objects,
}
// if len(format) == 0 {
// format = []int{plist.BinaryFormat}
// }
// return plist.Marshal(archiver, format[0])
return plist.Marshal(archiver, plist.BinaryFormat)
}
func archive(_objects []interface{}, _value interface{}) (objects []interface{}, uid plist.UID) {
val := reflect.ValueOf(_value)
typ := val.Type()
switch typ.Kind() {
case reflect.String,
reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Uintptr:
uid = plist.UID(len(_objects))
objects = append(_objects, _value)
return
case reflect.Map:
uid = plist.UID(len(_objects))
vv := make(map[string]interface{})
keys := val.MapKeys()
for _, k := range keys {
vv[k.String()] = val.MapIndex(k).Interface()
}
objects = NewNSDictionary(vv).archive(_objects)
return
case reflect.Slice, reflect.Array:
uid = plist.UID(len(_objects))
vv := make([]interface{}, val.Len())
for i := 0; i < val.Len(); i++ {
vv[i] = val.Index(i).Interface()
}
objects = NewNSArray(vv).archive(_objects)
return
case reflect.Struct, reflect.Ptr:
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
val = val.Elem()
}
switch typ.Name() {
case "NSUUID":
uid = plist.UID(len(_objects))
objects = NewNSUUID(val.Field(0).Bytes()).archive(_objects)
return
case "NSURL":
uid = plist.UID(len(_objects))
objects = NewNSURL(val.Field(0).String()).archive(_objects)
return
case "XCTestConfiguration":
uid = plist.UID(len(_objects))
objects = newXCTestConfiguration(_value).archive(_objects)
return
}
}
return
}
// TODO unarchive

View File

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

View File

@@ -0,0 +1,27 @@
package nskeyedarchiver
import (
"howett.net/plist"
)
type NSNull struct{}
func NewNSNull() *NSNull {
return &NSNull{}
}
func (ns *NSNull) archive(objects []interface{}) []interface{} {
info := map[string]interface{}{}
objects = append(objects, info)
info["$class"] = plist.UID(len(objects))
cls := map[string]interface{}{
"$classname": "NSNull",
"$classes": []interface{}{"NSNull", "NSObject"},
}
objects = append(objects, cls)
return objects
}

View File

@@ -0,0 +1,38 @@
package nskeyedarchiver
import (
"fmt"
"howett.net/plist"
)
type NSURL struct {
internal string
}
func NewNSURL(path string) *NSURL {
return &NSURL{
internal: path,
}
}
func (ns *NSURL) archive(objects []interface{}) []interface{} {
info := map[string]interface{}{}
objects = append(objects, info)
uid := plist.UID(0)
info["NS.base"] = uid
objects, uid = archive(objects, fmt.Sprintf("file://%s", ns.internal))
info["NS.relative"] = uid
info["$class"] = plist.UID(len(objects))
cls := map[string]interface{}{
"$classname": "NSURL",
"$classes": []interface{}{"NSURL", "NSObject"},
}
objects = append(objects, cls)
return objects
}

View File

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

View File

@@ -0,0 +1,51 @@
package nskeyedarchiver
import (
"encoding/hex"
"howett.net/plist"
)
type NSUUID struct {
internal []byte
}
func NewNSUUID(uuid []byte) *NSUUID {
return &NSUUID{
internal: uuid,
}
}
func (ns *NSUUID) archive(objects []interface{}) []interface{} {
info := map[string]interface{}{
"NS.uuidbytes": ns.internal,
}
objects = append(objects, info)
info["$class"] = plist.UID(len(objects))
cls := map[string]interface{}{
"$classname": "NSUUID",
"$classes": []interface{}{"NSUUID", "NSObject"},
}
objects = append(objects, cls)
return objects
}
func (ns *NSUUID) String() string {
buf := make([]byte, 36)
hex.Encode(buf[0:8], ns.internal[0:4])
buf[8] = '-'
hex.Encode(buf[9:13], ns.internal[4:6])
buf[13] = '-'
hex.Encode(buf[14:18], ns.internal[6:8])
buf[18] = '-'
hex.Encode(buf[19:23], ns.internal[8:10])
buf[23] = '-'
hex.Encode(buf[24:], ns.internal[10:])
return string(buf)
}

View File

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

View File

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

View File

@@ -0,0 +1,89 @@
package nskeyedarchiver
import (
"reflect"
"howett.net/plist"
)
type XCTestConfiguration struct {
internal map[string]interface{}
}
func newXCTestConfiguration(cfg interface{}) *XCTestConfiguration {
return cfg.(*XCTestConfiguration)
}
func NewXCTestConfiguration(nsuuid *NSUUID, nsurl *NSURL, targetBundleID, targetAppPath string) *XCTestConfiguration {
contents := map[string]interface{}{
"aggregateStatisticsBeforeCrash": map[string]interface{}{
"XCSuiteRecordsKey": map[string]interface{}{},
},
"automationFrameworkPath": "/Developer/Library/PrivateFrameworks/XCTAutomationSupport.framework",
"baselineFileRelativePath": nil,
"baselineFileURL": nil,
"defaultTestExecutionTimeAllowance": nil,
"disablePerformanceMetrics": false,
"emitOSLogs": false,
"formatVersion": 2,
"gatherLocalizableStringsData": false,
"initializeForUITesting": true,
"maximumTestExecutionTimeAllowance": nil,
"productModuleName": "WebDriverAgentRunner", // set to other value is also OK
"randomExecutionOrderingSeed": nil,
"reportActivities": true,
"reportResultsToIDE": true,
"systemAttachmentLifetime": 2,
"targetApplicationArguments": []interface{}{}, // maybe useless
"targetApplicationEnvironment": nil,
"targetApplicationPath": targetAppPath,
"testApplicationDependencies": map[string]interface{}{},
"testApplicationUserOverrides": nil,
"testBundleRelativePath": nil,
"testExecutionOrdering": 0,
"testTimeoutsEnabled": false,
"testsDrivenByIDE": false,
"testsMustRunOnMainThread": true,
"testsToRun": nil,
"testsToSkip": nil,
"treatMissingBaselinesAsFailures": false,
"userAttachmentLifetime": 1,
"testBundleURL": nsurl,
"sessionIdentifier": nsuuid,
"targetApplicationBundleID": targetBundleID,
// "targetApplicationBundleID": "",
}
return &XCTestConfiguration{internal: contents}
}
func (cfg *XCTestConfiguration) archive(objects []interface{}) []interface{} {
info := map[string]interface{}{}
objects = append(objects, info)
info["$class"] = plist.UID(len(objects))
cls := map[string]interface{}{
"$classname": "XCTestConfiguration",
"$classes": []interface{}{"XCTestConfiguration", "NSObject"},
}
objects = append(objects, cls)
for k, v := range cfg.internal {
val := reflect.ValueOf(v)
if !val.IsValid() {
info[k] = plist.UID(0)
continue
}
typ := val.Type()
if k != "formatVersion" && (typ.Kind() == reflect.Bool || typ.Kind() == reflect.Uintptr || typ.Kind() == reflect.Int) {
info[k] = v
} else {
var uid plist.UID
objects, uid = archive(objects, v)
info[k] = uid
}
}
return objects
}

View File

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