rupnp 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -156,7 +156,7 @@ module RUPNP
156
156
  end
157
157
 
158
158
  searcher.discovery_responses.subscribe do |notification|
159
- log :debug, 'receive a notification'
159
+ log :debug, "receive a notification:\n#{notification}"
160
160
  create_device notification
161
161
  end
162
162
  end
data/lib/rupnp/cp/base.rb CHANGED
@@ -50,6 +50,7 @@ module RUPNP
50
50
  unless h['SERVER'] =~ /UPnP\/1\.\d/
51
51
  log :error, "Not a supported UPnP response : #{h['SERVER']}"
52
52
  http.cancel_callback callback
53
+ http.fail
53
54
  end
54
55
  end
55
56
 
@@ -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
- else
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['confgid.upnp.org']
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
- unless description
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
- end
152
-
153
- extract_url_base
154
- extract_device_info
155
- extract_icons
149
+ else
150
+ extract_url_base
151
+ extract_device_info
152
+ extract_icons
156
153
 
157
- @services_extracted = @devices_extracted = false
158
- extract_services
159
- extract_devices
154
+ @services_extracted = @devices_extracted = false
155
+ extract_services
156
+ extract_devices
160
157
 
161
- tick_loop = EM.tick_loop do
162
- :stop if @services_extracted and @devices_extracted
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
- bd |= @xmlns != 'urn:schemas-upnp-org:device-1-0'
204
- bd |= @description[:root][:spec_version][:major].to_i != 1
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
- bd |= !@description[:root][:device]
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 = @notification['date'] || ''
298
- @cache_control = @notification['cache-control'] || ''
296
+ @date = notification['date'] || ''
297
+ @cache_control = notification['cache-control'] || ''
299
298
 
300
- if @notification['nts'] == 'ssdp:alive'
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)
@@ -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
@@ -6,7 +6,7 @@ require 'eventmachine-le'
6
6
  module RUPNP
7
7
 
8
8
  # RUPNP version
9
- VERSION = '0.2.0'
9
+ VERSION = '0.2.1'
10
10
 
11
11
  @logdev = STDERR
12
12
  @log_level = :info
@@ -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
- <<EOD
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>1</major>
67
- <minor>1</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:Base:1-0</deviceType>
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.7'
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.0
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-13 00:00:00.000000000 Z
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.7
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.7
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