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.
@@ -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