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.
@@ -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
+