dnssd 0.7.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.
- data.tar.gz.sig +0 -0
- data/History.txt +5 -0
- data/Manifest.txt +12 -0
- data/README.txt +48 -0
- data/Rakefile +34 -0
- data/ext/{dns_sd.h → dnssd/dns_sd.h} +0 -0
- data/ext/dnssd/dnssd.c +141 -0
- data/ext/dnssd/dnssd.h +60 -0
- data/ext/dnssd/dnssd_service.c +698 -0
- data/ext/dnssd/dnssd_structs.c +397 -0
- data/ext/dnssd/dnssd_tr.c +248 -0
- data/ext/dnssd/extconf.rb +51 -0
- data/lib/dnssd.rb +38 -25
- data/test/test_dnssd_flags.rb +152 -0
- metadata +107 -29
- metadata.gz.sig +0 -0
- data/ext/MANIFEST +0 -0
- data/ext/extconf.rb +0 -58
- data/ext/rdnssd.c +0 -150
- data/ext/rdnssd.h +0 -63
- data/ext/rdnssd_service.c +0 -712
- data/ext/rdnssd_structs.c +0 -710
- data/ext/rdnssd_tr.c +0 -257
- data/ext/run_rdoc +0 -4
@@ -0,0 +1,397 @@
|
|
1
|
+
/*
|
2
|
+
* Copyright (c) 2004 Chad Fowler, Charles Mills, Rich Kilmer
|
3
|
+
* Licensed under the same terms as Ruby.
|
4
|
+
* This software has absolutely no warranty.
|
5
|
+
*/
|
6
|
+
#include "dnssd.h"
|
7
|
+
|
8
|
+
static VALUE cDNSSDFlags;
|
9
|
+
static VALUE cDNSSDReply;
|
10
|
+
|
11
|
+
static ID dnssd_iv_flags;
|
12
|
+
static ID dnssd_iv_interface;
|
13
|
+
static ID dnssd_iv_fullname;
|
14
|
+
static ID dnssd_iv_target;
|
15
|
+
static ID dnssd_iv_port;
|
16
|
+
static ID dnssd_iv_text_record;
|
17
|
+
static ID dnssd_iv_name;
|
18
|
+
static ID dnssd_iv_type;
|
19
|
+
static ID dnssd_iv_domain;
|
20
|
+
static ID dnssd_iv_service;
|
21
|
+
|
22
|
+
/* dns sd flags, flag ID's, flag names */
|
23
|
+
#define DNSSD_MAX_FLAGS 9
|
24
|
+
|
25
|
+
static const DNSServiceFlags dnssd_flag[DNSSD_MAX_FLAGS] = {
|
26
|
+
kDNSServiceFlagsMoreComing,
|
27
|
+
|
28
|
+
kDNSServiceFlagsAdd,
|
29
|
+
kDNSServiceFlagsDefault,
|
30
|
+
|
31
|
+
kDNSServiceFlagsNoAutoRename,
|
32
|
+
|
33
|
+
kDNSServiceFlagsShared,
|
34
|
+
kDNSServiceFlagsUnique,
|
35
|
+
|
36
|
+
kDNSServiceFlagsBrowseDomains,
|
37
|
+
kDNSServiceFlagsRegistrationDomains,
|
38
|
+
|
39
|
+
kDNSServiceFlagsLongLivedQuery
|
40
|
+
};
|
41
|
+
|
42
|
+
static const char *dnssd_flag_name[DNSSD_MAX_FLAGS] = {
|
43
|
+
"more_coming",
|
44
|
+
"add",
|
45
|
+
"default",
|
46
|
+
"no_auto_rename",
|
47
|
+
"shared",
|
48
|
+
"unique",
|
49
|
+
"browse_domains",
|
50
|
+
"registration_domains",
|
51
|
+
"long_lived_query"
|
52
|
+
};
|
53
|
+
|
54
|
+
static VALUE
|
55
|
+
dnssd_struct_inspect(VALUE self, VALUE data) {
|
56
|
+
VALUE buf = rb_str_buf_new(20 + RSTRING_LEN(data));
|
57
|
+
rb_str_buf_cat2(buf, "#<");
|
58
|
+
rb_str_buf_cat2(buf, rb_obj_classname(self));
|
59
|
+
if (RSTRING_LEN(data) > 0) {
|
60
|
+
rb_str_buf_cat2(buf, " ");
|
61
|
+
rb_str_buf_append(buf, data);
|
62
|
+
}
|
63
|
+
rb_str_buf_cat2(buf, ">");
|
64
|
+
return buf;
|
65
|
+
}
|
66
|
+
|
67
|
+
VALUE
|
68
|
+
dnssd_create_fullname(const char *name, const char *regtype, const char *domain, int err_flag) {
|
69
|
+
char buffer[kDNSServiceMaxDomainName];
|
70
|
+
if ( DNSServiceConstructFullName(buffer, name, regtype, domain) ) {
|
71
|
+
static const char msg[] = "could not construct full service name";
|
72
|
+
if (err_flag) {
|
73
|
+
rb_raise(rb_eArgError, msg);
|
74
|
+
} else {
|
75
|
+
VALUE buf;
|
76
|
+
rb_warn(msg);
|
77
|
+
/* just join them all */
|
78
|
+
buf = rb_str_buf_new2(name);
|
79
|
+
rb_str_buf_cat2(buf, regtype);
|
80
|
+
rb_str_buf_cat2(buf, domain);
|
81
|
+
return buf;
|
82
|
+
}
|
83
|
+
}
|
84
|
+
buffer[kDNSServiceMaxDomainName - 1] = '\000'; /* just in case */
|
85
|
+
return rb_str_new2(buffer);
|
86
|
+
}
|
87
|
+
|
88
|
+
VALUE
|
89
|
+
dnssd_split_fullname(VALUE fullname) {
|
90
|
+
static const char re[] = "(?:\\\\.|[^\\.])+\\.";
|
91
|
+
VALUE regexp = rb_reg_new(re, sizeof(re)-1, 0);
|
92
|
+
return rb_funcall2(fullname, rb_intern("scan"), 1, ®exp);
|
93
|
+
}
|
94
|
+
|
95
|
+
#if 0
|
96
|
+
static void
|
97
|
+
quote_and_append(VALUE buf, VALUE str)
|
98
|
+
{
|
99
|
+
const char *ptr;
|
100
|
+
long i, last_mark, len;
|
101
|
+
|
102
|
+
ptr = RSTRING_PTR(str);
|
103
|
+
len = RSTRING_LEN(str);
|
104
|
+
last_mark = 0;
|
105
|
+
/* last character should be '.' */
|
106
|
+
for (i=0; i<len-1; i++) {
|
107
|
+
if (ptr[i] == '.') {
|
108
|
+
/* write 1 extra character and replace it with '\\' */
|
109
|
+
rb_str_buf_cat(buf, ptr + last_mark, i + 1 - last_mark);
|
110
|
+
RSTRING_PTR(buf)[i] = '\\';
|
111
|
+
last_mark = i;
|
112
|
+
}
|
113
|
+
}
|
114
|
+
rb_str_buf_cat(buf, ptr + last_mark, len - last_mark);
|
115
|
+
}
|
116
|
+
#endif
|
117
|
+
|
118
|
+
static VALUE
|
119
|
+
dnssd_join_names(int argc, VALUE *argv) {
|
120
|
+
int i;
|
121
|
+
VALUE buf;
|
122
|
+
long len = 0;
|
123
|
+
|
124
|
+
for (i=0; i<argc; i++) {
|
125
|
+
argv[i] = StringValue(argv[i]);
|
126
|
+
len += RSTRING_LEN(argv[i]);
|
127
|
+
}
|
128
|
+
buf = rb_str_buf_new(len);
|
129
|
+
for (i=0; i<argc; i++) {
|
130
|
+
rb_str_buf_append(buf, argv[i]);
|
131
|
+
}
|
132
|
+
return buf;
|
133
|
+
}
|
134
|
+
|
135
|
+
static void
|
136
|
+
reply_add_names(VALUE self, const char *name,
|
137
|
+
const char *regtype, const char *domain) {
|
138
|
+
rb_ivar_set(self, dnssd_iv_name, rb_str_new2(name));
|
139
|
+
rb_ivar_set(self, dnssd_iv_type, rb_str_new2(regtype));
|
140
|
+
rb_ivar_set(self, dnssd_iv_domain, rb_str_new2(domain));
|
141
|
+
rb_ivar_set(self, dnssd_iv_fullname, dnssd_create_fullname(name, regtype, domain, 0));
|
142
|
+
}
|
143
|
+
|
144
|
+
static void
|
145
|
+
reply_add_names2(VALUE self, const char *fullname) {
|
146
|
+
VALUE fn = rb_str_new2(fullname);
|
147
|
+
VALUE ary = dnssd_split_fullname(fn);
|
148
|
+
VALUE type[2] = { rb_ary_entry(ary, 1), rb_ary_entry(ary, 2) };
|
149
|
+
|
150
|
+
rb_ivar_set(self, dnssd_iv_name, rb_ary_entry(ary, 0));
|
151
|
+
rb_ivar_set(self, dnssd_iv_type, dnssd_join_names(2, type));
|
152
|
+
rb_ivar_set(self, dnssd_iv_domain, rb_ary_entry(ary, -1));
|
153
|
+
rb_ivar_set(self, dnssd_iv_fullname, fn);
|
154
|
+
}
|
155
|
+
|
156
|
+
static void
|
157
|
+
reply_set_interface(VALUE self, uint32_t interface) {
|
158
|
+
VALUE if_value;
|
159
|
+
char buffer[IF_NAMESIZE];
|
160
|
+
if (if_indextoname(interface, buffer)) {
|
161
|
+
if_value = rb_str_new2(buffer);
|
162
|
+
} else {
|
163
|
+
if_value = ULONG2NUM(interface);
|
164
|
+
}
|
165
|
+
rb_ivar_set(self, dnssd_iv_interface, if_value);
|
166
|
+
}
|
167
|
+
|
168
|
+
static void
|
169
|
+
reply_set_tr(VALUE self, uint16_t txt_len, const char *txt_rec) {
|
170
|
+
rb_ivar_set(self, dnssd_iv_text_record, dnssd_tr_new((long)txt_len, txt_rec));
|
171
|
+
}
|
172
|
+
|
173
|
+
static VALUE
|
174
|
+
reply_new(VALUE service, DNSServiceFlags flags) {
|
175
|
+
VALUE self = rb_obj_alloc(cDNSSDReply);
|
176
|
+
rb_ivar_set(self, dnssd_iv_service, service);
|
177
|
+
rb_ivar_set(self, dnssd_iv_flags,
|
178
|
+
rb_funcall(cDNSSDFlags, rb_intern("new"), 1, flags));
|
179
|
+
return self;
|
180
|
+
}
|
181
|
+
|
182
|
+
VALUE
|
183
|
+
dnssd_domain_enum_new(VALUE service, DNSServiceFlags flags,
|
184
|
+
uint32_t interface, const char *domain) {
|
185
|
+
VALUE d, self = reply_new(service, flags);
|
186
|
+
reply_set_interface(self, interface);
|
187
|
+
d = rb_str_new2(domain);
|
188
|
+
rb_ivar_set(self, dnssd_iv_domain, d);
|
189
|
+
rb_ivar_set(self, dnssd_iv_fullname, d);
|
190
|
+
return self;
|
191
|
+
}
|
192
|
+
|
193
|
+
VALUE
|
194
|
+
dnssd_browse_new(VALUE service, DNSServiceFlags flags, uint32_t interface,
|
195
|
+
const char *name, const char *regtype, const char *domain) {
|
196
|
+
VALUE self = reply_new(service, flags);
|
197
|
+
reply_set_interface(self, interface);
|
198
|
+
reply_add_names(self, name, regtype, domain);
|
199
|
+
return self;
|
200
|
+
}
|
201
|
+
|
202
|
+
#if 0
|
203
|
+
static VALUE
|
204
|
+
dnssd_gethostname(void)
|
205
|
+
{
|
206
|
+
#if HAVE_GETHOSTNAME
|
207
|
+
#ifndef MAXHOSTNAMELEN
|
208
|
+
#define MAXHOSTNAMELEN 256
|
209
|
+
#endif
|
210
|
+
char buffer[MAXHOSTNAMELEN + 1];
|
211
|
+
if (gethostname(buffer, MAXHOSTNAMELEN))
|
212
|
+
return Qnil;
|
213
|
+
buffer[MAXHOSTNAMELEN] = '\000';
|
214
|
+
return rb_str_new2(buffer);
|
215
|
+
#else
|
216
|
+
return Qnil;
|
217
|
+
#endif
|
218
|
+
}
|
219
|
+
#endif
|
220
|
+
|
221
|
+
VALUE
|
222
|
+
dnssd_register_new(VALUE service, DNSServiceFlags flags, const char *name,
|
223
|
+
const char *regtype, const char *domain) {
|
224
|
+
VALUE self = reply_new(service, flags);
|
225
|
+
reply_add_names(self, name, regtype, domain);
|
226
|
+
/* HACK */
|
227
|
+
/* See HACK in dnssd_service.c */
|
228
|
+
rb_ivar_set(self, dnssd_iv_interface, rb_ivar_get(service, dnssd_iv_interface));
|
229
|
+
rb_ivar_set(self, dnssd_iv_port, rb_ivar_get(service, dnssd_iv_port));
|
230
|
+
rb_ivar_set(self, dnssd_iv_text_record, rb_ivar_get(service, dnssd_iv_text_record));
|
231
|
+
/********/
|
232
|
+
return self;
|
233
|
+
}
|
234
|
+
|
235
|
+
VALUE
|
236
|
+
dnssd_resolve_new(VALUE service, DNSServiceFlags flags, uint32_t interface,
|
237
|
+
const char *fullname, const char *host_target,
|
238
|
+
uint16_t opaqueport, uint16_t txt_len, const char *txt_rec) {
|
239
|
+
uint16_t port = ntohs(opaqueport);
|
240
|
+
VALUE self = reply_new(service, flags);
|
241
|
+
reply_set_interface(self, interface);
|
242
|
+
reply_add_names2(self, fullname);
|
243
|
+
rb_ivar_set(self, dnssd_iv_target, rb_str_new2(host_target));
|
244
|
+
rb_ivar_set(self, dnssd_iv_port, UINT2NUM(port));
|
245
|
+
reply_set_tr(self, txt_len, txt_rec);
|
246
|
+
return self;
|
247
|
+
}
|
248
|
+
|
249
|
+
/*
|
250
|
+
* call-seq:
|
251
|
+
* reply.inspect => string
|
252
|
+
*
|
253
|
+
*/
|
254
|
+
static VALUE
|
255
|
+
reply_inspect(VALUE self) {
|
256
|
+
VALUE fullname = rb_ivar_get(self, dnssd_iv_fullname);
|
257
|
+
return dnssd_struct_inspect(self, StringValue(fullname));
|
258
|
+
}
|
259
|
+
|
260
|
+
/*
|
261
|
+
* call-seq:
|
262
|
+
* DNSSD::Reply.new() => raises a RuntimeError
|
263
|
+
*
|
264
|
+
*/
|
265
|
+
static VALUE
|
266
|
+
reply_initialize(int argc, VALUE *argv, VALUE reply) {
|
267
|
+
dnssd_instantiation_error(rb_obj_classname(reply));
|
268
|
+
return Qnil;
|
269
|
+
}
|
270
|
+
|
271
|
+
void
|
272
|
+
Init_DNSSD_Replies(void) {
|
273
|
+
int i;
|
274
|
+
VALUE flags_hash;
|
275
|
+
/* hack so rdoc documents the project correctly */
|
276
|
+
#ifdef mDNSSD_RDOC_HACK
|
277
|
+
mDNSSD = rb_define_module("DNSSD");
|
278
|
+
#endif
|
279
|
+
|
280
|
+
dnssd_iv_flags = rb_intern("@flags");
|
281
|
+
dnssd_iv_interface = rb_intern("@interface");
|
282
|
+
dnssd_iv_fullname = rb_intern("@fullname");
|
283
|
+
dnssd_iv_target = rb_intern("@target");
|
284
|
+
dnssd_iv_port = rb_intern("@port");
|
285
|
+
dnssd_iv_text_record = rb_intern("@text_record");
|
286
|
+
dnssd_iv_name = rb_intern("@name");
|
287
|
+
dnssd_iv_type = rb_intern("@type");
|
288
|
+
dnssd_iv_domain = rb_intern("@domain");
|
289
|
+
dnssd_iv_service = rb_intern("@service");
|
290
|
+
|
291
|
+
cDNSSDFlags = rb_define_class_under(mDNSSD, "Flags", rb_cObject);
|
292
|
+
|
293
|
+
cDNSSDReply = rb_define_class_under(mDNSSD, "Reply", rb_cObject);
|
294
|
+
/* DNSSD::Reply objects can only be instantiated by
|
295
|
+
* DNSSD.browse(), DNSSD.register(), DNSSD.resolve(), DNSSD.enumerate_domains(). */
|
296
|
+
rb_define_method(cDNSSDReply, "initialize", reply_initialize, -1);
|
297
|
+
/* The service associated with the reply. See DNSSD::Service for more information. */
|
298
|
+
rb_define_attr(cDNSSDReply, "service", 1, 0);
|
299
|
+
/* Flags describing the reply. See DNSSD::Flags for more information. */
|
300
|
+
rb_define_attr(cDNSSDReply, "flags", 1, 0);
|
301
|
+
/* The service name. (Not used by DNSSD.enumerate_domains().) */
|
302
|
+
rb_define_attr(cDNSSDReply, "name", 1, 0);
|
303
|
+
/* The service type. (Not used by DNSSD.enumerate_domains().) */
|
304
|
+
rb_define_attr(cDNSSDReply, "type", 1, 0);
|
305
|
+
/* The service domain. */
|
306
|
+
rb_define_attr(cDNSSDReply, "domain", 1, 0);
|
307
|
+
/* The interface on which the service is available. (Used only by DNSSSD.resolve().) */
|
308
|
+
rb_define_attr(cDNSSDReply, "interface", 1, 0);
|
309
|
+
/* The full service domain name, in the form "<servicename>.<protocol>.<domain>.".
|
310
|
+
* (Any literal dots (".") are escaped with a backslash ("\."), and literal
|
311
|
+
* backslashes are escaped with a second backslash ("\\"), e.g. a web server
|
312
|
+
* named "Dr. Pepper" would have the fullname "Dr\.\032Pepper._http._tcp.local.".)
|
313
|
+
* See DNSSD::Service.fullname() for more information. */
|
314
|
+
rb_define_attr(cDNSSDReply, "fullname", 1, 0);
|
315
|
+
/* The service's primary text record, see DNSSD::TextRecord for more information. */
|
316
|
+
rb_define_attr(cDNSSDReply, "text_record", 1, 0);
|
317
|
+
/* The target hostname of the machine providing the service.
|
318
|
+
* This name can be passed to functions like Socket.gethostbyname()
|
319
|
+
* to identify the host's IP address. */
|
320
|
+
rb_define_attr(cDNSSDReply, "target", 1, 0);
|
321
|
+
/* The port on which connections are accepted for this service. */
|
322
|
+
rb_define_attr(cDNSSDReply, "port", 1, 0);
|
323
|
+
|
324
|
+
rb_define_method(cDNSSDReply, "inspect", reply_inspect, 0);
|
325
|
+
|
326
|
+
/* flag constants */
|
327
|
+
#if DNSSD_MAX_FLAGS != 9
|
328
|
+
#error The code below needs to be updated.
|
329
|
+
#endif
|
330
|
+
/* MoreComing indicates that at least one more result is queued and will be delivered following immediately after this one.
|
331
|
+
* Applications should not update their UI to display browse
|
332
|
+
* results when the MoreComing flag is set, because this would
|
333
|
+
* result in a great deal of ugly flickering on the screen.
|
334
|
+
* Applications should instead wait until until MoreComing is not set,
|
335
|
+
* and then update their UI.
|
336
|
+
* When MoreComing is not set, that doesn't mean there will be no more
|
337
|
+
* answers EVER, just that there are no more answers immediately
|
338
|
+
* available right now at this instant. If more answers become available
|
339
|
+
* in the future they will be delivered as usual.
|
340
|
+
*/
|
341
|
+
rb_define_const(cDNSSDFlags, "MoreComing", ULONG2NUM(kDNSServiceFlagsMoreComing));
|
342
|
+
|
343
|
+
|
344
|
+
/* Flags for domain enumeration and DNSSD.browse() reply callbacks.
|
345
|
+
* DNSSD::Flags::Default applies only to enumeration and is only valid in
|
346
|
+
* conjuction with DNSSD::Flags::Add. An enumeration callback with the DNSSD::Flags::Add
|
347
|
+
* flag NOT set indicates a DNSSD::Flags::Remove, i.e. the domain is no longer valid.
|
348
|
+
*/
|
349
|
+
rb_define_const(cDNSSDFlags, "Add", ULONG2NUM(kDNSServiceFlagsAdd));
|
350
|
+
rb_define_const(cDNSSDFlags, "Default", ULONG2NUM(kDNSServiceFlagsDefault));
|
351
|
+
|
352
|
+
/* Flag for specifying renaming behavior on name conflict when registering non-shared records.
|
353
|
+
* By default, name conflicts are automatically handled by renaming the service.
|
354
|
+
* DNSSD::Flags::NoAutoRename overrides this behavior - with this
|
355
|
+
* flag set, name conflicts will result in a callback. The NoAutoRename flag
|
356
|
+
* is only valid if a name is explicitly specified when registering a service
|
357
|
+
* (ie the default name is not used.)
|
358
|
+
*/
|
359
|
+
rb_define_const(cDNSSDFlags, "NoAutoRename", ULONG2NUM(kDNSServiceFlagsNoAutoRename));
|
360
|
+
|
361
|
+
/* Flag for registering individual records on a connected DNSServiceRef.
|
362
|
+
* DNSSD::Flags::Shared indicates that there may be multiple records
|
363
|
+
* with this name on the network (e.g. PTR records). DNSSD::Flags::Unique indicates that the
|
364
|
+
* record's name is to be unique on the network (e.g. SRV records).
|
365
|
+
* (DNSSD::Flags::Shared and DNSSD::Flags::Unique are currently not used by the Ruby API.)
|
366
|
+
*/
|
367
|
+
rb_define_const(cDNSSDFlags, "Shared", ULONG2NUM(kDNSServiceFlagsShared));
|
368
|
+
rb_define_const(cDNSSDFlags, "Unique", ULONG2NUM(kDNSServiceFlagsUnique));
|
369
|
+
|
370
|
+
/* Flags for specifying domain enumeration type in DNSSD.enumerate_domains()
|
371
|
+
* (currently not part of the Ruby API).
|
372
|
+
* DNSSD::Flags::BrowseDomains enumerates domains recommended for browsing,
|
373
|
+
* DNSSD::Flags::RegistrationDomains enumerates domains recommended for registration.
|
374
|
+
*/
|
375
|
+
rb_define_const(cDNSSDFlags, "BrowseDomains", ULONG2NUM(kDNSServiceFlagsBrowseDomains));
|
376
|
+
rb_define_const(cDNSSDFlags, "RegistrationDomains", ULONG2NUM(kDNSServiceFlagsRegistrationDomains));
|
377
|
+
|
378
|
+
/* Flag for creating a long-lived unicast query for the DNSDS.query_record()
|
379
|
+
* (currently not part of the Ruby API). */
|
380
|
+
rb_define_const(cDNSSDFlags, "LongLivedQuery", ULONG2NUM(kDNSServiceFlagsLongLivedQuery));
|
381
|
+
|
382
|
+
flags_hash = rb_hash_new();
|
383
|
+
|
384
|
+
for (i = 0; i < DNSSD_MAX_FLAGS; i++) {
|
385
|
+
rb_hash_aset(flags_hash, rb_str_new2(dnssd_flag_name[i]),
|
386
|
+
ULONG2NUM(dnssd_flag[i]));
|
387
|
+
}
|
388
|
+
|
389
|
+
rb_define_const(cDNSSDFlags, "FLAGS", flags_hash);
|
390
|
+
}
|
391
|
+
|
392
|
+
/* Document-class: DNSSD::Reply
|
393
|
+
*
|
394
|
+
* DNSSD::Reply is used to return information
|
395
|
+
*
|
396
|
+
*/
|
397
|
+
|
@@ -0,0 +1,248 @@
|
|
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 "dnssd.h"
|
7
|
+
#include <string.h> /* for strchr() */
|
8
|
+
|
9
|
+
static VALUE cDNSSDTextRecord;
|
10
|
+
|
11
|
+
static void
|
12
|
+
dnssd_tr_decode_buffer(VALUE self, long buf_len, const char *buf_ptr) {
|
13
|
+
/* iterate through text record, inserting key, value pairs into hash */
|
14
|
+
long i = 0;
|
15
|
+
while(i < buf_len) {
|
16
|
+
VALUE key, value;
|
17
|
+
const char *p;
|
18
|
+
long key_len, len = (long)(uint8_t)buf_ptr[i++];
|
19
|
+
if (i + len > buf_len)
|
20
|
+
rb_raise(rb_eArgError, "invalid text record (string longer than buffer)");
|
21
|
+
|
22
|
+
if (len == 0) {
|
23
|
+
/* 0-length key/value, handle this by skipping over it.
|
24
|
+
* Probably the RR should have zero-length, but some services have been
|
25
|
+
* found to advertise a non-zero length TXT record, with a zero-length
|
26
|
+
* key/value string.
|
27
|
+
*/
|
28
|
+
continue;
|
29
|
+
}
|
30
|
+
|
31
|
+
p = memchr(buf_ptr + i, '=', buf_len - i);
|
32
|
+
if (p == NULL) {
|
33
|
+
/* key, with no value, this is OK */
|
34
|
+
key_len = len;
|
35
|
+
key = rb_str_new(buf_ptr + i, key_len);
|
36
|
+
value = Qnil;
|
37
|
+
} else {
|
38
|
+
key_len = p - (buf_ptr + i);
|
39
|
+
if(key_len == 0)
|
40
|
+
rb_raise(rb_eArgError, "invalid text record (zero-length key)");
|
41
|
+
|
42
|
+
key = rb_str_new(buf_ptr + i, key_len);
|
43
|
+
key_len++; /* step over '=' */
|
44
|
+
value = rb_str_new(buf_ptr + i + key_len, len - key_len);
|
45
|
+
}
|
46
|
+
rb_hash_aset(self, key, value);
|
47
|
+
i += len;
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
static void
|
52
|
+
dnssd_tr_decode_str(VALUE self, VALUE str) {
|
53
|
+
str = StringValue(str);
|
54
|
+
/* text records cannot be longer than 65535 (0xFFFF) */
|
55
|
+
if (RSTRING_LEN(str) > UINT16_MAX)
|
56
|
+
rb_raise(rb_eArgError, "string is to large to encode");
|
57
|
+
dnssd_tr_decode_buffer (self, RSTRING_LEN(str), RSTRING_PTR(str));
|
58
|
+
}
|
59
|
+
|
60
|
+
/*
|
61
|
+
* call-seq:
|
62
|
+
* DNSSD::TextRecord.decode(binary_string) => text_record
|
63
|
+
*
|
64
|
+
* Create a new DNSSD::TextRecord be decoding the key
|
65
|
+
* value pairs contained in _binary_string_. See DNSSD::TextRecord.encode()
|
66
|
+
* for more information.
|
67
|
+
*/
|
68
|
+
|
69
|
+
static VALUE
|
70
|
+
dnssd_tr_decode(VALUE klass, VALUE str) {
|
71
|
+
/* self needs to be on the stack - we add (allocate)
|
72
|
+
* lots of key, value pairs when decoding and this could
|
73
|
+
* cause the gc to run. */
|
74
|
+
volatile VALUE self = rb_obj_alloc(klass);
|
75
|
+
dnssd_tr_decode_str(self, str);
|
76
|
+
return self;
|
77
|
+
}
|
78
|
+
|
79
|
+
VALUE
|
80
|
+
dnssd_tr_new(long len, const char *buf) {
|
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
|
+
VALUE encoded_str;
|
105
|
+
rb_scan_args(argc, argv, "01", &encoded_str);
|
106
|
+
if (argc == 1) {
|
107
|
+
/* try to decode the string */
|
108
|
+
dnssd_tr_decode_str(self, encoded_str);
|
109
|
+
}
|
110
|
+
return self;
|
111
|
+
}
|
112
|
+
|
113
|
+
static void
|
114
|
+
dnssd_tr_valid_key(const char *key_cstr, long len) {
|
115
|
+
/* keys cannot contain '=' */
|
116
|
+
if (strchr(key_cstr, '='))
|
117
|
+
rb_raise(eDNSSDError, "key '%s' contains '='", key_cstr);
|
118
|
+
|
119
|
+
if (len <= 0)
|
120
|
+
rb_raise(eDNSSDError, "empty key given");
|
121
|
+
}
|
122
|
+
|
123
|
+
static long
|
124
|
+
dnssd_tr_convert_pairs(volatile VALUE ary) {
|
125
|
+
long i, tot_len = 0;
|
126
|
+
VALUE *ptr = RARRAY_PTR(ary);
|
127
|
+
/* iterate over key, value pairs checking if each one is valid */
|
128
|
+
for(i=0; i<RARRAY_LEN(ary); i++) {
|
129
|
+
VALUE key = RARRAY_PTR(ptr[i])[0];
|
130
|
+
VALUE value = RARRAY_PTR(ptr[i])[1];
|
131
|
+
/* checks if key is a valid C String (null terminated)
|
132
|
+
* note: StringValueCStr takes a pointer to key by &key
|
133
|
+
* this may cause key to be reassigned. */
|
134
|
+
const char *key_cstr = StringValueCStr(key);
|
135
|
+
long len = RSTRING_LEN(key);
|
136
|
+
dnssd_tr_valid_key(key_cstr, len);
|
137
|
+
|
138
|
+
if (!NIL_P(value)) {
|
139
|
+
value = StringValue(value);
|
140
|
+
len += 1 + RSTRING_LEN(value);
|
141
|
+
}
|
142
|
+
/* len == sum(key length, 1 for '=' if value != nil, value length) */
|
143
|
+
if (len > UINT8_MAX)
|
144
|
+
rb_raise(eDNSSDError, "key, value pair at '%s' is too large to encode", key_cstr);
|
145
|
+
|
146
|
+
/* now that we know no errors are going to occur */
|
147
|
+
if (RSTRING_LEN(key) > 14)
|
148
|
+
rb_warn("key '%s' is greator than 14 bytes, may not be compatible with all clients", key_cstr);
|
149
|
+
/* key and value may have been reassigned by StringValue macros */
|
150
|
+
RARRAY_PTR(ptr[i])[0] = key;
|
151
|
+
RARRAY_PTR(ptr[i])[1] = value;
|
152
|
+
tot_len += len + 1; /* plus 1 to hold key, value pair length */
|
153
|
+
}
|
154
|
+
return tot_len;
|
155
|
+
}
|
156
|
+
|
157
|
+
/*
|
158
|
+
* call-seq:
|
159
|
+
* text_record.encode => an_encoded_string
|
160
|
+
*
|
161
|
+
* Encodes the contents of _text_record_ into a sequence of <em>binary strings</em>
|
162
|
+
* (one for each key, value pair).
|
163
|
+
* The each <em>binary string</em> comprises of a <em>length</em> and a <em>payload</em>.
|
164
|
+
* The <em>length</em> gives the number of bytes in the <em>payload</em>
|
165
|
+
* (must be between <code>0</code> and <code>255</code>).
|
166
|
+
* This is an unsigned integer packed into the first byte of the binary string.
|
167
|
+
* The <em>payload</em> contains a key, value pair separated by a <code>=</code> character.
|
168
|
+
* Because <code>=</code> is used as a separator, keys must not contain any
|
169
|
+
* <code>=</code> characters. Here is an example of how the key, value pair
|
170
|
+
* <code>"1rst"</code>, <code>"Sam"</code> is encoded.
|
171
|
+
*
|
172
|
+
* [00][01][02][03][04][05][06][07][08]
|
173
|
+
* \010 1 r s t = S a m
|
174
|
+
*
|
175
|
+
* It is recommended to use keys with a length less than or equal to <code>14</code> bytes
|
176
|
+
* to ensure compatibility with all clients.
|
177
|
+
*
|
178
|
+
* text_record = DNSSD::TextRecord.new
|
179
|
+
* text_record["1rst"]="Sam"
|
180
|
+
* text_record["Last"]="Green"
|
181
|
+
* text_record["email"]="sam@green.org"
|
182
|
+
* s = text_record.encode #=> "\nLast=Green\0101rst=Sam\023email=sam@green.org"
|
183
|
+
* DNSSD::TextRecord.decode(s) #=> {"Last"=>"Green", "1rst"=>"Sam", "email"=>"sam@green.org"}
|
184
|
+
*
|
185
|
+
*/
|
186
|
+
|
187
|
+
static VALUE
|
188
|
+
dnssd_tr_encode(VALUE self) {
|
189
|
+
long i;
|
190
|
+
VALUE buf;
|
191
|
+
/* Declare ary volatile to prevent it from being reclaimed when:
|
192
|
+
* buf is allocated later, key/values are converted to strings */
|
193
|
+
volatile VALUE ary = rb_funcall2(self, rb_intern("to_a"), 0, 0);
|
194
|
+
/* array of key, value pairs */
|
195
|
+
VALUE *ptr = RARRAY_PTR(ary);
|
196
|
+
|
197
|
+
buf = rb_str_buf_new(dnssd_tr_convert_pairs(ary));
|
198
|
+
for(i=0; i<RARRAY_LEN(ary); i++) {
|
199
|
+
int len;
|
200
|
+
VALUE key = RARRAY_PTR(ptr[i])[0];
|
201
|
+
VALUE value = RARRAY_PTR(ptr[i])[1];
|
202
|
+
if (!NIL_P(value)) {
|
203
|
+
len = (RSTRING_LEN(key) + RSTRING_LEN(value) + 1);
|
204
|
+
/* FIXME: This can't work. WTF */
|
205
|
+
/* len is a number, not a pointer to a character. */
|
206
|
+
rb_str_buf_cat(buf, &len, 1);
|
207
|
+
rb_str_buf_append(buf, key);
|
208
|
+
rb_str_buf_cat(buf, "=", 1);
|
209
|
+
rb_str_buf_append(buf, value);
|
210
|
+
} else {
|
211
|
+
len = RSTRING_LEN(key);
|
212
|
+
rb_str_buf_cat(buf, &len, 1);
|
213
|
+
rb_str_buf_append(buf, key);
|
214
|
+
}
|
215
|
+
}
|
216
|
+
return buf;
|
217
|
+
}
|
218
|
+
|
219
|
+
VALUE
|
220
|
+
dnssd_tr_to_encoded_str(VALUE v) {
|
221
|
+
if (rb_obj_is_kind_of(v, rb_cHash) == Qtrue)
|
222
|
+
return dnssd_tr_encode(v);
|
223
|
+
/* allow the user to use arbitrary strings as text records */
|
224
|
+
return StringValue(v);
|
225
|
+
}
|
226
|
+
|
227
|
+
/*
|
228
|
+
* Document-class: DNSSD::TextRecord
|
229
|
+
*
|
230
|
+
* DNSSD::TextRecord is a Hash with the ability to encode its contents into
|
231
|
+
* a binary string that can be send over the wire as using the DNSSD protocol.
|
232
|
+
*
|
233
|
+
*/
|
234
|
+
|
235
|
+
void
|
236
|
+
Init_DNSSD_TextRecord(void) {
|
237
|
+
/* hack so rdoc documents the project correctly */
|
238
|
+
#ifdef mDNSSD_RDOC_HACK
|
239
|
+
mDNSSD = rb_define_module("DNSSD");
|
240
|
+
#endif
|
241
|
+
cDNSSDTextRecord = rb_define_class_under(mDNSSD, "TextRecord", rb_cHash);
|
242
|
+
|
243
|
+
rb_define_singleton_method(cDNSSDTextRecord, "decode", dnssd_tr_decode, 1);
|
244
|
+
|
245
|
+
rb_define_method(cDNSSDTextRecord, "initialize", dnssd_tr_initialize, -1);
|
246
|
+
rb_define_method(cDNSSDTextRecord, "encode", dnssd_tr_encode, 0);
|
247
|
+
}
|
248
|
+
|