diff options
Diffstat (limited to 'python-client')
| -rw-r--r-- | python-client/.gitignore | 3 | ||||
| -rw-r--r-- | python-client/tcprelay.py | 148 | ||||
| -rw-r--r-- | python-client/usbmux.py | 246 |
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 | |||
| 21 | import usbmux | ||
| 22 | import SocketServer | ||
| 23 | import select | ||
| 24 | from optparse import OptionParser | ||
| 25 | import sys | ||
| 26 | import threading | ||
| 27 | |||
| 28 | class 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 | |||
| 69 | class 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 | |||
| 93 | class TCPServer(SocketServer.TCPServer): | ||
| 94 | allow_reuse_address = True | ||
| 95 | |||
| 96 | class ThreadedTCPServer(SocketServer.ThreadingMixIn, TCPServer): | ||
| 97 | pass | ||
| 98 | |||
| 99 | HOST = "localhost" | ||
| 100 | |||
| 101 | parser = OptionParser(usage="usage: %prog [OPTIONS] RemotePort[:LocalPort] [RemotePort[:LocalPort]]...") | ||
| 102 | parser.add_option("-t", "--threaded", dest='threaded', action='store_true', default=False, help="use threading to handle multiple connections at once") | ||
| 103 | parser.add_option("-b", "--bufsize", dest='bufsize', action='store', metavar='KILOBYTES', type='int', default=128, help="specify buffer size for socket forwarding") | ||
| 104 | parser.add_option("-s", "--socket", dest='sockpath', action='store', metavar='PATH', type='str', default=None, help="specify the path of the usbmuxd socket") | ||
| 105 | |||
| 106 | options, args = parser.parse_args() | ||
| 107 | |||
| 108 | serverclass = TCPServer | ||
| 109 | if options.threaded: | ||
| 110 | serverclass = ThreadedTCPServer | ||
| 111 | |||
| 112 | if len(args) == 0: | ||
| 113 | parser.print_help() | ||
| 114 | sys.exit(1) | ||
| 115 | |||
| 116 | ports = [] | ||
| 117 | |||
| 118 | for 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 | |||
| 131 | servers=[] | ||
| 132 | |||
| 133 | for 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 | |||
| 140 | alive = True | ||
| 141 | |||
| 142 | while 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 | |||
| 21 | import socket, struct, select, sys | ||
| 22 | |||
| 23 | try: | ||
| 24 | import plistlib | ||
| 25 | haveplist = True | ||
| 26 | except: | ||
| 27 | haveplist = False | ||
| 28 | |||
| 29 | class MuxError(Exception): | ||
| 30 | pass | ||
| 31 | |||
| 32 | class MuxVersionError(MuxError): | ||
| 33 | pass | ||
| 34 | |||
| 35 | class 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 | |||
| 55 | class 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 | |||
| 64 | class 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 | |||
| 115 | class 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 | |||
| 148 | class 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 | |||
| 212 | class 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 | |||
| 237 | if __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() | ||
