UPnP 1.1.0 → 1.2.0
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.tar.gz.sig +5 -2
- data/History.txt +13 -1
- data/Manifest.txt +1 -0
- data/Rakefile +7 -6
- data/bin/upnp_discover +13 -4
- data/bin/upnp_listen +20 -8
- data/lib/UPnP.rb +1 -1
- data/lib/UPnP/SSDP.rb +35 -7
- data/lib/UPnP/control/service.rb +15 -3
- data/lib/UPnP/device.rb +22 -2
- data/lib/UPnP/service.rb +17 -1
- data/lib/UPnP/test_utilities.rb +13 -0
- data/test/test_UPnP_SSDP.rb +2 -1
- data/test/test_UPnP_device.rb +20 -3
- data/test/test_UPnP_service.rb +9 -1
- metadata +8 -5
- metadata.gz.sig +0 -0
data.tar.gz.sig
CHANGED
@@ -1,2 +1,5 @@
|
|
1
|
-
|
2
|
-
�
|
1
|
+
���%2v��Py;�٦��pH���]�z�Thj�*��6�O)��J�^X :�٠ݷ)(9��Ĝ������$B�8C�Ľ�a��*L��o��~ٓ�6:�Qm�
|
2
|
+
�+q��́w;�
|
3
|
+
�1(NU����X�
|
4
|
+
��qה
|
5
|
+
�w.�a��M��4\��sD���:��堼�z�")X����r]
|
data/History.txt
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
=== 1.2.0 / 2009-06-16
|
2
|
+
|
3
|
+
* 2 minor enhancements
|
4
|
+
* Workaround for missing socket constants on Windows. Reported by Yuri.
|
5
|
+
* upnp_discover now shows action argument and return value names.
|
6
|
+
|
7
|
+
* 4 bug fixes
|
8
|
+
* Method name must not include entire URI. Reported by Ian Macdonald.
|
9
|
+
* Step in allowedValueRange is optional. Reported by Ian Macdonald.
|
10
|
+
* upnp_listen works with all notification types. Reported by Ian Macdonald.
|
11
|
+
* upnp_discover now warns when a device failed to instantiate. Reported by
|
12
|
+
Ian Macdonald.
|
13
|
+
|
1
14
|
=== 1.1.0 / 2008-07-23
|
2
15
|
|
3
16
|
* 2 major enhancements
|
@@ -9,6 +22,5 @@
|
|
9
22
|
=== 1.0.0 / 2008-06-25
|
10
23
|
|
11
24
|
* 1 major enhancement
|
12
|
-
|
13
25
|
* Birthday!
|
14
26
|
|
data/Manifest.txt
CHANGED
data/Rakefile
CHANGED
@@ -2,14 +2,15 @@
|
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'hoe'
|
5
|
-
require './lib/upnp.rb'
|
6
5
|
|
7
|
-
Hoe.
|
8
|
-
p.rubyforge_name = 'seattlerb'
|
9
|
-
p.developer('Eric Hodel', 'drbrain@segment7.net')
|
6
|
+
Hoe.plugin :perforce
|
10
7
|
|
11
|
-
|
12
|
-
|
8
|
+
Hoe.spec 'UPnP' do
|
9
|
+
self.rubyforge_name = 'seattlerb'
|
10
|
+
developer 'Eric Hodel', 'drbrain@segment7.net'
|
11
|
+
|
12
|
+
extra_deps << 'soap4r'
|
13
|
+
extra_deps << 'builder'
|
13
14
|
end
|
14
15
|
|
15
16
|
# vim: syntax=Ruby
|
data/bin/upnp_discover
CHANGED
@@ -22,8 +22,13 @@ Prints information about UPnP internet gateway devices
|
|
22
22
|
ssdp.timeout = timeout
|
23
23
|
|
24
24
|
devices = ssdp.search(:root).map do |resource|
|
25
|
-
|
26
|
-
|
25
|
+
begin
|
26
|
+
UPnP::Control::Device.new resource.location
|
27
|
+
rescue UPnP::Error => e
|
28
|
+
puts "Error creating device:\n\t#{e}"
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
end.compact
|
27
32
|
|
28
33
|
if devices.empty? then
|
29
34
|
puts 'No UPnP devices found'
|
@@ -72,8 +77,12 @@ devices.each do |device|
|
|
72
77
|
puts " Event subscription URL: #{service.event_sub_url}"
|
73
78
|
puts
|
74
79
|
puts " Actions:"
|
75
|
-
service.
|
76
|
-
|
80
|
+
service.actions.sort.each do |method, arguments|
|
81
|
+
inn, out = arguments.partition { |dir,| dir == 'in' }
|
82
|
+
out = out.map { |dir, name,| name }
|
83
|
+
out = out.empty? ? '' : " => #{out.join ', '}"
|
84
|
+
inn = inn.map { |dir, name,| name }
|
85
|
+
puts " #{method}(#{inn.join ', '})#{out}"
|
77
86
|
end
|
78
87
|
|
79
88
|
puts
|
data/bin/upnp_listen
CHANGED
@@ -8,19 +8,31 @@ ssdp = UPnP::SSDP.new
|
|
8
8
|
ssdp.discover do |notification|
|
9
9
|
schemas = Regexp.union UPnP::DEVICE_SCHEMA_PREFIX, UPnP::SERVICE_SCHEMA_PREFIX
|
10
10
|
|
11
|
-
|
11
|
+
case notification
|
12
|
+
when UPnP::SSDP::Notification then
|
13
|
+
type = notification.type.sub(/#{schemas}:/, '')
|
14
|
+
|
15
|
+
if notification.alive? then
|
16
|
+
puts "#{type} is alive"
|
17
|
+
puts "Description: #{notification.location}"
|
18
|
+
else
|
19
|
+
puts "#{type} says byebye"
|
20
|
+
end
|
21
|
+
|
22
|
+
puts "USN: #{notification.name}"
|
23
|
+
when UPnP::SSDP::Response then
|
24
|
+
puts "Response from #{target}"
|
25
|
+
puts "Description: #{location}"
|
26
|
+
puts "USN: #{notification.name}"
|
27
|
+
when UPnP::SSDP::Search then
|
28
|
+
puts "Search for #{notification.target}"
|
29
|
+
end
|
12
30
|
|
13
|
-
if notification.
|
14
|
-
puts "#{type} is alive"
|
15
|
-
puts "Description: #{notification.location}"
|
31
|
+
if notification.expiration then
|
16
32
|
expiration = notification.expiration.strftime '%c'
|
17
33
|
puts "Valid until #{expiration}"
|
18
|
-
else
|
19
|
-
puts "#{type} says byebye"
|
20
34
|
end
|
21
35
|
|
22
|
-
puts "USN: #{notification.name}"
|
23
|
-
|
24
36
|
puts
|
25
37
|
end
|
26
38
|
|
data/lib/UPnP.rb
CHANGED
data/lib/UPnP/SSDP.rb
CHANGED
@@ -45,14 +45,14 @@ class UPnP::SSDP
|
|
45
45
|
# Expiration time of this advertisement
|
46
46
|
|
47
47
|
def expiration
|
48
|
-
date + max_age
|
48
|
+
date + max_age if date and max_age
|
49
49
|
end
|
50
50
|
|
51
51
|
##
|
52
52
|
# True if this advertisement has expired
|
53
53
|
|
54
54
|
def expired?
|
55
|
-
Time.now > expiration
|
55
|
+
Time.now > expiration if expiration
|
56
56
|
end
|
57
57
|
|
58
58
|
end
|
@@ -303,6 +303,13 @@ class UPnP::SSDP
|
|
303
303
|
@wait_time = wait_time
|
304
304
|
end
|
305
305
|
|
306
|
+
##
|
307
|
+
# Expiration time of this advertisement
|
308
|
+
|
309
|
+
def expiration
|
310
|
+
date + wait_time
|
311
|
+
end
|
312
|
+
|
306
313
|
##
|
307
314
|
# A friendlier inspect
|
308
315
|
|
@@ -536,11 +543,11 @@ class UPnP::SSDP
|
|
536
543
|
adv = parse response
|
537
544
|
|
538
545
|
info = case adv
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
546
|
+
when Notification then adv.type
|
547
|
+
when Response then adv.target
|
548
|
+
when Search then adv.target
|
549
|
+
else 'unknown'
|
550
|
+
end
|
544
551
|
|
545
552
|
response =~ /\A(\S+)/
|
546
553
|
log :debug, "SSDP recv #{$1} #{hostname}:#{port} #{info}"
|
@@ -756,3 +763,24 @@ ST: #{search_target}\r
|
|
756
763
|
|
757
764
|
end
|
758
765
|
|
766
|
+
# :stopdoc:
|
767
|
+
|
768
|
+
##
|
769
|
+
# Workaround for mising constants on Windows
|
770
|
+
|
771
|
+
module Socket::Constants
|
772
|
+
IP_ADD_MEMBERSHIP = 12 unless defined? IP_ADD_MEMBERSHIP
|
773
|
+
IP_MULTICAST_LOOP = 11 unless defined? IP_MULTICAST_LOOP
|
774
|
+
IP_MULTICAST_TTL = 10 unless defined? IP_MULTICAST_TTL
|
775
|
+
IP_TTL = 4 unless defined? IP_TTL
|
776
|
+
end
|
777
|
+
|
778
|
+
class Socket
|
779
|
+
IP_ADD_MEMBERSHIP = 12 unless defined? IP_ADD_MEMBERSHIP
|
780
|
+
IP_MULTICAST_LOOP = 11 unless defined? IP_MULTICAST_LOOP
|
781
|
+
IP_MULTICAST_TTL = 10 unless defined? IP_MULTICAST_TTL
|
782
|
+
IP_TTL = 4 unless defined? IP_TTL
|
783
|
+
end
|
784
|
+
|
785
|
+
# :startdoc:
|
786
|
+
|
data/lib/UPnP/control/service.rb
CHANGED
@@ -175,6 +175,16 @@ class UPnP::Control::Service
|
|
175
175
|
|
176
176
|
end
|
177
177
|
|
178
|
+
##
|
179
|
+
# Hash mapping UPnP Actions to arguments
|
180
|
+
#
|
181
|
+
# {
|
182
|
+
# 'GetTotalPacketsSent' =>
|
183
|
+
# [['out', 'NewTotalPacketsSent', 'TotalPacketsSent']]
|
184
|
+
# }
|
185
|
+
|
186
|
+
attr_reader :actions
|
187
|
+
|
178
188
|
##
|
179
189
|
# Control URL
|
180
190
|
|
@@ -259,7 +269,7 @@ class UPnP::Control::Service
|
|
259
269
|
|
260
270
|
@actions.each do |name, arguments|
|
261
271
|
soapaction = "#{@type}##{name}"
|
262
|
-
qname = XSD::QName.new @type,
|
272
|
+
qname = XSD::QName.new @type, name
|
263
273
|
|
264
274
|
# TODO map ranges, enumerations
|
265
275
|
arguments = arguments.map do |direction, arg_name, variable|
|
@@ -278,7 +288,6 @@ class UPnP::Control::Service
|
|
278
288
|
|
279
289
|
@driver.mapping_registry = mapping_registry
|
280
290
|
|
281
|
-
@actions = nil
|
282
291
|
@variables = nil
|
283
292
|
end
|
284
293
|
|
@@ -385,7 +394,8 @@ class UPnP::Control::Service
|
|
385
394
|
maximum = range.elements['maximum']
|
386
395
|
step = range.elements['step']
|
387
396
|
|
388
|
-
range = [minimum, maximum
|
397
|
+
range = [minimum, maximum]
|
398
|
+
range << step if step
|
389
399
|
|
390
400
|
range.map do |value|
|
391
401
|
value = value.text
|
@@ -405,6 +415,8 @@ class UPnP::Control::Service
|
|
405
415
|
|
406
416
|
service_state_table = description.elements['scpd/serviceStateTable']
|
407
417
|
parse_service_state_table service_state_table
|
418
|
+
rescue OpenURI::HTTPError
|
419
|
+
raise Error, "Unable to open SCPD at #{@scpd_url.inspect} from device #{@url.inspect}"
|
408
420
|
end
|
409
421
|
|
410
422
|
##
|
data/lib/UPnP/device.rb
CHANGED
@@ -190,8 +190,12 @@ class UPnP::Device
|
|
190
190
|
@option_parser = nil
|
191
191
|
@options = nil
|
192
192
|
|
193
|
-
|
194
|
-
|
193
|
+
##
|
194
|
+
# Sets the serivceId for +service+ using +domain+ and +id+. Used in
|
195
|
+
# UPnP::Service#description via #description.
|
196
|
+
|
197
|
+
def self.add_service_id(service, id, domain = 'upnp.org')
|
198
|
+
SERVICE_IDS[self][service] = "urn:#{domain.tr '.', '-'}:serviceId:#{id}"
|
195
199
|
end
|
196
200
|
|
197
201
|
##
|
@@ -374,6 +378,8 @@ class UPnP::Device
|
|
374
378
|
@sub_services ||= []
|
375
379
|
@parent ||= parent_device
|
376
380
|
|
381
|
+
@cache_dir = nil
|
382
|
+
|
377
383
|
yield self if block_given?
|
378
384
|
|
379
385
|
@name ||= "uuid:#{UPnP::UUID.generate}"
|
@@ -438,6 +444,20 @@ class UPnP::Device
|
|
438
444
|
end
|
439
445
|
end
|
440
446
|
|
447
|
+
##
|
448
|
+
# A directory for storing device-specific persistent data
|
449
|
+
|
450
|
+
def cache_dir
|
451
|
+
return @cache_dir if @cache_dir
|
452
|
+
|
453
|
+
@cache_dir = File.join '~', '.UPnP', '_cache', @name
|
454
|
+
@cache_dir = File.expand_path @cache_dir
|
455
|
+
|
456
|
+
FileUtils.mkdir_p @cache_dir
|
457
|
+
|
458
|
+
@cache_dir
|
459
|
+
end
|
460
|
+
|
441
461
|
##
|
442
462
|
# Returns an XML document describing the root device
|
443
463
|
|
data/lib/UPnP/service.rb
CHANGED
@@ -156,6 +156,8 @@ class UPnP::Service < SOAP::RPC::StandaloneServer
|
|
156
156
|
@device = device
|
157
157
|
@type = type
|
158
158
|
|
159
|
+
@cache_dir = nil
|
160
|
+
|
159
161
|
# HACK PS3 disobeys spec
|
160
162
|
SOAP::NS::KNOWN_TAG[type_urn] = 'u'
|
161
163
|
SOAP::NS::KNOWN_TAG[SOAP::EnvelopeNamespace] = 's'
|
@@ -194,6 +196,20 @@ class UPnP::Service < SOAP::RPC::StandaloneServer
|
|
194
196
|
end
|
195
197
|
end
|
196
198
|
|
199
|
+
##
|
200
|
+
# A directory for storing service-specific persistent data
|
201
|
+
|
202
|
+
def cache_dir
|
203
|
+
return @cache_dir if @cache_dir
|
204
|
+
|
205
|
+
@cache_dir = File.join '~', '.UPnP', '_cache', "#{@device.name}-#{@type}"
|
206
|
+
@cache_dir = File.expand_path @cache_dir
|
207
|
+
|
208
|
+
FileUtils.mkdir_p @cache_dir
|
209
|
+
|
210
|
+
@cache_dir
|
211
|
+
end
|
212
|
+
|
197
213
|
##
|
198
214
|
# The control URL for this service
|
199
215
|
|
@@ -216,7 +232,7 @@ class UPnP::Service < SOAP::RPC::StandaloneServer
|
|
216
232
|
def description(xml)
|
217
233
|
xml.service do
|
218
234
|
xml.serviceType type_urn
|
219
|
-
xml.serviceId
|
235
|
+
xml.serviceId root_device.service_id(self)
|
220
236
|
xml.SCPDURL scpd_url
|
221
237
|
xml.controlURL control_url
|
222
238
|
xml.eventSubURL event_sub_url
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'UPnP/device'
|
2
|
+
require 'UPnP/service'
|
3
|
+
|
4
|
+
class UPnP::Service::TestService < UPnP::Service
|
5
|
+
VERSION = '1.0'
|
6
|
+
end
|
7
|
+
|
8
|
+
class UPnP::Device::TestDevice < UPnP::Device
|
9
|
+
VERSION = '1.0'
|
10
|
+
|
11
|
+
add_service_id UPnP::Service::TestService, 'TestService', 'example.com'
|
12
|
+
end
|
13
|
+
|
data/test/test_UPnP_SSDP.rb
CHANGED
@@ -2,6 +2,7 @@ require 'test/unit'
|
|
2
2
|
require 'test/utilities'
|
3
3
|
require 'UPnP/SSDP'
|
4
4
|
require 'UPnP/device'
|
5
|
+
require 'UPnP/test_utilities'
|
5
6
|
|
6
7
|
class TestUPnPSSDP < UPnP::TestCase
|
7
8
|
|
@@ -293,7 +294,7 @@ ST: bunnies\r
|
|
293
294
|
end
|
294
295
|
|
295
296
|
def util_device_version
|
296
|
-
"UPnP::Device::TestDevice/#{UPnP::VERSION}"
|
297
|
+
"UPnP::Device::TestDevice/#{UPnP::Device::TestDevice::VERSION}"
|
297
298
|
end
|
298
299
|
|
299
300
|
end
|
data/test/test_UPnP_device.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'test/unit'
|
2
2
|
require 'test/utilities'
|
3
3
|
require 'UPnP/device'
|
4
|
+
require 'UPnP/test_utilities'
|
4
5
|
|
5
6
|
class TestUPnPDevice < UPnP::TestCase
|
6
7
|
|
@@ -18,6 +19,11 @@ class TestUPnPDevice < UPnP::TestCase
|
|
18
19
|
@service = @device.add_service 'TestService'
|
19
20
|
end
|
20
21
|
|
22
|
+
def test_self_add_serivce_id
|
23
|
+
assert_equal 'urn:example-com:serviceId:TestService',
|
24
|
+
@device.service_id(@service)
|
25
|
+
end
|
26
|
+
|
21
27
|
def test_self_create
|
22
28
|
device1 = UPnP::Device.create 'TestDevice', 'test device'
|
23
29
|
|
@@ -98,6 +104,13 @@ class TestUPnPDevice < UPnP::TestCase
|
|
98
104
|
assert @device.sub_services.include?(@service)
|
99
105
|
end
|
100
106
|
|
107
|
+
def test_cache_dir
|
108
|
+
assert_match %r%.UPnP/_cache/uuid:.{8}-.{4}-.{4}-.{4}-.{12}$%,
|
109
|
+
@device.cache_dir
|
110
|
+
|
111
|
+
assert File.exist?(@device.cache_dir)
|
112
|
+
end
|
113
|
+
|
101
114
|
def test_description
|
102
115
|
desc = @device.description
|
103
116
|
|
@@ -120,7 +133,7 @@ class TestUPnPDevice < UPnP::TestCase
|
|
120
133
|
<serviceList>
|
121
134
|
<service>
|
122
135
|
<serviceType>urn:schemas-upnp-org:service:TestService:1</serviceType>
|
123
|
-
<serviceId>urn:
|
136
|
+
<serviceId>urn:example-com:serviceId:TestService</serviceId>
|
124
137
|
<SCPDURL>/TestDevice/TestService</SCPDURL>
|
125
138
|
<controlURL>/TestDevice/TestService/control</controlURL>
|
126
139
|
<eventSubURL>/TestDevice/TestService/event_sub</eventSubURL>
|
@@ -235,11 +248,15 @@ class TestUPnPDevice < UPnP::TestCase
|
|
235
248
|
end
|
236
249
|
|
237
250
|
def test_service_id
|
238
|
-
assert_equal 'TestService',
|
251
|
+
assert_equal 'urn:example-com:serviceId:TestService',
|
252
|
+
@device.service_id(@service)
|
239
253
|
end
|
240
254
|
|
241
255
|
def test_service_ids
|
242
|
-
expected = {
|
256
|
+
expected = {
|
257
|
+
UPnP::Service::TestService => 'urn:example-com:serviceId:TestService'
|
258
|
+
}
|
259
|
+
|
243
260
|
assert_equal expected, @device.service_ids
|
244
261
|
end
|
245
262
|
|
data/test/test_UPnP_service.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'test/unit'
|
2
2
|
require 'test/utilities'
|
3
3
|
require 'UPnP/service'
|
4
|
+
require 'UPnP/test_utilities'
|
4
5
|
|
5
6
|
class TestUPnPService < UPnP::TestCase
|
6
7
|
|
@@ -47,6 +48,13 @@ class TestUPnPService < UPnP::TestCase
|
|
47
48
|
assert_equal [qname], operations.keys
|
48
49
|
end
|
49
50
|
|
51
|
+
def test_cache_dir
|
52
|
+
assert_match %r%.UPnP/_cache/uuid:.{8}-.{4}-.{4}-.{4}-.{12}-TestService$%,
|
53
|
+
@service.cache_dir
|
54
|
+
|
55
|
+
assert File.exist?(@service.cache_dir)
|
56
|
+
end
|
57
|
+
|
50
58
|
def test_control_url
|
51
59
|
assert_equal '/TestDevice/TestService/control', @service.control_url
|
52
60
|
end
|
@@ -64,7 +72,7 @@ class TestUPnPService < UPnP::TestCase
|
|
64
72
|
expected = <<-XML
|
65
73
|
<service>
|
66
74
|
<serviceType>urn:schemas-upnp-org:service:TestService:1</serviceType>
|
67
|
-
<serviceId>urn:
|
75
|
+
<serviceId>urn:example-com:serviceId:TestService</serviceId>
|
68
76
|
<SCPDURL>/TestDevice/TestService</SCPDURL>
|
69
77
|
<controlURL>/TestDevice/TestService/control</controlURL>
|
70
78
|
<eventSubURL>/TestDevice/TestService/event_sub</eventSubURL>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: UPnP
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Hodel
|
@@ -30,7 +30,7 @@ cert_chain:
|
|
30
30
|
x52qPcexcYZR7w==
|
31
31
|
-----END CERTIFICATE-----
|
32
32
|
|
33
|
-
date:
|
33
|
+
date: 2009-06-16 00:00:00 -07:00
|
34
34
|
default_executable:
|
35
35
|
dependencies:
|
36
36
|
- !ruby/object:Gem::Dependency
|
@@ -61,7 +61,7 @@ dependencies:
|
|
61
61
|
requirements:
|
62
62
|
- - ">="
|
63
63
|
- !ruby/object:Gem::Version
|
64
|
-
version: 1.
|
64
|
+
version: 2.1.0
|
65
65
|
version:
|
66
66
|
description: An implementation of the UPnP protocol
|
67
67
|
email:
|
@@ -92,6 +92,7 @@ files:
|
|
92
92
|
- lib/UPnP/device.rb
|
93
93
|
- lib/UPnP/root_server.rb
|
94
94
|
- lib/UPnP/service.rb
|
95
|
+
- lib/UPnP/test_utilities.rb
|
95
96
|
- test/test_UPnP_SSDP.rb
|
96
97
|
- test/test_UPnP_SSDP_notification.rb
|
97
98
|
- test/test_UPnP_SSDP_response.rb
|
@@ -104,6 +105,8 @@ files:
|
|
104
105
|
- test/utilities.rb
|
105
106
|
has_rdoc: true
|
106
107
|
homepage: http://seattlerb.org/UPnP
|
108
|
+
licenses: []
|
109
|
+
|
107
110
|
post_install_message:
|
108
111
|
rdoc_options:
|
109
112
|
- --main
|
@@ -125,9 +128,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
125
128
|
requirements: []
|
126
129
|
|
127
130
|
rubyforge_project: seattlerb
|
128
|
-
rubygems_version: 1.
|
131
|
+
rubygems_version: 1.3.4.2217
|
129
132
|
signing_key:
|
130
|
-
specification_version:
|
133
|
+
specification_version: 3
|
131
134
|
summary: An implementation of the UPnP protocol
|
132
135
|
test_files:
|
133
136
|
- test/test_UPnP_control_device.rb
|
metadata.gz.sig
CHANGED
Binary file
|