toolmantim-zeroconf 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|