rupnp 0.2.0 → 0.2.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/lib/rupnp/control_point.rb +1 -1
- data/lib/rupnp/cp/base.rb +1 -0
- data/lib/rupnp/cp/remote_device.rb +25 -26
- data/lib/rupnp/cp/remote_service.rb +4 -0
- data/lib/rupnp/ssdp/searcher.rb +2 -1
- data/lib/rupnp.rb +1 -1
- data/spec/control_point_spec.rb +11 -1
- data/spec/cp/remote_device_spec.rb +199 -0
- data/spec/spec_helper.rb +23 -7
- data/tasks/gem.rake +1 -1
- metadata +5 -4
data/lib/rupnp/control_point.rb
CHANGED
data/lib/rupnp/cp/base.rb
CHANGED
@@ -118,14 +118,16 @@ module RUPNP
|
|
118
118
|
# Get device from its description
|
119
119
|
# @return [void]
|
120
120
|
def fetch
|
121
|
+
upnp_minor_ver = @notification['server'].match(/UPnP\/1\.(\d)/)[1].to_i
|
121
122
|
if @notification['nextbootid.upnp.org']
|
122
123
|
@boot_id = @notification['nextbootid.upnp.org'].to_i
|
123
124
|
elsif @notification['bootid.upnp.org']
|
124
125
|
@boot_id = @notification['bootid.upnp.org'].to_i
|
125
|
-
|
126
|
+
elsif upnp_minor_ver > 0
|
126
127
|
fail self, 'no BOOTID.UPNP.ORG field. Message discarded.'
|
128
|
+
return
|
127
129
|
end
|
128
|
-
@config_id = @notification['
|
130
|
+
@config_id = @notification['configid.upnp.org']
|
129
131
|
@config_id = @config_id.to_i if @config_id
|
130
132
|
|
131
133
|
description_getter = EM::DefaultDeferrable.new
|
@@ -134,34 +136,30 @@ module RUPNP
|
|
134
136
|
msg = "Failed getting description"
|
135
137
|
log :error, "Fetching device: #{msg}"
|
136
138
|
fail self, msg
|
139
|
+
next
|
137
140
|
end
|
138
141
|
|
139
142
|
extract_from_ssdp_notification description_getter
|
140
143
|
|
141
144
|
description_getter.callback do |description|
|
142
145
|
@description = description
|
143
|
-
|
144
|
-
fail self, 'Blank description returned'
|
145
|
-
next
|
146
|
-
end
|
147
|
-
|
148
|
-
if bad_description?
|
146
|
+
if bad_description?
|
149
147
|
fail self, "Bad description returned: #@description"
|
150
148
|
next
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
extract_icons
|
149
|
+
else
|
150
|
+
extract_url_base
|
151
|
+
extract_device_info
|
152
|
+
extract_icons
|
156
153
|
|
157
|
-
|
158
|
-
|
159
|
-
|
154
|
+
@services_extracted = @devices_extracted = false
|
155
|
+
extract_services
|
156
|
+
extract_devices
|
160
157
|
|
161
|
-
|
162
|
-
|
158
|
+
tick_loop = EM.tick_loop do
|
159
|
+
:stop if @services_extracted and @devices_extracted
|
160
|
+
end
|
161
|
+
tick_loop.on_stop { succeed self }
|
163
162
|
end
|
164
|
-
tick_loop.on_stop { succeed self }
|
165
163
|
end
|
166
164
|
end
|
167
165
|
|
@@ -198,13 +196,14 @@ module RUPNP
|
|
198
196
|
|
199
197
|
def bad_description?
|
200
198
|
if @description[:root]
|
201
|
-
bd = false
|
202
199
|
@xmlns = @description[:root][:@xmlns]
|
203
|
-
|
204
|
-
|
200
|
+
return true unless @xmlns == 'urn:schemas-upnp-org:device-1-0'
|
201
|
+
return true unless @description[:root][:spec_version]
|
202
|
+
return true unless @description[:root][:spec_version][:major].to_i == 1
|
205
203
|
@upnp_version = @description[:root][:spec_version][:major] + '.'
|
206
204
|
@upnp_version += @description[:root][:spec_version][:minor]
|
207
|
-
|
205
|
+
return true unless @description[:root][:device]
|
206
|
+
false
|
208
207
|
else
|
209
208
|
true
|
210
209
|
end
|
@@ -294,10 +293,10 @@ module RUPNP
|
|
294
293
|
end
|
295
294
|
|
296
295
|
def update_expiration(notification)
|
297
|
-
@date =
|
298
|
-
@cache_control =
|
296
|
+
@date = notification['date'] || ''
|
297
|
+
@cache_control = notification['cache-control'] || ''
|
299
298
|
|
300
|
-
if
|
299
|
+
if notification['nts'] == 'ssdp:alive' or @cache_control != ''
|
301
300
|
max_age = @cache_control.match(/max-age\s*=\s*(\d+)/)[1].to_i
|
302
301
|
else
|
303
302
|
max_age = DEFAULT_MAX_AGE
|
@@ -64,6 +64,9 @@ module RUPNP
|
|
64
64
|
# Get service type
|
65
65
|
# @return [String]
|
66
66
|
attr_reader :type
|
67
|
+
# Get service id
|
68
|
+
# @return [String]
|
69
|
+
attr_reader :id
|
67
70
|
# URL for service description
|
68
71
|
# @return [String]
|
69
72
|
attr_reader :scpd_url
|
@@ -96,6 +99,7 @@ module RUPNP
|
|
96
99
|
@description = service
|
97
100
|
|
98
101
|
@type = service[:service_type].to_s
|
102
|
+
@id = service[:service_id].to_s
|
99
103
|
@scpd_url = build_url(url_base, service[:scpdurl].to_s)
|
100
104
|
@control_url = build_url(url_base, service[:control_url].to_s)
|
101
105
|
@event_sub_url = build_url(url_base, service[:event_sub_url].to_s)
|
data/lib/rupnp/ssdp/searcher.rb
CHANGED
@@ -39,7 +39,8 @@ module RUPNP
|
|
39
39
|
# @private
|
40
40
|
def receive_data(data)
|
41
41
|
port, ip = peer_info
|
42
|
-
log :debug, "Response from #{ip}:#{port}"
|
42
|
+
log :debug, "Response from #{ip}:#{port}:"
|
43
|
+
log :debug, data
|
43
44
|
|
44
45
|
response = StringIO.new(data)
|
45
46
|
if !is_http_status_ok?(response)
|
data/lib/rupnp.rb
CHANGED
data/spec/control_point_spec.rb
CHANGED
@@ -136,7 +136,17 @@ module RUPNP
|
|
136
136
|
end
|
137
137
|
end
|
138
138
|
|
139
|
-
it '#find_device_by_udn should get known devices'
|
139
|
+
it '#find_device_by_udn should get known devices' do
|
140
|
+
uuid1 = UUID.generate
|
141
|
+
cp.devices << double('rdevice1', :udn => uuid1)
|
142
|
+
uuid2 = UUID.generate
|
143
|
+
cp.devices << double('rdevice2', :udn => uuid2)
|
144
|
+
uuid3 = UUID.generate
|
145
|
+
|
146
|
+
expect(cp.find_device_by_udn(uuid1)).to eq(cp.devices[0])
|
147
|
+
expect(cp.find_device_by_udn(uuid2)).to eq(cp.devices[1])
|
148
|
+
expect(cp.find_device_by_udn(uuid3)).to be_nil
|
149
|
+
end
|
140
150
|
end
|
141
151
|
|
142
152
|
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
module RUPNP
|
4
|
+
module CP
|
5
|
+
|
6
|
+
describe RemoteDevice do
|
7
|
+
include EM::SpecHelper
|
8
|
+
|
9
|
+
DESCRIPTIONS =
|
10
|
+
['this is not a UPnP description',
|
11
|
+
'<?xml version="1.0"?><tag>UPnP</tag>',
|
12
|
+
'<?xml version="1.0"?><root xmlns="urn:schemas-upnp-org:device-0-9" configId="1"></root>',
|
13
|
+
'<?xml version="1.0"?><root xmlns="urn:schemas-upnp-org:device-1-0" configId="1"></root>',
|
14
|
+
'<?xml version="1.0"?><root xmlns="urn:schemas-upnp-org:device-1-0" configId="1"><spec_version></spec_version></root>',
|
15
|
+
'<?xml version="1.0"?><root xmlns="urn:schemas-upnp-org:device-1-0" configId="1"><spec_version><major>0</major><minor>9</minor></spec_version></root>',
|
16
|
+
'<?xml version="1.0"?><root xmlns="urn:schemas-upnp-org:device-1-0" configId="1"><spec_version><major>1</major><minor>9</minor></spec_version></root>',
|
17
|
+
'<?xml version="1.0"?><root xmlns="urn:schemas-upnp-org:device-1-0" configId="1"><spec_version><major>1</major><minor>9</minor></spec_version><device></device></root>',]
|
18
|
+
|
19
|
+
|
20
|
+
let(:location) { 'http://127.0.0.1:1234/root_description.xml' }
|
21
|
+
let(:uuid) { UUID.generate }
|
22
|
+
let(:max_age) { 1800 }
|
23
|
+
let(:notification) { {
|
24
|
+
'cache-control' => "max-age=#{max_age}",
|
25
|
+
'date' => Time.now.strftime("%a, %d %b %Y %H:%M:%S %Z"),
|
26
|
+
'ext' => '',
|
27
|
+
'location' => location,
|
28
|
+
'server' => 'OS/1.0 UPnP/1.1 TEST/1.0',
|
29
|
+
'st' => 'upnp:rootdevice',
|
30
|
+
'usn' => "uuid:#{uuid}::upnp:rootdevice",
|
31
|
+
'bootid.upnp.org' => 10,
|
32
|
+
'configid.upnp.org' => 23,
|
33
|
+
} }
|
34
|
+
let(:rd) { RemoteDevice.new(double('control_point'), notification)}
|
35
|
+
|
36
|
+
context '#fetch' do
|
37
|
+
|
38
|
+
it "should fail when notification has no BOOTID.UPNP.ORG field" do
|
39
|
+
notification.delete 'bootid.upnp.org'
|
40
|
+
em do
|
41
|
+
rd.errback do |dev, msg|
|
42
|
+
expect(dev).to eq(rd)
|
43
|
+
expect(msg).to match(/no BOOTID/)
|
44
|
+
done
|
45
|
+
end
|
46
|
+
rd.callback { fail 'RemoteDevice#fetch should not work' }
|
47
|
+
rd.fetch
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should accept headers without BOOTID for UPnP 1.0 response" do
|
52
|
+
notification.delete 'bootid.upnp.org'
|
53
|
+
notification['server'] = 'OS/1.0 UPnP/1.0 TEST/1.0'
|
54
|
+
em do
|
55
|
+
stub_request(:get, location).
|
56
|
+
to_return(:headers => { 'SERVER' => 'OS/1.0 UPnP/1.0 TEST/1.0'},
|
57
|
+
:body => generate_xml_device_description(uuid))
|
58
|
+
rd.errback { fail 'RemoteDevice#fetch should work' }
|
59
|
+
rd.callback { done }
|
60
|
+
rd.fetch
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should fail when location is unreachable" do
|
65
|
+
em do
|
66
|
+
stub_request(:get, location).to_timeout
|
67
|
+
rd.errback do |dev, msg|
|
68
|
+
expect(dev).to eq(rd)
|
69
|
+
expect(msg).to match(/Failed getting description/)
|
70
|
+
done
|
71
|
+
end
|
72
|
+
rd.callback { fail 'RemoteDevice#fetch should not work' }
|
73
|
+
rd.fetch
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should fail when description header is not a UPnP 1.x response" do
|
78
|
+
desc = generate_xml_device_description(uuid)
|
79
|
+
em do
|
80
|
+
stub_request(:get, location).
|
81
|
+
to_return(:body => generate_xml_device_description(uuid),
|
82
|
+
:headers => { 'SERVER' => 'Linux/1.2 Apache/1.0' },
|
83
|
+
:body => desc)
|
84
|
+
|
85
|
+
rd.errback do |dev, msg|
|
86
|
+
expect(dev).to eq(rd)
|
87
|
+
expect(msg).to match(/Failed getting description/)
|
88
|
+
|
89
|
+
stub_request(:get, location).
|
90
|
+
to_return(:headers => { 'SERVER' => 'OS/1.0 UPnP/0.9 TEST/1.0'},
|
91
|
+
:body => desc)
|
92
|
+
|
93
|
+
rd.errback do |dev, msg|
|
94
|
+
expect(dev).to eq(rd)
|
95
|
+
expect(msg).to match(/Failed getting description/)
|
96
|
+
done
|
97
|
+
end
|
98
|
+
rd.fetch
|
99
|
+
end
|
100
|
+
rd.callback { fail 'RemoteDevice#fetch should not work' }
|
101
|
+
rd.fetch
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should fail when description does not conform to UPnP spec" do
|
106
|
+
DESCRIPTIONS.each do |desc|
|
107
|
+
em do
|
108
|
+
stub_request(:get, location).
|
109
|
+
to_return(:headers => { 'SERVER' => 'OS/1.0 UPnP/1.1 TEST/1.0'},
|
110
|
+
:body => desc)
|
111
|
+
|
112
|
+
rd.errback do |dev, msg|
|
113
|
+
expect(dev).to eq(rd)
|
114
|
+
expect(msg).to match(/Bad description/)
|
115
|
+
done
|
116
|
+
end
|
117
|
+
rd.callback { fail 'RemoteDevice#fetch should not work' }
|
118
|
+
rd.fetch
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should fetch its description" do
|
124
|
+
em do
|
125
|
+
stub_request(:get, location).
|
126
|
+
to_return(:headers => { 'SERVER' => 'OS/1.0 UPnP/1.1 TEST/1.0'},
|
127
|
+
:body => generate_xml_device_description(uuid))
|
128
|
+
rd.errback { fail 'RemoteDevice#fetch should work' }
|
129
|
+
rd.callback do
|
130
|
+
done
|
131
|
+
end
|
132
|
+
rd.fetch
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should extract services if any"
|
137
|
+
it "should not fail when a service cannot be extracted"
|
138
|
+
end
|
139
|
+
|
140
|
+
context "#update" do
|
141
|
+
it 'should update expiration date' do
|
142
|
+
em do
|
143
|
+
stub_request(:get, location).
|
144
|
+
to_return(:headers => { 'SERVER' => 'OS/1.0 UPnP/1.1 TEST/1.0'},
|
145
|
+
:body => generate_xml_device_description(uuid))
|
146
|
+
rd.errback { fail 'RemoteDevice#fetch should work' }
|
147
|
+
rd.callback do
|
148
|
+
not2 = notification.dup
|
149
|
+
expiration_old = Time.parse(notification['date']) + max_age
|
150
|
+
|
151
|
+
not2['date'] = (Time.now + 5).strftime("%a, %d %b %Y %H:%M:%S %Z")
|
152
|
+
expiration_new = Time.parse(not2['date']) + max_age
|
153
|
+
|
154
|
+
expect(not2['date']).not_to eq(notification['date'])
|
155
|
+
expect { rd.update(not2) }.to change { rd.expiration }.
|
156
|
+
from(expiration_old).to(expiration_new)
|
157
|
+
done
|
158
|
+
end
|
159
|
+
rd.fetch
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'should update BOOTID' do
|
164
|
+
em do
|
165
|
+
stub_request(:get, location).
|
166
|
+
to_return(:headers => { 'SERVER' => 'OS/1.0 UPnP/1.1 TEST/1.0' },
|
167
|
+
:body => generate_xml_device_description(uuid))
|
168
|
+
rd.errback { fail 'RemoteDevice#fetch should work' }
|
169
|
+
rd.callback do
|
170
|
+
not2 = notification.merge('nextbootid.upnp.org' => 15)
|
171
|
+
expect { rd.update(not2) }.to change{ rd.boot_id }.from(10).to(15)
|
172
|
+
done
|
173
|
+
end
|
174
|
+
rd.fetch
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
it 'should update CONFIGID.UPNP.ORG' do
|
180
|
+
em do
|
181
|
+
stub_request(:get, location).
|
182
|
+
to_return(:headers => { 'SERVER' => 'OS/1.0 UPnP/1.1 TEST/1.0' },
|
183
|
+
:body => generate_xml_device_description(uuid))
|
184
|
+
rd.errback { fail 'RemoteDevice#fetch should work' }
|
185
|
+
rd.callback do
|
186
|
+
not2 = notification.merge('configid.upnp.org' => 47)
|
187
|
+
expect { rd.update(not2) }.to change{ rd.config_id }.
|
188
|
+
from(23).to(47)
|
189
|
+
done
|
190
|
+
end
|
191
|
+
rd.fetch
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -58,23 +58,39 @@ EOR
|
|
58
58
|
end
|
59
59
|
|
60
60
|
|
61
|
-
def generate_xml_device_description(uuid)
|
62
|
-
|
61
|
+
def generate_xml_device_description(uuid, options={})
|
62
|
+
opt = {
|
63
|
+
:version_major => 1,
|
64
|
+
:version_minor => 1,
|
65
|
+
:device_type => :base,
|
66
|
+
}.merge(options)
|
67
|
+
|
68
|
+
desc=<<EOD
|
63
69
|
<?xml version="1.0"?>
|
64
70
|
<root xmlns="urn:schemas-upnp-org:device-1-0" configId="1">
|
65
71
|
<specVersion>
|
66
|
-
<major
|
67
|
-
<minor
|
72
|
+
<major>#{opt[:version_major]}</major>
|
73
|
+
<minor>#{opt[:version_minor]}</minor>
|
68
74
|
</specVersion>
|
69
75
|
<device>
|
70
|
-
<deviceType>urn:schemas-upnp-org:device:
|
76
|
+
<deviceType>urn:schemas-upnp-org:device:#{opt[:device_type].capitalize}:1-0</deviceType>
|
71
77
|
<friendlyName>Friendly name</friendlyName>
|
72
78
|
<manufacturer>RUPNP</manufacturer>
|
73
79
|
<modelName>Model name</modelName>
|
74
80
|
<UDN>uuid:#{uuid}</UDN>
|
75
|
-
</device>
|
76
|
-
</root>
|
77
81
|
EOD
|
82
|
+
if opt[:device_type] != :base
|
83
|
+
desc << <<EOD
|
84
|
+
<serviceList>
|
85
|
+
<service>
|
86
|
+
<serviceType>usrn:schemas-upnp-org:service:ContentDirectory:1</serviceType>
|
87
|
+
<serviceId>urn:upnp-org:serviceId:</serviceId>
|
88
|
+
|
89
|
+
</service>
|
90
|
+
</serviceList>
|
91
|
+
EOD
|
92
|
+
end
|
93
|
+
desc << " </device>\n</root>\n"
|
78
94
|
end
|
79
95
|
|
80
96
|
|
data/tasks/gem.rake
CHANGED
@@ -28,7 +28,7 @@ EOF
|
|
28
28
|
s.add_dependency 'savon', '~>2.3.0'
|
29
29
|
s.add_dependency 'pry', '~>0.9.12'
|
30
30
|
|
31
|
-
s.add_development_dependency 'rspec', '~>2.14.
|
31
|
+
s.add_development_dependency 'rspec', '~>2.14.0'
|
32
32
|
s.add_development_dependency 'em-spec', '~>0.2.6'
|
33
33
|
s.add_development_dependency 'simplecov', '~>0.8.2'
|
34
34
|
s.add_development_dependency 'webmock', '~>1.16.1'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rupnp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-01-
|
12
|
+
date: 2014-01-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: uuid
|
@@ -114,7 +114,7 @@ dependencies:
|
|
114
114
|
requirements:
|
115
115
|
- - ~>
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: 2.14.
|
117
|
+
version: 2.14.0
|
118
118
|
type: :development
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -122,7 +122,7 @@ dependencies:
|
|
122
122
|
requirements:
|
123
123
|
- - ~>
|
124
124
|
- !ruby/object:Gem::Version
|
125
|
-
version: 2.14.
|
125
|
+
version: 2.14.0
|
126
126
|
- !ruby/object:Gem::Dependency
|
127
127
|
name: em-spec
|
128
128
|
requirement: !ruby/object:Gem::Requirement
|
@@ -185,6 +185,7 @@ files:
|
|
185
185
|
- spec/ssdp/listener_spec.rb
|
186
186
|
- spec/ssdp/notifier_spec.rb
|
187
187
|
- spec/ssdp/searcher_spec.rb
|
188
|
+
- spec/cp/remote_device_spec.rb
|
188
189
|
- spec/control_point_spec.rb
|
189
190
|
- spec/spec_helper.rb
|
190
191
|
- lib/rupnp.rb
|