pNet-DNS 0.0.1

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.
Files changed (71) hide show
  1. data/README +68 -0
  2. data/lib/Net/DNS.rb +879 -0
  3. data/lib/Net/DNS/Header.rb +303 -0
  4. data/lib/Net/DNS/Nameserver.rb +601 -0
  5. data/lib/Net/DNS/Packet.rb +851 -0
  6. data/lib/Net/DNS/Question.rb +117 -0
  7. data/lib/Net/DNS/RR.rb +630 -0
  8. data/lib/Net/DNS/RR/A.rb +103 -0
  9. data/lib/Net/DNS/RR/AAAA.rb +147 -0
  10. data/lib/Net/DNS/RR/AFSDB.rb +114 -0
  11. data/lib/Net/DNS/RR/CERT.rb +191 -0
  12. data/lib/Net/DNS/RR/CNAME.rb +89 -0
  13. data/lib/Net/DNS/RR/DNAME.rb +84 -0
  14. data/lib/Net/DNS/RR/EID.rb +70 -0
  15. data/lib/Net/DNS/RR/HINFO.rb +108 -0
  16. data/lib/Net/DNS/RR/ISDN.rb +118 -0
  17. data/lib/Net/DNS/RR/LOC.rb +341 -0
  18. data/lib/Net/DNS/RR/MB.rb +92 -0
  19. data/lib/Net/DNS/RR/MG.rb +96 -0
  20. data/lib/Net/DNS/RR/MINFO.rb +109 -0
  21. data/lib/Net/DNS/RR/MR.rb +92 -0
  22. data/lib/Net/DNS/RR/MX.rb +124 -0
  23. data/lib/Net/DNS/RR/NAPTR.rb +182 -0
  24. data/lib/Net/DNS/RR/NIMLOC.rb +70 -0
  25. data/lib/Net/DNS/RR/NS.rb +100 -0
  26. data/lib/Net/DNS/RR/NSAP.rb +273 -0
  27. data/lib/Net/DNS/RR/NULL.rb +68 -0
  28. data/lib/Net/DNS/RR/OPT.rb +251 -0
  29. data/lib/Net/DNS/RR/PTR.rb +93 -0
  30. data/lib/Net/DNS/RR/PX.rb +131 -0
  31. data/lib/Net/DNS/RR/RP.rb +108 -0
  32. data/lib/Net/DNS/RR/RT.rb +115 -0
  33. data/lib/Net/DNS/RR/SOA.rb +195 -0
  34. data/lib/Net/DNS/RR/SPF.rb +46 -0
  35. data/lib/Net/DNS/RR/SRV.rb +153 -0
  36. data/lib/Net/DNS/RR/SSHFP.rb +190 -0
  37. data/lib/Net/DNS/RR/TKEY.rb +219 -0
  38. data/lib/Net/DNS/RR/TSIG.rb +358 -0
  39. data/lib/Net/DNS/RR/TXT.rb +162 -0
  40. data/lib/Net/DNS/RR/UNKNOWN.rb +76 -0
  41. data/lib/Net/DNS/RR/X25.rb +90 -0
  42. data/lib/Net/DNS/Resolver.rb +2090 -0
  43. data/lib/Net/DNS/Resolver/Recurse.rb +478 -0
  44. data/lib/Net/DNS/Update.rb +189 -0
  45. data/test/custom.txt +4 -0
  46. data/test/resolv.conf +4 -0
  47. data/test/tc_escapedchars.rb +498 -0
  48. data/test/tc_header.rb +91 -0
  49. data/test/tc_inet6.rb +169 -0
  50. data/test/tc_misc.rb +137 -0
  51. data/test/tc_online.rb +236 -0
  52. data/test/tc_packet.rb +174 -0
  53. data/test/tc_packet_unique_push.rb +126 -0
  54. data/test/tc_question.rb +49 -0
  55. data/test/tc_recurse.rb +69 -0
  56. data/test/tc_res_env.rb +59 -0
  57. data/test/tc_res_file.rb +55 -0
  58. data/test/tc_res_opt.rb +135 -0
  59. data/test/tc_resolver.rb +102 -0
  60. data/test/tc_rr-opt.rb +40 -0
  61. data/test/tc_rr-rrsort.rb +116 -0
  62. data/test/tc_rr-txt.rb +138 -0
  63. data/test/tc_rr-unknown.rb +95 -0
  64. data/test/tc_rr.rb +246 -0
  65. data/test/tc_tcp.rb +34 -0
  66. data/test/tc_tkey.rb +115 -0
  67. data/test/tc_update.rb +226 -0
  68. data/test/ts_netdns.rb +17 -0
  69. data/test/ts_offline.rb +32 -0
  70. data/test/ts_online.rb +33 -0
  71. metadata +119 -0
@@ -0,0 +1,341 @@
1
+ # The contents of this file are subject to the Mozilla
2
+ # Public Licence Version 1.1 (the "Licence"); you may
3
+ # not use this file except in compliance with the
4
+ # Licence. You may obtain a copy of the Licence at
5
+ # http://www.mozilla.org/MPL
6
+ # Software distributed under the Licence is distributed
7
+ # on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
8
+ # either express or implied. See the Licence of the
9
+ # specific language governing rights and limitations
10
+ # under the Licence.
11
+ # The Original Code is pNet::DNS.
12
+ # The Initial Developer of the Original Code is
13
+ # Nominet UK (www.nominet.org.uk). Portions created by
14
+ # Nominet UK are Copyright (c) Nominet UK 2006.
15
+ # All rights reserved.
16
+ module Net
17
+ module DNS
18
+ class RR
19
+ #= NAME
20
+ #
21
+ #Net::DNS::RR::LOC - DNS LOC resource record
22
+ #
23
+ #=head1 DESCRIPTION
24
+ #
25
+ #Class for DNS Location (LOC) resource records. See RFC 1876 for
26
+ #details.
27
+ #
28
+ #=head1 COPYRIGHT
29
+ #
30
+ #Copyright (c) 1997-2002 Michael Fuhr.
31
+ #
32
+ #Portions Copyright (c) 2002-2004 Chris Reinhardt.
33
+ #
34
+ #Ruby version Copyright (c) 2006 AlexD (Nominet UK)
35
+ #
36
+ #All rights reserved. This program is free software; you may redistribute
37
+ #it and/or modify it under the same terms as Perl itself.
38
+ #Some of the code and documentation is based on RFC 1876 and on code
39
+ #contributed by Christopher Davis.
40
+ #
41
+ #= SEE ALSO
42
+ #
43
+ #Net::DNS, Net::DNS::Resolver, Net::DNS::Packet,
44
+ #Net::DNS::Header, Net::DNS::Question, Net::DNS::RR,
45
+ #RFC 1876
46
+ class LOC < RR
47
+ #Returns the version number of the representation; programs should
48
+ #always check this. C<Net::DNS> currently supports only version 0.
49
+ #
50
+ # print "version = ", rr.version, "\n"
51
+ #
52
+ attr_accessor :version
53
+ #Returns the diameter of a sphere enclosing the described entity,
54
+ #in centimeters.
55
+ #
56
+ # print "size = ", rr.size, "\n"
57
+ #
58
+ attr_accessor :size
59
+ #Returns the horizontal precision of the data, in centimeters.
60
+ #
61
+ # print "horiz_pre = ", rr.horiz_pre, "\n"
62
+ #
63
+ attr_accessor :horiz_pre
64
+ #Returns the vertical precision of the data, in centimeters.
65
+ #
66
+ # print "vert_pre = ", rr.vert_pre, "\n"
67
+ #
68
+ attr_accessor :vert_pre
69
+ #Returns the latitude of the center of the sphere described by
70
+ #the size method, in thousandths of a second of arc. 2**31
71
+ #represents the equator; numbers above that are north latitude.
72
+ #
73
+ # print "latitude = ", rr.latitude, "\n"
74
+ #
75
+ attr_accessor :latitude
76
+ #Returns the longitude of the center of the sphere described by
77
+ #the size method, in thousandths of a second of arc. 2**31
78
+ #represents the prime meridian; numbers above that are east
79
+ #longitude.
80
+ #
81
+ # print "longitude = ", rr.longitude, "\n"
82
+ #
83
+ attr_accessor :longitude
84
+ #Returns the altitude of the center of the sphere described by
85
+ #the size method, in centimeters, from a base of 100,000m
86
+ #below the WGS 84 reference spheroid used by GPS.
87
+ #
88
+ # print "altitude = ", rr.altitude, "\n"
89
+ #
90
+ attr_accessor :altitude
91
+ # Powers of 10 from 0 to 9 (used to speed up calculations).
92
+ POWEROFTEN = [1, 10, 100, 1_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000, 1_000_000_000]
93
+
94
+ # Reference altitude in centimeters (see RFC 1876).
95
+ REFERENCE_ALT = 100_000 * 100;
96
+
97
+ # Reference lat/lon (see RFC 1876).
98
+ REFERENCE_LATLON = 2**31;
99
+
100
+ # Conversions to/from thousandths of a degree.
101
+ CONV_SEC = 1000;
102
+ CONV_MIN = 60 * CONV_SEC;
103
+ CONV_DEG = 60 * CONV_MIN;
104
+
105
+ # Defaults (from RFC 1876, Section 3).
106
+ DEFAULT_MIN = 0;
107
+ DEFAULT_SEC = 0;
108
+ DEFAULT_SIZE = 1;
109
+ DEFAULT_HORIZ_PRE = 10_000;
110
+ DEFAULT_VERT_PRE = 10;
111
+
112
+ def new_from_data(data, offset)
113
+ if (@rdlength > 0)
114
+ version = data.unpack("\@#{offset} C")[0]
115
+ offset+=1
116
+
117
+ @version = version;
118
+
119
+ if (version == 0)
120
+ size = data.unpack("\@#{offset} C")[0];
121
+ @size = precsize_ntoval(size);
122
+ offset+=1;
123
+
124
+ horiz_pre = data.unpack("\@#{offset} C")[0];
125
+ @horiz_pre = precsize_ntoval(horiz_pre);
126
+ offset+=1;
127
+
128
+ vert_pre = data.unpack("\@#{offset} C")[0];
129
+ @vert_pre = precsize_ntoval(vert_pre);
130
+ offset+=1
131
+
132
+ @latitude = data.unpack("\@#{offset} N")[0];
133
+ offset += Net::DNS::INT32SZ;
134
+
135
+ @longitude = data.unpack("\@#{offset} N")[0];
136
+ offset += Net::DNS::INT32SZ;
137
+
138
+ @altitude = data.unpack("\@#{offset} N")[0]
139
+ offset += Net::DNS::INT32SZ;
140
+ else
141
+ # What to do for unsupported versions?
142
+ end
143
+ end
144
+ end
145
+
146
+ def new_from_hash(values)
147
+ if values.has_key?(:size)
148
+ @size = values[:size]
149
+ else
150
+ @size = DEFAULT_SIZE
151
+ end
152
+ if values.has_key?(:horiz_pre)
153
+ @horiz_pre = values[:horiz_pre]
154
+ else
155
+ @horiz_pre = DEFAULT_HORIZ_PRE * 100
156
+ end
157
+ if values.has_key?(:vert_pre)
158
+ @vert_pre = values[:vert_pre]
159
+ else
160
+ @vert_pre = DEFAULT_VERT_PRE * 100
161
+ end
162
+ if values.has_key?(:latitude)
163
+ @latitude = values[:latitude]
164
+ end
165
+ if values.has_key?(:longitude)
166
+ @longitude = values[:longitude]
167
+ end
168
+ if values.has_key?(:altitude)
169
+ @altitude = values[:altitude]
170
+ end
171
+ if values.has_key?(:version)
172
+ @version = values[:version]
173
+ # else
174
+ # @version = DEFAULT_VERSION
175
+ end
176
+ end
177
+
178
+ def new_from_string(string)
179
+ if (string &&
180
+ string =~ /^ (\d+) \s+ # deg lat
181
+ ((\d+) \s+)? # min lat
182
+ (([\d.]+) \s+)? # sec lat
183
+ (N|S) \s+ # hem lat
184
+ (\d+) \s+ # deg lon
185
+ ((\d+) \s+)? # min lon
186
+ (([\d.]+) \s+)? # sec lon
187
+ (E|W) \s+ # hem lon
188
+ (-?[\d.]+) m? # altitude
189
+ (\s+ ([\d.]+) m?)? # size
190
+ (\s+ ([\d.]+) m?)? # horiz precision
191
+ (\s+ ([\d.]+) m?)? # vert precision
192
+ /ix) #
193
+
194
+ # What to do for other versions?
195
+ version = 0;
196
+
197
+ latdeg, latmin, latsec, lathem = $1.to_i, $3.to_i, $5.to_i, $6;
198
+ londeg, lonmin, lonsec, lonhem = $7.to_i, $9.to_i, $11.to_i, $12
199
+ alt, size, horiz_pre, vert_pre = $13.to_i, $15.to_i, $17.to_i, $19.to_i
200
+
201
+ latmin = DEFAULT_MIN unless latmin;
202
+ latsec = DEFAULT_SEC unless latsec;
203
+ lathem = lathem.upcase;
204
+
205
+ lonmin = DEFAULT_MIN unless lonmin;
206
+ lonsec = DEFAULT_SEC unless lonsec;
207
+ lonhem = lonhem.upcase
208
+
209
+ size = DEFAULT_SIZE unless size;
210
+ horiz_pre = DEFAULT_HORIZ_PRE unless horiz_pre;
211
+ vert_pre = DEFAULT_VERT_PRE unless vert_pre;
212
+
213
+ @version = version;
214
+ @size = size * 100;
215
+ @horiz_pre = horiz_pre * 100;
216
+ @vert_pre = vert_pre * 100;
217
+ @latitude = dms2latlon(latdeg, latmin, latsec, lathem);
218
+ @longitude = dms2latlon(londeg, lonmin, lonsec, lonhem);
219
+ @altitude = alt * 100 + REFERENCE_ALT;
220
+ end
221
+ end
222
+
223
+ def rdatastr
224
+ rdatastr=""
225
+
226
+ if (defined?@version)
227
+ if (@version == 0)
228
+ lat = @latitude;
229
+ lon = @longitude;
230
+ altitude = @altitude;
231
+ size = @size;
232
+ horiz_pre = @horiz_pre;
233
+ vert_pre = @vert_pre;
234
+
235
+ altitude = (altitude - REFERENCE_ALT) / 100;
236
+ size /= 100;
237
+ horiz_pre /= 100;
238
+ vert_pre /= 100;
239
+
240
+ rdatastr = latlon2dms(lat, "NS") + " " +
241
+ latlon2dms(lon, "EW") + " " +
242
+ sprintf("%.2fm", altitude) + " " +
243
+ sprintf("%.2fm", size) + " " +
244
+ sprintf("%.2fm", horiz_pre) + " " +
245
+ sprintf("%.2fm", vert_pre);
246
+ else
247
+ rdatastr = "; version " + @version + " not supported";
248
+ end
249
+ else
250
+ rdatastr = '';
251
+ end
252
+
253
+ return rdatastr;
254
+ end
255
+
256
+ def rr_rdata(*args)
257
+ rdata = "";
258
+
259
+ if (defined?@version)
260
+ rdata += [@version].pack("C");
261
+ if (@version == 0)
262
+ rdata += [precsize_valton(@size), precsize_valton(@horiz_pre), precsize_valton(@vert_pre)].pack("C3");
263
+ rdata += [@latitude, @longitude, @altitude].pack("N3");
264
+ else
265
+ # What to do for other versions?
266
+ end
267
+ end
268
+
269
+ return rdata;
270
+ end
271
+
272
+ def precsize_ntoval(prec)
273
+ mantissa = ((prec >> 4) & 0x0f) % 10;
274
+ exponent = (prec & 0x0f) % 10;
275
+ return mantissa * POWEROFTEN[exponent];
276
+ end
277
+
278
+ def precsize_valton(val)
279
+ exponent = 0;
280
+ while (val >= 10)
281
+ val /= 10;
282
+ exponent+=1
283
+ end
284
+ return (val.round << 4) | (exponent & 0x0f);
285
+ end
286
+
287
+ def latlon2dms(rawmsec, hems)
288
+ # Tried to use modulus here, but Perl dumped core if
289
+ # the value was >= 2**31.
290
+
291
+ abs = (rawmsec - REFERENCE_LATLON).abs;
292
+ deg = (abs / CONV_DEG).round;
293
+ abs -= deg * CONV_DEG;
294
+ min = (abs / CONV_MIN).round;
295
+ abs -= min * CONV_MIN;
296
+ sec = (abs / CONV_SEC).round; # $conv_sec
297
+ abs -= sec * CONV_SEC;
298
+ msec = abs;
299
+
300
+ hem = hems[(rawmsec >= REFERENCE_LATLON ? 0 : 1), 1]
301
+
302
+ return sprintf("%d %02d %02d.%03d %s", deg, min, sec, msec, hem);
303
+ end
304
+
305
+ def dms2latlon(deg, min, sec, hem)
306
+ retval=0
307
+
308
+ retval = (deg * CONV_DEG) + (min * CONV_MIN) + (sec * CONV_SEC);
309
+ retval = -retval if ((hem != nil) && ((hem == "S") || (hem == "W")));
310
+ retval += REFERENCE_LATLON;
311
+ return retval;
312
+ end
313
+
314
+ #Returns the latitude and longitude as floating-point degrees.
315
+ #Positive numbers represent north latitude or east longitude;
316
+ #negative numbers represent south latitude or west longitude.
317
+ #
318
+ # lat, lon = rr.latlon
319
+ # system("xearth", "-pos", "fixed #{lat} #{lon}")
320
+ #
321
+ def latlon
322
+ retlat, retlon = nil
323
+
324
+ if (@version == 0)
325
+ retlat = latlon2deg(@latitude);
326
+ retlon = latlon2deg(@longitude);
327
+ end
328
+
329
+ return retlat, retlon
330
+ end
331
+
332
+ def latlon2deg(rawmsec)
333
+ deg=0;
334
+
335
+ deg = (rawmsec - reference_latlon) / CONV_DEG;
336
+ return deg;
337
+ end
338
+ end
339
+ end
340
+ end
341
+ end
@@ -0,0 +1,92 @@
1
+ # The contents of this file are subject to the Mozilla
2
+ # Public Licence Version 1.1 (the "Licence"); you may
3
+ # not use this file except in compliance with the
4
+ # Licence. You may obtain a copy of the Licence at
5
+ # http://www.mozilla.org/MPL
6
+ # Software distributed under the Licence is distributed
7
+ # on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
8
+ # either express or implied. See the Licence of the
9
+ # specific language governing rights and limitations
10
+ # under the Licence.
11
+ # The Original Code is pNet::DNS.
12
+ # The Initial Developer of the Original Code is
13
+ # Nominet UK (www.nominet.org.uk). Portions created by
14
+ # Nominet UK are Copyright (c) Nominet UK 2006.
15
+ # All rights reserved.
16
+ module Net
17
+ module DNS
18
+ class RR
19
+ #= NAME
20
+ #
21
+ #Net::DNS::RR::MB - DNS MB resource record
22
+ #
23
+ #= DESCRIPTION
24
+ #
25
+ #Class for DNS Mailbox (MB) resource records.
26
+ #
27
+ #= COPYRIGHT
28
+ #
29
+ #Copyright (c) 1997-2002 Michael Fuhr.
30
+ #
31
+ #Portions Copyright (c) 2002-2004 Chris Reinhardt.
32
+ #
33
+ #Ruby version Copyright (c) 2006 AlexD (Nominet UK)
34
+ #
35
+ #All rights reserved. This program is free software; you may redistribute
36
+ #it and/or modify it under the same terms as Perl itself.
37
+ #=head1 SEE ALSO
38
+ #
39
+ #Net::DNS, Net::DNS::Resolver, Net::DNS::Packet,
40
+ #Net::DNS::Header, Net::DNS::Question, Net::DNS::RR,
41
+ #RFC 1035 Section 3.3.3
42
+ class MB < RR
43
+ #Returns the domain name of the host which has the specified mailbox.
44
+ #
45
+ #
46
+ # print "madname = ", rr.madname, "\n"
47
+ #
48
+ attr_accessor :madname
49
+ def new_from_data(data, offset)
50
+ if (@rdlength > 0)
51
+ @madname = Net::DNS::Packet::dn_expand(data, offset)[0];
52
+ end
53
+ end
54
+
55
+ def new_from_string(s)
56
+ if (s)
57
+ string = s.sub(/\.+$/,"");
58
+ @madname = string;
59
+ end
60
+ end
61
+
62
+ def new_from_hash(values)
63
+ if values.has_key?(:madname)
64
+ @madname = values[:madname]
65
+ end
66
+ end
67
+
68
+ def rdatastr
69
+ return @madname ? "#{@madname}." : '';
70
+ end
71
+
72
+ def rr_rdata(packet, offset)
73
+ rdata = "";
74
+
75
+ if (defined?@madname)
76
+ rdata += packet.dn_comp(@madname, offset);
77
+ end
78
+
79
+ return rdata;
80
+ end
81
+
82
+ def _canonicalRdata
83
+ rdata = "";
84
+ if (defined?@madname)
85
+ rdata += _name2wire(@madname);
86
+ end
87
+ return rdata;
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,96 @@
1
+ # The contents of this file are subject to the Mozilla
2
+ # Public Licence Version 1.1 (the "Licence"); you may
3
+ # not use this file except in compliance with the
4
+ # Licence. You may obtain a copy of the Licence at
5
+ # http://www.mozilla.org/MPL
6
+ # Software distributed under the Licence is distributed
7
+ # on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
8
+ # either express or implied. See the Licence of the
9
+ # specific language governing rights and limitations
10
+ # under the Licence.
11
+ # The Original Code is pNet::DNS.
12
+ # The Initial Developer of the Original Code is
13
+ # Nominet UK (www.nominet.org.uk). Portions created by
14
+ # Nominet UK are Copyright (c) Nominet UK 2006.
15
+ # All rights reserved.
16
+ module Net
17
+ module DNS
18
+ class RR
19
+ #= NAME
20
+ #
21
+ #Net::DNS::RR::MG - DNS MG resource record
22
+ #
23
+ #= DESCRIPTION
24
+ #
25
+ #Class for DNS Mail Group (MG) resource records.
26
+ #
27
+ #= COPYRIGHT
28
+ #
29
+ #Copyright (c) 1997-2002 Michael Fuhr.
30
+ #
31
+ #Portions Copyright (c) 2002-2004 Chris Reinhardt.
32
+ #
33
+ #Ruby version Copyright (c) 2006 AlexD (Nominet UK)
34
+ #
35
+ #All rights reserved. This program is free software; you may redistribute
36
+ #it and/or modify it under the same terms as Perl itself.
37
+ #=head1 SEE ALSO
38
+ #
39
+ #Net::DNS, Net::DNS::Resolver, Net::DNS::Packet,
40
+ #Net::DNS::Header, Net::DNS::Question, Net::DNS::RR,
41
+ #RFC 1035 Section 3.3.6
42
+ class MG < RR
43
+ #Returns the RR's mailbox field.
44
+ #
45
+ # print "mgmname = ", rr.mgmname, "\n"
46
+ #
47
+ attr_accessor :mgmname
48
+ def new_from_data(data, offset)
49
+ if (@rdlength > 0)
50
+ @mgmname = Net::DNS::Packet::dn_expand(data, offset)[0];
51
+ end
52
+ end
53
+
54
+ def new_from_string(s)
55
+ if (s)
56
+ string = s.sub(/\.+$/,"");
57
+ @mgmname = string;
58
+ end
59
+ end
60
+
61
+ def new_from_hash(values)
62
+ if values.has_key?(:mgmname)
63
+ @mgmname = values[:mgmname]
64
+ end
65
+ end
66
+
67
+ def rdatastr
68
+ if defined?@mgmname
69
+ return "#{@mgmname}."
70
+ else
71
+ return ''
72
+ end
73
+ end
74
+
75
+ def rr_rdata(packet, offset)
76
+ rdata = "";
77
+
78
+ if (defined?@mgmname)
79
+ rdata += packet.dn_comp(@mgmname, offset);
80
+ end
81
+
82
+ return rdata;
83
+ end
84
+
85
+
86
+ def _canonicalRdata
87
+ rdata = "";
88
+ if (defined?@mgmname)
89
+ rdata += _name2wire(@mgmname);
90
+ end
91
+ return rdata;
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end