frisky 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+ require 'frisky/ssdp/notifier'
3
+
4
+
5
+ describe Frisky::SSDP::Notifier do
6
+ around(:each) do |example|
7
+ EM.synchrony do
8
+ example.run
9
+ EM.stop
10
+ end
11
+ end
12
+
13
+ let(:nt) { 'en tee' }
14
+ let(:usn) { 'you ess en' }
15
+ let(:ddf_url) { 'ddf url' }
16
+ let(:duration) { 567 }
17
+
18
+ subject do
19
+ Frisky::SSDP::Notifier.new(1, nt, usn, ddf_url, duration)
20
+ end
21
+
22
+ describe '#initialize' do
23
+ it 'creates a notification' do
24
+ expect_any_instance_of(Frisky::SSDP::Notifier).to receive(:notification).
25
+ with(nt, usn, ddf_url, duration)
26
+
27
+ subject
28
+ end
29
+ end
30
+
31
+ describe '#post_init' do
32
+ context 'send_datagram returns positive value' do
33
+ before do
34
+ expect(subject).to receive(:send_datagram).and_return 1
35
+ end
36
+
37
+ it 'logs what was sent' do
38
+ expect(subject).to receive(:log).with /Sent notification/
39
+
40
+ subject.post_init
41
+ end
42
+ end
43
+
44
+ context 'send_datagram returns 0' do
45
+ before do
46
+ expect(subject).to receive(:send_datagram).and_return 0
47
+ end
48
+
49
+ it 'does not log what was sent' do
50
+ expect(subject).to_not receive(:log)
51
+
52
+ subject.post_init
53
+ end
54
+ end
55
+ end
56
+
57
+ describe '#notification' do
58
+ before do
59
+ subject.instance_variable_set(:@os, 'my OS')
60
+ end
61
+
62
+ it 'builds the notification message' do
63
+ expect(subject.notification(nt, usn, ddf_url, duration)).to eq <<-NOTE
64
+ NOTIFY * HTTP/1.1\r
65
+ HOST: 239.255.255.250:1900\r
66
+ CACHE-CONTROL: max-age=567\r
67
+ LOCATION: ddf url\r
68
+ NT: en tee\r
69
+ NTS: ssdp:alive\r
70
+ SERVER: my OS UPnP/1.0 Frisky/#{Frisky::VERSION}\r
71
+ USN: you ess en\r
72
+ \r
73
+ NOTE
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,110 @@
1
+ require 'spec_helper'
2
+ require 'frisky/ssdp/searcher'
3
+
4
+
5
+ describe Frisky::SSDP::Searcher do
6
+ around(:each) do |example|
7
+ EM.synchrony do
8
+ example.run
9
+ EM.stop
10
+ end
11
+ end
12
+
13
+ before do
14
+ Frisky.logging_enabled = false
15
+ allow_any_instance_of(Frisky::SSDP::MulticastConnection).to receive(:setup_multicast_socket)
16
+ end
17
+
18
+ subject do
19
+ Frisky::SSDP::Searcher.new(1, 'ssdp:all', {})
20
+ end
21
+
22
+ it 'lets you read its responses' do
23
+ responses = double 'responses'
24
+ subject.instance_variable_set(:@discovery_responses, responses)
25
+ expect(subject.discovery_responses).to eq responses
26
+ end
27
+
28
+ describe '#initialize' do
29
+ it 'does an #m_search' do
30
+ expect_any_instance_of(Frisky::SSDP::Searcher).to receive(:m_search).and_return(<<-MSEARCH
31
+ M-SEARCH * HTTP/1.1\r
32
+ HOST: 239.255.255.250:1900\r
33
+ MAN: "ssdp:discover"\r
34
+ MX: 5\r
35
+ ST: ssdp:all\r
36
+ \r
37
+ MSEARCH
38
+ )
39
+ subject
40
+ end
41
+ end
42
+
43
+ describe '#receive_data' do
44
+ let(:parsed_response) do
45
+ parsed_response = double 'parsed response'
46
+ expect(parsed_response).to receive(:has_key?).with(:nts).and_return false
47
+ expect(parsed_response).to receive(:[]).and_return false
48
+
49
+ parsed_response
50
+ end
51
+
52
+ it 'takes a response and adds it to the list of responses' do
53
+ response = double 'response'
54
+ allow(subject).to receive(:peer_info).and_return(['0.0.0.0', 4567])
55
+
56
+ expect(subject).to receive(:parse).with(response).exactly(1).times.
57
+ and_return(parsed_response)
58
+ expect(subject.instance_variable_get(:@discovery_responses)).to receive(:<<).
59
+ with(parsed_response)
60
+
61
+ subject.receive_data(response)
62
+ end
63
+ end
64
+
65
+ describe '#post_init' do
66
+ before { allow_any_instance_of(Frisky::SSDP::Searcher).to receive(:m_search).and_return('hi') }
67
+
68
+ it 'sends an M-SEARCH as a datagram over 239.255.255.250:1900' do
69
+ m_search_count_times = subject.instance_variable_get(:@m_search_count)
70
+ expect(subject).to receive(:send_datagram).
71
+ with('hi', '239.255.255.250', 1900).
72
+ exactly(m_search_count_times).times.
73
+ and_return 0
74
+ subject.post_init
75
+ end
76
+ end
77
+
78
+ describe '#m_search' do
79
+ it 'builds the MSEARCH string using the given parameters' do
80
+ expect(subject.m_search('ssdp:all', 10)).to eq <<-MSEARCH
81
+ M-SEARCH * HTTP/1.1\r
82
+ HOST: 239.255.255.250:1900\r
83
+ MAN: "ssdp:discover"\r
84
+ MX: 10\r
85
+ ST: ssdp:all\r
86
+ \r
87
+ MSEARCH
88
+ end
89
+
90
+ it 'uses 239.255.255.250 as the HOST IP' do
91
+ expect(subject.m_search('ssdp:all', 10)).to match(/HOST: 239.255.255.250/m)
92
+ end
93
+
94
+ it 'uses 1900 as the HOST port' do
95
+ expect(subject.m_search('ssdp:all', 10)).to match(/HOST:.*1900/m)
96
+ end
97
+
98
+ it 'lets you search for undefined search target types' do
99
+ expect(subject.m_search('spaceship', 10)).to eq <<-MSEARCH
100
+ M-SEARCH * HTTP/1.1\r
101
+ HOST: 239.255.255.250:1900\r
102
+ MAN: "ssdp:discover"\r
103
+ MX: 10\r
104
+ ST: spaceship\r
105
+ \r
106
+ MSEARCH
107
+ end
108
+ end
109
+ end
110
+
@@ -0,0 +1,214 @@
1
+ require 'spec_helper'
2
+ require 'frisky/ssdp'
3
+
4
+
5
+ describe Frisky::SSDP do
6
+ subject { Frisky::SSDP }
7
+
8
+ describe '.listen' do
9
+ let(:listener) do
10
+ searcher = double 'Frisky::SSDP::Listener'
11
+ searcher.stub_chain(:alive_notifications, :pop).and_yield(%w[one two])
12
+ searcher.stub_chain(:byebye_notifications, :pop).and_yield(%w[three four])
13
+
14
+ searcher
15
+ end
16
+
17
+ before do
18
+ allow(EM).to receive(:run).and_yield
19
+ allow(EM).to receive(:add_timer)
20
+ allow(EM).to receive(:open_datagram_socket).and_return listener
21
+ end
22
+
23
+ context 'reactor is already running' do
24
+ it 'returns a Frisky::SSDP::Listener' do
25
+ allow(EM).to receive(:reactor_running?).and_return true
26
+ expect(subject.listen).to eq listener
27
+ end
28
+ end
29
+
30
+ context 'reactor is not already running' do
31
+ it 'returns a Hash of available and byebye responses' do
32
+ allow(EM).to receive(:add_shutdown_hook).and_yield
33
+ expect(subject.listen).to eq({
34
+ alive_notifications: %w[one two],
35
+ byebye_notifications: %w[three four]
36
+ })
37
+ end
38
+
39
+ it 'opens a UDP socket on 239.255.255.250, port 1900' do
40
+ allow(EM).to receive(:add_shutdown_hook)
41
+ expect(EM).to receive(:open_datagram_socket).with('239.255.255.250', 1900,
42
+ Frisky::SSDP::Listener, 4)
43
+ subject.listen
44
+ end
45
+ end
46
+ end
47
+
48
+ describe '.search' do
49
+ let(:multicast_searcher) do
50
+ searcher = double 'Frisky::SSDP::Searcher'
51
+ searcher.stub_chain(:discovery_responses, :subscribe).and_yield(%w[one two])
52
+
53
+ searcher
54
+ end
55
+
56
+ let(:broadcast_searcher) do
57
+ searcher = double 'Frisky::SSDP::BroadcastSearcher'
58
+ searcher.stub_chain(:discovery_responses, :subscribe).and_yield(%w[three four])
59
+
60
+ searcher
61
+ end
62
+
63
+ before do
64
+ allow(EM).to receive(:run).and_yield
65
+ allow(EM).to receive(:add_timer)
66
+ allow(EM).to receive(:open_datagram_socket).and_return multicast_searcher
67
+ end
68
+
69
+ context 'when search_target is not a String' do
70
+ it 'calls #to_upnp_s on search_target' do
71
+ search_target = double('search_target')
72
+ expect(search_target).to receive(:to_upnp_s)
73
+ subject.search(search_target)
74
+ end
75
+ end
76
+
77
+ context 'when search_target is a String' do
78
+ it 'calls #to_upnp_s on search_target but does not alter it' do
79
+ search_target = "I'm a string"
80
+ expect(search_target).to receive(:to_upnp_s).and_call_original
81
+
82
+ expect(EM).to receive(:open_datagram_socket).with('0.0.0.0', 0,
83
+ Frisky::SSDP::Searcher, "I'm a string", {})
84
+ subject.search(search_target)
85
+ end
86
+ end
87
+
88
+ context 'reactor is already running' do
89
+ it 'returns a Frisky::SSDP::Searcher' do
90
+ allow(EM).to receive(:reactor_running?).and_return true
91
+ expect(subject.search).to eq multicast_searcher
92
+ end
93
+ end
94
+
95
+ context 'reactor is not already running' do
96
+ context 'options hash includes do_broadcast_search' do
97
+ before do
98
+ allow(EM).to receive(:open_datagram_socket).
99
+ and_return(multicast_searcher, broadcast_searcher)
100
+ end
101
+
102
+ it 'returns an Array of responses' do
103
+ allow(EM).to receive(:add_shutdown_hook).and_yield
104
+ expect(subject.search(:all, do_broadcast_search: true)).to eq %w[one two three four]
105
+ end
106
+
107
+ it 'opens 2 UDP sockets on 0.0.0.0, port 0' do
108
+ allow(EM).to receive(:add_shutdown_hook)
109
+ expect(EM).to receive(:open_datagram_socket).with('0.0.0.0', 0, Frisky::SSDP::Searcher,
110
+ 'ssdp:all', {})
111
+ expect(EM).to receive(:open_datagram_socket).with('0.0.0.0', 0, Frisky::SSDP::BroadcastSearcher,
112
+ 'ssdp:all', 5, 4)
113
+ subject.search(:all, do_broadcast_search: true)
114
+ end
115
+ end
116
+
117
+ context 'options hash does not include do_broadcast_search' do
118
+ it 'returns an Array of responses' do
119
+ allow(EM).to receive(:add_shutdown_hook).and_yield
120
+ expect(subject.search).to eq %w[one two]
121
+ end
122
+
123
+ it 'opens a UDP socket on 0.0.0.0, port 0' do
124
+ allow(EM).to receive(:add_shutdown_hook)
125
+ expect(EM).to receive(:open_datagram_socket).with('0.0.0.0', 0, Frisky::SSDP::Searcher,
126
+ 'ssdp:all', {})
127
+ subject.search
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ describe '.notify' do
134
+ pending 'Implementation of UPnP Devices'
135
+ end
136
+
137
+ describe '.send_notification' do
138
+ pending 'Implementation of UPnP Devices'
139
+ end
140
+
141
+ =begin
142
+ context 'by default' do
143
+ it "searches for 'ssdp:all'" do
144
+ pending
145
+ end
146
+
147
+ it 'waits for 5 seconds for responses' do
148
+ before = Time.now
149
+
150
+ SSDP.search
151
+
152
+ after = Time.now
153
+ (after - before).should < 5.1
154
+ (after - before).should > 5.0
155
+ end
156
+ end
157
+
158
+ context "finds 'upnp:rootdevice's" do
159
+ it "by using the spec's string 'upnp:rootdevice'" do
160
+ SSDP.search('upnp:rootdevice').should == [
161
+ {
162
+ :cache_control=>'max-age=1200',
163
+ :date=>'Mon, 26 Sep 2011 06:40:19 GMT',
164
+ :location=>'http://192.168.10.3:5001/description/fetch',
165
+ :server=>'Linux-i386-2.6.38-10-generic-pae, UPnP/1.0, PMS/1.25.1',
166
+ :st=>'upnp:rootdevice',
167
+ :ext=>'',
168
+ :usn=>'uuid:3c202906-992d-3f0f-b94c-90e1902a136d::upnp:rootdevice',
169
+ :content_length=>'0'
170
+ }
171
+ ]
172
+ end
173
+
174
+ it 'by using :root' do
175
+ pending
176
+ end
177
+ end
178
+
179
+ it 'can wait for user-defined seconds for responses' do
180
+ before = Time.now
181
+
182
+ SSDP.search(:all, 1)
183
+
184
+ after = Time.now
185
+ (after - before).should < 1.1
186
+ (after - before).should > 1.0
187
+ end
188
+
189
+ it 'finds a device by its URN' do
190
+ pending
191
+ end
192
+
193
+ it 'finds a device by its UUID' do
194
+ pending
195
+ end
196
+
197
+ it 'finds a device by its UPnP device type' do
198
+ pending
199
+ end
200
+
201
+ it 'finds a device by its UPnP device type using a non-standard domain name' do
202
+ pending
203
+ end
204
+
205
+ it 'finds a service by its UPnP service type' do
206
+ pending
207
+ end
208
+
209
+ it 'find a service by its UPnP service type using a non-standard domain name' do
210
+ pending
211
+ end
212
+ =end
213
+ end
214
+
data/tasks/search.thor ADDED
@@ -0,0 +1,35 @@
1
+ require 'pp'
2
+ require File.expand_path(File.dirname(__FILE__)+ '/../lib/frisky/ssdp')
3
+ require File.expand_path(File.dirname(__FILE__)+ '/../lib/frisky/logger')
4
+
5
+ module Frisky
6
+ class Ssdp < Thor
7
+ #---------------------------------------------------------------------------
8
+ # search
9
+ #---------------------------------------------------------------------------
10
+ desc 'search TARGET', 'Searches for devices of type TARGET'
11
+ method_option :response_wait_time, default: 5
12
+ method_option :ttl, default: 4
13
+ method_option :do_broadcast_search, type: :boolean
14
+ method_option :log, type: :boolean
15
+ def search(target='upnp:rootdevice')
16
+ ::Frisky.logging_enabled = options[:log]
17
+ time_before = Time.now
18
+ results = ::Frisky::SSDP.search(target, options.dup)
19
+ .map { |r| r[:location] }
20
+ .sort
21
+ unique = results.uniq
22
+ time_after = Time.now
23
+
24
+ puts <<-RESULTS
25
+ size: #{results.size}
26
+ locations: #{results.join("\n ")}
27
+ unique size: #{unique.size}
28
+ unique locations: #{unique.join("\n ")}
29
+ search duration: #{time_after - time_before}
30
+ RESULTS
31
+
32
+ results
33
+ end
34
+ end
35
+ end
metadata ADDED
@@ -0,0 +1,181 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: frisky
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jon Frisby
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-09-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: eventmachine
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: em-http-request
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 1.0.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: em-synchrony
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: nori
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 2.0.2
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 2.0.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: log_switch
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.0.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.0.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: savon
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.0'
97
+ description: |-
98
+ frisky provides the tools you need to build an app that runs
99
+ in a UPnP environment.
100
+ email: jfrisby@mrjoy.com
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files:
104
+ - History.md
105
+ - README.md
106
+ - LICENSE.md
107
+ files:
108
+ - ".gemtest"
109
+ - ".gitignore"
110
+ - ".rspec"
111
+ - ".travis.yml"
112
+ - Gemfile
113
+ - History.md
114
+ - LICENSE.md
115
+ - README.md
116
+ - Rakefile
117
+ - features/device_discovery.feature
118
+ - features/step_definitions/.gitkeep
119
+ - features/support/env.rb
120
+ - features/support/world_extensions.rb
121
+ - frisky.gemspec
122
+ - lib/core_ext/hash_patch.rb
123
+ - lib/core_ext/socket_patch.rb
124
+ - lib/core_ext/to_upnp_s.rb
125
+ - lib/frisky.rb
126
+ - lib/frisky/logger.rb
127
+ - lib/frisky/ssdp.rb
128
+ - lib/frisky/ssdp/broadcast_searcher.rb
129
+ - lib/frisky/ssdp/error.rb
130
+ - lib/frisky/ssdp/listener.rb
131
+ - lib/frisky/ssdp/multicast_connection.rb
132
+ - lib/frisky/ssdp/network_constants.rb
133
+ - lib/frisky/ssdp/notifier.rb
134
+ - lib/frisky/ssdp/searcher.rb
135
+ - lib/frisky/version.rb
136
+ - spec/spec_helper.rb
137
+ - spec/support/search_responses.rb
138
+ - spec/unit/core_ext/to_upnp_s_spec.rb
139
+ - spec/unit/frisky/ssdp/listener_spec.rb
140
+ - spec/unit/frisky/ssdp/multicast_connection_spec.rb
141
+ - spec/unit/frisky/ssdp/notifier_spec.rb
142
+ - spec/unit/frisky/ssdp/searcher_spec.rb
143
+ - spec/unit/frisky/ssdp_spec.rb
144
+ - tasks/search.thor
145
+ homepage: http://github.com/MrJoy/frisky
146
+ licenses: []
147
+ metadata: {}
148
+ post_install_message:
149
+ rdoc_options: []
150
+ require_paths:
151
+ - lib
152
+ required_ruby_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: 1.9.1
157
+ required_rubygems_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ requirements: []
163
+ rubyforge_project:
164
+ rubygems_version: 2.4.5.1
165
+ signing_key:
166
+ specification_version: 4
167
+ summary: Use me to build a UPnP app!
168
+ test_files:
169
+ - features/device_discovery.feature
170
+ - features/step_definitions/.gitkeep
171
+ - features/support/env.rb
172
+ - features/support/world_extensions.rb
173
+ - spec/spec_helper.rb
174
+ - spec/support/search_responses.rb
175
+ - spec/unit/core_ext/to_upnp_s_spec.rb
176
+ - spec/unit/frisky/ssdp/listener_spec.rb
177
+ - spec/unit/frisky/ssdp/multicast_connection_spec.rb
178
+ - spec/unit/frisky/ssdp/notifier_spec.rb
179
+ - spec/unit/frisky/ssdp/searcher_spec.rb
180
+ - spec/unit/frisky/ssdp_spec.rb
181
+ has_rdoc: