playful 0.1.0.alpha.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.
- checksums.yaml +7 -0
- data/.gemtest +0 -0
- data/.gitignore +19 -0
- data/.rspec +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/History.rdoc +3 -0
- data/LICENSE.rdoc +22 -0
- data/README.rdoc +194 -0
- data/Rakefile +20 -0
- data/features/control_point.feature +13 -0
- data/features/device.feature +22 -0
- data/features/device_discovery.feature +9 -0
- data/features/step_definitions/control_point_steps.rb +19 -0
- data/features/step_definitions/device_discovery_steps.rb +40 -0
- data/features/step_definitions/device_steps.rb +28 -0
- data/features/support/common.rb +9 -0
- data/features/support/env.rb +17 -0
- data/features/support/fake_upnp_device_collection.rb +108 -0
- data/features/support/world_extensions.rb +15 -0
- data/lib/core_ext/hash_patch.rb +5 -0
- data/lib/core_ext/socket_patch.rb +16 -0
- data/lib/core_ext/to_upnp_s.rb +65 -0
- data/lib/playful.rb +5 -0
- data/lib/playful/control_point.rb +175 -0
- data/lib/playful/control_point/base.rb +74 -0
- data/lib/playful/control_point/device.rb +511 -0
- data/lib/playful/control_point/error.rb +13 -0
- data/lib/playful/control_point/service.rb +404 -0
- data/lib/playful/device.rb +28 -0
- data/lib/playful/logger.rb +8 -0
- data/lib/playful/ssdp.rb +195 -0
- data/lib/playful/ssdp/broadcast_searcher.rb +114 -0
- data/lib/playful/ssdp/error.rb +6 -0
- data/lib/playful/ssdp/listener.rb +38 -0
- data/lib/playful/ssdp/multicast_connection.rb +112 -0
- data/lib/playful/ssdp/network_constants.rb +17 -0
- data/lib/playful/ssdp/notifier.rb +41 -0
- data/lib/playful/ssdp/searcher.rb +87 -0
- data/lib/playful/version.rb +3 -0
- data/lib/rack/upnp_control_point.rb +70 -0
- data/playful.gemspec +38 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/search_responses.rb +134 -0
- data/spec/unit/core_ext/to_upnp_s_spec.rb +105 -0
- data/spec/unit/playful/control_point/device_spec.rb +7 -0
- data/spec/unit/playful/control_point_spec.rb +45 -0
- data/spec/unit/playful/ssdp/listener_spec.rb +29 -0
- data/spec/unit/playful/ssdp/multicast_connection_spec.rb +157 -0
- data/spec/unit/playful/ssdp/notifier_spec.rb +76 -0
- data/spec/unit/playful/ssdp/searcher_spec.rb +110 -0
- data/spec/unit/playful/ssdp_spec.rb +214 -0
- data/tasks/control_point.html +30 -0
- data/tasks/control_point.thor +43 -0
- data/tasks/search.thor +128 -0
- data/tasks/test_js/FABridge.js +1425 -0
- data/tasks/test_js/WebSocketMain.swf +807 -0
- data/tasks/test_js/swfobject.js +825 -0
- data/tasks/test_js/web_socket.js +1133 -0
- data/test/test_ssdp.rb +298 -0
- data/test/test_ssdp_notification.rb +74 -0
- data/test/test_ssdp_response.rb +31 -0
- data/test/test_ssdp_search.rb +23 -0
- metadata +339 -0
data/lib/playful/ssdp.rb
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
require_relative '../core_ext/socket_patch'
|
2
|
+
require 'eventmachine'
|
3
|
+
require 'em-synchrony'
|
4
|
+
require_relative '../core_ext/to_upnp_s'
|
5
|
+
require_relative 'logger'
|
6
|
+
require_relative 'ssdp/error'
|
7
|
+
require_relative 'ssdp/network_constants'
|
8
|
+
require_relative 'ssdp/listener'
|
9
|
+
require_relative 'ssdp/searcher'
|
10
|
+
require_relative 'ssdp/notifier'
|
11
|
+
|
12
|
+
require_relative 'ssdp/broadcast_searcher'
|
13
|
+
|
14
|
+
module Playful
|
15
|
+
|
16
|
+
# This is the main class for doing SSDP stuff. You can have a look at child
|
17
|
+
# classes, but you'll probably want to just use these methods here.
|
18
|
+
#
|
19
|
+
# SSDP is "Simple Service Discovery Protocol", which lets you find and learn
|
20
|
+
# about UPnP devices on your network. Of the six "steps" of UPnP (given in
|
21
|
+
# the UPnP spec--that's counting step 0), SSDP is what provides step 1, or the
|
22
|
+
# "discovery" step.
|
23
|
+
#
|
24
|
+
# Before you can do anything with any of the UPnP devices on your network, you
|
25
|
+
# need to +search+ your network to see what devices are available. Once you've
|
26
|
+
# found what's available, you can then decide device(s) you'd like to control
|
27
|
+
# (that's where Control Points come in; take a look at Playful::ControlPoint).
|
28
|
+
# After searching, you should then +listen+ to the activity on your network.
|
29
|
+
# New devices on your network may come online (via +ssdp:alive+) and devices
|
30
|
+
# that you care about may go offline (via +ssdp:byebye+), in which case you
|
31
|
+
# probably shouldn't try to talk to them anymore.
|
32
|
+
#
|
33
|
+
# @todo Add docs for Playful::Device perspective.
|
34
|
+
class SSDP
|
35
|
+
include LogSwitch::Mixin
|
36
|
+
include NetworkConstants
|
37
|
+
|
38
|
+
# Opens a multicast UDP socket on 239.255.255.250:1900 and listens for
|
39
|
+
# alive and byebye notifications from devices.
|
40
|
+
#
|
41
|
+
# @param [Fixnum] ttl The TTL to use on the UDP socket.
|
42
|
+
#
|
43
|
+
# @return [Hash<Array>,Playful::SSDP::Listener] If the EventMachine reactor is
|
44
|
+
# _not_ running, it returns two key/value pairs--one for
|
45
|
+
# alive_notifications, one for byebye_notifications. If the reactor _is_
|
46
|
+
# running, it returns a Playful::SSDP::Listener so that that object can be
|
47
|
+
# used however desired. The latter method is used in Playful::ControlPoints
|
48
|
+
# so that an object of that type can keep track of devices it cares about.
|
49
|
+
def self.listen(ttl=TTL)
|
50
|
+
alive_notifications = Set.new
|
51
|
+
byebye_notifications = Set.new
|
52
|
+
|
53
|
+
listener = proc do
|
54
|
+
l = EM.open_datagram_socket(MULTICAST_IP, MULTICAST_PORT,
|
55
|
+
Playful::SSDP::Listener, ttl)
|
56
|
+
i = 0
|
57
|
+
EM.add_periodic_timer(5) { i += 5; Playful.log "Listening for #{i}\n" }
|
58
|
+
l
|
59
|
+
end
|
60
|
+
|
61
|
+
if EM.reactor_running?
|
62
|
+
return listener.call
|
63
|
+
else
|
64
|
+
EM.synchrony do
|
65
|
+
l = listener.call
|
66
|
+
|
67
|
+
alive_getter = Proc.new do |notification|
|
68
|
+
alive_notifications << notification
|
69
|
+
EM.next_tick { l.alive_notifications.pop(&live_getter) }
|
70
|
+
end
|
71
|
+
l.alive_notifications.pop(&alive_getter)
|
72
|
+
|
73
|
+
byebye_getter = Proc.new do |notification|
|
74
|
+
byebye_notifications << notification
|
75
|
+
EM.next_tick { l.byebye_notifications.pop(&byebye_getter) }
|
76
|
+
end
|
77
|
+
l.byebye_notifications.pop(&byebye_getter)
|
78
|
+
|
79
|
+
trap_signals
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
{
|
84
|
+
alive_notifications: alive_notifications.to_a.flatten,
|
85
|
+
byebye_notifications: byebye_notifications.to_a.flatten
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
# Opens a UDP socket on 0.0.0.0, on an ephemeral port, has Playful::SSDP::Searcher
|
90
|
+
# build and send the search request, then receives the responses. The search
|
91
|
+
# will stop after +response_wait_time+.
|
92
|
+
#
|
93
|
+
# @param [String] search_target
|
94
|
+
#
|
95
|
+
# @param [Hash] options
|
96
|
+
#
|
97
|
+
# @option options [Fixnum] response_wait_time
|
98
|
+
# @option options [Fixnum] ttl
|
99
|
+
# @option options [Fixnum] m_search_count
|
100
|
+
# @option options [Boolean] do_broadcast_search Tells the search call to also send
|
101
|
+
# a M-SEARCH over 255.255.255.255. This is *NOT* part of the UPnP spec;
|
102
|
+
# it's merely a hack for working with some types of devices that don't
|
103
|
+
# properly implement the UPnP spec.
|
104
|
+
#
|
105
|
+
# @return [Array<Hash>,Playful::SSDP::Searcher] Returns a Hash that represents
|
106
|
+
# the headers from the M-SEARCH response. Each one of these can be passed
|
107
|
+
# in to Playful::ControlPoint::Device.new to download the device's
|
108
|
+
# description file, parse it, and interact with the device's devices
|
109
|
+
# and/or services. If the reactor is already running this will return a
|
110
|
+
# a Playful::SSDP::Searcher which will make its accessors available so you
|
111
|
+
# can get responses in real time.
|
112
|
+
def self.search(search_target=:all, options = {})
|
113
|
+
response_wait_time = options[:response_wait_time] || 5
|
114
|
+
ttl = options[:ttl] || TTL
|
115
|
+
do_broadcast_search = options[:do_broadcast_search]
|
116
|
+
|
117
|
+
searcher_options = options
|
118
|
+
searcher_options.delete :do_broadcast_search
|
119
|
+
|
120
|
+
responses = []
|
121
|
+
search_target = search_target.to_upnp_s
|
122
|
+
|
123
|
+
multicast_searcher = proc do
|
124
|
+
EM.open_datagram_socket('0.0.0.0', 0, Playful::SSDP::Searcher,
|
125
|
+
search_target, searcher_options)
|
126
|
+
end
|
127
|
+
|
128
|
+
broadcast_searcher = proc do
|
129
|
+
EM.open_datagram_socket('0.0.0.0', 0, Playful::SSDP::BroadcastSearcher,
|
130
|
+
search_target, response_wait_time, ttl)
|
131
|
+
end
|
132
|
+
|
133
|
+
if EM.reactor_running?
|
134
|
+
return multicast_searcher.call
|
135
|
+
else
|
136
|
+
EM.synchrony do
|
137
|
+
ms = multicast_searcher.call
|
138
|
+
|
139
|
+
ms.discovery_responses.subscribe do |notification|
|
140
|
+
responses << notification
|
141
|
+
end
|
142
|
+
|
143
|
+
if do_broadcast_search
|
144
|
+
bs = broadcast_searcher.call
|
145
|
+
|
146
|
+
bs.discovery_responses.subscribe do |notification|
|
147
|
+
responses << notification
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
EM.add_timer(response_wait_time) { EM.stop }
|
152
|
+
trap_signals
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
responses.flatten
|
157
|
+
end
|
158
|
+
|
159
|
+
# @todo This is for Playful::Devices, which aren't implemented yet, and thus
|
160
|
+
# this may not be working.
|
161
|
+
def self.notify(notification_type, usn, ddf_url, valid_for_duration=1800)
|
162
|
+
responses = []
|
163
|
+
notification_type = notification_type.to_upnp_s
|
164
|
+
|
165
|
+
EM.synchrony do
|
166
|
+
s = send_notification(notification_type, usn, ddf_url, valid_for_duration)
|
167
|
+
EM.add_shutdown_hook { responses = s.discovery_responses }
|
168
|
+
|
169
|
+
EM.add_periodic_timer(valid_for_duration) do
|
170
|
+
s = send_notification(notification_type, usn, ddf_url, valid_for_duration)
|
171
|
+
end
|
172
|
+
|
173
|
+
trap_signals
|
174
|
+
end
|
175
|
+
|
176
|
+
responses
|
177
|
+
end
|
178
|
+
|
179
|
+
# @todo This is for Playful::Devices, which aren't implemented yet, and thus
|
180
|
+
# this may not be working.
|
181
|
+
def self.send_notification(notification_type, usn, ddf_url, valid_for_duration)
|
182
|
+
EM.open_datagram_socket('0.0.0.0', 0, Playful::SSDP::Notifier, notification_type,
|
183
|
+
usn, ddf_url, valid_for_duration)
|
184
|
+
end
|
185
|
+
|
186
|
+
private
|
187
|
+
|
188
|
+
# Traps INT, TERM, and HUP signals and stops the reactor.
|
189
|
+
def self.trap_signals
|
190
|
+
trap('INT') { EM.stop }
|
191
|
+
trap('TERM') { EM.stop }
|
192
|
+
trap('HUP') { EM.stop } if RUBY_PLATFORM !~ /mswin|mingw/
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require_relative '../../core_ext/socket_patch'
|
2
|
+
require_relative '../logger'
|
3
|
+
require_relative 'network_constants'
|
4
|
+
require 'ipaddr'
|
5
|
+
require 'socket'
|
6
|
+
require 'eventmachine'
|
7
|
+
|
8
|
+
|
9
|
+
# TODO: DRY this up!! (it's mostly the same as Playful::SSDP::MulticastConnection)
|
10
|
+
module Playful
|
11
|
+
class SSDP
|
12
|
+
class BroadcastSearcher < EventMachine::Connection
|
13
|
+
include LogSwitch::Mixin
|
14
|
+
include EventMachine::Deferrable
|
15
|
+
include Playful::SSDP::NetworkConstants
|
16
|
+
|
17
|
+
# @return [Array] The list of responses from the current discovery request.
|
18
|
+
attr_reader :discovery_responses
|
19
|
+
|
20
|
+
attr_reader :available_responses
|
21
|
+
attr_reader :byebye_responses
|
22
|
+
|
23
|
+
def initialize(search_target, response_wait_time, ttl=TTL)
|
24
|
+
@ttl = ttl
|
25
|
+
@discovery_responses = []
|
26
|
+
@alive_notifications = []
|
27
|
+
@byebye_notifications = []
|
28
|
+
|
29
|
+
setup_broadcast_socket
|
30
|
+
|
31
|
+
@search = m_search(search_target, response_wait_time)
|
32
|
+
end
|
33
|
+
|
34
|
+
def post_init
|
35
|
+
if send_datagram(@search, BROADCAST_IP, MULTICAST_PORT) > 0
|
36
|
+
log "Sent broadcast datagram search:\n#{@search}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def m_search(search_target, response_wait_time)
|
41
|
+
<<-MSEARCH
|
42
|
+
M-SEARCH * HTTP/1.1\r
|
43
|
+
HOST: #{MULTICAST_IP}:#{MULTICAST_PORT}\r
|
44
|
+
MAN: "ssdp:discover"\r
|
45
|
+
MX: #{response_wait_time}\r
|
46
|
+
ST: #{search_target}\r
|
47
|
+
\r
|
48
|
+
MSEARCH
|
49
|
+
end
|
50
|
+
|
51
|
+
# Gets the IP and port from the peer that just sent data.
|
52
|
+
#
|
53
|
+
# @return [Array<String,Fixnum>] The IP and port.
|
54
|
+
def peer_info
|
55
|
+
peer_bytes = get_peername[2, 6].unpack('nC4')
|
56
|
+
port = peer_bytes.first.to_i
|
57
|
+
ip = peer_bytes[1, 4].join('.')
|
58
|
+
|
59
|
+
[ip, port]
|
60
|
+
end
|
61
|
+
|
62
|
+
def receive_data(response)
|
63
|
+
ip, port = peer_info
|
64
|
+
log "Response from #{ip}:#{port}:\n#{response}\n"
|
65
|
+
parsed_response = parse(response)
|
66
|
+
|
67
|
+
if parsed_response.has_key? :nts
|
68
|
+
if parsed_response[:nts] == 'ssdp:alive'
|
69
|
+
@alive_notifications << parsed_response
|
70
|
+
elsif parsed_response[:nts] == 'ssdp:bye-bye'
|
71
|
+
@byebye_notifications << parsed_response
|
72
|
+
else
|
73
|
+
raise "Unknown NTS value: #{parsed_response[:nts]}"
|
74
|
+
end
|
75
|
+
else
|
76
|
+
@discovery_responses << parsed_response
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Converts the headers to a set of key-value pairs.
|
81
|
+
#
|
82
|
+
# @param [String] data The data to convert.
|
83
|
+
# @return [Hash] The converted data. Returns an empty Hash if it didn't
|
84
|
+
# know how to parse.
|
85
|
+
def parse(data)
|
86
|
+
new_data = {}
|
87
|
+
|
88
|
+
unless data =~ /\n/
|
89
|
+
log 'Received response as a single-line String. Discarding.'
|
90
|
+
log "Bad response looked like:\n#{data}"
|
91
|
+
return new_data
|
92
|
+
end
|
93
|
+
|
94
|
+
data.each_line do |line|
|
95
|
+
line =~ /(\S*):(.*)/
|
96
|
+
|
97
|
+
unless $1.nil?
|
98
|
+
key = $1
|
99
|
+
value = $2
|
100
|
+
key = key.gsub('-', '_').downcase.to_sym
|
101
|
+
new_data[key] = value.strip
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
new_data
|
106
|
+
end
|
107
|
+
|
108
|
+
# Sets Socket options to allow for brodcasting.
|
109
|
+
def setup_broadcast_socket
|
110
|
+
set_sock_opt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative 'multicast_connection'
|
2
|
+
|
3
|
+
|
4
|
+
class Playful::SSDP::Listener < Playful::SSDP::MulticastConnection
|
5
|
+
include LogSwitch::Mixin
|
6
|
+
|
7
|
+
# @return [EventMachine::Channel] Provides subscribers with notifications
|
8
|
+
# from devices that have come online (sent +ssdp:alive+ notifications).
|
9
|
+
attr_reader :alive_notifications
|
10
|
+
|
11
|
+
# @return [EventMachine::Channel] Provides subscribers with notifications
|
12
|
+
# from devices that have gone offline (sent +ssd:byebye+ notifications).
|
13
|
+
attr_reader :byebye_notifications
|
14
|
+
|
15
|
+
# This is the callback called by EventMachine when it receives data on the
|
16
|
+
# socket that's been opened for this connection. In this case, the method
|
17
|
+
# parses the SSDP notifications into Hashes and adds them to the
|
18
|
+
# appropriate EventMachine::Channel (provided as accessor methods). This
|
19
|
+
# effectively means that in each Channel, you get a Hash that represents
|
20
|
+
# the headers for each notification that comes in on the socket.
|
21
|
+
#
|
22
|
+
# @param [String] response The data received on this connection's socket.
|
23
|
+
def receive_data(response)
|
24
|
+
ip, port = peer_info
|
25
|
+
log "Response from #{ip}:#{port}:\n#{response}\n"
|
26
|
+
parsed_response = parse(response)
|
27
|
+
|
28
|
+
return unless parsed_response.has_key? :nts
|
29
|
+
|
30
|
+
if parsed_response[:nts] == 'ssdp:alive'
|
31
|
+
@alive_notifications << parsed_response
|
32
|
+
elsif parsed_response[:nts] == 'ssdp:byebye'
|
33
|
+
@byebye_notifications << parsed_response
|
34
|
+
else
|
35
|
+
raise "Unknown NTS value: #{parsed_response[:nts]}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require_relative '../../core_ext/socket_patch'
|
2
|
+
require_relative 'network_constants'
|
3
|
+
require_relative '../logger'
|
4
|
+
require_relative 'error'
|
5
|
+
require 'ipaddr'
|
6
|
+
require 'socket'
|
7
|
+
require 'eventmachine'
|
8
|
+
require 'em-synchrony'
|
9
|
+
|
10
|
+
|
11
|
+
module Playful
|
12
|
+
class SSDP
|
13
|
+
class MulticastConnection < EventMachine::Connection
|
14
|
+
include Playful::SSDP::NetworkConstants
|
15
|
+
include LogSwitch::Mixin
|
16
|
+
|
17
|
+
# @param [Fixnum] ttl The TTL value to use when opening the UDP socket
|
18
|
+
# required for SSDP actions.
|
19
|
+
def initialize(ttl=TTL)
|
20
|
+
@ttl = ttl
|
21
|
+
|
22
|
+
@discovery_responses = EM::Channel.new
|
23
|
+
@alive_notifications = EM::Channel.new
|
24
|
+
@byebye_notifications = EM::Channel.new
|
25
|
+
|
26
|
+
setup_multicast_socket
|
27
|
+
end
|
28
|
+
|
29
|
+
# Gets the IP and port from the peer that just sent data.
|
30
|
+
#
|
31
|
+
# @return [Array<String,Fixnum>] The IP and port.
|
32
|
+
def peer_info
|
33
|
+
peer_bytes = get_peername[2, 6].unpack('nC4')
|
34
|
+
port = peer_bytes.first.to_i
|
35
|
+
ip = peer_bytes[1, 4].join('.')
|
36
|
+
|
37
|
+
[ip, port]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Converts the headers to a set of key-value pairs.
|
41
|
+
#
|
42
|
+
# @param [String] data The data to convert.
|
43
|
+
# @return [Hash] The converted data. Returns an empty Hash if it didn't
|
44
|
+
# know how to parse.
|
45
|
+
def parse(data)
|
46
|
+
new_data = {}
|
47
|
+
|
48
|
+
unless data =~ /\n/
|
49
|
+
log 'Received response as a single-line String. Discarding.'
|
50
|
+
log "Bad response looked like:\n#{data}"
|
51
|
+
return new_data
|
52
|
+
end
|
53
|
+
|
54
|
+
data.each_line do |line|
|
55
|
+
line =~ /(\S+):(.*)/
|
56
|
+
|
57
|
+
unless $1.nil?
|
58
|
+
key = $1
|
59
|
+
value = $2
|
60
|
+
key = key.gsub('-', '_').downcase.to_sym
|
61
|
+
new_data[key] = value.strip
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
new_data
|
66
|
+
end
|
67
|
+
|
68
|
+
# Sets Socket options to allow for multicasting. If ENV["RUBY_UPNP_ENV"] is
|
69
|
+
# equal to "testing", then it doesn't turn off multicast looping.
|
70
|
+
def setup_multicast_socket
|
71
|
+
set_membership(IPAddr.new(MULTICAST_IP).hton +
|
72
|
+
IPAddr.new('0.0.0.0').hton)
|
73
|
+
set_multicast_ttl(@ttl)
|
74
|
+
set_ttl(@ttl)
|
75
|
+
|
76
|
+
unless ENV['RUBY_UPNP_ENV'] == 'testing'
|
77
|
+
switch_multicast_loop :off
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# @param [String] membership The network byte ordered String that represents
|
82
|
+
# the IP(s) that should join the membership group.
|
83
|
+
def set_membership(membership)
|
84
|
+
set_sock_opt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, membership)
|
85
|
+
end
|
86
|
+
|
87
|
+
# @param [Fixnum] ttl TTL to set IP_MULTICAST_TTL to.
|
88
|
+
def set_multicast_ttl(ttl)
|
89
|
+
set_sock_opt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_TTL,
|
90
|
+
[ttl].pack('i'))
|
91
|
+
end
|
92
|
+
|
93
|
+
# @param [Fixnum] ttl TTL to set IP_TTL to.
|
94
|
+
def set_ttl(ttl)
|
95
|
+
set_sock_opt(Socket::IPPROTO_IP, Socket::IP_TTL, [ttl].pack('i'))
|
96
|
+
end
|
97
|
+
|
98
|
+
# @param [Symbol] on_off Turn on/off multicast looping. Supply :on or :off.
|
99
|
+
def switch_multicast_loop(on_off)
|
100
|
+
hex_value = case on_off
|
101
|
+
when :on then "\001"
|
102
|
+
when "\001" then "\001"
|
103
|
+
when :off then "\000"
|
104
|
+
when "\000" then "\000"
|
105
|
+
else raise SSDP::Error, "Can't switch IP_MULTICAST_LOOP to '#{on_off}'"
|
106
|
+
end
|
107
|
+
|
108
|
+
set_sock_opt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_LOOP, hex_value)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|