upnp-nickewing 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.gemtest +0 -0
  2. data/.gitignore +19 -0
  3. data/.infinity_test +5 -0
  4. data/.rspec +1 -0
  5. data/.travis.yml +3 -0
  6. data/Gemfile +6 -0
  7. data/History.rdoc +3 -0
  8. data/LICENSE.rdoc +22 -0
  9. data/README.rdoc +190 -0
  10. data/Rakefile +20 -0
  11. data/features/control_point.feature +13 -0
  12. data/features/device.feature +22 -0
  13. data/features/device_discovery.feature +9 -0
  14. data/features/step_definitions/control_point_steps.rb +19 -0
  15. data/features/step_definitions/device_discovery_steps.rb +40 -0
  16. data/features/step_definitions/device_steps.rb +28 -0
  17. data/features/support/common.rb +9 -0
  18. data/features/support/env.rb +17 -0
  19. data/features/support/fake_upnp_device_collection.rb +108 -0
  20. data/features/support/world_extensions.rb +15 -0
  21. data/lib/core_ext/hash_patch.rb +5 -0
  22. data/lib/core_ext/socket_patch.rb +16 -0
  23. data/lib/core_ext/to_upnp_s.rb +65 -0
  24. data/lib/rack/upnp_control_point.rb +70 -0
  25. data/lib/upnp.rb +6 -0
  26. data/lib/upnp/control_point.rb +175 -0
  27. data/lib/upnp/control_point/base.rb +74 -0
  28. data/lib/upnp/control_point/device.rb +511 -0
  29. data/lib/upnp/control_point/error.rb +13 -0
  30. data/lib/upnp/control_point/service.rb +404 -0
  31. data/lib/upnp/device.rb +28 -0
  32. data/lib/upnp/logger.rb +8 -0
  33. data/lib/upnp/ssdp.rb +194 -0
  34. data/lib/upnp/ssdp/broadcast_searcher.rb +114 -0
  35. data/lib/upnp/ssdp/error.rb +6 -0
  36. data/lib/upnp/ssdp/listener.rb +38 -0
  37. data/lib/upnp/ssdp/multicast_connection.rb +109 -0
  38. data/lib/upnp/ssdp/network_constants.rb +17 -0
  39. data/lib/upnp/ssdp/notifier.rb +41 -0
  40. data/lib/upnp/ssdp/searcher.rb +86 -0
  41. data/lib/upnp/ssdp/version.rb +3 -0
  42. data/lib/upnp/version.rb +3 -0
  43. data/spec/spec_helper.rb +12 -0
  44. data/spec/support/search_responses.rb +137 -0
  45. data/spec/unit/core_ext/to_upnp_s_spec.rb +105 -0
  46. data/spec/unit/upnp/control_point/device_spec.rb +7 -0
  47. data/spec/unit/upnp/control_point_spec.rb +45 -0
  48. data/spec/unit/upnp/ssdp/multicast_connection_spec.rb +158 -0
  49. data/spec/unit/upnp/ssdp/searcher_spec.rb +110 -0
  50. data/spec/unit/upnp/ssdp_spec.rb +214 -0
  51. data/tasks/control_point.html +30 -0
  52. data/tasks/control_point.thor +43 -0
  53. data/tasks/search.thor +128 -0
  54. data/tasks/test_js/FABridge.js +1425 -0
  55. data/tasks/test_js/WebSocketMain.swf +807 -0
  56. data/tasks/test_js/swfobject.js +825 -0
  57. data/tasks/test_js/web_socket.js +1133 -0
  58. data/test/test_ssdp.rb +298 -0
  59. data/test/test_ssdp_notification.rb +74 -0
  60. data/test/test_ssdp_response.rb +31 -0
  61. data/test/test_ssdp_search.rb +23 -0
  62. data/upnp.gemspec +43 -0
  63. metadata +403 -0
File without changes
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+
5
+ .autotest
6
+ .DS_Store
7
+ .loadpath
8
+ .project
9
+ .yardoc/
10
+ .bundle/
11
+ .idea/
12
+
13
+ coverage/
14
+ doc/
15
+ pkg/
16
+ tmp/
17
+
18
+ */.DS_Store
19
+ atlassian-ide-plugin.xml
@@ -0,0 +1,5 @@
1
+ infinity_test do
2
+ notifications :growl
3
+
4
+ use :rubies => %w(1.9.2 1.9.3), :test_framework => :rspec
5
+ end
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --format documentation --color
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "http://rubygems.org"
2
+ gemspec
3
+
4
+ gem 'coveralls', require: false
5
+ #gem "httpi", '>=2.0.0.rc1'
6
+ #gem 'em-synchrony'
@@ -0,0 +1,3 @@
1
+ === 0.1.0 TBD
2
+
3
+ * I do stuffs!
@@ -0,0 +1,22 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2012 Steve Loveless
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ 'Software'), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,190 @@
1
+ = upnp
2
+
3
+ * {Homepage}[http://github.com/turboladen/upnp]
4
+ * {UPnP Device Architecture Documentation}[http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0.pdf]
5
+
6
+ {<img src="https://travis-ci.org/turboladen/upnp.png?branch=master" alt="Build Status" />}[https://travis-ci.org/turboladen/upnp]
7
+ {<img src="https://coveralls.io/repos/turboladen/upnp/badge.png" alt="Coverage Status" />}[https://coveralls.io/r/turboladen/upnp]
8
+
9
+
10
+ == Description
11
+
12
+ Ruby's UPnP RubyGem was outdated in Ruby 1.9 when support for Soap4r was
13
+ dropped. This gem intends to fill that void for Ruby >= 1.9 and allow for SSDP
14
+ search, discovery, advertisement, the ability to act as a UPnP control point, as
15
+ well as provide UPnP devices and services.
16
+
17
+ This uses EventMachine[http://github.com/eventmachine/eventmachine], so if
18
+ you're not already, getting familiar with its concepts will be helpful here.
19
+
20
+ === Getting Started
21
+
22
+ I'm still working out the overall design of these components, and thus won't be
23
+ working towards a gem release until this settles down. As such, don't expect
24
+ interfaces to stay the same if you update. In the mean time, I do intend to
25
+ keep the master branch (mostly) stable, so please feel free to give it whirl and
26
+ report any issues you encounter.
27
+
28
+ * <code>gem install bundler</code>
29
+ * <code>bundle install</code>
30
+
31
+ == Features
32
+
33
+ === Implemented
34
+
35
+ * SSDP search, discovery. (almost settled down)
36
+ * Ability to act as a UPnP Control Point. (in progress)
37
+ * Rack middleware to allow for device access in a Rack app.
38
+
39
+ === Coming
40
+
41
+ * UPnP Devices & Services (server)
42
+
43
+ == Examples
44
+
45
+ Take a look at the +tasks+ directory; I've created some working examples using
46
+ Thor[https://github.com/wycats/thor]. You can get a list of these tasks by
47
+ doing `thor -T`.
48
+
49
+ There's also a more involved, in-progress, working example at
50
+ http://github.com/turboladen/upnp_cp_on_sinatra that uses the Rack middleware
51
+ to build a Sinatra app that allows for controling devices in your network.
52
+
53
+ === SSDP Searches
54
+
55
+ An SSDP search simply sends the M-SEARCH out to the multicast group and listens
56
+ for responses for a given (or default of 5 seconds) amount of time. The return
57
+ from this depends on if you're running it within an EventMachine reactor or not.
58
+ If not, it returns is an Array of responses as Hashes, where keys are the header
59
+ names, values are the header values. Take a look at the SSDP.search docs for
60
+ more on the options here.
61
+
62
+ require 'upnp/ssdp'
63
+
64
+ # Search for all devices (do an M-SEARCH with the ST header set to 'ssdp:all')
65
+ all_devices = UPnP::SSDP.search # this is default
66
+ all_devices = UPnP::SSDP.search 'ssdp:all' # or be explicit
67
+ all_devices = UPnP::SSDP.search :all # or use short-hand
68
+
69
+ # Search for root devices (do an M-SEARCH with ST header set to 'upnp:rootdevices')
70
+ root_devices = UPnP::SSDP.search 'upnp:rootdevices'
71
+ root_devices = UPnP::SSDP.search :root # or use short-hand
72
+
73
+ # Search for a device with a specific UUID
74
+ my_device = UPnP::SSDP.search 'uuid:3c202906-992d-3f0f-b94c-90e1902a136d'
75
+
76
+ # Search for devices of a specific type
77
+ my_media_server = UPnP::SSDP.search 'urn:schemas-upnp-org:device:MediaServer:1'
78
+
79
+ # All of these searches will return something that looks like
80
+ # => [
81
+ # {
82
+ # :control => "max-age=1200",
83
+ # :date => "Sun, 23 Sep 2012 20:31:48 GMT",
84
+ # :location => "http://192.168.10.3:5001/description/fetch",
85
+ # :server => "Linux-i386-2.6.38-15-generic-pae, UPnP/1.0, PMS/1.50.0",
86
+ # :st => "upnp:rootdevice",
87
+ # :ext => "",
88
+ # :usn => "uuid:3c202906-992d-3f0f-b94c-90e1902a136d::upnp:rootdevice",
89
+ # :length => "0"
90
+ # }
91
+ # ]
92
+
93
+ If you do the search inside of an EventMachine reactor, as the
94
+ UPnP::SSDP::Searcher receives and parses responses, it adds them to the accessor
95
+ #discovery_responses, which is an EventMachine::Channel. This lets you subscribe
96
+ to the resposnes and do what you want with them (most likely you'll want to create
97
+ UPnP::ControlPoint::Device objects so you can control your device) as you
98
+ receive them.
99
+
100
+ require 'upnp/ssdp'
101
+ require 'upnp/control_point/device'
102
+
103
+ EM.run do
104
+ searcher = UPnP::SSDP.search 'uuid:3c202906-992d-3f0f-b94c-90e1902a136d'
105
+
106
+ # Create a deferrable object that can be notified when the device we want
107
+ # has been found and created.
108
+ device_controller = EventMachine::DefaultDeferrable.new
109
+
110
+ # This callback will get called when the device_creator callback is called
111
+ # (which is called after the device has been created).
112
+ device_controller.callback do |device|
113
+ p device.service_list.first.class # UPnP::ControlPoint::Service
114
+ p device.service_list.first.service_type # "urn:schemas-upnp-org:service:ContentDirectory:1"
115
+
116
+ # SOAP actions are converted to Ruby methods--show those
117
+ p device.service_list.first.singleton_methods # [:GetSystemUpdateID, :Search, :GetSearchCapabilities, :GetSortCapabilities, :Browse]
118
+
119
+ # Call a SOAP method defined in the service. The response is extracted from the
120
+ # XML SOAP response and the value is converted from the UPnP dataType to
121
+ # the related Ruby type. Reponses are always contained in a Hash, so as
122
+ # to maintain the relation defined in the service.
123
+ p device.service_list.first.GetSystemUpdateID # { :Id => 1 }
124
+ end
125
+
126
+ # Note that you don't have to check for items in the Channel or for when the
127
+ # Channel is empty: EventMachine will pop objects off the Channel as soon as
128
+ # they're put there and stop when there are none left.
129
+ searcher.discovery_responses.pop do |notification|
130
+
131
+ # UPnP::ControlPoint::Device objects are EventMachine::Deferrables, so you
132
+ # need to define callback and errback blocks to handle when the Device
133
+ # object is done being created.
134
+ device_creator = UPnP::ControlPoint::Device.new(ssdp_notification: notification)
135
+
136
+ device_creator.errback do
137
+ puts "Failed creating the device."
138
+ exit!
139
+ end
140
+
141
+ device_creator.callback do |built_device|
142
+ puts "Device has been created now."
143
+
144
+ # This lets the device_controller know that the device has been created,
145
+ # calls its callback, and passes the built device to it.
146
+ device_controller.set_deferred_status(:succeeded, built_device)
147
+ end
148
+
149
+ # This actually starts the Device creation process and will call the
150
+ # callback or errback (above) when it's done.
151
+ device_creator.fetch
152
+ end
153
+ end
154
+
155
+ === ControlPoints
156
+
157
+ If you're wanting to control devices and their services, you'll probably be more
158
+ interested in using a UPnP::ControlPoint, instead of doing all that work (above)
159
+ to create a UPnP::ControlPoint::Device. The ControlPoint will handle doing the
160
+ search and device/service creation for you and will hand you over Devices to
161
+ control them (and present them in a UI, perhaps?) as you need. More to come on
162
+ this as the design settles down.
163
+
164
+ == Requirements
165
+
166
+ * Rubies (tested)
167
+ * 1.9.3
168
+ * Gems
169
+ * eventmachine
170
+ * nori
171
+ * log_switch
172
+ * savon
173
+ * thin
174
+ * Gems (development)
175
+ * bundler
176
+ * rspec
177
+ * simplecov
178
+ * tailor
179
+ * yard
180
+
181
+ == Install
182
+
183
+ # This won't work yet, as the gem has not yet been released...
184
+ $ gem install upnp
185
+
186
+ == Copyright
187
+
188
+ Copyright (c) 2012 sloveless
189
+
190
+ See LICENSE.rdoc for details.
@@ -0,0 +1,20 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'cucumber/rake/task'
4
+ require 'yard'
5
+
6
+
7
+ YARD::Rake::YardocTask.new
8
+ Cucumber::Rake::Task.new(:features)
9
+ RSpec::Core::RakeTask.new
10
+
11
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
12
+ spec.pattern = 'spec/**/*_spec.rb'
13
+ spec.rcov = true
14
+ end
15
+
16
+ # Alias for rubygems-test
17
+ task test: :spec
18
+
19
+ task default: :test
20
+
@@ -0,0 +1,13 @@
1
+ Feature: Control Point
2
+ As a consumer of UPnP devices and services
3
+ I want to act as a UPnP control point
4
+ So that I can control the devices and services that fulfill my needs
5
+
6
+ Scenario: Search for devices on startup
7
+ Given I have a non-local IP address
8
+ And a UDP port on that IP is free
9
+ When I create my control point
10
+ And tell it to find all root devices
11
+ And tell it to find all services
12
+ Then it gets a list of root devices
13
+ And it gets a list of services
@@ -0,0 +1,22 @@
1
+ Feature: Controlled Device
2
+ As a UPnP device user
3
+ I want to use the device that offers some service
4
+ So that I can consume that service
5
+
6
+ Scenario: Device added to the network
7
+ Given I have a non-local IP address
8
+ And a UDP port on that IP is free
9
+ When I start my device on that IP address and port
10
+ Then the device multicasts a discovery message
11
+
12
+ @negative
13
+ Scenario: Device startup without an IP
14
+ Given I don't have an IP address
15
+ When I start the device
16
+ Then I get an error message saying I don't have an IP address
17
+
18
+ Scenario: Device startup with a local-link IP
19
+ Given I have a local-link IP address
20
+ When I start the device
21
+ Then the device starts running normally
22
+
@@ -0,0 +1,9 @@
1
+ Feature: Device discovery
2
+ As a device control point, I want to be able to discover devices
3
+ so that I can use the services those devices provide
4
+
5
+ Scenario: A single root device
6
+ Given there's at least 1 root device in my network
7
+ When I come online
8
+ Then I should discover at least 1 root device
9
+ And the location of that device should match my fake device's location
@@ -0,0 +1,19 @@
1
+ When /^I create my control point$/ do
2
+ @control_point = UPnP::ControlPoint.new
3
+ end
4
+
5
+ When /^tell it to find all root devices$/ do
6
+ @control_point.find_devices(:root, 5)
7
+ end
8
+
9
+ When /^tell it to find all services$/ do
10
+ @control_point.find_services
11
+ end
12
+
13
+ Then /^it gets a list of root devices$/ do
14
+ @control_point.device_list.should_not be_empty
15
+ end
16
+
17
+ Then /^it gets a list of services$/ do
18
+ @control_point.services.should_not be_empty
19
+ end
@@ -0,0 +1,40 @@
1
+ require_relative '../support/fake_upnp_device_collection'
2
+ require 'cucumber/rspec/doubles'
3
+
4
+ Thread.abort_on_exception = true
5
+ UPnP::SSDP.log = false
6
+
7
+ Given /^there's at least (\d+) root device in my network$/ do |device_count|
8
+ fake_device_collection.respond_with = <<-ROOT_DEVICE
9
+ HTTP/1.1 200 OK\r
10
+ CACHE-CONTROL: max-age=1200\r
11
+ DATE: Mon, 26 Sep 2011 06:40:19 GMT\r
12
+ LOCATION: http://#{local_ip}:4567\r
13
+ SERVER: Linux-i386-2.6.38-10-generic-pae, UPnP/1.0, PMS/1.25.1\r
14
+ ST: upnp:rootdevice\r
15
+ EXT:\r
16
+ USN: uuid:3c202906-992d-3f0f-b94c-90e1902a136d::upnp:rootdevice\r
17
+ Content-Length: 0\r
18
+
19
+ ROOT_DEVICE
20
+
21
+ Thread.start { fake_device_collection.start_ssdp_listening }
22
+ Thread.start { fake_device_collection.start_serving_description }
23
+ sleep 0.2
24
+ end
25
+
26
+ When /^I come online$/ do
27
+ control_point.should be_a UPnP::ControlPoint
28
+ end
29
+
30
+ Then /^I should discover at least (\d+) root device$/ do |device_count|
31
+ control_point.find_devices(:root)
32
+ fake_device_collection.stop_ssdp_listening
33
+ fake_device_collection.stop_serving_description
34
+ control_point.device_list.should have_at_least(device_count.to_i).items
35
+ end
36
+
37
+ Then /^the location of that device should match my fake device's location$/ do
38
+ locations = control_point.device_list.map { |device| device[:location] }
39
+ locations.should include "http://#{local_ip}:4567"
40
+ end
@@ -0,0 +1,28 @@
1
+ When /^I start my device on that IP address and port$/ do
2
+ @device = UPnP::Device.new(@local_ip, @port)
3
+ @device.start.should be_true
4
+ end
5
+
6
+ Then /^the device multicasts a discovery message$/ do
7
+ pending # express the regexp above with the code you wish you had
8
+ end
9
+
10
+ Given /^I don't have an IP address$/ do
11
+ pending # express the regexp above with the code you wish you had
12
+ end
13
+
14
+ When /^I start the device$/ do
15
+ pending # express the regexp above with the code you wish you had
16
+ end
17
+
18
+ Then /^I get an error message saying I don't have an IP address$/ do
19
+ pending # express the regexp above with the code you wish you had
20
+ end
21
+
22
+ Given /^I have a local\-link IP address$/ do
23
+ pending # express the regexp above with the code you wish you had
24
+ end
25
+
26
+ Then /^the device starts running normally$/ do
27
+ pending # express the regexp above with the code you wish you had
28
+ end
@@ -0,0 +1,9 @@
1
+ Given /^I have a non-local IP address$/ do
2
+ @local_ip, @port = local_ip_and_port
3
+ @local_ip.should_not be_nil
4
+ @local_ip.should_not match /^127.0.0/
5
+ end
6
+
7
+ Given /^a UDP port on that IP is free$/ do
8
+ @port.should_not be_nil
9
+ end
@@ -0,0 +1,17 @@
1
+ require 'socket'
2
+ require 'log_buddy'
3
+ require_relative '../../lib/upnp/control_point'
4
+
5
+ def local_ip_and_port
6
+ orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true
7
+
8
+ UDPSocket.open do |s|
9
+ s.connect '64.233.187.99', 1
10
+ s.addr.last
11
+ [s.addr.last, s.addr[1]]
12
+ end
13
+ ensure
14
+ Socket.do_not_reverse_lookup = orig
15
+ end
16
+
17
+ ENV["RUBY_UPNP_ENV"] = "testing"