dnssd 1.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,10 +7,13 @@ require 'dnssd/dnssd'
7
7
  # Service Discovery aware.
8
8
 
9
9
  module DNSSD
10
- VERSION = '1.0'
10
+ VERSION = '1.1.0'
11
11
  end
12
12
 
13
13
  require 'dnssd/flags'
14
+ require 'dnssd/reply'
15
+ require 'dnssd/service'
16
+ require 'dnssd/text_record'
14
17
 
15
18
  =begin
16
19
 
@@ -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
@@ -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
+
@@ -0,0 +1,11 @@
1
+ require 'dnssd'
2
+
3
+ browser = DNSSD.browse '_presence._tcp' do |reply|
4
+ p reply
5
+ end
6
+
7
+ trap 'INT' do browser.stop; exit end
8
+ trap 'TERM' do browser.stop; exit end
9
+
10
+ sleep
11
+
@@ -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
+
@@ -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
+
@@ -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
+