usbmux 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm --create ruby-1.9.3-p362@usbmuxd
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in usbmuxd.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Jayme Deffenbaugh
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # Usbmux
2
+
3
+ The Usbmux gem is a ruby port of the python script, usbmux.py, by Hector Martin
4
+ "marcan" <hector@marcansoft.com>. All credit goes to him and the work that the
5
+ libusbmuxd guys have done. This project was more for my personal benefit and
6
+ curiosity.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'usbmux'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install usbmux
21
+
22
+ ## Usage
23
+
24
+ TODO: Write usage instructions here
25
+
26
+ ## Contributing
27
+
28
+ 1. Fork it
29
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
30
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
31
+ 4. Push to the branch (`git push origin my-new-feature`)
32
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'usbmux'
4
+
5
+ mux = Usbmux::USBMux.new
6
+ puts "Waiting for devices..."
7
+ if !mux.devices
8
+ mux.process(0.1)
9
+ end
10
+ loop do
11
+ puts "Devices: "
12
+ mux.devices.each do |device|
13
+ puts device
14
+ end
15
+ mux.process()
16
+ end
@@ -0,0 +1,333 @@
1
+ require 'rbconfig'
2
+ require 'cfpropertylist'
3
+ require 'socket'
4
+
5
+ module Usbmux
6
+ class MuxError < RuntimeError; end
7
+ class MuxVersionError < MuxError; end
8
+
9
+ class SafeStreamSocket
10
+ attr_accessor :sock
11
+
12
+ def initialize(family, address)
13
+ @sock = Socket.new(family, Socket::SOCK_STREAM)
14
+ @sock.connect(address)
15
+ end
16
+
17
+ def send(message)
18
+ total_sent = 0
19
+ while total_sent < message.length
20
+ sent = @sock.send(message[total_sent..-1], 0)
21
+ if sent == 0
22
+ raise MuxError.new('Socket connection broken')
23
+ end
24
+ total_sent += sent
25
+ end
26
+ end
27
+
28
+ def receive(size)
29
+ message = ''
30
+ while message.length < size
31
+ chunk = @sock.recv(size - message.length)
32
+ if chunk.empty?
33
+ raise MuxError.new('Socket connection broken')
34
+ end
35
+ message = message + chunk
36
+ end
37
+ message
38
+ end
39
+
40
+ def close
41
+ @sock.close
42
+ end
43
+ end
44
+
45
+ class MuxDevice
46
+ attr_accessor :id, :usbprod, :serial, :location
47
+
48
+ def initialize(id, usbprod, serial, location)
49
+ @id = id
50
+ @usbprod = usbprod
51
+ @serial = serial
52
+ @location = location
53
+ end
54
+
55
+ def to_s
56
+ "<MuxDevice: ID #{@id} ProdID 0x#{@usbprod.to_s.rjust(4, '0')} Serial '#{@serial}' Location 0x#{@usbprod.to_s.rjust(4, '0')}>"
57
+ end
58
+ end
59
+
60
+ class BinaryProtocol
61
+ TYPE_RESULT = 1
62
+ TYPE_CONNECT = 2
63
+ TYPE_LISTEN = 3
64
+ TYPE_DEVICE_ADD = 4
65
+ TYPE_DEVICE_REMOVE = 5
66
+ VERSION = 0
67
+
68
+ attr_accessor :socket, :connected
69
+
70
+ def initialize(socket)
71
+ @socket = socket
72
+ @connected = false
73
+ end
74
+
75
+ def send_packet(request, tag, payload = {})
76
+ payload = _pack(request, payload)
77
+ if connected?
78
+ raise MuxError.new('Mux is connected, cannot issue control packets')
79
+ end
80
+ length = 16 + payload.length
81
+ data = [length, self.class::VERSION, request, tag].pack('IIII') + payload
82
+ @socket.send(data)
83
+ end
84
+
85
+ def get_packet
86
+ if connected?
87
+ raise MuxError.new('Mux is connected, cannot issue control packets')
88
+ end
89
+ dlen = @socket.receive(4)
90
+ dlen = dlen.unpack('I').first
91
+ body = @socket.receive(dlen - 4)
92
+ version, response, tag = body[0..0xc].unpack('III')
93
+ if version != self.class::VERSION
94
+ raise MuxVersionError.new("Version mismatch: expected #{self.class::VERSION}, got #{version}")
95
+ end
96
+ payload = _unpack(response, body[0xc..-1])
97
+
98
+ [response, tag, payload]
99
+ end
100
+
101
+ def connected?
102
+ @connected
103
+ end
104
+
105
+ def _pack(request, payload)
106
+ if request == TYPE_CONNECT
107
+ [payload['DeviceID'], payload['PortNumber']].pack('IS_') + '\x00\x00'
108
+ elsif request == TYPE_LISTEN
109
+ ''
110
+ else
111
+ raise ArgumentError.new("Invalid outgoing request type #{request}")
112
+ end
113
+ end
114
+ private :_pack
115
+
116
+ def _unpack(response, payload)
117
+ if response == TYPE_RESULT
118
+ { 'Number' => payload[0].unpack('I') }
119
+ elsif response == TYPE_DEVICE_ADD
120
+ id, usbpid, serial, pad, location = payload.unpack('IS_b256S_I')
121
+ serial = serial.split("\0")[0]
122
+ {
123
+ 'DeviceID' => id,
124
+ 'Properties' =>
125
+ {
126
+ 'LocationID' => location,
127
+ 'SerialNumber' => serial,
128
+ 'ProductID' => usbpid
129
+ }
130
+ }
131
+ elsif response == TYPE_DEVICE_REMOVE
132
+ { 'DeviceID' => payload.unpack('I').first }
133
+ else
134
+ raise MuxError.new("Invalid incoming request type #{request}")
135
+ end
136
+ end
137
+ private :_unpack
138
+
139
+ end
140
+
141
+ class PlistProtocol < BinaryProtocol
142
+ TYPE_RESULT = 'Result'
143
+ TYPE_CONNECT = 'Connect'
144
+ TYPE_LISTEN = 'Listen'
145
+ TYPE_DEVICE_ADD = 'Attached'
146
+ TYPE_DEVICE_REMOVE = 'Detached'
147
+ TYPE_PLIST = 8
148
+ VERSION = 1
149
+
150
+ def send_packet(request, tag, payload = {})
151
+ payload['ClientVersionString'] = 'usbmux.py by marcan'
152
+ if request.is_a? Integer
153
+ request = [TYPE_CONNECT, TYPE_LISTEN][request - 2]
154
+ end
155
+ payload['MessageType'] = request
156
+ payload['ProgName'] = 'tcprelay'
157
+ plist = CFPropertyList::List.new
158
+ plist.value = CFPropertyList.guess(payload)
159
+ xml = plist.to_str(CFPropertyList::List::FORMAT_XML, :formatted => true)
160
+
161
+ super(TYPE_PLIST, tag, xml)
162
+ end
163
+
164
+ def get_packet
165
+ response, tag, payload = super()
166
+ if response != TYPE_PLIST
167
+ raise MuxError.new("Received non-plist type #{response}")
168
+ end
169
+
170
+ [payload['MessageType'], tag, payload]
171
+ end
172
+
173
+ def _pack(request, payload)
174
+ payload + "\n"
175
+ end
176
+
177
+ def _unpack(response, payload)
178
+ payload = CFPropertyList::List.new(:data => payload).value.value
179
+ response = payload['MessageType'].value
180
+ if response == TYPE_RESULT
181
+ { 'MessageType' => response, 'Number' => payload['Number'].value }
182
+ elsif response == TYPE_DEVICE_ADD
183
+ properties = payload['Properties'].value
184
+ {
185
+ 'MessageType' => response,
186
+ 'DeviceID' => payload['DeviceID'].value,
187
+ 'Properties' =>
188
+ {
189
+ 'LocationID' => properties['LocationID'].value,
190
+ 'SerialNumber' => properties['SerialNumber'].value,
191
+ 'ProductID' => properties['ProductID'].value
192
+ }
193
+ }
194
+ elsif response == TYPE_DEVICE_REMOVE
195
+ { 'MessageType' => response, 'DeviceID' => payload['DeviceID'].value }
196
+ else
197
+ raise MuxError.new("Invalid incoming response type #{response}")
198
+ end
199
+ end
200
+ end
201
+
202
+ class MuxConnection
203
+ attr_accessor :socket_path, :socket, :protocol, :pkttag, :devices
204
+ def initialize(socket_path, protocol_class)
205
+ @socket_path = socket_path
206
+ if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
207
+ family = Socket::PF_INET
208
+ address = Addrinfo.tcp('127.0.0.1', 27015)
209
+ else
210
+ family = Socket::PF_UNIX
211
+ address = Addrinfo.unix('/var/run/usbmuxd')
212
+ end
213
+ @socket = SafeStreamSocket.new(family, address)
214
+ @protocol = protocol_class.new(@socket)
215
+ @pkttag = 1
216
+ @devices = []
217
+ end
218
+
219
+ def listen
220
+ ret = _exchange(@protocol.class::TYPE_LISTEN)
221
+ if ret != 0
222
+ raise MuxError.new("Listen failed: error #{ret}")
223
+ end
224
+ end
225
+
226
+ def process(timeout = nil)
227
+ if @protocol.connected?
228
+ raise MuxError.new('Socket is connected, cannot process listener events')
229
+ end
230
+ rlo, wlo, xlo = IO.select([@socket.sock], [], [@socket.sock], timeout)
231
+ if xlo.length > 0
232
+ @socket.close
233
+ raise MuxError.new("Exception in listener socket")
234
+ elsif rlo.length > 0
235
+ _process_packet
236
+ end
237
+ end
238
+
239
+ def connect(device, port)
240
+ payload = {
241
+ 'DeviceID' => device.id,
242
+ 'PortNumber' => (( port << 8) & 0xFF00) | (port >> 8)
243
+ }
244
+ ret = _exchange(@protocol.class::TYPE_CONNECT, payload)
245
+ if ret != 0
246
+ raise MuxError.new("Connect failed: error #{ret}")
247
+ end
248
+ @protocol.connected = true
249
+ @socket.sock
250
+ end
251
+
252
+ def close
253
+ @socket.close
254
+ end
255
+
256
+ def _get_reply
257
+ response, tag, data = @protocol.get_packet
258
+ if response == @protocol.class::TYPE_RESULT
259
+ [tag, data]
260
+ else
261
+ raise MuxError.new("Invalid packet type received: #{response}")
262
+ end
263
+ end
264
+ private :_get_reply
265
+
266
+ def _process_packet
267
+ response, tag, data = @protocol.get_packet
268
+ if response == @protocol.class::TYPE_DEVICE_ADD
269
+ device_id = data['DeviceID']
270
+ product_id = data['Properties']['ProductID']
271
+ udid = data['Properties']['SerialNumber']
272
+ location_id = data['Properties']['LocationID']
273
+ @devices << MuxDevice.new(device_id, product_id, udid, location_id)
274
+ elsif response == @protocol.class::TYPE_DEVICE_REMOVE
275
+ @devices.delete_if do |device|
276
+ device.id == data['DeviceID']
277
+ end
278
+ elsif response == @protocol.class::TYPE_RESULT
279
+ raise MuxError.new("Unexpected result: #{response}")
280
+ else
281
+ raise MuxError.new("Invalid packet type received: #{response}")
282
+ end
283
+ end
284
+ private :_process_packet
285
+
286
+ def _exchange(request, payload = {})
287
+ my_tag = @pkttag
288
+ @pkttag += 1
289
+ @protocol.send_packet(request, my_tag, payload)
290
+ receive_tag, data = _get_reply
291
+ if receive_tag != my_tag
292
+ raise MuxError.new("Reply tag mismatch: expected #{my_tag}, got #{receive_tag}")
293
+ end
294
+ data['Number']
295
+ end
296
+ private :_exchange
297
+ end
298
+
299
+ class USBMux
300
+ attr_accessor :socket_path, :listener, :version, :protocol_class, :devices
301
+ def initialize(socket_path = nil)
302
+ if socket_path.nil?
303
+ if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
304
+ socket_path = '/var/run/usbmuxd'
305
+ else
306
+ socket_path = '/var/run/usbmuxd'
307
+ end
308
+ end
309
+ @socket_path = socket_path
310
+ begin
311
+ @protocol_class = BinaryProtocol
312
+ @version = 0
313
+ @listener = MuxConnection.new(@socket_path, @protocol_class)
314
+ @listener.listen()
315
+ rescue MuxVersionError
316
+ @protocol_class = PlistProtocol
317
+ @version = 1
318
+ @listener = MuxConnection.new(@socket_path, @protocol_class)
319
+ @listener.listen()
320
+ end
321
+ @devices = @listener.devices
322
+ end
323
+
324
+ def process(timeout = nil)
325
+ @listener.process(timeout)
326
+ end
327
+
328
+ def connect(device, port)
329
+ connection = MuxConnection.new(@socket_path, @protocol_class)
330
+ connection.connect(device, port)
331
+ end
332
+ end
333
+ end
@@ -0,0 +1,3 @@
1
+ module Usbmux
2
+ VERSION = "0.0.1"
3
+ end
data/lib/usbmux.rb ADDED
@@ -0,0 +1,4 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), "usbmux"))
2
+
3
+ # Require the core module
4
+ require 'usbmux'
data/test.log ADDED
Binary file
data/usbmux.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'usbmux/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "usbmux"
8
+ gem.version = Usbmux::VERSION
9
+ gem.authors = ["Jayme Deffenbaugh"]
10
+ gem.email = ["jdeffenbaugh@me.com"]
11
+ gem.description = %q{Connecting and communicating to iDevices over USB}
12
+ gem.summary = %q{Connecting and communicating to iDevices over USB}
13
+ gem.homepage = "https://github.com/jdeff/usbmux-gem"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency 'CFPropertyList'
21
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: usbmux
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jayme Deffenbaugh
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: CFPropertyList
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: Connecting and communicating to iDevices over USB
31
+ email:
32
+ - jdeffenbaugh@me.com
33
+ executables:
34
+ - idevice_monitor.rb
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - .gitignore
39
+ - .rvmrc
40
+ - Gemfile
41
+ - LICENSE.txt
42
+ - README.md
43
+ - Rakefile
44
+ - bin/idevice_monitor.rb
45
+ - lib/usbmux.rb
46
+ - lib/usbmux/usbmux.rb
47
+ - lib/usbmux/version.rb
48
+ - test.log
49
+ - usbmux.gemspec
50
+ homepage: https://github.com/jdeff/usbmux-gem
51
+ licenses: []
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubyforge_project:
70
+ rubygems_version: 1.8.24
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: Connecting and communicating to iDevices over USB
74
+ test_files: []