em_ws_discovery 0.0.2
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/Gemfile +2 -0
- data/History.rdoc +7 -0
- data/README.rdoc +77 -0
- data/Rakefile +16 -0
- data/em_ws_discovery.gemspec +32 -0
- data/lib/ws_discovery.rb +61 -0
- data/lib/ws_discovery/core_ext/socket_patch.rb +16 -0
- data/lib/ws_discovery/error.rb +4 -0
- data/lib/ws_discovery/multicast_connection.rb +77 -0
- data/lib/ws_discovery/network_constants.rb +13 -0
- data/lib/ws_discovery/response.rb +104 -0
- data/lib/ws_discovery/searcher.rb +93 -0
- data/lib/ws_discovery/version.rb +3 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/ws_discovery/multicast_connection_spec.rb +108 -0
- data/spec/ws_discovery/response_spec.rb +118 -0
- data/spec/ws_discovery/searcher_spec.rb +85 -0
- data/spec/ws_discovery_spec.rb +43 -0
- metadata +229 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 93d68c56aa005b016b846239e46d917572eca2ae
|
4
|
+
data.tar.gz: a72c499cae86b83efac040c6dfbbaebc76a14643
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bdd03e310cb58aa1559e35f0c07b688cd18b5212dd341386dddaa228388fec0ab7372af1da98fbc1a24d34cdedfdd9cf6f22546214f6ac87561a57096ba937b8
|
7
|
+
data.tar.gz: bdb1976aba0ec6160ff7246a5a53ceafc86bce9615b2679385b8d72345fb77c30e4c8be1bfcb6fb1694c3d74c34d45c266b46734318f1f19907d1c4e379e1e5a
|
data/Gemfile
ADDED
data/History.rdoc
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
= ws_discovery
|
2
|
+
|
3
|
+
* {Homepage}[https://github.com/pelco-automation/ws-discovery]
|
4
|
+
* {WS-Discovery 1.0 Specification}[http://specs.xmlsoap.org/ws/2005/04/discovery/ws-discovery.pdf]
|
5
|
+
* {SOAP-over-UDP Specification}[http://specs.xmlsoap.org/ws/2004/09/soap-over-udp/soap-over-udp.pdf]
|
6
|
+
|
7
|
+
== Description
|
8
|
+
|
9
|
+
This gem aims to provide the ability to search for WS-Discovery compatible target
|
10
|
+
services.
|
11
|
+
|
12
|
+
This uses EventMachine[http://github.com/eventmachine/eventmachine], so if
|
13
|
+
you're not already, getting familiar with its concepts will be helpful here.
|
14
|
+
|
15
|
+
== Features
|
16
|
+
|
17
|
+
* Search for WS-Discovery compatible target services.
|
18
|
+
|
19
|
+
== Examples
|
20
|
+
|
21
|
+
=== WS-Discovery Searches
|
22
|
+
|
23
|
+
A WS-Discovery search simply sends the probe out to the multicast group and
|
24
|
+
listens for responses for a given (or default of 5 seconds) amount of time. The
|
25
|
+
return from this depends on if you're running it within an EventMachine reactor
|
26
|
+
or not. If not, it returns an Array of responses as WSDiscovery::Responses.
|
27
|
+
Take a look at the WSDiscovery#search docs for more on the options here.
|
28
|
+
|
29
|
+
require 'ws_discovery'
|
30
|
+
|
31
|
+
# Search for all devices (do a probe with Types left unspecified)
|
32
|
+
all_devices = WSDiscovery.search # this is default
|
33
|
+
|
34
|
+
# Search for devices of a specific Type
|
35
|
+
network_video_transmitters = WSDiscovery.search(
|
36
|
+
env_namespaces: { "xmlns:dn" => "http://www.onvif.org/ver10/network/wsdl" },
|
37
|
+
types: "dn:NetworkVideoTransmitter")
|
38
|
+
|
39
|
+
# These searches will return an Array of WSDiscovery::Responses. See the
|
40
|
+
# WSDiscovery::Response documentation for more information.
|
41
|
+
|
42
|
+
If you do the search inside of an EventMachine reactor, as the
|
43
|
+
WSDiscovery::Searcher receives and parses responses, it adds them to the accessor
|
44
|
+
#discovery_responses, which is an EventMachine::Channel. This lets you subscribe
|
45
|
+
to the responses and do what you want with them.
|
46
|
+
|
47
|
+
== Requirements
|
48
|
+
|
49
|
+
* Ruby
|
50
|
+
* 1.9.3
|
51
|
+
* Gems
|
52
|
+
* builder
|
53
|
+
* eventmachine
|
54
|
+
* log_switch
|
55
|
+
* nokogiri
|
56
|
+
* nori
|
57
|
+
* uuid
|
58
|
+
* Gems (development)
|
59
|
+
* bundler
|
60
|
+
* rake
|
61
|
+
* rspec
|
62
|
+
* simplecov
|
63
|
+
* simplecov-rcov
|
64
|
+
* yard
|
65
|
+
|
66
|
+
== Install
|
67
|
+
|
68
|
+
$ gem install ws_discovery
|
69
|
+
|
70
|
+
== THANKS
|
71
|
+
|
72
|
+
The initial core of this gem came from https://github.com/turboladen/upnp due to
|
73
|
+
the similarities in how SSDP and WS-Discovery searches are performed.
|
74
|
+
|
75
|
+
The WSDiscovery::Response class reuses parts of https://github.com/savonrb/savon.
|
76
|
+
It made sense to me that WSDiscovery::Responses would behave similarly to
|
77
|
+
Savon::SOAP::Responses.
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'yard'
|
4
|
+
|
5
|
+
YARD::Rake::YardocTask.new
|
6
|
+
RSpec::Core::RakeTask.new
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
9
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
10
|
+
spec.rcov = true
|
11
|
+
end
|
12
|
+
|
13
|
+
task default: :install
|
14
|
+
|
15
|
+
# Alias for rubygems-test
|
16
|
+
task test: :spec
|
@@ -0,0 +1,32 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
require 'ws_discovery/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "em_ws_discovery"
|
6
|
+
s.version = WSDiscovery::VERSION
|
7
|
+
s.homepage = "https://github.com/jimxl/em-ws-discovery"
|
8
|
+
s.author = "jimxl"
|
9
|
+
s.email = "tianxiaxl@gmail.com"
|
10
|
+
s.description = "ruby实现的ws_discovery, 基于ws-discovery项目"
|
11
|
+
s.summary = "ruby实现的ws_discovery, 基于ws-discovery项目"
|
12
|
+
|
13
|
+
s.required_rubygems_version = ">=1.8.0"
|
14
|
+
s.required_ruby_version = Gem::Requirement.new(">= 1.9.3")
|
15
|
+
s.files = Dir.glob("{lib,spec}/**/*") + Dir.glob("*.rdoc") +
|
16
|
+
%w(Gemfile em_ws_discovery.gemspec Rakefile)
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_dependency("builder")
|
20
|
+
s.add_dependency("eventmachine")
|
21
|
+
s.add_dependency("log_switch", ">=0.1.4")
|
22
|
+
s.add_dependency("nokogiri")
|
23
|
+
s.add_dependency("nori", '>=2.0.0')
|
24
|
+
s.add_dependency("uuid")
|
25
|
+
|
26
|
+
s.add_development_dependency("bundler", ">= 1.0.21")
|
27
|
+
s.add_development_dependency("rake", ">= 0")
|
28
|
+
s.add_development_dependency("rspec", "~> 2.6")
|
29
|
+
s.add_development_dependency("simplecov", ">= 0")
|
30
|
+
s.add_development_dependency("simplecov-rcov", ">= 0")
|
31
|
+
s.add_development_dependency("yard", ">= 0.7.2")
|
32
|
+
end
|
data/lib/ws_discovery.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require_relative 'ws_discovery/core_ext/socket_patch'
|
2
|
+
require 'eventmachine'
|
3
|
+
|
4
|
+
require_relative 'ws_discovery/error'
|
5
|
+
require_relative 'ws_discovery/network_constants'
|
6
|
+
require_relative 'ws_discovery/searcher'
|
7
|
+
|
8
|
+
module WSDiscovery
|
9
|
+
include NetworkConstants
|
10
|
+
|
11
|
+
DEFAULT_WAIT_TIME = 5
|
12
|
+
|
13
|
+
# Opens a UDP socket on 0.0.0.0, on an ephemeral port, has WSDiscovery::Searcher
|
14
|
+
# build and send the search request, then receives the responses. The search
|
15
|
+
# will stop after +response_wait_time+.
|
16
|
+
#
|
17
|
+
# @param [Hash] options The options for the probe.
|
18
|
+
# @option options [Hash<String>] :env_namespaces Additional envelope namespaces.
|
19
|
+
# @option options [Hash<String>] :type_attributes Type attributes.
|
20
|
+
# @option options [String] :types Types.
|
21
|
+
# @option options [Hash<String>] :scope_attributes Scope attributes.
|
22
|
+
# @option options [String] :scopes Scopes.
|
23
|
+
# @return [Array<WSDiscovery::Response>,WSDiscovery::Searcher] Returns an
|
24
|
+
# Array of probe responses. If the reactor is already running this will return
|
25
|
+
# a WSDiscovery::Searcher which will make its accessors available so you can
|
26
|
+
# get responses in real time.
|
27
|
+
def self.search(options={})
|
28
|
+
response_wait_time = options[:response_wait_time] || DEFAULT_WAIT_TIME
|
29
|
+
responses = []
|
30
|
+
|
31
|
+
multicast_searcher = proc do
|
32
|
+
EM.open_datagram_socket('0.0.0.0', 0, WSDiscovery::Searcher, options)
|
33
|
+
end
|
34
|
+
|
35
|
+
if EM.reactor_running?
|
36
|
+
return multicast_searcher.call
|
37
|
+
else
|
38
|
+
EM.run do
|
39
|
+
ms = multicast_searcher.call
|
40
|
+
|
41
|
+
ms.discovery_responses.subscribe do |notification|
|
42
|
+
responses << notification
|
43
|
+
end
|
44
|
+
|
45
|
+
EM.add_timer(response_wait_time) { EM.stop }
|
46
|
+
trap_signals
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
responses.flatten
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# Traps INT, TERM, and HUP signals and stops the reactor.
|
56
|
+
def self.trap_signals
|
57
|
+
trap('INT') { EM.stop }
|
58
|
+
trap('TERM') { EM.stop }
|
59
|
+
trap('HUP') { EM.stop } if RUBY_PLATFORM !~ /mswin|mingw/
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
# Workaround for missing constants on Windows
|
4
|
+
module Socket::Constants
|
5
|
+
IP_ADD_MEMBERSHIP = 12 unless defined? IP_ADD_MEMBERSHIP
|
6
|
+
IP_MULTICAST_LOOP = 11 unless defined? IP_MULTICAST_LOOP
|
7
|
+
IP_MULTICAST_TTL = 10 unless defined? IP_MULTICAST_TTL
|
8
|
+
IP_TTL = 4 unless defined? IP_TTL
|
9
|
+
end
|
10
|
+
|
11
|
+
class Socket
|
12
|
+
IP_ADD_MEMBERSHIP = 12 unless defined? IP_ADD_MEMBERSHIP
|
13
|
+
IP_MULTICAST_LOOP = 11 unless defined? IP_MULTICAST_LOOP
|
14
|
+
IP_MULTICAST_TTL = 10 unless defined? IP_MULTICAST_TTL
|
15
|
+
IP_TTL = 4 unless defined? IP_TTL
|
16
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require_relative 'core_ext/socket_patch'
|
2
|
+
require_relative 'network_constants'
|
3
|
+
require_relative 'error'
|
4
|
+
require 'ipaddr'
|
5
|
+
require 'socket'
|
6
|
+
require 'eventmachine'
|
7
|
+
|
8
|
+
module WSDiscovery
|
9
|
+
class MulticastConnection < EventMachine::Connection
|
10
|
+
include WSDiscovery::NetworkConstants
|
11
|
+
|
12
|
+
# @param [Fixnum] ttl The TTL value to use when opening the UDP socket
|
13
|
+
# required for WSDiscovery actions.
|
14
|
+
def initialize ttl=TTL
|
15
|
+
@ttl = ttl
|
16
|
+
@discovery_responses = EM::Channel.new
|
17
|
+
|
18
|
+
setup_multicast_socket
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# Gets the IP and port from the peer that just sent data.
|
24
|
+
#
|
25
|
+
# @return [Array<String,Fixnum>] The IP and port.
|
26
|
+
def peer_info
|
27
|
+
peer_bytes = get_peername[2, 6].unpack("nC4")
|
28
|
+
port = peer_bytes.first.to_i
|
29
|
+
ip = peer_bytes[1, 4].join(".")
|
30
|
+
|
31
|
+
[ip, port]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sets Socket options to allow for multicasting. If ENV["RUBY_TESTING_ENV"]
|
35
|
+
# is equal to "testing", then it doesn't turn off multicast looping.
|
36
|
+
def setup_multicast_socket
|
37
|
+
set_membership(IPAddr.new(MULTICAST_IP).hton + IPAddr.new('0.0.0.0').hton)
|
38
|
+
set_multicast_ttl(@ttl)
|
39
|
+
set_ttl(@ttl)
|
40
|
+
|
41
|
+
unless ENV["RUBY_TESTING_ENV"] == "testing"
|
42
|
+
switch_multicast_loop :off
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param [String] membership The network byte ordered String that represents
|
47
|
+
# the IP(s) that should join the membership group.
|
48
|
+
def set_membership(membership)
|
49
|
+
set_sock_opt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, membership)
|
50
|
+
end
|
51
|
+
|
52
|
+
# @param [Fixnum] ttl TTL to set IP_MULTICAST_TTL to.
|
53
|
+
def set_multicast_ttl(ttl)
|
54
|
+
set_sock_opt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_TTL, [ttl].pack('i'))
|
55
|
+
end
|
56
|
+
|
57
|
+
# @param [Fixnum] ttl TTL to set IP_TTL to.
|
58
|
+
def set_ttl(ttl)
|
59
|
+
set_sock_opt(Socket::IPPROTO_IP, Socket::IP_TTL, [ttl].pack('i'))
|
60
|
+
end
|
61
|
+
|
62
|
+
# @param [Symbol] on_off Turn on/off multicast looping. Supply :on or :off.
|
63
|
+
# @raise [WSDiscovery::Error] If invalid option is given.
|
64
|
+
def switch_multicast_loop(on_off)
|
65
|
+
hex_value = case on_off
|
66
|
+
when :on, "\001"
|
67
|
+
"\001"
|
68
|
+
when :off, "\000"
|
69
|
+
"\000"
|
70
|
+
else
|
71
|
+
raise WSDiscovery::Error, "Can't switch IP_MULTICAST_LOOP to '#{on_off}'"
|
72
|
+
end
|
73
|
+
|
74
|
+
set_sock_opt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_LOOP, hex_value)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'nori'
|
3
|
+
require_relative 'error'
|
4
|
+
|
5
|
+
module WSDiscovery
|
6
|
+
|
7
|
+
# Represents the probe response.
|
8
|
+
class Response
|
9
|
+
|
10
|
+
attr_accessor :response
|
11
|
+
|
12
|
+
# @param [String] response Text of the response to a WSDiscovery probe.
|
13
|
+
def initialize(response)
|
14
|
+
@response = response
|
15
|
+
end
|
16
|
+
|
17
|
+
# Shortcut accessor for the SOAP response body Hash.
|
18
|
+
#
|
19
|
+
# @param [Symbol] key The key to access in the body Hash.
|
20
|
+
# @return [Hash,String] The accessed value.
|
21
|
+
def [](key)
|
22
|
+
body[key]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the SOAP response header as a Hash.
|
26
|
+
#
|
27
|
+
# @return [Hash] SOAP response header.
|
28
|
+
# @raise [WSDiscovery::Error] If unable to parse response.
|
29
|
+
def header
|
30
|
+
unless hash.has_key? :envelope
|
31
|
+
raise WSDiscovery::Error, "Unable to parse response body '#{to_xml}'"
|
32
|
+
end
|
33
|
+
|
34
|
+
hash[:envelope][:header]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the SOAP response body as a Hash.
|
38
|
+
#
|
39
|
+
# @return [Hash] SOAP response body.
|
40
|
+
# @raise [WSDiscovery::Error] If unable to parse response.
|
41
|
+
def body
|
42
|
+
unless hash.has_key? :envelope
|
43
|
+
raise WSDiscovery::Error, "Unable to parse response body '#{to_xml}'"
|
44
|
+
end
|
45
|
+
|
46
|
+
hash[:envelope][:body]
|
47
|
+
end
|
48
|
+
|
49
|
+
alias to_hash body
|
50
|
+
|
51
|
+
# Returns the complete SOAP response XML without normalization.
|
52
|
+
#
|
53
|
+
# @return [Hash] Complete SOAP response Hash.
|
54
|
+
def hash
|
55
|
+
@hash ||= nori.parse(to_xml)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns the SOAP response XML.
|
59
|
+
#
|
60
|
+
# @return [String] Raw SOAP response XML.
|
61
|
+
def to_xml
|
62
|
+
response
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns a Nokogiri::XML::Document for the SOAP response XML.
|
66
|
+
#
|
67
|
+
# @return [Nokogiri::XML::Document] Document for the SOAP response.
|
68
|
+
def doc
|
69
|
+
@doc ||= Nokogiri::XML(to_xml)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns an Array of Nokogiri::XML::Node objects retrieved with the given +path+.
|
73
|
+
# Automatically adds all of the document's namespaces unless a +namespaces+ hash is provided.
|
74
|
+
#
|
75
|
+
# @param [String] path XPath to search.
|
76
|
+
# @param [Hash<String>] namespaces Namespaces to append.
|
77
|
+
def xpath(path, namespaces = nil)
|
78
|
+
doc.xpath(path, namespaces || xml_namespaces)
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
# XML Namespaces from the Document.
|
84
|
+
#
|
85
|
+
# @return [Hash] Namespaces from the Document.
|
86
|
+
def xml_namespaces
|
87
|
+
@xml_namespaces ||= doc.collect_namespaces
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns a Nori parser.
|
91
|
+
#
|
92
|
+
# @return [Nori] Nori parser.
|
93
|
+
def nori
|
94
|
+
return @nori if @nori
|
95
|
+
|
96
|
+
nori_options = {
|
97
|
+
strip_namespaces: true,
|
98
|
+
convert_tags_to: lambda { |tag| tag.snakecase.to_sym }
|
99
|
+
}
|
100
|
+
|
101
|
+
@nori = Nori.new(nori_options)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require_relative 'multicast_connection'
|
2
|
+
require_relative 'response'
|
3
|
+
require 'builder'
|
4
|
+
require 'log_switch'
|
5
|
+
require 'uuid'
|
6
|
+
|
7
|
+
class WSDiscovery::Searcher < WSDiscovery::MulticastConnection
|
8
|
+
extend LogSwitch
|
9
|
+
self.logger.datetime_format = "%Y-%m-%d %H:%M:%S "
|
10
|
+
|
11
|
+
# @return [EventMachine::Channel] Provides subscribers with responses from
|
12
|
+
# their search request.
|
13
|
+
attr_reader :discovery_responses
|
14
|
+
|
15
|
+
# @param [Hash] options The options for the probe.
|
16
|
+
# @option options [Hash<String>] :env_namespaces Additional envelope namespaces.
|
17
|
+
# @option options [Hash<String>] :type_attributes Type attributes.
|
18
|
+
# @option options [String] :types Types.
|
19
|
+
# @option options [Hash<String>] :scope_attributes Scope attributes.
|
20
|
+
# @option options [String] :scopes Scopes.
|
21
|
+
# @option options [Fixnum] :ttl TTL for the probe.
|
22
|
+
def initialize(options={})
|
23
|
+
options[:ttl] ||= TTL
|
24
|
+
|
25
|
+
@search = probe(options)
|
26
|
+
|
27
|
+
super options[:ttl]
|
28
|
+
end
|
29
|
+
|
30
|
+
# This is the callback called by EventMachine when it receives data on the
|
31
|
+
# socket that's been opened for this connection. In this case, the method
|
32
|
+
# parses the probe matches into WSDiscovery::Responses and adds them to the
|
33
|
+
# appropriate EventMachine::Channel (provided as accessor methods). This
|
34
|
+
# effectively means that in each Channel, you get a WSDiscovery::Response
|
35
|
+
# for each response that comes in on the socket.
|
36
|
+
#
|
37
|
+
# @param [String] response The data received on this connection's socket.
|
38
|
+
def receive_data(response)
|
39
|
+
ip, port = peer_info
|
40
|
+
#WSDiscovery::Searcher.log "<#{self.class}> Response from #{ip}:#{port}:\n#{response}\n"
|
41
|
+
parsed_response = parse(response)
|
42
|
+
@discovery_responses << parsed_response
|
43
|
+
end
|
44
|
+
|
45
|
+
# Converts the headers to a set of key-value pairs.
|
46
|
+
#
|
47
|
+
# @param [String] data The data to convert.
|
48
|
+
# @return [WSDiscovery::Response] The converted data.
|
49
|
+
def parse(data)
|
50
|
+
WSDiscovery::Response.new(data)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Sends the probe that was built during init. Logs what was sent if the
|
54
|
+
# send was successful.
|
55
|
+
def post_init
|
56
|
+
if send_datagram(@search, MULTICAST_IP, MULTICAST_PORT) > 0
|
57
|
+
WSDiscovery::Searcher.log("Sent datagram search:\n#{@search}")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Probe for target services supporting WS-Discovery.
|
62
|
+
#
|
63
|
+
# @param [Hash] options The options for the probe.
|
64
|
+
# @option options [Hash<String>] :env_namespaces Additional envelope namespaces.
|
65
|
+
# @option options [Hash<String>] :type_attributes Type attributes.
|
66
|
+
# @option options [String] :types Types.
|
67
|
+
# @option options [Hash<String>] :scope_attributes Scope attributes.
|
68
|
+
# @option options [String] :scopes Scopes.
|
69
|
+
# @return [String] Probe SOAP message.
|
70
|
+
def probe(options={})
|
71
|
+
namespaces = {
|
72
|
+
'xmlns:a' => 'http://schemas.xmlsoap.org/ws/2004/08/addressing',
|
73
|
+
'xmlns:d' => 'http://schemas.xmlsoap.org/ws/2005/04/discovery',
|
74
|
+
'xmlns:s' => 'http://www.w3.org/2003/05/soap-envelope'
|
75
|
+
}
|
76
|
+
namespaces.merge! options[:env_namespaces] if options[:env_namespaces]
|
77
|
+
|
78
|
+
Builder::XmlMarkup.new.s(:Envelope, namespaces) do |xml|
|
79
|
+
xml.s(:Header) do
|
80
|
+
xml.a(:Action, 'http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe')
|
81
|
+
xml.a(:MessageID, "uuid:#{UUID.generate}")
|
82
|
+
xml.a(:To, 'urn:schemas-xmlsoap-org:ws:2005:04:discovery')
|
83
|
+
end
|
84
|
+
|
85
|
+
xml.s(:Body) do
|
86
|
+
xml.d(:Probe) do
|
87
|
+
xml.d(:Types, options[:type_attributes], options[:types])
|
88
|
+
xml.d(:Scopes, options[:scope_attributes], options[:scopes])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
require 'simplecov-rcov'
|
3
|
+
|
4
|
+
class SimpleCov::Formatter::MergedFormatter
|
5
|
+
def format(result)
|
6
|
+
SimpleCov::Formatter::HTMLFormatter.new.format(result)
|
7
|
+
SimpleCov::Formatter::RcovFormatter.new.format(result)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
SimpleCov.formatter = SimpleCov::Formatter::MergedFormatter
|
12
|
+
|
13
|
+
SimpleCov.start do
|
14
|
+
add_filter "/spec"
|
15
|
+
add_filter "/lib/deps"
|
16
|
+
end
|
17
|
+
|
18
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
19
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
20
|
+
require 'rspec'
|
21
|
+
|
22
|
+
# Requires supporting files with custom matchers and macros, etc,
|
23
|
+
# in ./support/ and its subdirectories.
|
24
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |file| require file }
|
25
|
+
|
26
|
+
ENV["RUBY_TESTING_ENV"] = "testing"
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ws_discovery/multicast_connection'
|
3
|
+
|
4
|
+
describe WSDiscovery::MulticastConnection do
|
5
|
+
around(:each) do |example|
|
6
|
+
EM.run do
|
7
|
+
example.run
|
8
|
+
EM.stop
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
subject { WSDiscovery::MulticastConnection.new(1) }
|
13
|
+
|
14
|
+
describe "#peer_info" do
|
15
|
+
before do
|
16
|
+
WSDiscovery::MulticastConnection.any_instance.stub(:setup_multicast_socket)
|
17
|
+
subject.stub_chain(:get_peername, :[], :unpack).and_return(%w(1234 1 2 3 4))
|
18
|
+
end
|
19
|
+
|
20
|
+
it "returns an Array with IP and port" do
|
21
|
+
subject.send(:peer_info).should == ['1.2.3.4', 1234]
|
22
|
+
end
|
23
|
+
|
24
|
+
it "returns IP as a String" do
|
25
|
+
subject.send(:peer_info).first.should be_a String
|
26
|
+
end
|
27
|
+
|
28
|
+
it "returns port as a Fixnum" do
|
29
|
+
subject.send(:peer_info).last.should be_a Fixnum
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#setup_multicast_socket" do
|
34
|
+
before do
|
35
|
+
WSDiscovery::MulticastConnection.any_instance.stub(:set_membership)
|
36
|
+
WSDiscovery::MulticastConnection.any_instance.stub(:switch_multicast_loop)
|
37
|
+
WSDiscovery::MulticastConnection.any_instance.stub(:set_multicast_ttl)
|
38
|
+
WSDiscovery::MulticastConnection.any_instance.stub(:set_ttl)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "adds 0.0.0.0 and 239.255.255.250 to the membership group" do
|
42
|
+
subject.should_receive(:set_membership).with(
|
43
|
+
IPAddr.new('239.255.255.250').hton + IPAddr.new('0.0.0.0').hton
|
44
|
+
)
|
45
|
+
subject.send(:setup_multicast_socket)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "sets multicast TTL to 1" do
|
49
|
+
subject.should_receive(:set_multicast_ttl).with(1)
|
50
|
+
subject.send(:setup_multicast_socket)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "sets TTL to 1" do
|
54
|
+
subject.should_receive(:set_ttl).with(1)
|
55
|
+
subject.send(:setup_multicast_socket)
|
56
|
+
end
|
57
|
+
|
58
|
+
context "ENV['RUBY_TESTING_ENV'] != testing" do
|
59
|
+
after { ENV['RUBY_TESTING_ENV'] = "testing" }
|
60
|
+
|
61
|
+
it "turns multicast loop off" do
|
62
|
+
ENV['RUBY_TESTING_ENV'] = "development"
|
63
|
+
subject.should_receive(:switch_multicast_loop).with(:off)
|
64
|
+
subject.send(:setup_multicast_socket)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "#switch_multicast_loop" do
|
70
|
+
before do
|
71
|
+
WSDiscovery::MulticastConnection.any_instance.stub(:setup_multicast_socket)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "passes '\\001' to the socket option call when param == :on" do
|
75
|
+
subject.should_receive(:set_sock_opt).with(
|
76
|
+
0, Socket::IP_MULTICAST_LOOP, "\001"
|
77
|
+
)
|
78
|
+
subject.send(:switch_multicast_loop, :on)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "passes '\\001' to the socket option call when param == '\\001'" do
|
82
|
+
subject.should_receive(:set_sock_opt).with(
|
83
|
+
0, Socket::IP_MULTICAST_LOOP, "\001"
|
84
|
+
)
|
85
|
+
subject.send(:switch_multicast_loop,"\001")
|
86
|
+
end
|
87
|
+
|
88
|
+
it "passes '\\000' to the socket option call when param == :off" do
|
89
|
+
subject.should_receive(:set_sock_opt).with(
|
90
|
+
0, Socket::IP_MULTICAST_LOOP, "\000"
|
91
|
+
)
|
92
|
+
subject.send(:switch_multicast_loop,:off)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "passes '\\000' to the socket option call when param == '\\000'" do
|
96
|
+
subject.should_receive(:set_sock_opt).with(
|
97
|
+
0, Socket::IP_MULTICAST_LOOP, "\000"
|
98
|
+
)
|
99
|
+
subject.send(:switch_multicast_loop,"\000")
|
100
|
+
end
|
101
|
+
|
102
|
+
it "raises when not :on, :off, '\\000', or '\\001'" do
|
103
|
+
expect { subject.send(:switch_multicast_loop, 12312312) }.
|
104
|
+
to raise_error(WSDiscovery::Error)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ws_discovery/response'
|
3
|
+
|
4
|
+
describe WSDiscovery::Response do
|
5
|
+
let(:probe_response) do
|
6
|
+
<<-PROBE
|
7
|
+
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
|
8
|
+
xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing"
|
9
|
+
xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery">
|
10
|
+
<s:Header>
|
11
|
+
<a:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:To>
|
12
|
+
<a:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches</a:Action>
|
13
|
+
<a:MessageID>urn:uuid:18523f7e-7a54-d92d-a18d-e7165bec8e7e</a:MessageID>
|
14
|
+
<a:RelatesTo>uuid:7dbdede0-0f2b-0130-5861-002564b29b24</a:RelatesTo>
|
15
|
+
<d:AppSequence MessageNumber="42" InstanceId="2"/>
|
16
|
+
</s:Header>
|
17
|
+
<s:Body>
|
18
|
+
<d:ProbeMatches>
|
19
|
+
<d:ProbeMatch>
|
20
|
+
<a:EndpointReference>
|
21
|
+
<a:Address>urn:uuid:0b679890-fc54-14ba-d428-f73b3e7c2400</a:Address>
|
22
|
+
</a:EndpointReference>
|
23
|
+
<d:Types xmlns:dn="http://www.onvif.org/ver10/network/wsdl">dn:NetworkVideoTransmitter</d:Types>
|
24
|
+
<d:Scopes>onvif://www.onvif.org/Profile/Streaming onvif://www.onvif.org/hardware/NET5404T onvif://www.onvif.org/type/ptz onvif://www.onvif.org/type/video_encoder onvif://www.onvif.org/location/country/usa onvif://www.onvif.org/name/NET5404T-ABEPZH7</d:Scopes>
|
25
|
+
<d:XAddrs>http://10.221.222.74/onvif/device_service</d:XAddrs>
|
26
|
+
<d:MetadataVersion>1</d:MetadataVersion>
|
27
|
+
</d:ProbeMatch>
|
28
|
+
</d:ProbeMatches>
|
29
|
+
</s:Body>
|
30
|
+
</s:Envelope>
|
31
|
+
PROBE
|
32
|
+
end
|
33
|
+
|
34
|
+
let(:probe_body_hash) do
|
35
|
+
{ probe_matches: {
|
36
|
+
probe_match: {
|
37
|
+
endpoint_reference: {
|
38
|
+
address: "urn:uuid:0b679890-fc54-14ba-d428-f73b3e7c2400" },
|
39
|
+
types: "dn:NetworkVideoTransmitter",
|
40
|
+
scopes: "onvif://www.onvif.org/Profile/Streaming onvif://www.onvif.org/hardware/NET5404T onvif://www.onvif.org/type/ptz onvif://www.onvif.org/type/video_encoder onvif://www.onvif.org/location/country/usa onvif://www.onvif.org/name/NET5404T-ABEPZH7",
|
41
|
+
x_addrs: "http://10.221.222.74/onvif/device_service",
|
42
|
+
metadata_version: "1" } } }
|
43
|
+
end
|
44
|
+
|
45
|
+
let(:probe_header_hash) do
|
46
|
+
{ to: "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous",
|
47
|
+
action: "http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches",
|
48
|
+
message_id: "urn:uuid:18523f7e-7a54-d92d-a18d-e7165bec8e7e",
|
49
|
+
relates_to: "uuid:7dbdede0-0f2b-0130-5861-002564b29b24",
|
50
|
+
app_sequence: {
|
51
|
+
:@message_number => "42",
|
52
|
+
:@instance_id => "2" } }
|
53
|
+
end
|
54
|
+
|
55
|
+
let(:probe_full_hash) do
|
56
|
+
{ envelope: { header: probe_header_hash, body: probe_body_hash,
|
57
|
+
:"@xmlns:s" => "http://www.w3.org/2003/05/soap-envelope",
|
58
|
+
:"@xmlns:a" => "http://schemas.xmlsoap.org/ws/2004/08/addressing",
|
59
|
+
:"@xmlns:d" => "http://schemas.xmlsoap.org/ws/2005/04/discovery" } }
|
60
|
+
end
|
61
|
+
|
62
|
+
subject { WSDiscovery::Response.new(probe_response) }
|
63
|
+
|
64
|
+
describe "#[]" do
|
65
|
+
it "should return the SOAP response body as a Hash" do
|
66
|
+
subject[:probe_matches].should == probe_body_hash[:probe_matches]
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should throw an exception when the response body isn't parsable" do
|
70
|
+
expect { WSDiscovery::Response.new('').body }.to raise_error WSDiscovery::Error
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "#header" do
|
75
|
+
it "should return the SOAP response header as a Hash" do
|
76
|
+
subject.header[:app_sequence].should == probe_header_hash[:app_sequence]
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should throw an exception when the response header isn't parsable" do
|
80
|
+
expect { WSDiscovery::Response.new('').header }.to raise_error WSDiscovery::Error
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
%w(body to_hash).each do |method|
|
85
|
+
describe "##{method}" do
|
86
|
+
it "should return the SOAP response body as a Hash" do
|
87
|
+
subject.send(method)[:probe_matches].should == probe_body_hash[:probe_matches]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "#hash" do
|
93
|
+
it "should return the complete SOAP response XML as a Hash" do
|
94
|
+
subject.hash.should == probe_full_hash
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "#to_xml" do
|
99
|
+
it "should return the raw SOAP response body" do
|
100
|
+
subject.to_xml.should == probe_response
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe "#doc" do
|
105
|
+
it "returns a Nokogiri::XML::Document for the SOAP response XML" do
|
106
|
+
subject.doc.should be_a(Nokogiri::XML::Document)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe "#xpath" do
|
111
|
+
it "permits XPath access to elements in the request" do
|
112
|
+
subject.xpath("//a:Address").first.inner_text.
|
113
|
+
should == "urn:uuid:0b679890-fc54-14ba-d428-f73b3e7c2400"
|
114
|
+
subject.xpath("//d:ProbeMatch/d:XAddrs").first.inner_text.
|
115
|
+
should == "http://10.221.222.74/onvif/device_service"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ws_discovery/searcher'
|
3
|
+
|
4
|
+
describe WSDiscovery::Searcher do
|
5
|
+
let(:default_probe) do
|
6
|
+
"<s:Envelope xmlns:a=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" " +
|
7
|
+
"xmlns:d=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" xmlns:s=\"h" +
|
8
|
+
"ttp://www.w3.org/2003/05/soap-envelope\"><s:Header><a:Action>http://sch" +
|
9
|
+
"emas.xmlsoap.org/ws/2005/04/discovery/Probe</a:Action><a:MessageID>uuid" +
|
10
|
+
":a-uuid</a:MessageID><a:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery" +
|
11
|
+
"</a:To></s:Header><s:Body><d:Types/><d:Scopes/></s:Body></s:Envelope>"
|
12
|
+
end
|
13
|
+
|
14
|
+
around(:each) do |example|
|
15
|
+
EM.run do
|
16
|
+
example.run
|
17
|
+
EM.stop
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
before do
|
22
|
+
WSDiscovery::Searcher.log = false
|
23
|
+
WSDiscovery::MulticastConnection.any_instance.stub(:setup_multicast_socket)
|
24
|
+
UUID.stub(:generate).and_return('a-uuid')
|
25
|
+
end
|
26
|
+
|
27
|
+
subject do
|
28
|
+
WSDiscovery::Searcher.new(1)
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#initialize" do
|
32
|
+
it "does a #probe" do
|
33
|
+
WSDiscovery::Searcher.any_instance.should_receive(:probe)
|
34
|
+
|
35
|
+
subject
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#receive_data" do
|
40
|
+
let(:parsed_response) do
|
41
|
+
double 'parsed response'
|
42
|
+
end
|
43
|
+
|
44
|
+
it "takes a response and adds it to the list of responses" do
|
45
|
+
response = double 'response'
|
46
|
+
subject.stub(:peer_info).and_return(['0.0.0.0', 4567])
|
47
|
+
|
48
|
+
subject.should_receive(:parse).with(response).exactly(1).times.
|
49
|
+
and_return(parsed_response)
|
50
|
+
subject.instance_variable_get(:@discovery_responses).should_receive(:<<).
|
51
|
+
with(parsed_response)
|
52
|
+
|
53
|
+
subject.receive_data(response)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#parse" do
|
58
|
+
before do
|
59
|
+
WSDiscovery::MulticastConnection.any_instance.stub(:setup_multicast_socket)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "turns probe matches into WSDiscovery::Responses" do
|
63
|
+
result = subject.parse "<Envelope />"
|
64
|
+
result.should be_a WSDiscovery::Response
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "#post_init" do
|
69
|
+
before { WSDiscovery::Searcher.any_instance.stub(:m_search).and_return("hi") }
|
70
|
+
|
71
|
+
it "sends a probe as a datagram over 239.255.255.250:3702" do
|
72
|
+
subject.should_receive(:send_datagram).
|
73
|
+
with(default_probe, '239.255.255.250', 3702).
|
74
|
+
and_return 0
|
75
|
+
subject.post_init
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "#probe" do
|
80
|
+
it "builds the MSEARCH string using the given parameters" do
|
81
|
+
subject.probe.should == default_probe
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ws_discovery'
|
3
|
+
|
4
|
+
describe WSDiscovery do
|
5
|
+
subject { WSDiscovery }
|
6
|
+
|
7
|
+
describe '.search' do
|
8
|
+
let(:multicast_searcher) do
|
9
|
+
searcher = double "WSDiscovery::Searcher"
|
10
|
+
searcher.stub_chain(:discovery_responses, :subscribe).and_yield(%w[one two])
|
11
|
+
|
12
|
+
searcher
|
13
|
+
end
|
14
|
+
|
15
|
+
before do
|
16
|
+
EM.stub(:run).and_yield
|
17
|
+
EM.stub(:add_timer)
|
18
|
+
EM.stub(:open_datagram_socket).and_return multicast_searcher
|
19
|
+
end
|
20
|
+
|
21
|
+
context "reactor is already running" do
|
22
|
+
it "returns a WSDiscovery::Searcher" do
|
23
|
+
EM.stub(:reactor_running?).and_return true
|
24
|
+
subject.search.should == multicast_searcher
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "reactor is not already running" do
|
29
|
+
it "returns an Array of responses" do
|
30
|
+
EM.stub(:add_shutdown_hook).and_yield
|
31
|
+
subject.search.should == %w[one two]
|
32
|
+
end
|
33
|
+
|
34
|
+
it "opens a UDP socket on '0.0.0.0', port 0" do
|
35
|
+
EM.stub(:add_shutdown_hook)
|
36
|
+
EM.should_receive(:open_datagram_socket).with('0.0.0.0', 0,
|
37
|
+
WSDiscovery::Searcher, {})
|
38
|
+
subject.search
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
metadata
ADDED
@@ -0,0 +1,229 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: em_ws_discovery
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- jimxl
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-07-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: builder
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: eventmachine
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: log_switch
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.1.4
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.1.4
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: nokogiri
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: nori
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 2.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: 2.0.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: uuid
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: bundler
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.0.21
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.0.21
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rake
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rspec
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ~>
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '2.6'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ~>
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '2.6'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: simplecov
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - '>='
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - '>='
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: simplecov-rcov
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - '>='
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - '>='
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: yard
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 0.7.2
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - '>='
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: 0.7.2
|
181
|
+
description: ruby实现的ws_discovery, 基于ws-discovery项目
|
182
|
+
email: tianxiaxl@gmail.com
|
183
|
+
executables: []
|
184
|
+
extensions: []
|
185
|
+
extra_rdoc_files: []
|
186
|
+
files:
|
187
|
+
- lib/ws_discovery.rb
|
188
|
+
- lib/ws_discovery/error.rb
|
189
|
+
- lib/ws_discovery/multicast_connection.rb
|
190
|
+
- lib/ws_discovery/searcher.rb
|
191
|
+
- lib/ws_discovery/network_constants.rb
|
192
|
+
- lib/ws_discovery/version.rb
|
193
|
+
- lib/ws_discovery/response.rb
|
194
|
+
- lib/ws_discovery/core_ext/socket_patch.rb
|
195
|
+
- spec/spec_helper.rb
|
196
|
+
- spec/ws_discovery_spec.rb
|
197
|
+
- spec/ws_discovery/multicast_connection_spec.rb
|
198
|
+
- spec/ws_discovery/searcher_spec.rb
|
199
|
+
- spec/ws_discovery/response_spec.rb
|
200
|
+
- History.rdoc
|
201
|
+
- README.rdoc
|
202
|
+
- Gemfile
|
203
|
+
- em_ws_discovery.gemspec
|
204
|
+
- Rakefile
|
205
|
+
homepage: https://github.com/jimxl/em-ws-discovery
|
206
|
+
licenses: []
|
207
|
+
metadata: {}
|
208
|
+
post_install_message:
|
209
|
+
rdoc_options: []
|
210
|
+
require_paths:
|
211
|
+
- lib
|
212
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
213
|
+
requirements:
|
214
|
+
- - '>='
|
215
|
+
- !ruby/object:Gem::Version
|
216
|
+
version: 1.9.3
|
217
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
218
|
+
requirements:
|
219
|
+
- - '>='
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: 1.8.0
|
222
|
+
requirements: []
|
223
|
+
rubyforge_project:
|
224
|
+
rubygems_version: 2.0.3
|
225
|
+
signing_key:
|
226
|
+
specification_version: 4
|
227
|
+
summary: ruby实现的ws_discovery, 基于ws-discovery项目
|
228
|
+
test_files: []
|
229
|
+
has_rdoc:
|