discovery 0.0.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/lib/discovery/udp.rb +54 -0
- data/lib/discovery/util/udp.rb +115 -0
- data/lib/discovery.rb +2 -0
- metadata +60 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 204a4d851db30ec0c717303d933b3a2f02aa8ef4
|
4
|
+
data.tar.gz: fee2b6b4a7b55e80cf87f8a535c3ff1d42487c73
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 98eee54e5dd469906d625ab2a25f5040a1b58d2cbb36dcde8f048fb93e323660767e196314ecbd7c0a667c6bef79facab4000d51a813810a59cb4287444e9c0b
|
7
|
+
data.tar.gz: d8f6778c0b2a8b56a48ca568043acc770ef664c9a4522a6d6927868c924bd89c497e252e962e681d8654ad86f2e3887103500eb0691fbe2e8dbf9c019631054f
|
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
require_relative 'util/udp'
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
module Discovery
|
6
|
+
class UDP
|
7
|
+
@port = 0
|
8
|
+
@group = "0.0.0.0"
|
9
|
+
|
10
|
+
def self.new; raise NotImplementedError nil end
|
11
|
+
|
12
|
+
# Register a callback to be used in the event of a recognized beacon.
|
13
|
+
# Each non-nil response object for each UDP beacon will be yielded to it.
|
14
|
+
def self.listen &block
|
15
|
+
@blocks ||= []
|
16
|
+
(@blocks << block).uniq!
|
17
|
+
|
18
|
+
@thread ||= Thread.new do
|
19
|
+
loop do
|
20
|
+
@discoverables.values.each_with_object(next_beacon) do |blk,beacon|
|
21
|
+
obj = blk.call beacon
|
22
|
+
@blocks.each do |callback|
|
23
|
+
callback.call(obj)
|
24
|
+
end unless obj.nil?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Return the info hash of the next DDDP beacon detected - blocking
|
31
|
+
# Optional :timeout keyword argument specifies maximum time to block
|
32
|
+
def self.next_beacon timeout:nil
|
33
|
+
@udp_rx ||= Util::UDP::RX.new(@group, @port)
|
34
|
+
beacon_to_hash(Timeout.timeout(timeout) { @udp_rx.gets })
|
35
|
+
end
|
36
|
+
|
37
|
+
# Convert the incoming beacon to a Hash containing all necessary info
|
38
|
+
# Implement to something nontrivial in the inherited module
|
39
|
+
def self.beacon_to_hash beacon
|
40
|
+
{beacon:beacon}
|
41
|
+
end
|
42
|
+
|
43
|
+
# Register a block to be used to convert a recognized
|
44
|
+
# beacon info-hash into a response object.
|
45
|
+
# The block should accept the info hash as an argument and
|
46
|
+
# return the response object or nil if the beacon isn't recognized.
|
47
|
+
class << self; attr_reader :discoverables; end
|
48
|
+
def self.register cls, &block
|
49
|
+
@discoverables ||= {}
|
50
|
+
@discoverables[cls] = block
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
|
2
|
+
require 'socket'
|
3
|
+
require 'ipaddr'
|
4
|
+
|
5
|
+
module Discovery
|
6
|
+
module Util
|
7
|
+
module UDP
|
8
|
+
|
9
|
+
class << self; attr_accessor :max_length; end
|
10
|
+
@max_length = 1024 # default max payload length
|
11
|
+
|
12
|
+
class Xceiver
|
13
|
+
attr_reader :group, :port, :socket, :local_ip, :local_port
|
14
|
+
|
15
|
+
def self.new(*args)
|
16
|
+
if (self.class==Xceiver)
|
17
|
+
raise TypeError, "#{self.class} is an 'abstract class' only."\
|
18
|
+
" Inherit it; don't instantiate it!"; end
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(group, port, **kwargs)
|
23
|
+
@group = group
|
24
|
+
@port = port
|
25
|
+
kwargs.each_pair { |k,v| instance_variable_set("@#{k}".to_sym, v) }
|
26
|
+
open
|
27
|
+
end
|
28
|
+
|
29
|
+
def open
|
30
|
+
@socket.close if @socket
|
31
|
+
@socket = UDPSocket.new
|
32
|
+
configure
|
33
|
+
@local_ip = Socket.ip_address_list.detect{|intf| intf.ipv4_private?}
|
34
|
+
@local_port = @socket.addr[1]
|
35
|
+
return @socket
|
36
|
+
ensure
|
37
|
+
@finalizer ||= ObjectSpace.define_finalizer self, Proc.new { close }
|
38
|
+
end
|
39
|
+
|
40
|
+
def close
|
41
|
+
@socket.close if @socket
|
42
|
+
@socket = nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
class TX < Xceiver
|
48
|
+
def configure
|
49
|
+
# Set up for multicasting
|
50
|
+
@socket.setsockopt Socket::IPPROTO_IP,
|
51
|
+
Socket::IP_MULTICAST_TTL,
|
52
|
+
[1].pack('i')
|
53
|
+
# Bind to any available port
|
54
|
+
@socket.bind "0.0.0.0", (@bind_port or 0)
|
55
|
+
end
|
56
|
+
|
57
|
+
def puts(m)
|
58
|
+
max = UDP.max_length
|
59
|
+
if m.size > max
|
60
|
+
self.puts m[0...max]
|
61
|
+
self.puts m[max...m.size]
|
62
|
+
else
|
63
|
+
@socket.send(m, 0, @group, @port)
|
64
|
+
m
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
class RX < Xceiver
|
71
|
+
def configure
|
72
|
+
# Add membership to the multicast group
|
73
|
+
@socket.setsockopt Socket::IPPROTO_IP,
|
74
|
+
Socket::IP_ADD_MEMBERSHIP,
|
75
|
+
IPAddr.new(@group).hton + IPAddr.new("0.0.0.0").hton
|
76
|
+
# Don't prevent future listening peers on the same machine
|
77
|
+
@socket.setsockopt(Socket::SOL_SOCKET,
|
78
|
+
Socket::SO_REUSEADDR,
|
79
|
+
[1].pack('i')) unless @selfish
|
80
|
+
# Bind the socket to the specified port or any open port on the machine
|
81
|
+
@socket.bind Socket::INADDR_ANY, @port
|
82
|
+
end
|
83
|
+
|
84
|
+
def gets
|
85
|
+
msg, addrinfo = @socket.recvfrom(UDP.max_length)
|
86
|
+
msg.instance_variable_set :@source, addrinfo[3].to_s+':'+addrinfo[1].to_s
|
87
|
+
class << msg; attr_reader :source; end
|
88
|
+
msg
|
89
|
+
end
|
90
|
+
|
91
|
+
def test!(message="#{self}.test!")
|
92
|
+
tx = UDP::TX.new @group, @port
|
93
|
+
rx = self
|
94
|
+
|
95
|
+
outer_thread = Thread.current
|
96
|
+
passed = false
|
97
|
+
thr = Thread.new do
|
98
|
+
rx.gets
|
99
|
+
passed = true
|
100
|
+
outer_thread.wakeup
|
101
|
+
end
|
102
|
+
Thread.pass
|
103
|
+
|
104
|
+
tx.puts message
|
105
|
+
sleep 1 if thr.status
|
106
|
+
thr.kill
|
107
|
+
tx.close
|
108
|
+
|
109
|
+
passed
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/discovery.rb
ADDED
metadata
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: discovery
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joe McIlvain
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-10-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: Device and service discovery.
|
28
|
+
email: joe.eli.mac@gmail.com
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- lib/discovery.rb
|
34
|
+
- lib/discovery/util/udp.rb
|
35
|
+
- lib/discovery/udp.rb
|
36
|
+
homepage: https://github.com/jemc/discovery/
|
37
|
+
licenses:
|
38
|
+
- Copyright 2013 Joe McIlvain. MIT Licensed.
|
39
|
+
metadata: {}
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
requirements: []
|
55
|
+
rubyforge_project:
|
56
|
+
rubygems_version: 2.1.5
|
57
|
+
signing_key:
|
58
|
+
specification_version: 4
|
59
|
+
summary: discovery
|
60
|
+
test_files: []
|