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