toolmantim-zeroconf 0.0.2
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/README.rdoc +44 -0
- data/Rakefile +71 -0
- data/lib/dnssd.rb +137 -0
- data/lib/net/dns.rb +49 -0
- data/lib/net/dns/mdns-sd.rb +240 -0
- data/lib/net/dns/mdns.rb +1189 -0
- data/lib/net/dns/resolv-mdns.rb +230 -0
- data/lib/net/dns/resolv-replace.rb +66 -0
- data/lib/net/dns/resolv.rb +2012 -0
- data/lib/net/dns/resolvx.rb +219 -0
- data/lib/zeroconf.rb +15 -0
- data/lib/zeroconf/common.rb +0 -0
- data/lib/zeroconf/ext.rb +7 -0
- data/lib/zeroconf/pure.rb +13 -0
- data/lib/zeroconf/version.rb +3 -0
- data/originals/dnssd-0.6.0/COPYING +56 -0
- data/originals/dnssd-0.6.0/README +50 -0
- data/originals/net-mdns-0.4/COPYING +58 -0
- data/originals/net-mdns-0.4/README +21 -0
- data/originals/net-mdns-0.4/TODO +278 -0
- data/samples/exhttp.rb +50 -0
- data/samples/exhttpv1.rb +29 -0
- data/samples/exwebrick.rb +56 -0
- data/samples/mdns-watch.rb +132 -0
- data/samples/mdns.rb +238 -0
- data/samples/test_dns.rb +128 -0
- data/samples/v1demo.rb +167 -0
- data/samples/v1mdns.rb +111 -0
- data/test/stress/stress_register.rb +48 -0
- data/test/test_browse.rb +24 -0
- data/test/test_highlevel_api.rb +35 -0
- data/test/test_register.rb +32 -0
- data/test/test_resolve.rb +23 -0
- data/test/test_resolve_ichat.rb +56 -0
- data/test/test_textrecord.rb +58 -0
- metadata +93 -0
data/README.rdoc
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
= Zeroconf
|
2
|
+
|
3
|
+
Frankenstein marriage of net-mdns and dnssd.
|
4
|
+
|
5
|
+
|
6
|
+
== Installation
|
7
|
+
|
8
|
+
sudo gem sources -a http://gems.github.com
|
9
|
+
sudo gem install lachie-zeroconf
|
10
|
+
|
11
|
+
== Usage
|
12
|
+
|
13
|
+
Use zeroconf like dnssd. If you find a disparity between the pure and ext that trips you up, send me a patch!
|
14
|
+
|
15
|
+
Currently I'm thinking that the interface should be more dnssd-like, since I develop an a mac and get that for free :)
|
16
|
+
|
17
|
+
The basic discovery and publishing interfaces are similar. However the details, semantics (esp threading model, exceptions) and implementations are obviously quite different.
|
18
|
+
|
19
|
+
== Raison d'être
|
20
|
+
|
21
|
+
The interfaces of the C-based dnssd and pure ruby net-mdns are quite similar. However, there's no gem-based mechanism for switching between them based on availability.
|
22
|
+
|
23
|
+
This has lead to the forking of many of the *jour apps into dnssd and net-mdns based versions.
|
24
|
+
|
25
|
+
Zeroconf provides:
|
26
|
+
|
27
|
+
* a json-gem-style way of falling back to the pure-ruby implementation if the ext doesn't work.
|
28
|
+
* bridging discrepancies between the two extant libraries' implementations.
|
29
|
+
* perhaps a rubycocoa based implementation for osx.
|
30
|
+
|
31
|
+
Additionally, I'm hoping that this fork will breathe new life into the maintenance and development of the code; net-mdns 0.4.0 was released on 2006-05-30; dnssd 0.6.0 was released on 2004-10-07.
|
32
|
+
|
33
|
+
== Thanks
|
34
|
+
|
35
|
+
To the original authors of
|
36
|
+
|
37
|
+
* dnssd: Charlie Mills, Rich Kilmer, Chad Fowler and Stuart Cheshire.
|
38
|
+
* net-mdns: Sam Roberts
|
39
|
+
|
40
|
+
== TODO
|
41
|
+
|
42
|
+
* make the build failing warn but be non-fatal, so that the gem will install on systems without dnssd native libraries.
|
43
|
+
* make a windows gem.
|
44
|
+
* continue bridging discrepancies between dnssd and net-mdns interfaces
|
data/Rakefile
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
begin
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
rescue LoadError
|
4
|
+
end
|
5
|
+
require 'rake/clean'
|
6
|
+
|
7
|
+
require 'rbconfig'
|
8
|
+
include Config
|
9
|
+
|
10
|
+
require "./lib/zeroconf/version"
|
11
|
+
|
12
|
+
PKG = "zeroconf"
|
13
|
+
ON_WINDOWS = RUBY_PLATFORM =~ /mswin32/i
|
14
|
+
EXT_ROOT = "ext"
|
15
|
+
EXT_DL = "#{EXT_ROOT}/rdnssd.#{CONFIG['DLEXT']}"
|
16
|
+
EXT_SRC = FileList.new("#{EXT_ROOT}/*.c","#{EXT_ROOT}/*.h")
|
17
|
+
CLEAN.include 'doc', 'coverage',
|
18
|
+
FileList["ext/**/*.{so,bundle,#{CONFIG['DLEXT']},o,obj,pdb,lib,manifest,exp,def}"],
|
19
|
+
FileList["ext/**/Makefile"]
|
20
|
+
|
21
|
+
desc "compile the native extension"
|
22
|
+
task :compile => EXT_DL
|
23
|
+
|
24
|
+
file EXT_DL => EXT_SRC do
|
25
|
+
cd EXT_ROOT do
|
26
|
+
ruby 'extconf.rb'
|
27
|
+
sh 'make'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
zeroconf_gemspec = Gem::Specification.new do |s|
|
33
|
+
s.name = PKG
|
34
|
+
s.version = Zeroconf::VERSION
|
35
|
+
s.platform = Gem::Platform::RUBY
|
36
|
+
s.has_rdoc = true
|
37
|
+
s.extra_rdoc_files = ["README.rdoc"]
|
38
|
+
s.summary = "Cross-platform zeroconf (aka bonjour™) library."
|
39
|
+
s.description = s.summary
|
40
|
+
s.authors = ["Lachie Cox"]
|
41
|
+
s.email = "lachiec@gmail.com"
|
42
|
+
s.homepage = "http://github.com/lachie/zeroconf"
|
43
|
+
s.require_path = "lib"
|
44
|
+
s.files = %w(README.rdoc Rakefile) + Dir.glob("{bin,lib,spec,originals,samples,test}/**/*")
|
45
|
+
end
|
46
|
+
|
47
|
+
Rake::GemPackageTask.new(zeroconf_gemspec) do |pkg|
|
48
|
+
pkg.gem_spec = zeroconf_gemspec
|
49
|
+
end
|
50
|
+
|
51
|
+
namespace :gem do
|
52
|
+
namespace :spec do
|
53
|
+
desc "Update #{PKG}.gemspec"
|
54
|
+
task :generate do
|
55
|
+
File.open("#{PKG}.gemspec", "w") do |f|
|
56
|
+
f.puts(zeroconf_gemspec.to_ruby)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
desc "test spec in github cleanroom"
|
61
|
+
task :test => :generate do
|
62
|
+
require 'rubygems/specification'
|
63
|
+
data = File.read("#{PKG}.gemspec")
|
64
|
+
spec = nil
|
65
|
+
Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join
|
66
|
+
puts spec
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
task :install => [ :compile, :package ]
|
data/lib/dnssd.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'rdnssd'
|
2
|
+
|
3
|
+
=begin
|
4
|
+
|
5
|
+
module DNSSD
|
6
|
+
class MalformedDomainException < Exception; end
|
7
|
+
class MalformedPortException < Exception; end
|
8
|
+
|
9
|
+
def self.new_text_record(hash={})
|
10
|
+
TextRecord.new(hash)
|
11
|
+
end
|
12
|
+
|
13
|
+
class ServiceDescription
|
14
|
+
|
15
|
+
class Location
|
16
|
+
attr_accessor :name, :port, :iface
|
17
|
+
def initialize(port, name=nil, iface=0)
|
18
|
+
@name = name
|
19
|
+
@port = port
|
20
|
+
@iface = iface
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(type, name, domain, location)
|
25
|
+
@type = type
|
26
|
+
@name = name
|
27
|
+
@domain = validate_domain(domain)
|
28
|
+
@location = validate_location(location)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.for_browse_notification(name, domain,
|
32
|
+
|
33
|
+
def stop
|
34
|
+
@registrar.stop
|
35
|
+
end
|
36
|
+
|
37
|
+
def validate_location(location)
|
38
|
+
unless(location.port.respond_to?(:to_int))
|
39
|
+
raise MalformedPortException.new("#{location.port} is not a valid port number")
|
40
|
+
end
|
41
|
+
location
|
42
|
+
end
|
43
|
+
|
44
|
+
def validate_domain(domain)
|
45
|
+
unless(domain.empty? || domain =~ /^[a-z_]+$/)
|
46
|
+
raise MalformedDomainException.new("#{domain} is not a valid domain name")
|
47
|
+
end
|
48
|
+
domain
|
49
|
+
end
|
50
|
+
|
51
|
+
def advertise_and_confirm
|
52
|
+
thread = Thread.current
|
53
|
+
@registrar = register(@name, @type, @domain, @location.port, TextRecord.new) do |service, name, type, domain|
|
54
|
+
@name = name
|
55
|
+
@type = type
|
56
|
+
@domain = domain
|
57
|
+
thread.wakeup
|
58
|
+
end
|
59
|
+
Thread.stop
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.advertise_http(name, port=80, domain="", iface=0, &block)
|
63
|
+
self.advertise("_http._tcp", name, port, domain, iface, &block)
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# iface: Numerical interface (0 = all interfaces, This should be used for most applications)
|
68
|
+
#
|
69
|
+
def self.advertise(type, name, port, domain="", iface=0, &block)
|
70
|
+
service_description = ServiceDescription.new(type, name, domain, Location.new(port,nil,iface))
|
71
|
+
service_description.advertise_and_confirm
|
72
|
+
yield service_description if block_given?
|
73
|
+
service_description
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class Browser
|
78
|
+
|
79
|
+
Context = Struct.new(:service, :name, :type, :domain, :operation, :interface)
|
80
|
+
|
81
|
+
class Context
|
82
|
+
def ==(other)
|
83
|
+
self.to_s == other.to_s
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_s
|
87
|
+
"#{name}.#{type}.#{domain}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def eql?(other)
|
91
|
+
self == other
|
92
|
+
end
|
93
|
+
|
94
|
+
def hash
|
95
|
+
to_s.hash
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def on_change(&block)
|
100
|
+
@change_listener ||= []
|
101
|
+
@change_listeners << block
|
102
|
+
end
|
103
|
+
|
104
|
+
def on_add(&block)
|
105
|
+
@add_listeners || = []
|
106
|
+
@add_listeners << block
|
107
|
+
end
|
108
|
+
|
109
|
+
def on_remove(&block)
|
110
|
+
@remove_listeners || = []
|
111
|
+
@remove_listeners << block
|
112
|
+
end
|
113
|
+
|
114
|
+
def initialize(type, domain="")
|
115
|
+
@list = []
|
116
|
+
@browse_service = DNSSD::Protocol.browse(type, domain) do
|
117
|
+
|service, name, type, domain, operation, interface|
|
118
|
+
context = Context.new(service, name, type, domain, operation, interface)
|
119
|
+
puts "Name: #{name} Type: #{type} Domain: #{domain} Operation: #{operation} Interface: #{interface}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def service_descriptions
|
124
|
+
@list.clone
|
125
|
+
end
|
126
|
+
|
127
|
+
def stop
|
128
|
+
@browse_service.stop
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.for_http(domain="")
|
132
|
+
self.new("_http._tcp", domain)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
=end
|
data/lib/net/dns.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2005 Sam Roberts
|
3
|
+
|
4
|
+
This library is free software; you can redistribute it and/or modify it
|
5
|
+
under the same terms as the ruby language itself, see the file COPYING for
|
6
|
+
details.
|
7
|
+
=end
|
8
|
+
|
9
|
+
require 'net/dns/resolvx'
|
10
|
+
|
11
|
+
BasicSocket.do_not_reverse_lookup = true
|
12
|
+
|
13
|
+
module Net
|
14
|
+
# DNS exposes some of Resolv::DNS from resolv.rb to make them easier to use
|
15
|
+
# outside of the context of the Resolv class and it's DNS resolver - such as
|
16
|
+
# in MDNS. In particular, Net::DNS can be included so that full names to DNS
|
17
|
+
# classes in Resolv::DNS can be imported into your namespace.
|
18
|
+
module DNS
|
19
|
+
|
20
|
+
Message = Resolv::DNS::Message
|
21
|
+
Name = Resolv::DNS::Name
|
22
|
+
DecodeError = Resolv::DNS::DecodeError
|
23
|
+
|
24
|
+
module IN
|
25
|
+
A = Resolv::DNS::Resource::IN::A
|
26
|
+
AAAA = Resolv::DNS::Resource::IN::AAAA
|
27
|
+
ANY = Resolv::DNS::Resource::IN::ANY
|
28
|
+
CNAME = Resolv::DNS::Resource::IN::CNAME
|
29
|
+
HINFO = Resolv::DNS::Resource::IN::HINFO
|
30
|
+
MINFO = Resolv::DNS::Resource::IN::MINFO
|
31
|
+
MX = Resolv::DNS::Resource::IN::MX
|
32
|
+
NS = Resolv::DNS::Resource::IN::NS
|
33
|
+
PTR = Resolv::DNS::Resource::IN::PTR
|
34
|
+
SOA = Resolv::DNS::Resource::IN::SOA
|
35
|
+
SRV = Resolv::DNS::Resource::IN::SRV
|
36
|
+
TXT = Resolv::DNS::Resource::IN::TXT
|
37
|
+
WKS = Resolv::DNS::Resource::IN::WKS
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the resource record name of +rr+ as a short string ("IN::A",
|
41
|
+
# ...).
|
42
|
+
def self.rrname(rr)
|
43
|
+
rr = rr.class unless rr.class == Class
|
44
|
+
rr = rr.to_s.sub(/.*Resource::/, '')
|
45
|
+
rr = rr.to_s.sub(/.*DNS::/, '')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -0,0 +1,240 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2005 Sam Roberts
|
3
|
+
|
4
|
+
This library is free software; you can redistribute it and/or modify it
|
5
|
+
under the same terms as the ruby language itself, see the file COPYING for
|
6
|
+
details.
|
7
|
+
=end
|
8
|
+
|
9
|
+
require 'net/dns/mdns'
|
10
|
+
|
11
|
+
module Net
|
12
|
+
module DNS
|
13
|
+
|
14
|
+
# = DNS-SD over mDNS
|
15
|
+
#
|
16
|
+
# An implementation of DNS Service-Discovery (DNS-SD) using Net::DNS::MDNS.
|
17
|
+
#
|
18
|
+
# DNS-SD is described in draft-cheshire-dnsext-dns-sd.txt, see
|
19
|
+
# http://www.dns-sd.org for more information. It is most often seen as part
|
20
|
+
# of Apple's OS X, but is widely useful.
|
21
|
+
#
|
22
|
+
# These APIs accept and return a set of arguments which are documented once,
|
23
|
+
# here, for convenience.
|
24
|
+
#
|
25
|
+
# - type: DNS-SD classifies services into types using a naming convention.
|
26
|
+
# That convention is <_service>.<_protocol>. The underscores ("_") serve
|
27
|
+
# to differentiate from normal DNS names. Protocol is always one of
|
28
|
+
# "_tcp" or "_udp". The service is a short name, see the list at
|
29
|
+
# http://www.dns-sd.org/ServiceTypes.html. A common service is "http", the type
|
30
|
+
# of which would be "_http._tcp".
|
31
|
+
#
|
32
|
+
# - domain: Services operate in a domain, theoretically. In current practice,
|
33
|
+
# that domain is always "local".
|
34
|
+
#
|
35
|
+
# - name: Service lookup with #browse results in a name of a service of that
|
36
|
+
# type. That name is associated with a target (a host name), port,
|
37
|
+
# priority, and weight, as well as series of key to value mappings,
|
38
|
+
# specific to the service. In practice, priority and weight are widely
|
39
|
+
# ignored.
|
40
|
+
#
|
41
|
+
# - fullname: The concatention of the service name (optionally), type, and
|
42
|
+
# domain results in a single dot-seperated domain name - the "fullname".
|
43
|
+
# See Util.parse_name for more information about the format.
|
44
|
+
#
|
45
|
+
# - text_record: Service information in the form of key/value pairs.
|
46
|
+
# See Util.parse_strings for more information about the format.
|
47
|
+
#
|
48
|
+
# - flags: should return flags, similar to DNSSD, but for now we just return the
|
49
|
+
# TTL of the DNS message. A TTL of zero means a deregistration of the record.
|
50
|
+
#
|
51
|
+
# Services are advertised and resolved over specific network interfaces.
|
52
|
+
# Currently, Net::DNS::MDNS supports only a single default interface, and
|
53
|
+
# the interface will always be +nil+.
|
54
|
+
module MDNSSD
|
55
|
+
|
56
|
+
# A reply yielded by #browse, see MDNSSD for a description of the attributes.
|
57
|
+
class BrowseReply
|
58
|
+
attr_reader :interface, :fullname, :name, :type, :domain, :flags
|
59
|
+
def initialize(an) # :nodoc:
|
60
|
+
@interface = nil
|
61
|
+
@fullname = an.name.to_s
|
62
|
+
@domain, @type, @name = MDNSSD::Util.parse_name(an.data.name)
|
63
|
+
@flags = an.ttl
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Lookup a service by +type+ and +domain+.
|
68
|
+
#
|
69
|
+
# Yields a BrowseReply as services are found, in a background thread, not
|
70
|
+
# the caller's thread!
|
71
|
+
#
|
72
|
+
# Returns a MDNS::BackgroundQuery, call MDNS::BackgroundQuery#stop when
|
73
|
+
# you have found all the replies you are interested in.
|
74
|
+
def self.browse(type, domain = '.local', *ignored) # :yield: BrowseReply
|
75
|
+
dnsname = DNS::Name.create(type)
|
76
|
+
dnsname << DNS::Name.create(domain)
|
77
|
+
dnsname.absolute = true
|
78
|
+
|
79
|
+
q = MDNS::BackgroundQuery.new(dnsname, IN::PTR) do |q, answers|
|
80
|
+
answers.each do |an|
|
81
|
+
yield BrowseReply.new( an )
|
82
|
+
end
|
83
|
+
end
|
84
|
+
q
|
85
|
+
end
|
86
|
+
|
87
|
+
# A reply yielded by #resolve, see MDNSSD for a description of the attributes.
|
88
|
+
class ResolveReply
|
89
|
+
attr_reader :interface, :fullname, :name, :type, :domain, :target, :port, :priority, :weight, :text_record, :flags
|
90
|
+
def initialize(ansrv, antxt) # :nodoc:
|
91
|
+
@interface = nil
|
92
|
+
@fullname = ansrv.name.to_s
|
93
|
+
@domain, @type, @name = MDNSSD::Util.parse_name(ansrv.name)
|
94
|
+
@target = ansrv.data.target.to_s
|
95
|
+
@port = ansrv.data.port
|
96
|
+
@priority = ansrv.data.priority
|
97
|
+
@weight = ansrv.data.weight
|
98
|
+
@text_record = MDNSSD::Util.parse_strings(antxt.data.strings)
|
99
|
+
@flags = ansrv.ttl
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Resolve a service instance by +name+, +type+ and +domain+.
|
104
|
+
#
|
105
|
+
# Yields a ResolveReply as service instances are found, in a background
|
106
|
+
# thread, not the caller's thread!
|
107
|
+
#
|
108
|
+
# Returns a MDNS::BackgroundQuery, call MDNS::BackgroundQuery#stop when
|
109
|
+
# you have found all the replies you are interested in.
|
110
|
+
def self.resolve(name, type, domain = '.local', *ignored) # :yield: ResolveReply
|
111
|
+
dnsname = DNS::Name.create(name)
|
112
|
+
dnsname << DNS::Name.create(type)
|
113
|
+
dnsname << DNS::Name.create(domain)
|
114
|
+
dnsname.absolute = true
|
115
|
+
|
116
|
+
rrs = {}
|
117
|
+
|
118
|
+
q = MDNS::BackgroundQuery.new(dnsname, IN::ANY) do |q, answers|
|
119
|
+
_rrs = {}
|
120
|
+
answers.each do |an|
|
121
|
+
if an.name == dnsname
|
122
|
+
_rrs[an.type] = an
|
123
|
+
end
|
124
|
+
end
|
125
|
+
# We queried for ANY, but don't yield unless we got a SRV or TXT.
|
126
|
+
if( _rrs[IN::SRV] || _rrs[IN::TXT] )
|
127
|
+
rrs.update _rrs
|
128
|
+
|
129
|
+
ansrv, antxt = rrs[IN::SRV], rrs[IN::TXT]
|
130
|
+
|
131
|
+
# puts "ansrv->#{ansrv}"
|
132
|
+
# puts "antxt->#{antxt}"
|
133
|
+
|
134
|
+
# Even though we got an SRV or TXT, we can't yield until we have both.
|
135
|
+
if ansrv && antxt
|
136
|
+
yield ResolveReply.new( ansrv, antxt )
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
q
|
141
|
+
end
|
142
|
+
|
143
|
+
# A reply yielded by #register, see MDNSSD for a description of the attributes.
|
144
|
+
class RegisterReply
|
145
|
+
attr_reader :interface, :fullname, :name, :type, :domain
|
146
|
+
def initialize(name, type, domain)
|
147
|
+
@interface = nil
|
148
|
+
@fullname = (DNS::Name.create(name) << type << domain).to_s
|
149
|
+
@name, @type, @domain = name, type, domain
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Register a service instance on the local host.
|
154
|
+
#
|
155
|
+
# +txt+ is a Hash of String keys to String values.
|
156
|
+
#
|
157
|
+
# Because the service +name+ may already be in use on the network, a
|
158
|
+
# different name may be registered than that requested. Because of this,
|
159
|
+
# if a block is supplied, a RegisterReply will be yielded so that the
|
160
|
+
# actual service name registered may be seen.
|
161
|
+
#
|
162
|
+
# Returns a MDNS::Service, call MDNS::Service#stop when you no longer
|
163
|
+
# want to advertise the service.
|
164
|
+
#
|
165
|
+
# NOTE - The service +name+ should be unique on the network, MDNSSD
|
166
|
+
# doesn't currently attempt to ensure this. This will be fixed in
|
167
|
+
# an upcoming release.
|
168
|
+
def self.register(name, type, domain, port, txt = {}, *ignored) # :yields: RegisterReply
|
169
|
+
dnsname = DNS::Name.create(name)
|
170
|
+
dnsname << DNS::Name.create(type)
|
171
|
+
dnsname << DNS::Name.create(domain)
|
172
|
+
dnsname.absolute = true
|
173
|
+
|
174
|
+
s = MDNS::Service.new(name, type, port, txt) do |s|
|
175
|
+
s.domain = domain
|
176
|
+
end
|
177
|
+
|
178
|
+
yield RegisterReply.new(name, type, domain) if block_given?
|
179
|
+
|
180
|
+
s
|
181
|
+
end
|
182
|
+
|
183
|
+
# Utility routines not for general use.
|
184
|
+
module Util
|
185
|
+
# Decode a DNS-SD domain name. The format is:
|
186
|
+
# [<instance>.]<_service>.<_protocol>.<domain>
|
187
|
+
#
|
188
|
+
# Examples are:
|
189
|
+
# _http._tcp.local
|
190
|
+
# guest._http._tcp.local
|
191
|
+
# Ensemble Musique._daap._tcp.local
|
192
|
+
#
|
193
|
+
# The <_service>.<_protocol> combined is the <type>.
|
194
|
+
#
|
195
|
+
# Return either:
|
196
|
+
# [ <domain>, <type> ]
|
197
|
+
# or
|
198
|
+
# [ <domain>, <type>, <instance>]
|
199
|
+
#
|
200
|
+
# Because of the order of the return values, it can be called like:
|
201
|
+
# domain, type = MDNSSD::Util.parse_name(fullname)
|
202
|
+
# or
|
203
|
+
# domain, type, name = MDNSSD::Util.parse_name(fullname)
|
204
|
+
# If there is no name component to fullname, name will be nil.
|
205
|
+
def self.parse_name(dnsname)
|
206
|
+
domain, t1, t0, name = dnsname.to_a.reverse.map {|n| n.to_s}
|
207
|
+
[ domain, t0 + '.' + t1, name].compact
|
208
|
+
end
|
209
|
+
|
210
|
+
# Decode TXT record strings, an array of String.
|
211
|
+
#
|
212
|
+
# DNS-SD defines formatting conventions for them:
|
213
|
+
# - Keys must be at least one char in range (0x20-0x7E), excluding '='
|
214
|
+
# (0x3D), and they must be matched case-insensitively.
|
215
|
+
# - There may be no '=', in which case value is nil.
|
216
|
+
# - There may be an '=' with no value, in which case value is empty string, "".
|
217
|
+
# - Anything following the '=' is a value, it is not case sensitive, can be binary,
|
218
|
+
# and can include whitespace.
|
219
|
+
# - Discard all keys but the first.
|
220
|
+
# - Discard a string that aren't formatting accorded to these rules.
|
221
|
+
def self.parse_strings(strings)
|
222
|
+
h = {}
|
223
|
+
|
224
|
+
strings.each do |kv|
|
225
|
+
if kv.match( /^([\x20-\x3c\x3f-\x7e]+)(?:=(.*))?$/ )
|
226
|
+
key = $1.downcase
|
227
|
+
value = $2
|
228
|
+
next if h.has_key? key
|
229
|
+
h[key] = value
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
h
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|