summaryrefslogtreecommitdiffstats
path: root/python-client
diff options
context:
space:
mode:
Diffstat (limited to 'python-client')
-rw-r--r--python-client/.gitignore3
-rw-r--r--python-client/tcprelay.py147
-rw-r--r--python-client/usbmux.py245
3 files changed, 395 insertions, 0 deletions
diff --git a/python-client/.gitignore b/python-client/.gitignore
new file mode 100644
index 0000000..5da7ef5
--- /dev/null
+++ b/python-client/.gitignore
@@ -0,0 +1,3 @@
1*.pyc
2*.pyo
3
diff --git a/python-client/tcprelay.py b/python-client/tcprelay.py
new file mode 100644
index 0000000..2874ae1
--- /dev/null
+++ b/python-client/tcprelay.py
@@ -0,0 +1,147 @@
1#!/usr/bin/python
2#
3# tcprelay.py - TCP connection relay for usbmuxd
4#
5# Copyright (C) 2009 Hector Martin "marcan" <hector@marcansoft.com>
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 2 or version 3.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
20import usbmux
21import SocketServer
22import select
23from optparse import OptionParser
24import sys
25import threading
26
27class SocketRelay(object):
28 def __init__(self, a, b, maxbuf=65535):
29 self.a = a
30 self.b = b
31 self.atob = ""
32 self.btoa = ""
33 self.maxbuf = maxbuf
34 def handle(self):
35 while True:
36 rlist = []
37 wlist = []
38 xlist = [self.a, self.b]
39 if self.atob:
40 wlist.append(self.b)
41 if self.btoa:
42 wlist.append(self.a)
43 if len(self.atob) < self.maxbuf:
44 rlist.append(self.a)
45 if len(self.btoa) < self.maxbuf:
46 rlist.append(self.b)
47 rlo, wlo, xlo = select.select(rlist, wlist, xlist)
48 if xlo:
49 return
50 if self.a in wlo:
51 n = self.a.send(self.btoa)
52 self.btoa = self.btoa[n:]
53 if self.b in wlo:
54 n = self.b.send(self.atob)
55 self.atob = self.atob[n:]
56 if self.a in rlo:
57 s = self.a.recv(self.maxbuf - len(self.atob))
58 if not s:
59 return
60 self.atob += s
61 if self.b in rlo:
62 s = self.b.recv(self.maxbuf - len(self.btoa))
63 if not s:
64 return
65 self.btoa += s
66 #print "Relay iter: %8d atob, %8d btoa, lists: %r %r %r"%(len(self.atob), len(self.btoa), rlo, wlo, xlo)
67
68class TCPRelay(SocketServer.BaseRequestHandler):
69 def handle(self):
70 print "Incoming connection to %d"%self.server.server_address[1]
71 mux = usbmux.USBMux(options.sockpath)
72 print "Waiting for devices..."
73 if not mux.devices:
74 mux.process(1.0)
75 if not mux.devices:
76 print "No device found"
77 self.request.close()
78 return
79 dev = mux.devices[0]
80 print "Connecting to device %s"%str(dev)
81 dsock = mux.connect(dev, self.server.rport)
82 lsock = self.request
83 print "Connection established, relaying data"
84 try:
85 fwd = SocketRelay(dsock, lsock, self.server.bufsize * 1024)
86 fwd.handle()
87 finally:
88 dsock.close()
89 lsock.close()
90 print "Connection closed"
91
92class TCPServer(SocketServer.TCPServer):
93 allow_reuse_address = True
94
95class ThreadedTCPServer(SocketServer.ThreadingMixIn, TCPServer):
96 pass
97
98HOST = "localhost"
99
100parser = OptionParser(usage="usage: %prog [OPTIONS] RemotePort[:LocalPort] [RemotePort[:LocalPort]]...")
101parser.add_option("-t", "--threaded", dest='threaded', action='store_true', default=False, help="use threading to handle multiple connections at once")
102parser.add_option("-b", "--bufsize", dest='bufsize', action='store', metavar='KILOBYTES', type='int', default=128, help="specify buffer size for socket forwarding")
103parser.add_option("-s", "--socket", dest='sockpath', action='store', metavar='PATH', type='str', default=None, help="specify the path of the usbmuxd socket")
104
105options, args = parser.parse_args()
106
107serverclass = TCPServer
108if options.threaded:
109 serverclass = ThreadedTCPServer
110
111if len(args) == 0:
112 parser.print_help()
113 sys.exit(1)
114
115ports = []
116
117for arg in args:
118 try:
119 if ':' in arg:
120 rport, lport = arg.split(":")
121 rport = int(rport)
122 lport = int(lport)
123 ports.append((rport, lport))
124 else:
125 ports.append((int(arg), int(arg)))
126 except:
127 parser.print_help()
128 sys.exit(1)
129
130servers=[]
131
132for rport, lport in ports:
133 print "Forwarding local port %d to remote port %d"%(lport, rport)
134 server = serverclass((HOST, lport), TCPRelay)
135 server.rport = rport
136 server.bufsize = options.bufsize
137 servers.append(server)
138
139alive = True
140
141while alive:
142 try:
143 rl, wl, xl = select.select(servers, [], [])
144 for server in rl:
145 server.handle_request()
146 except:
147 alive = False
diff --git a/python-client/usbmux.py b/python-client/usbmux.py
new file mode 100644
index 0000000..47112d3
--- /dev/null
+++ b/python-client/usbmux.py
@@ -0,0 +1,245 @@
1#!/usr/bin/python
2#
3# usbmux.py - usbmux client library for Python
4#
5# Copyright (C) 2009 Hector Martin "marcan" <hector@marcansoft.com>
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 2 or version 3.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
20import socket, struct, select, sys
21
22try:
23 import plistlib
24 haveplist = True
25except:
26 haveplist = False
27
28class MuxError(Exception):
29 pass
30
31class MuxVersionError(MuxError):
32 pass
33
34class SafeStreamSocket:
35 def __init__(self, address, family):
36 self.sock = socket.socket(family, socket.SOCK_STREAM)
37 self.sock.connect(address)
38 def send(self, msg):
39 totalsent = 0
40 while totalsent < len(msg):
41 sent = self.sock.send(msg[totalsent:])
42 if sent == 0:
43 raise MuxError("socket connection broken")
44 totalsent = totalsent + sent
45 def recv(self, size):
46 msg = ''
47 while len(msg) < size:
48 chunk = self.sock.recv(size-len(msg))
49 if chunk == '':
50 raise MuxError("socket connection broken")
51 msg = msg + chunk
52 return msg
53
54class MuxDevice(object):
55 def __init__(self, devid, usbprod, serial, location):
56 self.devid = devid
57 self.usbprod = usbprod
58 self.serial = serial
59 self.location = location
60 def __str__(self):
61 return "<MuxDevice: ID %d ProdID 0x%04x Serial '%s' Location 0x%x>"%(self.devid, self.usbprod, self.serial, self.location)
62
63class BinaryProtocol(object):
64 TYPE_RESULT = 1
65 TYPE_CONNECT = 2
66 TYPE_LISTEN = 3
67 TYPE_DEVICE_ADD = 4
68 TYPE_DEVICE_REMOVE = 5
69 VERSION = 0
70 def __init__(self, socket):
71 self.socket = socket
72 self.connected = False
73
74 def _pack(self, req, payload):
75 if req == self.TYPE_CONNECT:
76 return struct.pack("IH", payload['DeviceID'], payload['PortNumber']) + "\x00\x00"
77 elif req == self.TYPE_LISTEN:
78 return ""
79 else:
80 raise ValueError("Invalid outgoing request type %d"%req)
81
82 def _unpack(self, resp, payload):
83 if resp == self.TYPE_RESULT:
84 return {'Number':struct.unpack("I", payload)[0]}
85 elif resp == self.TYPE_DEVICE_ADD:
86 devid, usbpid, serial, pad, location = struct.unpack("IH256sHI", payload)
87 serial = serial.split("\0")[0]
88 return {'DeviceID': devid, 'Properties': {'LocationID': location, 'SerialNumber': serial, 'ProductID': usbpid}}
89 elif resp == self.TYPE_DEVICE_REMOVE:
90 devid = struct.unpack("I", payload)[0]
91 return {'DeviceID': devid}
92 else:
93 raise MuxError("Invalid incoming request type %d"%req)
94
95 def sendpacket(self, req, tag, payload={}):
96 payload = self._pack(req, payload)
97 if self.connected:
98 raise MuxError("Mux is connected, cannot issue control packets")
99 length = 16 + len(payload)
100 data = struct.pack("IIII", length, self.VERSION, req, tag) + payload
101 self.socket.send(data)
102 def getpacket(self):
103 if self.connected:
104 raise MuxError("Mux is connected, cannot issue control packets")
105 dlen = self.socket.recv(4)
106 dlen = struct.unpack("I", dlen)[0]
107 body = self.socket.recv(dlen - 4)
108 version, resp, tag = struct.unpack("III",body[:0xc])
109 if version != self.VERSION:
110 raise MuxVersionError("Version mismatch: expected %d, got %d"%(self.VERSION,version))
111 payload = self._unpack(resp, body[0xc:])
112 return (resp, tag, payload)
113
114class PlistProtocol(BinaryProtocol):
115 TYPE_RESULT = "Result"
116 TYPE_CONNECT = "Connect"
117 TYPE_LISTEN = "Listen"
118 TYPE_DEVICE_ADD = "Attached"
119 TYPE_DEVICE_REMOVE = "Detached" #???
120 TYPE_PLIST = 8
121 VERSION = 1
122 def __init__(self, socket):
123 if not haveplist:
124 raise Exception("You need the plistlib module")
125 BinaryProtocol.__init__(self, socket)
126
127 def _pack(self, req, payload):
128 return payload
129
130 def _unpack(self, resp, payload):
131 return payload
132
133 def sendpacket(self, req, tag, payload={}):
134 payload['ClientVersionString'] = 'usbmux.py by marcan'
135 if isinstance(req, int):
136 req = [self.TYPE_CONNECT, self.TYPE_LISTEN][req-2]
137 payload['MessageType'] = req
138 payload['ProgName'] = 'tcprelay'
139 BinaryProtocol.sendpacket(self, self.TYPE_PLIST, tag, plistlib.writePlistToString(payload))
140 def getpacket(self):
141 resp, tag, payload = BinaryProtocol.getpacket(self)
142 if resp != self.TYPE_PLIST:
143 raise MuxError("Received non-plist type %d"%resp)
144 payload = plistlib.readPlistFromString(payload)
145 return payload['MessageType'], tag, payload
146
147class MuxConnection(object):
148 def __init__(self, socketpath, protoclass):
149 self.socketpath = socketpath
150 if sys.platform in ['win32', 'cygwin']:
151 family = socket.AF_INET
152 address = ('127.0.0.1', 27015)
153 else:
154 family = socket.AF_UNIX
155 address = self.socketpath
156 self.socket = SafeStreamSocket(address, family)
157 self.proto = protoclass(self.socket)
158 self.pkttag = 1
159 self.devices = []
160
161 def _getreply(self):
162 while True:
163 resp, tag, data = self.proto.getpacket()
164 if resp == self.proto.TYPE_RESULT:
165 return tag, data
166 else:
167 raise MuxError("Invalid packet type received: %d"%resp)
168 def _processpacket(self):
169 resp, tag, data = self.proto.getpacket()
170 if resp == self.proto.TYPE_DEVICE_ADD:
171 self.devices.append(MuxDevice(data['DeviceID'], data['Properties']['ProductID'], data['Properties']['SerialNumber'], data['Properties']['LocationID']))
172 elif resp == self.proto.TYPE_DEVICE_REMOVE:
173 for dev in self.devices:
174 if dev.devid == data['DeviceID']:
175 self.devices.remove(dev)
176 elif resp == self.proto.TYPE_RESULT:
177 raise MuxError("Unexpected result: %d"%resp)
178 else:
179 raise MuxError("Invalid packet type received: %d"%resp)
180 def _exchange(self, req, payload={}):
181 mytag = self.pkttag
182 self.pkttag += 1
183 self.proto.sendpacket(req, mytag, payload)
184 recvtag, data = self._getreply()
185 if recvtag != mytag:
186 raise MuxError("Reply tag mismatch: expected %d, got %d"%(mytag, recvtag))
187 return data['Number']
188
189 def listen(self):
190 ret = self._exchange(self.proto.TYPE_LISTEN)
191 if ret != 0:
192 raise MuxError("Listen failed: error %d"%ret)
193 def process(self, timeout=None):
194 if self.proto.connected:
195 raise MuxError("Socket is connected, cannot process listener events")
196 rlo, wlo, xlo = select.select([self.socket.sock], [], [self.socket.sock], timeout)
197 if xlo:
198 self.socket.sock.close()
199 raise MuxError("Exception in listener socket")
200 if rlo:
201 self._processpacket()
202 def connect(self, device, port):
203 ret = self._exchange(self.proto.TYPE_CONNECT, {'DeviceID':device.devid, 'PortNumber':((port<<8) & 0xFF00) | (port>>8)})
204 if ret != 0:
205 raise MuxError("Connect failed: error %d"%ret)
206 self.proto.connected = True
207 return self.socket.sock
208 def close(self):
209 self.socket.sock.close()
210
211class USBMux(object):
212 def __init__(self, socketpath=None):
213 if socketpath is None:
214 if sys.platform == 'darwin':
215 socketpath = "/var/run/usbmuxd"
216 else:
217 socketpath = "/var/run/usbmuxd"
218 self.socketpath = socketpath
219 self.listener = MuxConnection(socketpath, BinaryProtocol)
220 try:
221 self.listener.listen()
222 self.version = 0
223 self.protoclass = BinaryProtocol
224 except MuxVersionError:
225 self.listener = MuxConnection(socketpath, PlistProtocol)
226 self.listener.listen()
227 self.protoclass = PlistProtocol
228 self.version = 1
229 self.devices = self.listener.devices
230 def process(self, timeout=None):
231 self.listener.process(timeout)
232 def connect(self, device, port):
233 connector = MuxConnection(self.socketpath, self.protoclass)
234 return connector.connect(device, port)
235
236if __name__ == "__main__":
237 mux = USBMux()
238 print "Waiting for devices..."
239 if not mux.devices:
240 mux.process(0.1)
241 while True:
242 print "Devices:"
243 for dev in mux.devices:
244 print dev
245 mux.process()