libusb 0.6.0-x86-linux

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.travis.yml +17 -0
  4. data/.yardopts +6 -0
  5. data/COPYING +165 -0
  6. data/Gemfile +11 -0
  7. data/History.md +124 -0
  8. data/README.md +159 -0
  9. data/Rakefile +145 -0
  10. data/appveyor.yml +23 -0
  11. data/lib/libusb.rb +58 -0
  12. data/lib/libusb/bos.rb +306 -0
  13. data/lib/libusb/call.rb +446 -0
  14. data/lib/libusb/compat.rb +376 -0
  15. data/lib/libusb/configuration.rb +155 -0
  16. data/lib/libusb/constants.rb +160 -0
  17. data/lib/libusb/context.rb +426 -0
  18. data/lib/libusb/dependencies.rb +7 -0
  19. data/lib/libusb/dev_handle.rb +564 -0
  20. data/lib/libusb/device.rb +365 -0
  21. data/lib/libusb/endpoint.rb +194 -0
  22. data/lib/libusb/eventmachine.rb +183 -0
  23. data/lib/libusb/interface.rb +60 -0
  24. data/lib/libusb/setting.rb +132 -0
  25. data/lib/libusb/ss_companion.rb +69 -0
  26. data/lib/libusb/stdio.rb +25 -0
  27. data/lib/libusb/transfer.rb +377 -0
  28. data/lib/libusb/version_gem.rb +19 -0
  29. data/lib/libusb/version_struct.rb +63 -0
  30. data/libusb.gemspec +30 -0
  31. data/test/test_libusb_bos.rb +118 -0
  32. data/test/test_libusb_bulk_stream_transfer.rb +50 -0
  33. data/test/test_libusb_capability.rb +23 -0
  34. data/test/test_libusb_compat.rb +78 -0
  35. data/test/test_libusb_compat_mass_storage.rb +81 -0
  36. data/test/test_libusb_descriptors.rb +212 -0
  37. data/test/test_libusb_event_machine.rb +118 -0
  38. data/test/test_libusb_gc.rb +37 -0
  39. data/test/test_libusb_hotplug.rb +127 -0
  40. data/test/test_libusb_iso_transfer.rb +50 -0
  41. data/test/test_libusb_mass_storage.rb +268 -0
  42. data/test/test_libusb_mass_storage2.rb +96 -0
  43. data/test/test_libusb_structs.rb +58 -0
  44. data/test/test_libusb_threads.rb +89 -0
  45. data/test/test_libusb_version.rb +40 -0
  46. data/wireshark-usb-sniffer.png +0 -0
  47. metadata +150 -0
@@ -0,0 +1,365 @@
1
+ # This file is part of Libusb for Ruby.
2
+ #
3
+ # Libusb for Ruby is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU Lesser General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # Libusb for Ruby is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public License
14
+ # along with Libusb for Ruby. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ require 'libusb/call'
17
+
18
+ module LIBUSB
19
+ # Class representing a USB device detected on the system.
20
+ #
21
+ # Devices of the system can be obtained with {Context#devices} .
22
+ class Device
23
+ include Comparable
24
+
25
+ # @return [Context] the context this device belongs to.
26
+ attr_reader :context
27
+
28
+ def initialize context, pDev
29
+ @context = context
30
+ def pDev.unref_device(id)
31
+ Call.libusb_unref_device(self)
32
+ end
33
+ ObjectSpace.define_finalizer(self, pDev.method(:unref_device))
34
+ Call.libusb_ref_device(pDev)
35
+ @pDev = pDev
36
+
37
+ @pDevDesc = Call::DeviceDescriptor.new
38
+ res = Call.libusb_get_device_descriptor(@pDev, @pDevDesc)
39
+ LIBUSB.raise_error res, "in libusb_get_device_descriptor" if res!=0
40
+ end
41
+
42
+ # Open the device and obtain a device handle.
43
+ #
44
+ # A handle allows you to perform I/O on the device in question.
45
+ # This is a non-blocking function; no requests are sent over the bus.
46
+ #
47
+ # If called with a block, the handle is passed to the block
48
+ # and is closed when the block has finished.
49
+ #
50
+ # You need proper device access:
51
+ # * Linux: read+write permissions to <tt>/dev/bus/usb/<bus>/<dev></tt>
52
+ # * Windows: by installing a WinUSB-driver for the device (see {file:README.rdoc#Usage_on_Windows} )
53
+ #
54
+ # @return [DevHandle] Handle to the device.
55
+ def open
56
+ ppHandle = FFI::MemoryPointer.new :pointer
57
+ res = Call.libusb_open(@pDev, ppHandle)
58
+ LIBUSB.raise_error res, "in libusb_open" if res!=0
59
+ handle = DevHandle.new self, ppHandle.read_pointer
60
+ return handle unless block_given?
61
+ begin
62
+ yield handle
63
+ ensure
64
+ handle.close
65
+ end
66
+ end
67
+
68
+ # Open the device and claim an interface.
69
+ #
70
+ # This is a convenience method to {Device#open} and {DevHandle#claim_interface}.
71
+ # Must be called with a block. When the block has finished, the interface
72
+ # will be released and the device will be closed.
73
+ #
74
+ # @param [Interface, Fixnum] interface the interface or it's bInterfaceNumber you wish to claim
75
+ def open_interface(interface)
76
+ open do |dev|
77
+ dev.claim_interface(interface) do
78
+ yield dev
79
+ end
80
+ end
81
+ end
82
+
83
+ # Get the number of the bus that a device is connected to.
84
+ def bus_number
85
+ Call.libusb_get_bus_number(@pDev)
86
+ end
87
+
88
+ # Get the address of the device on the bus it is connected to.
89
+ def device_address
90
+ Call.libusb_get_device_address(@pDev)
91
+ end
92
+
93
+ if Call.respond_to?(:libusb_get_port_number)
94
+ # Get the number of the port that a device is connected to.
95
+ # Available since libusb-1.0.12.
96
+ #
97
+ # @return [Fixnum, nil] the port number (+nil+ if not available)
98
+ # @see #port_numbers
99
+ def port_number
100
+ r = Call.libusb_get_port_number(@pDev)
101
+ r==0 ? nil : r
102
+ end
103
+
104
+ # Get the the parent from the specified device [EXPERIMENTAL].
105
+ # Available since libusb-1.0.12.
106
+ #
107
+ # @return [Device, nil] the device parent or +nil+ if not available
108
+ # @see #port_numbers
109
+ def parent
110
+ pppDevs = FFI::MemoryPointer.new :pointer
111
+ Call.libusb_get_device_list(@context.instance_variable_get(:@ctx), pppDevs)
112
+ ppDevs = pppDevs.read_pointer
113
+ pParent = Call.libusb_get_parent(@pDev)
114
+ parent = pParent.null? ? nil : Device.new(@context, pParent)
115
+ Call.libusb_free_device_list(ppDevs, 1)
116
+ parent
117
+ end
118
+
119
+ # Get the list of all port numbers from root for the specified device.
120
+ # Available since libusb-1.0.12.
121
+ #
122
+ # @return [Array<Fixnum>]
123
+ # @see #parent
124
+ # @see #port_number
125
+ def port_numbers
126
+ # As per the USB 3.0 specs, the current maximum limit for the depth is 7.
127
+ path_len = 7
128
+ pPath = FFI::MemoryPointer.new :pointer, path_len
129
+
130
+ l = if Call.respond_to?(:libusb_get_port_numbers)
131
+ Call.libusb_get_port_numbers(@pDev, pPath, path_len)
132
+ else
133
+ Call.libusb_get_port_path(@context.instance_variable_get(:@ctx), @pDev, pPath, path_len)
134
+ end
135
+ pPath.read_array_of_uint8(l)
136
+ end
137
+ alias port_path port_numbers
138
+ end
139
+
140
+ if Call.respond_to?(:libusb_get_device_speed)
141
+ # Get the negotiated connection speed for a device.
142
+ # Available since libusb-1.0.9.
143
+ #
144
+ # @return [Symbol] a {Call::Speeds Speeds} symbol, where +:SPEED_UNKNOWN+ means that
145
+ # the OS doesn't know or doesn't support returning the negotiated speed.
146
+ def device_speed
147
+ Call.libusb_get_device_speed(@pDev)
148
+ end
149
+ end
150
+
151
+ # Convenience function to retrieve the wMaxPacketSize value for a
152
+ # particular endpoint in the active device configuration.
153
+ #
154
+ # @param [Endpoint, Fixnum] endpoint (address of) the endpoint in question
155
+ # @return [Fixnum] the wMaxPacketSize value
156
+ def max_packet_size(endpoint)
157
+ endpoint = endpoint.bEndpointAddress if endpoint.respond_to? :bEndpointAddress
158
+ res = Call.libusb_get_max_packet_size(@pDev, endpoint)
159
+ LIBUSB.raise_error res, "in libusb_get_max_packet_size" unless res>=0
160
+ res
161
+ end
162
+
163
+ # Calculate the maximum packet size which a specific endpoint is capable is
164
+ # sending or receiving in the duration of 1 microframe.
165
+ #
166
+ # Only the active configution is examined. The calculation is based on the
167
+ # wMaxPacketSize field in the endpoint descriptor as described in section 9.6.6
168
+ # in the USB 2.0 specifications.
169
+ #
170
+ # If acting on an isochronous or interrupt endpoint, this function will
171
+ # multiply the value found in bits 0:10 by the number of transactions per
172
+ # microframe (determined by bits 11:12). Otherwise, this function just returns
173
+ # the numeric value found in bits 0:10.
174
+ #
175
+ # This function is useful for setting up isochronous transfers, for example
176
+ # you might use the return value from this function to call
177
+ # IsoPacket#alloc_buffer in order to set the length field
178
+ # of an isochronous packet in a transfer.
179
+ #
180
+ # @param [Endpoint, Fixnum] endpoint (address of) the endpoint in question
181
+ # @return [Fixnum] the maximum packet size which can be sent/received on this endpoint
182
+ def max_iso_packet_size(endpoint)
183
+ endpoint = endpoint.bEndpointAddress if endpoint.respond_to? :bEndpointAddress
184
+ res = Call.libusb_get_max_iso_packet_size(@pDev, endpoint)
185
+ LIBUSB.raise_error res, "in libusb_get_max_iso_packet_size" unless res>=0
186
+ res
187
+ end
188
+
189
+ # Obtain a config descriptor of the device.
190
+ #
191
+ # @param [Fixnum] index number of the config descriptor
192
+ # @return Configuration
193
+ def config_descriptor(index)
194
+ ppConfig = FFI::MemoryPointer.new :pointer
195
+ res = Call.libusb_get_config_descriptor(@pDev, index, ppConfig)
196
+ LIBUSB.raise_error res, "in libusb_get_config_descriptor" if res!=0
197
+ pConfig = ppConfig.read_pointer
198
+ config = Configuration.new(self, pConfig)
199
+ config
200
+ end
201
+
202
+ # Size of the Descriptor in Bytes (18 bytes)
203
+ def bLength
204
+ @pDevDesc[:bLength]
205
+ end
206
+
207
+ # Device Descriptor (0x01)
208
+ def bDescriptorType
209
+ @pDevDesc[:bDescriptorType]
210
+ end
211
+
212
+ # USB specification release number which device complies too
213
+ #
214
+ # @return [Integer] in binary-coded decimal
215
+ def bcdUSB
216
+ @pDevDesc[:bcdUSB]
217
+ end
218
+
219
+ # USB-IF class code for the device (Assigned by USB Org)
220
+ #
221
+ # * If equal to 0x00, each interface specifies it's own class code
222
+ # * If equal to 0xFF, the class code is vendor specified
223
+ # * Otherwise field is valid Class Code
224
+ def bDeviceClass
225
+ @pDevDesc[:bDeviceClass]
226
+ end
227
+
228
+ # USB-IF subclass code for the device, qualified by the {Device#bDeviceClass}
229
+ # value (Assigned by USB Org)
230
+ def bDeviceSubClass
231
+ @pDevDesc[:bDeviceSubClass]
232
+ end
233
+
234
+ # USB-IF protocol code for the device, qualified by the {Device#bDeviceClass}
235
+ # and {Device#bDeviceSubClass} values (Assigned by USB Org)
236
+ def bDeviceProtocol
237
+ @pDevDesc[:bDeviceProtocol]
238
+ end
239
+
240
+ # Maximum Packet Size for Endpoint 0. Valid Sizes are 8, 16, 32, 64
241
+ def bMaxPacketSize0
242
+ @pDevDesc[:bMaxPacketSize0]
243
+ end
244
+
245
+ # USB-IF vendor ID (Assigned by USB Org)
246
+ def idVendor
247
+ @pDevDesc[:idVendor]
248
+ end
249
+
250
+ # USB-IF product ID (Assigned by Manufacturer)
251
+ def idProduct
252
+ @pDevDesc[:idProduct]
253
+ end
254
+
255
+ # Device release number in binary-coded decimal.
256
+ def bcdDevice
257
+ @pDevDesc[:bcdDevice]
258
+ end
259
+
260
+ # Index of string descriptor describing manufacturer.
261
+ def iManufacturer
262
+ @pDevDesc[:iManufacturer]
263
+ end
264
+
265
+ # Index of string descriptor describing product.
266
+ def iProduct
267
+ @pDevDesc[:iProduct]
268
+ end
269
+
270
+ # Index of string descriptor containing device serial number.
271
+ def iSerialNumber
272
+ @pDevDesc[:iSerialNumber]
273
+ end
274
+
275
+ # Number of Possible Configurations
276
+ def bNumConfigurations
277
+ @pDevDesc[:bNumConfigurations]
278
+ end
279
+
280
+
281
+ def inspect
282
+ attrs = []
283
+ attrs << "#{self.bus_number}/#{self.device_address}"
284
+ attrs << ("%04x:%04x" % [self.idVendor, self.idProduct])
285
+ attrs << self.manufacturer
286
+ attrs << self.product
287
+ attrs << self.serial_number
288
+ if self.bDeviceClass == LIBUSB::CLASS_PER_INTERFACE
289
+ devclass = self.settings.map {|i|
290
+ LIBUSB.dev_string(i.bInterfaceClass, i.bInterfaceSubClass, i.bInterfaceProtocol)
291
+ }.join(", ")
292
+ else
293
+ devclass = LIBUSB.dev_string(self.bDeviceClass, self.bDeviceSubClass, self.bDeviceProtocol)
294
+ end
295
+ attrs << "(#{devclass})"
296
+ attrs.compact!
297
+ "\#<#{self.class} #{attrs.join(' ')}>"
298
+ end
299
+
300
+ def try_string_descriptor_ascii(i)
301
+ begin
302
+ open{|h| h.string_descriptor_ascii(i) }
303
+ rescue
304
+ "?"
305
+ end
306
+ end
307
+
308
+ # Return manufacturer of the device
309
+ # @return String
310
+ def manufacturer
311
+ return @manufacturer if defined? @manufacturer
312
+ @manufacturer = try_string_descriptor_ascii(self.iManufacturer)
313
+ @manufacturer.strip! if @manufacturer
314
+ @manufacturer
315
+ end
316
+
317
+ # Return product name of the device.
318
+ # @return String
319
+ def product
320
+ return @product if defined? @product
321
+ @product = try_string_descriptor_ascii(self.iProduct)
322
+ @product.strip! if @product
323
+ @product
324
+ end
325
+
326
+ # Return serial number of the device.
327
+ # @return String
328
+ def serial_number
329
+ return @serial_number if defined? @serial_number
330
+ @serial_number = try_string_descriptor_ascii(self.iSerialNumber)
331
+ @serial_number.strip! if @serial_number
332
+ @serial_number
333
+ end
334
+
335
+ # Return configurations of the device.
336
+ # @return [Array<Configuration>]
337
+ def configurations
338
+ configs = []
339
+ bNumConfigurations.times do |config_index|
340
+ begin
341
+ configs << config_descriptor(config_index)
342
+ rescue RuntimeError
343
+ # On Windows some devices don't return it's configuration.
344
+ end
345
+ end
346
+ configs
347
+ end
348
+
349
+ # Return all interfaces of this device.
350
+ # @return [Array<Interface>]
351
+ def interfaces() self.configurations.map {|d| d.interfaces }.flatten end
352
+ # Return all interface decriptions of this device.
353
+ # @return [Array<Setting>]
354
+ def settings() self.interfaces.map {|d| d.settings }.flatten end
355
+ # Return all endpoints of all interfaces of this device.
356
+ # @return [Array<Endpoint>]
357
+ def endpoints() self.settings.map {|d| d.endpoints }.flatten end
358
+
359
+ def <=>(o)
360
+ t = bus_number<=>o.bus_number
361
+ t = device_address<=>o.device_address if t==0
362
+ t
363
+ end
364
+ end
365
+ end
@@ -0,0 +1,194 @@
1
+ # This file is part of Libusb for Ruby.
2
+ #
3
+ # Libusb for Ruby is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU Lesser General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # Libusb for Ruby is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public License
14
+ # along with Libusb for Ruby. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ require 'libusb/call'
17
+
18
+ module LIBUSB
19
+ class Endpoint < FFI::Struct
20
+ include Comparable
21
+
22
+ layout :bLength, :uint8,
23
+ :bDescriptorType, :uint8,
24
+ :bEndpointAddress, :uint8,
25
+ :bmAttributes, :uint8,
26
+ :wMaxPacketSize, :uint16,
27
+ :bInterval, :uint8,
28
+ :bRefresh, :uint8,
29
+ :bSynchAddress, :uint8,
30
+ :extra, :pointer,
31
+ :extra_length, :int
32
+
33
+ # Size of Descriptor in Bytes (7 bytes)
34
+ def bLength
35
+ self[:bLength]
36
+ end
37
+
38
+ # Descriptor type (0x05)
39
+ def bDescriptorType
40
+ self[:bDescriptorType]
41
+ end
42
+
43
+ # The address of the endpoint described by this descriptor.
44
+ #
45
+ # * Bits 0..3: Endpoint Number.
46
+ # * Bits 4..6: Reserved. Set to Zero
47
+ # * Bits 7: Direction 0 = Out, 1 = In (Ignored for Control Endpoints)
48
+ #
49
+ # @return [Integer]
50
+ #
51
+ # @see #endpoint_number
52
+ # @see #direction
53
+ def bEndpointAddress
54
+ self[:bEndpointAddress]
55
+ end
56
+
57
+ # @return [Integer]
58
+ def endpoint_number
59
+ bEndpointAddress & 0b1111
60
+ end
61
+
62
+ # @return [Symbol] Either +:in+ or +:out+
63
+ def direction
64
+ bEndpointAddress & ENDPOINT_IN == 0 ? :out : :in
65
+ end
66
+
67
+ # Attributes which apply to the endpoint when it is configured using the {Configuration#bConfigurationValue}.
68
+ #
69
+ # * Bits 1..0: Transfer Type
70
+ # * 00 = Control
71
+ # * 01 = Isochronous
72
+ # * 10 = Bulk
73
+ # * 11 = Interrupt
74
+ # * Bits 7..2: are reserved. If Isochronous endpoint,
75
+ # * Bits 3..2: Synchronisation Type (Iso Mode)
76
+ # * 00 = No Synchonisation
77
+ # * 01 = Asynchronous
78
+ # * 10 = Adaptive
79
+ # * 11 = Synchronous
80
+ # * Bits 5..4: Usage Type (Iso Mode)
81
+ # * 00 = Data Endpoint
82
+ # * 01 = Feedback Endpoint
83
+ # * 10 = Explicit Feedback Data Endpoint
84
+ # * 11 = Reserved
85
+ #
86
+ # @return [Integer]
87
+ #
88
+ # @see #transfer_type
89
+ # @see #usage_type
90
+ # @see #synchronization_type
91
+ def bmAttributes
92
+ self[:bmAttributes]
93
+ end
94
+
95
+ TransferTypes = [:control, :isochronous, :bulk, :interrupt]
96
+ # @return [Symbol] One of {TransferTypes}
97
+ def transfer_type
98
+ TransferTypes[bmAttributes & 0b11]
99
+ end
100
+
101
+ SynchronizationTypes = [:no_synchronization, :asynchronous, :adaptive, :synchronous]
102
+ # @return [Symbol] One of {SynchronizationTypes}
103
+ def synchronization_type
104
+ return unless transfer_type == :isochronous
105
+ SynchronizationTypes[(bmAttributes & 0b1100) >> 2]
106
+ end
107
+
108
+ UsageTypes = [:data, :feedback, :implicit_feedback, :unknown]
109
+ # @return [Symbol] One of {UsageTypes}
110
+ def usage_type
111
+ return unless transfer_type == :isochronous
112
+ UsageTypes[(bmAttributes & 0b110000) >> 4]
113
+ end
114
+
115
+ # Maximum Packet Size this endpoint is capable of sending or receiving
116
+ def wMaxPacketSize
117
+ self[:wMaxPacketSize]
118
+ end
119
+
120
+ # Interval for polling endpoint data transfers. Value in frame counts.
121
+ # Ignored for Bulk & Control Endpoints. Isochronous must equal 1 and field
122
+ # may range from 1 to 255 for interrupt endpoints.
123
+ #
124
+ # The interval is respected by the kernel driver, so user mode processes
125
+ # using libusb don't need to care about it.
126
+ def bInterval
127
+ self[:bInterval]
128
+ end
129
+
130
+ # For audio devices only: the rate at which synchronization feedback is provided.
131
+ def bRefresh
132
+ self[:bRefresh]
133
+ end
134
+
135
+ # For audio devices only: the address if the synch endpoint.
136
+ def bSynchAddress
137
+ self[:bSynchAddress]
138
+ end
139
+
140
+ # Extra descriptors.
141
+ #
142
+ # @return [String]
143
+ def extra
144
+ return if self[:extra].null?
145
+ self[:extra].read_string(self[:extra_length])
146
+ end
147
+
148
+ def initialize(setting, *args)
149
+ @setting = setting
150
+ super(*args)
151
+ end
152
+
153
+ # @return [Setting] the setting this endpoint belongs to.
154
+ attr_reader :setting
155
+
156
+ def inspect
157
+ type = [transfer_type, synchronization_type, usage_type].compact
158
+ "\#<#{self.class} #{endpoint_number} #{direction} #{type.join(" ")}>"
159
+ end
160
+
161
+ # The {Device} this Endpoint belongs to.
162
+ def device() self.setting.interface.configuration.device end
163
+ # The {Configuration} this Endpoint belongs to.
164
+ def configuration() self.setting.interface.configuration end
165
+ # The {Interface} this Endpoint belongs to.
166
+ def interface() self.setting.interface end
167
+
168
+ def <=>(o)
169
+ t = setting<=>o.setting
170
+ t = bEndpointAddress<=>o.bEndpointAddress if t==0
171
+ t
172
+ end
173
+
174
+ if Call.respond_to?(:libusb_get_ss_endpoint_companion_descriptor)
175
+
176
+ # @method ss_companion
177
+ # Get the endpoints superspeed endpoint companion descriptor (if any).
178
+ #
179
+ # Since libusb version 1.0.16.
180
+ #
181
+ # @return [SsCompanion]
182
+ def ss_companion
183
+ ep_comp = FFI::MemoryPointer.new :pointer
184
+ res = Call.libusb_get_ss_endpoint_companion_descriptor(
185
+ device.context.instance_variable_get(:@ctx),
186
+ pointer,
187
+ ep_comp
188
+ )
189
+ LIBUSB.raise_error res, "in libusb_get_ss_endpoint_companion_descriptor" if res!=0
190
+ SsCompanion.new ep_comp.read_pointer
191
+ end
192
+ end
193
+ end
194
+ end