dnssd 1.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|