playful 0.1.0.alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|