dnssd 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ext/MANIFEST +0 -0
- data/ext/dns_sd.h +1493 -0
- data/ext/extconf.rb +42 -0
- data/ext/rdnssd.c +151 -0
- data/ext/rdnssd.h +38 -0
- data/ext/rdnssd_service.c +515 -0
- data/ext/rdnssd_structs.c +588 -0
- data/ext/rdnssd_tr.c +252 -0
- data/ext/run_rdoc +4 -0
- data/lib/dnssd.rb +137 -0
- metadata +47 -0
data/ext/rdnssd_tr.c
ADDED
@@ -0,0 +1,252 @@
|
|
1
|
+
/*
|
2
|
+
* Copyright (c) 2004 Chad Fowler, Charles Mills, Rich Kilmer
|
3
|
+
* Licenced under the same terms as Ruby.
|
4
|
+
* This software has absolutely no warrenty.
|
5
|
+
*/
|
6
|
+
#include "rdnssd.h"
|
7
|
+
#include <intern.h>
|
8
|
+
#include <string.h> /* for strchr() */
|
9
|
+
|
10
|
+
static VALUE cDNSSDTextRecord;
|
11
|
+
|
12
|
+
static void
|
13
|
+
dnssd_tr_decode_error(void)
|
14
|
+
{
|
15
|
+
rb_raise(rb_eArgError, "buffer contains invalid text record");
|
16
|
+
}
|
17
|
+
|
18
|
+
static void
|
19
|
+
dnssd_tr_decode_buffer(VALUE self, long buf_len, const char *buf_ptr)
|
20
|
+
{
|
21
|
+
/* iterate through text record, inserting key, value pairs into hash */
|
22
|
+
long i = 0;
|
23
|
+
while(i < buf_len) {
|
24
|
+
VALUE key, value;
|
25
|
+
const char *p;
|
26
|
+
long key_len, len = (long)(uint8_t)buf_ptr[i++];
|
27
|
+
if (i >= buf_len || i + len > buf_len)
|
28
|
+
dnssd_tr_decode_error();
|
29
|
+
|
30
|
+
p = strchr(buf_ptr + i, '=');
|
31
|
+
if (p == NULL) {
|
32
|
+
key_len = len;
|
33
|
+
key = rb_str_new(buf_ptr + i, key_len);
|
34
|
+
value = Qnil;
|
35
|
+
} else {
|
36
|
+
key_len = p - (buf_ptr + i);
|
37
|
+
if (key_len >= len)
|
38
|
+
dnssd_tr_decode_error();
|
39
|
+
key = rb_str_new(buf_ptr + i, key_len);
|
40
|
+
key_len++; /* step over '=' */
|
41
|
+
value = rb_str_new(buf_ptr + i + key_len, len - key_len);
|
42
|
+
}
|
43
|
+
rb_hash_aset(self, key, value);
|
44
|
+
i += len;
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
static void
|
49
|
+
dnssd_tr_decode_str(VALUE self, VALUE str)
|
50
|
+
{
|
51
|
+
str = StringValue(str);
|
52
|
+
/* text records cannot be longer than 65535 (0xFFFF) */
|
53
|
+
if (RSTRING(str)->len > UINT16_MAX)
|
54
|
+
rb_raise(rb_eArgError, "string is to large to encode");
|
55
|
+
dnssd_tr_decode_buffer (self, RSTRING(str)->len, RSTRING(str)->ptr);
|
56
|
+
}
|
57
|
+
|
58
|
+
/*
|
59
|
+
* call-seq:
|
60
|
+
* DNSSD::TextRecord.decode(binary_string) => text_record
|
61
|
+
*
|
62
|
+
* Create a new DNSSD::TextRecord be decoding the key
|
63
|
+
* value pairs contained in _binary_string_. See DNSSD::TextRecord.encode()
|
64
|
+
* for more information.
|
65
|
+
*/
|
66
|
+
|
67
|
+
static VALUE
|
68
|
+
dnssd_tr_decode(VALUE klass, VALUE str)
|
69
|
+
{
|
70
|
+
/* self needs to be on the stack - we add (allocate)
|
71
|
+
* lots of key, value pairs when decoding and this could
|
72
|
+
* cause the gc to run. */
|
73
|
+
volatile VALUE self = rb_obj_alloc(klass);
|
74
|
+
dnssd_tr_decode_str(self, str);
|
75
|
+
return self;
|
76
|
+
}
|
77
|
+
|
78
|
+
VALUE
|
79
|
+
dnssd_tr_new(long len, const char *buf)
|
80
|
+
{
|
81
|
+
VALUE self = rb_obj_alloc(cDNSSDTextRecord);
|
82
|
+
dnssd_tr_decode_buffer(self, len, buf);
|
83
|
+
return self;
|
84
|
+
}
|
85
|
+
|
86
|
+
/*
|
87
|
+
* call-seq:
|
88
|
+
* DNSSD::TextRecord.new() => text_record
|
89
|
+
* DNSSD::TextRecord.new(binary_string) => text_record
|
90
|
+
*
|
91
|
+
* The first form creates an empty text record. The second
|
92
|
+
* creates a new text record populated with the key, value pairs
|
93
|
+
* found in the encoded string _binary_string_. See
|
94
|
+
* DNSSD::TextRecord.encode() for more information.
|
95
|
+
*
|
96
|
+
* tr = DNSSD::TextRecord.new #=> {}
|
97
|
+
* tr["name"] = "Chad"
|
98
|
+
* tr["port"] = 3871.to_s
|
99
|
+
*
|
100
|
+
*/
|
101
|
+
|
102
|
+
static VALUE
|
103
|
+
dnssd_tr_initialize(int argc, VALUE *argv, VALUE self)
|
104
|
+
{
|
105
|
+
VALUE encoded_str;
|
106
|
+
rb_scan_args(argc, argv, "01", &encoded_str);
|
107
|
+
if (argc == 1) {
|
108
|
+
/* try to decode the string */
|
109
|
+
dnssd_tr_decode_str(self, encoded_str);
|
110
|
+
}
|
111
|
+
return self;
|
112
|
+
}
|
113
|
+
|
114
|
+
static void
|
115
|
+
dnssd_tr_valid_key(const char *key_cstr, long len)
|
116
|
+
{
|
117
|
+
/* keys cannot contain '=' */
|
118
|
+
if (strchr(key_cstr, '='))
|
119
|
+
rb_raise(rb_eRuntimeError, "key '%s' contains '='", key_cstr);
|
120
|
+
|
121
|
+
if (len <= 0)
|
122
|
+
rb_raise(rb_eRuntimeError, "empty key given");
|
123
|
+
}
|
124
|
+
|
125
|
+
static long
|
126
|
+
dnssd_tr_convert_pairs(volatile VALUE ary)
|
127
|
+
{
|
128
|
+
long i, tot_len = 0;
|
129
|
+
VALUE *ptr = RARRAY(ary)->ptr;
|
130
|
+
/* iterate over key, value pairs checking if each one is valid */
|
131
|
+
for(i=0; i<RARRAY(ary)->len; i++) {
|
132
|
+
VALUE key = RARRAY(ptr[i])->ptr[0];
|
133
|
+
VALUE value = RARRAY(ptr[i])->ptr[1];
|
134
|
+
/* checks if key is a valid C String (null terminated)
|
135
|
+
* note: StringValueCStr takes a pointer to key by &key
|
136
|
+
* this may cause key to be reassigned. */
|
137
|
+
const char *key_cstr = StringValueCStr(key);
|
138
|
+
long len = RSTRING(key)->len;
|
139
|
+
dnssd_tr_valid_key(key_cstr, len);
|
140
|
+
|
141
|
+
if (!NIL_P(value)) {
|
142
|
+
value = StringValue(value);
|
143
|
+
len += 1 + RSTRING(value)->len;
|
144
|
+
}
|
145
|
+
/* len == sum(key length, 1 for '=' if value != nil, value length) */
|
146
|
+
if (len > UINT8_MAX)
|
147
|
+
rb_raise(rb_eRuntimeError, "key, value pair at '%s' is too large to encode", key_cstr);
|
148
|
+
|
149
|
+
/* now that we know no errors are going to occur */
|
150
|
+
if (RSTRING(key)->len > 14)
|
151
|
+
rb_warn("key '%s' is greator than 14 bytes, may not be compatible with all clients", key_cstr);
|
152
|
+
/* key and value may have been reassigned by StringValue macros */
|
153
|
+
RARRAY(ptr[i])->ptr[0] = key;
|
154
|
+
RARRAY(ptr[i])->ptr[1] = value;
|
155
|
+
tot_len += len + 1; /* plus 1 to hold key, value pair length */
|
156
|
+
}
|
157
|
+
return tot_len;
|
158
|
+
}
|
159
|
+
|
160
|
+
/*
|
161
|
+
* call-seq:
|
162
|
+
* text_record.encode => an_encoded_string
|
163
|
+
*
|
164
|
+
* Encodes the contents of _text_record_ into a sequence of <em>binary strings</em>
|
165
|
+
* (one for each key, value pair).
|
166
|
+
* The each <em>binary string</em> comprises of a <em>length</em> and a <em>payload</em>.
|
167
|
+
* The <em>length</em> gives the number of bytes in the <em>payload</em>
|
168
|
+
* (must be between <code>0</code> and <code>255</code>).
|
169
|
+
* This is an unsigned integer packed into the first byte of the binary string.
|
170
|
+
* The <em>payload</em> contains a key, value pair separated by a <code>=</code> character.
|
171
|
+
* Because <code>=</code> is used as a separator, keys must not contain any
|
172
|
+
* <code>=</code> characters. Here is an example of how the key, value pair
|
173
|
+
* <code>"1rst"</code>, <code>"Sam"</code> is encoded.
|
174
|
+
*
|
175
|
+
* [00][01][02][03][04][05][06][07][08]
|
176
|
+
* \010 1 r s t = S a m
|
177
|
+
*
|
178
|
+
* It is recommended to use keys with a length less than or equal to <code>14</code> bytes
|
179
|
+
* to ensure compatibility with all clients.
|
180
|
+
*
|
181
|
+
* text_record = DNSSD::TextRecord.new
|
182
|
+
* text_record["1rst"]="Sam"
|
183
|
+
* text_record["Last"]="Green"
|
184
|
+
* text_record["email"]="sam@green.org"
|
185
|
+
* s = text_record.encode #=> "\nLast=Green\0101rst=Sam\023email=sam@green.org"
|
186
|
+
* DNSSD::TextRecord.decode(s) #=> {"Last"=>"Green", "1rst"=>"Sam", "email"=>"sam@green.org"}
|
187
|
+
*
|
188
|
+
*/
|
189
|
+
|
190
|
+
static VALUE
|
191
|
+
dnssd_tr_encode(VALUE self)
|
192
|
+
{
|
193
|
+
long i;
|
194
|
+
VALUE buf;
|
195
|
+
/* Declare ary volatile to prevent it from being reclaimed when:
|
196
|
+
* buf is allocated later, key/values are converted to strings */
|
197
|
+
volatile VALUE ary = rb_funcall2(self, rb_intern("to_a"), 0, 0);
|
198
|
+
/* array of key, value pairs */
|
199
|
+
VALUE *ptr = RARRAY(ary)->ptr;
|
200
|
+
|
201
|
+
buf = rb_str_buf_new(dnssd_tr_convert_pairs(ary));
|
202
|
+
for(i=0; i<RARRAY(ary)->len; i++) {
|
203
|
+
uint8_t len;
|
204
|
+
VALUE key = RARRAY(ptr[i])->ptr[0];
|
205
|
+
VALUE value = RARRAY(ptr[i])->ptr[1];
|
206
|
+
if (!NIL_P(value)) {
|
207
|
+
len = (uint8_t)(RSTRING(key)->len + RSTRING(value)->len + 1);
|
208
|
+
rb_str_buf_cat(buf, &len, 1);
|
209
|
+
rb_str_buf_append(buf, key);
|
210
|
+
rb_str_buf_cat(buf, "=", 1);
|
211
|
+
rb_str_buf_append(buf, value);
|
212
|
+
} else {
|
213
|
+
len = (uint8_t)RSTRING(key)->len;
|
214
|
+
rb_str_buf_cat(buf, &len, 1);
|
215
|
+
rb_str_buf_append(buf, key);
|
216
|
+
}
|
217
|
+
}
|
218
|
+
return buf;
|
219
|
+
}
|
220
|
+
|
221
|
+
VALUE
|
222
|
+
dnssd_tr_to_encoded_str(VALUE v)
|
223
|
+
{
|
224
|
+
if (rb_obj_is_kind_of(v, rb_cHash) == Qtrue)
|
225
|
+
return dnssd_tr_encode(v);
|
226
|
+
/* allow the user to use arbitrary strings as text records */
|
227
|
+
return StringValue(v);
|
228
|
+
}
|
229
|
+
|
230
|
+
/*
|
231
|
+
* Document-class: DNSSD::TextRecord
|
232
|
+
*
|
233
|
+
* DNSSD::TextRecord is a Hash with the ability to encode its contents into
|
234
|
+
* a binary string that can be send over the wire as using the DNSSD protocol.
|
235
|
+
*
|
236
|
+
*/
|
237
|
+
|
238
|
+
void
|
239
|
+
Init_DNSSD_TextRecord(void)
|
240
|
+
{
|
241
|
+
/* hack so rdoc documents the project correctly */
|
242
|
+
#ifdef mDNSSD_RDOC_HACK
|
243
|
+
mDNSSD = rb_define_module("DNSSD");
|
244
|
+
#endif
|
245
|
+
cDNSSDTextRecord = rb_define_class_under(mDNSSD, "TextRecord", rb_cHash);
|
246
|
+
|
247
|
+
rb_define_singleton_method(cDNSSDTextRecord, "decode", dnssd_tr_decode, 1);
|
248
|
+
|
249
|
+
rb_define_method(cDNSSDTextRecord, "initialize", dnssd_tr_initialize, -1);
|
250
|
+
rb_define_method(cDNSSDTextRecord, "encode", dnssd_tr_encode, 0);
|
251
|
+
}
|
252
|
+
|
data/ext/run_rdoc
ADDED
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
|
metadata
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.1
|
3
|
+
specification_version: 1
|
4
|
+
name: dnssd
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.6.0
|
7
|
+
date: 2004-10-07
|
8
|
+
summary: DNS Service Discovery (aka Rendezvous) API for Ruby
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
- ext
|
12
|
+
author: Charlie Mills
|
13
|
+
email: cmills@freeshell.org
|
14
|
+
homepage: http://dnssd.rubyforge.org
|
15
|
+
rubyforge_project:
|
16
|
+
description:
|
17
|
+
autorequire: dnssd
|
18
|
+
default_executable:
|
19
|
+
bindir: bin
|
20
|
+
has_rdoc: false
|
21
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
22
|
+
requirements:
|
23
|
+
-
|
24
|
+
- ">"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.0.0
|
27
|
+
version:
|
28
|
+
platform: ruby
|
29
|
+
files:
|
30
|
+
- lib/dnssd.rb
|
31
|
+
- ext/dns_sd.h
|
32
|
+
- ext/extconf.rb
|
33
|
+
- ext/MANIFEST
|
34
|
+
- ext/rdnssd.c
|
35
|
+
- ext/rdnssd.h
|
36
|
+
- ext/rdnssd_service.c
|
37
|
+
- ext/rdnssd_structs.c
|
38
|
+
- ext/rdnssd_tr.c
|
39
|
+
- ext/run_rdoc
|
40
|
+
test_files: []
|
41
|
+
rdoc_options: []
|
42
|
+
extra_rdoc_files: []
|
43
|
+
executables: []
|
44
|
+
extensions:
|
45
|
+
- ext/extconf.rb
|
46
|
+
requirements: []
|
47
|
+
dependencies: []
|