dnssd 1.0 → 1.1.0
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.
- data/.autotest +15 -0
- data/History.txt +12 -0
- data/Manifest.txt +17 -3
- data/Rakefile +3 -0
- data/ext/dnssd/dnssd.c +8 -128
- data/ext/dnssd/dnssd.h +0 -27
- data/ext/dnssd/errors.c +105 -0
- data/ext/dnssd/extconf.rb +12 -7
- data/ext/dnssd/flags.c +124 -0
- data/ext/dnssd/{dnssd_service.c → service.c} +350 -248
- data/lib/dnssd.rb +4 -1
- data/lib/dnssd/flags.rb +104 -0
- data/lib/dnssd/reply.rb +87 -0
- data/lib/dnssd/service.rb +15 -0
- data/lib/dnssd/text_record.rb +69 -0
- data/sample/browse.rb +11 -0
- data/sample/growl.rb +14 -0
- data/sample/highlevel_api.rb +30 -0
- data/sample/register.rb +30 -0
- data/sample/resolve.rb +13 -0
- data/sample/resolve_ichat.rb +36 -0
- data/test/test_dnssd_reply.rb +64 -0
- data/test/test_dnssd_text_record.rb +83 -0
- metadata +23 -28
- data.tar.gz.sig +0 -0
- data/ext/dnssd/dnssd_structs.c +0 -397
- data/ext/dnssd/dnssd_tr.c +0 -248
- metadata.gz.sig +0 -0
data/lib/dnssd.rb
CHANGED
data/lib/dnssd/flags.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
##
|
2
|
+
# Flags used in DNSSD Ruby API.
|
3
|
+
|
4
|
+
class DNSSD::Flags
|
5
|
+
|
6
|
+
constants.each do |name|
|
7
|
+
next unless name =~ /[a-z]/
|
8
|
+
attr = name.to_s.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
9
|
+
|
10
|
+
flag = const_get name
|
11
|
+
|
12
|
+
define_method "#{attr}=" do |bool|
|
13
|
+
if bool then
|
14
|
+
set_flag flag
|
15
|
+
else
|
16
|
+
clear_flag flag
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
define_method "#{attr}?" do
|
21
|
+
self & flag == flag
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
ALL_FLAGS = FLAGS.values.inject { |flag, all| flag | all }
|
26
|
+
|
27
|
+
##
|
28
|
+
# Returns a new set of flags
|
29
|
+
|
30
|
+
def initialize(*flags)
|
31
|
+
@flags = flags.inject 0 do |flag, acc| flag | acc end
|
32
|
+
|
33
|
+
verify
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Returns the intersection of flags in +self+ and +flags+.
|
38
|
+
|
39
|
+
def &(flags)
|
40
|
+
self.class.new(to_i & flags.to_i)
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# +self+ is equal if +other+ has the same flags
|
45
|
+
|
46
|
+
def ==(other)
|
47
|
+
to_i == other.to_i
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Clears +flag+
|
52
|
+
|
53
|
+
def clear_flag(flag)
|
54
|
+
@flags &= ~flag
|
55
|
+
|
56
|
+
verify
|
57
|
+
end
|
58
|
+
|
59
|
+
def inspect # :nodoc:
|
60
|
+
flags = to_a.sort.join ', '
|
61
|
+
flags[0, 0] = ' ' unless flags.empty?
|
62
|
+
"#<#{self.class}#{flags}>"
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# Sets +flag+
|
67
|
+
|
68
|
+
def set_flag(flag)
|
69
|
+
@flags |= flag
|
70
|
+
|
71
|
+
verify
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_a
|
75
|
+
FLAGS.map do |name, value|
|
76
|
+
(@flags & value == value) ? name : nil
|
77
|
+
end.compact
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_i # :nodoc:
|
81
|
+
@flags
|
82
|
+
end
|
83
|
+
|
84
|
+
def verify
|
85
|
+
@flags &= ALL_FLAGS
|
86
|
+
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
# Returns the union of flags in +self+ and +flags+
|
92
|
+
|
93
|
+
def |(flags)
|
94
|
+
self.class.new(to_i | flags.to_i)
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# Returns the complement of the flags in +self+
|
99
|
+
|
100
|
+
def ~
|
101
|
+
self.class.new ~to_i
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
data/lib/dnssd/reply.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
##
|
2
|
+
# DNSSD::Reply is used to return information
|
3
|
+
|
4
|
+
class DNSSD::Reply
|
5
|
+
|
6
|
+
##
|
7
|
+
# The service domain
|
8
|
+
|
9
|
+
attr_reader :domain
|
10
|
+
|
11
|
+
##
|
12
|
+
# Flags describing the reply, see DNSSD::Flags
|
13
|
+
|
14
|
+
attr_reader :flags
|
15
|
+
|
16
|
+
##
|
17
|
+
# The interface on which the service is available
|
18
|
+
|
19
|
+
attr_reader :interface
|
20
|
+
|
21
|
+
##
|
22
|
+
# The service name
|
23
|
+
|
24
|
+
attr_reader :name
|
25
|
+
|
26
|
+
##
|
27
|
+
# The port for this service
|
28
|
+
|
29
|
+
attr_reader :port
|
30
|
+
|
31
|
+
##
|
32
|
+
# The service associated with the reply, see DNSSD::Service
|
33
|
+
|
34
|
+
attr_reader :service
|
35
|
+
|
36
|
+
##
|
37
|
+
# The hostname of the host provide the service
|
38
|
+
|
39
|
+
attr_reader :target
|
40
|
+
|
41
|
+
##
|
42
|
+
# The service's primary text record
|
43
|
+
|
44
|
+
attr_reader :text_record
|
45
|
+
|
46
|
+
##
|
47
|
+
# The service type
|
48
|
+
|
49
|
+
attr_reader :type
|
50
|
+
|
51
|
+
def self.from_service(service, flags)
|
52
|
+
reply = new
|
53
|
+
reply.instance_variable_set :@service, service
|
54
|
+
reply.instance_variable_set :@flags, DNSSD::Flags.new(flags)
|
55
|
+
reply
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# The full service domain name, see DNSS::Service#fullname
|
60
|
+
|
61
|
+
def fullname
|
62
|
+
DNSSD::Service.fullname @name.gsub("\032", ' '), @type, @domain
|
63
|
+
end
|
64
|
+
|
65
|
+
def inspect
|
66
|
+
"#<%s:0x%x %p type: %s domain: %s interface: %s flags: %s>" % [
|
67
|
+
self.class, object_id, @name, @type, @domain, @interface, @flags
|
68
|
+
]
|
69
|
+
end
|
70
|
+
|
71
|
+
def set_fullname(fullname)
|
72
|
+
fullname = fullname.gsub(/\\([0-9]+)/) do $1.to_i.chr end
|
73
|
+
fullname = fullname.scan(/(?:[^\\.]|\\\.)+/).map do |part|
|
74
|
+
part.gsub "\\.", '.'
|
75
|
+
end
|
76
|
+
|
77
|
+
@name = fullname[0]
|
78
|
+
@type = fullname[1, 2].join '.'
|
79
|
+
@domain = fullname.last + '.'
|
80
|
+
end
|
81
|
+
|
82
|
+
def set_names(name, type, domain)
|
83
|
+
set_fullname [name, type, domain].join('.')
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class DNSSD::Service
|
2
|
+
|
3
|
+
##
|
4
|
+
# Access the services underlying thread. Returns nil if the service is
|
5
|
+
# synchronous.
|
6
|
+
|
7
|
+
attr_reader :thread
|
8
|
+
|
9
|
+
def inspect
|
10
|
+
stopped = stopped? ? 'stopped' : 'running'
|
11
|
+
"#<%s:0x%x %s>" % [self.class, object_id, stopped]
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
##
|
2
|
+
# DNSSD::TextRecord is a Hash wrapper that can encode its contents for DNSSD.
|
3
|
+
|
4
|
+
class DNSSD::TextRecord
|
5
|
+
|
6
|
+
##
|
7
|
+
# Creates a new TextRecord, decoding an encoded +text_record+ if given.
|
8
|
+
|
9
|
+
def initialize(text_record = nil)
|
10
|
+
@records = {}
|
11
|
+
|
12
|
+
return unless text_record
|
13
|
+
|
14
|
+
text_record = text_record.dup
|
15
|
+
|
16
|
+
until text_record.empty? do
|
17
|
+
size = text_record.slice! 0
|
18
|
+
next if size.zero?
|
19
|
+
|
20
|
+
raise ArgumentError, 'ran out of data in text record' if
|
21
|
+
text_record.length < size
|
22
|
+
|
23
|
+
entry = text_record.slice! 0, size
|
24
|
+
|
25
|
+
raise ArgumentError, 'key not found' unless entry =~ /^[^=]/
|
26
|
+
|
27
|
+
key, value = entry.split '=', 2
|
28
|
+
|
29
|
+
next unless key
|
30
|
+
|
31
|
+
@records[key] = value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def [](key)
|
36
|
+
@records[key]
|
37
|
+
end
|
38
|
+
|
39
|
+
def []=(key, value)
|
40
|
+
@records[key] = value
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Encodes this TextRecord. A key value pair must be less than 255 bytes in
|
45
|
+
# length. Keys longer than 14 bytes may not be compatible with all
|
46
|
+
# clients.
|
47
|
+
|
48
|
+
def encode
|
49
|
+
@records.sort.map do |key, value|
|
50
|
+
key = key.to_s
|
51
|
+
|
52
|
+
raise DNSSD::Error, "empty key" if key.empty?
|
53
|
+
raise DNSSD::Error, "key '#{key}' contains =" if key =~ /=/
|
54
|
+
|
55
|
+
record = value ? [key, value.to_s].join('=') : key
|
56
|
+
|
57
|
+
raise DNSSD::Error, "key value pair at '#{key}' too large to encode" if
|
58
|
+
record.length > 255
|
59
|
+
|
60
|
+
"#{record.length.chr}#{record}"
|
61
|
+
end.join ''
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_hash
|
65
|
+
@records.dup
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
data/sample/browse.rb
ADDED
data/sample/growl.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'dnssd'
|
2
|
+
|
3
|
+
browser = DNSSD.browse '_growl._tcp' do |b|
|
4
|
+
DNSSD.resolve b.name, b.type, b.domain do |r|
|
5
|
+
puts "#{b.name} of #{b.type} in #{b.domain} => #{r.target}:#{r.port} on #{b.interface} txt #{r.text_record.inspect}"
|
6
|
+
r.service.stop
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
trap 'INT' do browser.stop; exit end
|
11
|
+
trap 'TERM' do browser.stop; exit end
|
12
|
+
|
13
|
+
sleep
|
14
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'dnssd'
|
2
|
+
|
3
|
+
service = DNSSD::Service.advertise_http "Chad's server", 8808 do |service|
|
4
|
+
p service
|
5
|
+
#service.name_changed? {|name| my_widget.update(name) }
|
6
|
+
end
|
7
|
+
sleep 4
|
8
|
+
service.stop
|
9
|
+
|
10
|
+
# collects the resolve results and trys each one (overlap)...when one
|
11
|
+
# succeeds, it cancels the other checks and returns.
|
12
|
+
|
13
|
+
browser = DNSSD::Browser.for_http do |service|
|
14
|
+
host, port = service.resolve #optionally returns [host, port, iface]
|
15
|
+
end
|
16
|
+
|
17
|
+
sleep 4
|
18
|
+
|
19
|
+
browser.stop
|
20
|
+
if browser.more_coming?
|
21
|
+
puts "blah"
|
22
|
+
end
|
23
|
+
browser.service_discovered? {|service|}
|
24
|
+
browser.service_lost? {|service|}
|
25
|
+
browser.on_changed do
|
26
|
+
# get current values for UI update
|
27
|
+
end
|
28
|
+
browser.all_current #=> [service1, service2]
|
29
|
+
browser.changed?
|
30
|
+
|
data/sample/register.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'dnssd'
|
2
|
+
|
3
|
+
DNSSD.register "hey ruby", "_http._tcp", nil, 8081
|
4
|
+
|
5
|
+
registrar = DNSSD.register "chad ruby", "_http._tcp", nil, 8080 do |reply|
|
6
|
+
p :registered => reply.fullname
|
7
|
+
end
|
8
|
+
|
9
|
+
sleep 1
|
10
|
+
|
11
|
+
puts
|
12
|
+
|
13
|
+
found = {}
|
14
|
+
|
15
|
+
browser = DNSSD.browse '_http._tcp' do |reply|
|
16
|
+
if reply.flags.more_coming? then
|
17
|
+
found[reply.name] = true
|
18
|
+
else
|
19
|
+
puts "found:\n#{found.keys.join "\n"}"
|
20
|
+
puts
|
21
|
+
|
22
|
+
found.clear
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
sleep 0.1
|
27
|
+
|
28
|
+
browser.stop
|
29
|
+
registrar.stop
|
30
|
+
|
data/sample/resolve.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'dnssd'
|
2
|
+
|
3
|
+
abort "#{$0} \"http service name\"" if ARGV.empty?
|
4
|
+
|
5
|
+
resolver = DNSSD.resolve ARGV.shift, "_http._tcp", "local" do |reply|
|
6
|
+
p reply
|
7
|
+
end
|
8
|
+
|
9
|
+
trap 'INT' do resolver.stop; exit end
|
10
|
+
trap 'TERM' do resolver.stop; exit end
|
11
|
+
|
12
|
+
sleep
|
13
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'dnssd'
|
2
|
+
require 'pp'
|
3
|
+
|
4
|
+
class ChatNameResolver
|
5
|
+
def self.resolve_add(reply)
|
6
|
+
Thread.new reply do |reply|
|
7
|
+
DNSSD.resolve reply.name, reply.type, reply.domain do |resolve_reply|
|
8
|
+
puts "Adding: #{resolve_reply.inspect}"
|
9
|
+
pp resolve_reply.text_record
|
10
|
+
resolve_reply.service.stop
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
def self.resolve_remove(reply)
|
15
|
+
Thread.new reply do |reply|
|
16
|
+
DNSSD.resolve reply.name, reply.type, reply.domain do |resolve_reply|
|
17
|
+
puts "Removing: #{resolve_reply.inspect}"
|
18
|
+
resolve_reply.service.stop
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
browser = DNSSD.browse '_presence._tcp' do |reply|
|
25
|
+
if reply.flags.add? then
|
26
|
+
ChatNameResolver.resolve_add reply
|
27
|
+
else
|
28
|
+
ChatNameResolver.resolve_remove reply
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
trap 'INT' do browser.stop; exit end
|
33
|
+
trap 'TERM' do browser.stop; exit end
|
34
|
+
|
35
|
+
sleep
|
36
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'dnssd'
|
3
|
+
|
4
|
+
class TestDNSSDReply < MiniTest::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@reply = DNSSD::Reply.new
|
8
|
+
@fullname = "Eric\\032Hodel._http._tcp.local."
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_class_from_service
|
12
|
+
reply = DNSSD::Reply.from_service :service, 4
|
13
|
+
|
14
|
+
assert_equal :service, reply.service
|
15
|
+
assert_equal DNSSD::Flags::Default, reply.flags
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_fullname
|
19
|
+
@reply.set_fullname @fullname
|
20
|
+
|
21
|
+
assert_equal "Eric\\032Hodel._http._tcp.local.", @reply.fullname
|
22
|
+
|
23
|
+
@reply.instance_variable_set :@name, 'Dr. Pepper'
|
24
|
+
|
25
|
+
assert_equal "Dr\\.\\032Pepper._http._tcp.local.", @reply.fullname
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_inspect
|
29
|
+
flags = DNSSD::Flags.new
|
30
|
+
@reply.instance_variable_set :@fullname, 'blah'
|
31
|
+
@reply.instance_variable_set :@name, 'drbrain@pincer-tip'
|
32
|
+
@reply.instance_variable_set :@interface, 'en2'
|
33
|
+
@reply.instance_variable_set :@domain, 'local'
|
34
|
+
@reply.instance_variable_set :@flags, flags
|
35
|
+
@reply.instance_variable_set :@type, '_presence._tcp'
|
36
|
+
|
37
|
+
expected = "#<DNSSD::Reply:0x#{@reply.object_id.to_s 16} \"drbrain@pincer-tip\" type: _presence._tcp domain: local interface: en2 flags: #{flags}>"
|
38
|
+
assert_equal expected, @reply.inspect
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_set_fullname
|
42
|
+
@reply.set_fullname @fullname
|
43
|
+
|
44
|
+
assert_equal 'Eric Hodel', @reply.name
|
45
|
+
assert_equal '_http._tcp', @reply.type
|
46
|
+
assert_equal 'local.', @reply.domain
|
47
|
+
|
48
|
+
@reply.set_fullname "Dr\\.\\032Pepper._http._tcp.local."
|
49
|
+
|
50
|
+
assert_equal 'Dr. Pepper', @reply.name
|
51
|
+
assert_equal '_http._tcp', @reply.type
|
52
|
+
assert_equal 'local.', @reply.domain
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_set_names
|
56
|
+
@reply.set_names "Dr\\.\032Pepper", '_http._tcp', 'local.'
|
57
|
+
|
58
|
+
assert_equal "Dr.\032Pepper", @reply.name
|
59
|
+
assert_equal '_http._tcp', @reply.type
|
60
|
+
assert_equal 'local.', @reply.domain
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|