summaryrefslogtreecommitdiffstats
path: root/python-client/usbmux.py
diff options
context:
space:
mode:
Diffstat (limited to 'python-client/usbmux.py')
-rw-r--r--python-client/usbmux.py245
1 files changed, 245 insertions, 0 deletions
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()