dtx

package
v1.0.141 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 20, 2024 License: MIT Imports: 16 Imported by: 4

Documentation

Index

Constants

View Source
const (
	// DtxMessageMagic 4byte signature of every Message
	DtxMessageMagic uint32 = 0x795B3D1F
	// DtxMessageHeaderLength alwys 32 byte
	DtxMessageHeaderLength uint32 = 32
	// DtxMessagePayloadHeaderLength always 16 bytes
	DtxMessagePayloadHeaderLength uint32 = 16
	// DtxReservedBits are always 0x0
	DtxReservedBits uint32 = 0x0
)

Variables

View Source
var ErrConnectionClosed = errors.New("Connection closed")

Functions

func BuildAckMessage

func BuildAckMessage(msg Message) []byte

BuildAckMessage creates a 32+ 16 byte long message that can be used as a response for a message the had the ExpectsReply flag set.

func Encode

func Encode(
	Identifier int,
	ConversationIndex int,
	ChannelCode int,
	ExpectsReply bool,
	MessageType MessageType,
	payloadBytes []byte,
	Auxiliary PrimitiveDictionary,
) ([]byte, error)

Encode encodes the given parameters to a DtxMessage that can be sent to the device.

func IsIncomplete

func IsIncomplete(err error) bool

IsIncomplete returns true if the DtxMessage was incomplete

func IsOutOfSync

func IsOutOfSync(err error) bool

IsOutOfSync returns true if err is an OutOfSync error

func NewIncomplete

func NewIncomplete(message string) error

NewIncomplete when the Message was not complete

func NewOutOfSync

func NewOutOfSync(message string) error

NewOutOfSync should be used when the MagicBytes are wrong

func SendAckIfNeeded

func SendAckIfNeeded(dtxConn *Connection, msg Message)

Types

type AuxiliaryEncoder

type AuxiliaryEncoder struct {
	// contains filtered or unexported fields
}

func (*AuxiliaryEncoder) AddNsKeyedArchivedObject

func (a *AuxiliaryEncoder) AddNsKeyedArchivedObject(object interface{})

func (*AuxiliaryEncoder) GetBytes

func (a *AuxiliaryEncoder) GetBytes() []byte

type AuxiliaryHeader

type AuxiliaryHeader struct {
	BufferSize    uint32
	Unknown       uint32
	AuxiliarySize uint32
	Unknown2      uint32
}

The AuxiliaryHeader can actually be completely ignored. We do not need to care about the buffer size And we already know the AuxiliarySize. The other two ints seem to be always 0 anyway. Could also be that Buffer and Aux Size are Uint64

func (AuxiliaryHeader) String

func (a AuxiliaryHeader) String() string

type Channel

type Channel struct {
	// contains filtered or unexported fields
}

func (*Channel) AddResponseWaiter

func (d *Channel) AddResponseWaiter(identifier int, channel chan Message)

func (*Channel) Dispatch

func (d *Channel) Dispatch(msg Message)

func (*Channel) MethodCall

func (d *Channel) MethodCall(selector string, args ...interface{}) (Message, error)

MethodCall is the standard DTX style remote method invocation pattern. The ObjectiveC Selector goes as a NSKeyedArchiver.archived NSString into the DTXMessage payload, and the arguments are separately NSKeyArchiver.archived and put into the Auxiliary DTXPrimitiveDictionary. It returns the response message and an error.

func (*Channel) MethodCallAsync

func (d *Channel) MethodCallAsync(selector string, args ...interface{}) error

func (*Channel) ReceiveMethodCall

func (d *Channel) ReceiveMethodCall(selector string) Message

func (*Channel) RegisterMethodForRemote

func (d *Channel) RegisterMethodForRemote(selector string)

func (*Channel) Send

func (d *Channel) Send(expectsReply bool, messageType MessageType, payloadBytes []byte, auxiliary PrimitiveDictionary) error

func (*Channel) SendAndAwaitReply

func (d *Channel) SendAndAwaitReply(expectsReply bool, messageType MessageType, payloadBytes []byte, auxiliary PrimitiveDictionary) (Message, error)

type ChannelOption added in v1.0.24

type ChannelOption func(*Channel)

ChannelOption for configuring settings on dtx.Channels

func WithTimeout added in v1.0.24

func WithTimeout(seconds uint32) ChannelOption

WithTimeout adds a custom timeout in seconds to the channel. Some longer running synchronous operations need that.

type Connection

type Connection struct {
	// contains filtered or unexported fields
}

Connection manages channels, including the GlobalChannel, for a DtxConnection and dispatches received messages to the right channel.

func NewTunnelConnection added in v1.0.122

func NewTunnelConnection(device ios.DeviceEntry, serviceName string) (*Connection, error)

NewTunnelConnection connects and starts reading from a Dtx based service on the device, using tunnel interface instead of usbmuxd

func NewUsbmuxdConnection added in v1.0.122

func NewUsbmuxdConnection(device ios.DeviceEntry, serviceName string) (*Connection, error)

NewUsbmuxdConnection connects and starts reading from a Dtx based service on the device

func (*Connection) AddDefaultChannelReceiver added in v1.0.77

func (dtxConn *Connection) AddDefaultChannelReceiver(messageDispatcher Dispatcher) *Channel

AddDefaultChannelReceiver let's you set the Dispatcher for the Channel with code -1 ( or 4294967295 for uint32). I am just calling it the "default" channel now, without actually figuring out what it is for exactly from disassembled code. If someone wants to do that and bring some clarity, please go ahead :-) This channel seems to always be there without explicitly requesting it and sometimes it is used.

func (*Connection) Close

func (dtxConn *Connection) Close() error

Close closes the underlying deviceConnection

func (*Connection) Closed added in v1.0.122

func (dtxConn *Connection) Closed() <-chan struct{}

Closed is closed when the underlying DTX connection was closed for any reason (either initiated by calling Close() or due to an error)

func (*Connection) Err added in v1.0.122

func (dtxConn *Connection) Err() error

Err is non-nil when the connection was closed (when Close was called this will be ErrConnectionClosed)

func (*Connection) ForChannelRequest

func (dtxConn *Connection) ForChannelRequest(messageDispatcher Dispatcher) *Channel

func (*Connection) GlobalChannel

func (dtxConn *Connection) GlobalChannel() *Channel

GlobalChannel returns the connections automatically created global channel.

func (*Connection) RequestChannelIdentifier

func (dtxConn *Connection) RequestChannelIdentifier(identifier string, messageDispatcher Dispatcher, opts ...ChannelOption) *Channel

RequestChannelIdentifier requests a channel to be opened on the Connection with the given identifier, an automatically assigned channelCode and a Dispatcher for receiving messages.

func (*Connection) Send

func (dtxConn *Connection) Send(message []byte) error

Send sends the byte slice directly to the device using the underlying DeviceConnectionInterface

type Dispatcher

type Dispatcher interface {
	Dispatch(msg Message)
}

Dispatcher is a simple interface containing a Dispatch func to receive dtx.Messages

func NewGlobalDispatcher

func NewGlobalDispatcher(requestChannelMessages chan Message, dtxConnection *Connection) Dispatcher

NewGlobalDispatcher create a Dispatcher for the GlobalChannel

type FragmentDecoder

type FragmentDecoder struct {
	// contains filtered or unexported fields
}

FragmentDecoder collects DtxMessage fragments and merges them into a single DtxMessage when they are complete. This makes it a little easier and it works perfectly fine with Xcode. I don't get the point in fragmenting USB messages anyway.. DTX Fragment logic: 1. Fragment is only 32 bytes long, fragment index ==0, fragment length>1 following fragments contain the 32 bytes dtx header, then immediately the fragment data once the last fragment is received (index == length-1), we can: 1. merge all the fragment data 2. prepend the dtx header of the first message 3. set fragment length to 1 and index to 0, and we have a defragmented single message that Xcode will be able to use just the same as the fragmented one :-)

func NewFragmentDecoder

func NewFragmentDecoder(firstFragment Message) *FragmentDecoder

NewFragmentDecoder creates a new decoder with the first fragment

func (*FragmentDecoder) AddFragment

func (f *FragmentDecoder) AddFragment(fragment Message) bool

AddFragment adds fragments if they match the firstFragment this FragmentDecoder was created with. It returns true if the fragment was added and fals if the fragment was not matching this decoder's first fragment.

func (FragmentDecoder) Extract

func (f FragmentDecoder) Extract() []byte

Extract can be used to get an assembled DtxMessage from all the fragments. Never call this befor HasFinished is true.

func (FragmentDecoder) HasFinished

func (f FragmentDecoder) HasFinished() bool

HasFinished can be used to check if all fragments have been added

type GlobalDispatcher

type GlobalDispatcher struct {
	// contains filtered or unexported fields
}

GlobalDispatcher the message dispatcher for the automatically created global Channel

func (GlobalDispatcher) Dispatch

func (g GlobalDispatcher) Dispatch(msg Message)

Dispatch prints log messages and errors when they are received and also creates local Channels when requested by the device.

type Message

type Message struct {
	Fragments         uint16
	FragmentIndex     uint16
	MessageLength     int
	Identifier        int
	ConversationIndex int
	ChannelCode       int
	ExpectsReply      bool
	PayloadHeader     PayloadHeader
	Payload           []interface{}
	AuxiliaryHeader   AuxiliaryHeader
	Auxiliary         PrimitiveDictionary
	RawBytes          []byte
	// contains filtered or unexported fields
}

Message contains a decoded DtxMessage aka the most overcomplicated RPC protocol this planet has ever seen :-D

DTXMessages consist of a 32byte header that always starts with the DtxMessageMagic It is followed by the 16 bytes PayloadHeader. If there is an Auxiliary:

Next is the 16 byte AuxiliaryHeader followed by the DtxPrimitiveDictionary containing auxiliary data
Directly after the Auxiliary, the payload bytes are following.

If there is no Auxiliary:
The payload bytes follow directly after the PayloadHeader

There is also support for fragmenting DTX messages into multiple messages, see fragmentdecoder.go for details
how that works

func DecodeNonBlocking

func DecodeNonBlocking(messageBytes []byte) (Message, []byte, error)

DecodeNonBlocking should only be used for the debug proxy to on the fly decode DtxMessages. It is used because if the Decoder encounters an error, we can still keep reading and forwarding the raw bytes. This ensures that the debug proxy keeps working and the byte dump can be used to fix the DtxDecoder

func ReadMessage

func ReadMessage(reader io.Reader) (Message, error)

ReadMessage uses the reader to fully read a Message from it in blocking mode.

func (Message) HasAuxiliary

func (d Message) HasAuxiliary() bool

HasAuxiliary returns PayloadHeader.AuxiliaryLength > 0

func (Message) HasError

func (d Message) HasError() bool

HasError returns true when the MessageType in this message's PayloadHeader equals 0x4 and false otherwise.

func (Message) HasPayload

func (d Message) HasPayload() bool

HasPayload returns PayloadLength() > 0, it is true if the Message has payload bytes

func (Message) IsFirstFragment

func (d Message) IsFirstFragment() bool

IsFirstFragment returns true if the message is the first of a series of fragments.IsFirstFragment The first fragment message is only 32 bytes long

func (Message) IsFragment

func (d Message) IsFragment() bool

IsFragment returns true if the Message is a fragment

func (Message) IsLastFragment

func (d Message) IsLastFragment() bool

IsLastFragment returns true if this message is the last fragment

func (Message) MessageIsFirstFragmentFor

func (d Message) MessageIsFirstFragmentFor(otherMessage Message) bool

MessageIsFirstFragmentFor indicates whether the message you call this on, is the first part of a fragmented message, and if otherMessage is a subsequent fragment

func (Message) PayloadLength

func (d Message) PayloadLength() uint32

PayloadLength equals PayloadHeader.TotalPayloadLength - d.PayloadHeader.AuxiliaryLength so it is the Payload without the Auxiliary

func (Message) String

func (d Message) String() string

func (Message) StringDebug

func (d Message) StringDebug() string

StringDebug prints the Message and its Payload/Auxiliary

type MessageType added in v1.0.122

type MessageType uint32
const (
	// Ack is the messagetype for a 16 byte long acknowleding DtxMessage.
	Ack MessageType = 0x0
	// Unknown
	UnknownTypeOne MessageType = 0x1
	// Methodinvocation is the messagetype for a remote procedure call style DtxMessage.
	Methodinvocation MessageType = 0x2
	// ResponseWithReturnValueInPayload is the response for a method call that has a return value
	ResponseWithReturnValueInPayload MessageType = 0x3
	// DtxTypeError is the messagetype for a DtxMessage containing an error
	DtxTypeError         MessageType = 0x4
	LZ4CompressedMessage MessageType = 0x0707
)

All the known MessageTypes

func (MessageType) String added in v1.0.122

func (m MessageType) String() string

type MethodWithResponse

type MethodWithResponse func(msg Message) (interface{}, error)

type PayloadHeader

type PayloadHeader struct {
	MessageType        MessageType
	AuxiliaryLength    uint32
	TotalPayloadLength uint32
	Flags              uint32
}

PayloadHeader contains the message type and Payload length

func (PayloadHeader) String added in v1.0.122

func (p PayloadHeader) String() string

type PrimitiveDictionary

type PrimitiveDictionary struct {
	// contains filtered or unexported fields
}

PrimitiveDictionary contains a custom dictionary type only used for DTX. In practice however, the keys of all dictionaries are always null and the values are used as a simple array containing the method arguments for the method this message is invoking. (The payload object usually contains method names or returnvalues)

func DecodeAuxiliary added in v1.0.109

func DecodeAuxiliary(auxBytes []byte) PrimitiveDictionary

func NewPrimitiveDictionary

func NewPrimitiveDictionary() PrimitiveDictionary

func (*PrimitiveDictionary) AddBytes

func (d *PrimitiveDictionary) AddBytes(value []byte)

func (*PrimitiveDictionary) AddInt32

func (d *PrimitiveDictionary) AddInt32(value int)

func (*PrimitiveDictionary) AddNsKeyedArchivedObject

func (d *PrimitiveDictionary) AddNsKeyedArchivedObject(object interface{})

AddNsKeyedArchivedObject wraps the object in a NSKeyedArchiver envelope before saving it to the dictionary as a []byte. This will panic on error because NSKeyedArchiver has to support everything that is put in here during runtime. If not, it is a non-recoverable bug and needs to be fixed anyway.

func (PrimitiveDictionary) GetArguments

func (d PrimitiveDictionary) GetArguments() []interface{}

func (PrimitiveDictionary) String

func (d PrimitiveDictionary) String() string

func (PrimitiveDictionary) ToBytes

func (d PrimitiveDictionary) ToBytes() ([]byte, error)

ToBytes serializes this PrimitiveDictionary to a byte slice

type PrimitiveKeyValuePair

type PrimitiveKeyValuePair struct {
	// contains filtered or unexported fields
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL