rrs 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0d9733af6339a12ce11ff22d41f27142c86f16b46755c9b12c671f9a851fa6e
4
- data.tar.gz: a3df3377fa1a8f10e93281536fb4ccdcfc1cdd2bf19ab3bf5ce8e2840f575a60
3
+ metadata.gz: 5a851d73be2407a245945dd33ac1e669bd66ccad17f7f073212bf00e8105f358
4
+ data.tar.gz: c5c4d8f7c52bfad9aa0fe4cc85aff98b515f870c146242a5f263f82c10c75fe3
5
5
  SHA512:
6
- metadata.gz: f8ab7a7e7b4d8046101570b92a1ac8e7e07fd55100a3d43f4287b6c48ad317e5f13c07ccd80c743a6cd46a624d21dd82f5f27c993ea90430f79ace1e0b790f21
7
- data.tar.gz: 6e2479f8d855d39afb625f61699501ec4d2d00c7d177d2f950d63dc845c260e0a6a02b6daf37e8f1e5fdb9b73b51357d9e58f735ddc3b35599025674b4688317
6
+ metadata.gz: 789a9f105dbaf0f00c93cd0811e75fbc6956130ede2482bba8720ed207cc14e27d53889824f284dbde05c3131b8e61a69883deaf4dc52d4035684ed8d7a94936
7
+ data.tar.gz: ca30ac41e67c5f55bebdeba2d9c7b28acde254385d838eca61c579a998bb64e2852de7407582f1e74e078c8b694440fdeab77cb33e59a38a0cfd9fbcda241bc6
@@ -1,5 +1,5 @@
1
1
  module RRs
2
- class A
2
+ class IN::A < Resource
3
3
  TypeValue = 1
4
4
  ClassValue = 1
5
5
 
data/lib/rrs/in.rb ADDED
@@ -0,0 +1,4 @@
1
+ module RRs
2
+ module IN
3
+ end
4
+ end
data/lib/rrs/inflector.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require_relative "maps/qtype_map"
1
2
  module RRs
2
3
  class Inflector < Zeitwerk::Inflector
3
4
  def camelize(basename, abspath)
@@ -5,6 +6,10 @@ module RRs
5
6
  "RRs" + super($1, abspath)
6
7
  elsif basename =~ /\Aipv(.*)/
7
8
  "IPv" + super($1, abspath)
9
+ elsif basename =~ /\Ain(.*)/ && basename != "inflector"
10
+ "IN" + super($1, abspath)
11
+ elsif ::RRs::Maps::QTYPE_MAP.values.any? { |v| basename.upcase == v.to_s }
12
+ basename.upcase
8
13
  else
9
14
  super
10
15
  end
data/lib/rrs/label.rb ADDED
@@ -0,0 +1,39 @@
1
+ module RRs
2
+ module Label # :nodoc:
3
+ def self.split(arg)
4
+ labels = []
5
+ arg.scan(/[^\.]+/) {labels << Str.new($&)}
6
+ return labels
7
+ end
8
+
9
+ class Str # :nodoc:
10
+ def initialize(string)
11
+ @string = string
12
+ # case insensivity of DNS labels doesn't apply non-ASCII characters. [RFC 4343]
13
+ # This assumes @string is given in ASCII compatible encoding.
14
+ @downcase = string.b.downcase
15
+ end
16
+ attr_reader :string, :downcase
17
+
18
+ def to_s
19
+ return @string
20
+ end
21
+
22
+ def inspect
23
+ return "#<#{self.class} #{self}>"
24
+ end
25
+
26
+ def ==(other)
27
+ return self.class == other.class && @downcase == other.downcase
28
+ end
29
+
30
+ def eql?(other)
31
+ return self == other
32
+ end
33
+
34
+ def hash
35
+ return @downcase.hash
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,11 @@
1
+ module RRs
2
+ module Maps
3
+ QCLASS_MAP = Ractor.make_shareable({
4
+ 1 => :IN,
5
+ 2 => :CS,
6
+ 3 => :CH,
7
+ 4 => :HS,
8
+ 255 => :*
9
+ }.freeze)
10
+ end
11
+ end
@@ -0,0 +1,28 @@
1
+ module RRs
2
+ module Maps
3
+ QTYPE_MAP = Ractor.make_shareable({
4
+ 1 => :A,
5
+ 2 => :NS,
6
+ 3 => :MD,
7
+ 4 => :MF,
8
+ 5 => :CNAME,
9
+ 6 => :SOA,
10
+ 7 => :MB,
11
+ 8 => :MG,
12
+ 9 => :MR,
13
+ 10 => :NULL,
14
+ 11 => :WKS,
15
+ 12 => :PTR,
16
+ 13 => :HINFO,
17
+ 14 => :MINFO,
18
+ 15 => :MX,
19
+ 16 => :TXT,
20
+ 28 => :AAAA,
21
+ 41 => :OPT,
22
+ 252 => :AXFR,
23
+ 253 => :MAILB,
24
+ 254 => :MAILA,
25
+ 255 => :*
26
+ }.freeze)
27
+ end
28
+ end
data/lib/rrs/maps.rb ADDED
@@ -0,0 +1,6 @@
1
+ require_relative "maps/qclass_map"
2
+ require_relative "maps/qtype_map"
3
+ module RRs
4
+ module Maps
5
+ end
6
+ end
@@ -0,0 +1,352 @@
1
+ module RRs
2
+ class Message
3
+ @@identifier = -1
4
+
5
+ def initialize(id = (@@identifier += 1) & 0xffff)
6
+ @id = id
7
+ @qr = 0
8
+ @opcode = 0
9
+ @aa = 0
10
+ @tc = 0
11
+ @rd = 0 # recursion desired
12
+ @ra = 0 # recursion available
13
+ @rcode = 0
14
+ @question = []
15
+ @answer = []
16
+ @authority = []
17
+ @additional = []
18
+ end
19
+
20
+ attr_accessor :id, :qr, :opcode, :aa, :tc, :rd, :ra, :rcode
21
+ attr_reader :question, :answer, :authority, :additional
22
+
23
+ def ==(other)
24
+ return @id == other.id &&
25
+ @qr == other.qr &&
26
+ @opcode == other.opcode &&
27
+ @aa == other.aa &&
28
+ @tc == other.tc &&
29
+ @rd == other.rd &&
30
+ @ra == other.ra &&
31
+ @rcode == other.rcode &&
32
+ @question == other.question &&
33
+ @answer == other.answer &&
34
+ @authority == other.authority &&
35
+ @additional == other.additional
36
+ end
37
+
38
+ def add_question(name, typeclass)
39
+ @question << [Name.create(name), typeclass]
40
+ end
41
+
42
+ def each_question
43
+ @question.each {|name, typeclass|
44
+ yield name, typeclass
45
+ }
46
+ end
47
+
48
+ def add_answer(name, ttl, data)
49
+ @answer << [Name.create(name), ttl, data]
50
+ end
51
+
52
+ def each_answer
53
+ @answer.each {|name, ttl, data|
54
+ yield name, ttl, data
55
+ }
56
+ end
57
+
58
+ def add_authority(name, ttl, data)
59
+ @authority << [Name.create(name), ttl, data]
60
+ end
61
+
62
+ def each_authority
63
+ @authority.each {|name, ttl, data|
64
+ yield name, ttl, data
65
+ }
66
+ end
67
+
68
+ def add_additional(name, ttl, data)
69
+ @additional << [Name.create(name), ttl, data]
70
+ end
71
+
72
+ def each_additional
73
+ @additional.each {|name, ttl, data|
74
+ yield name, ttl, data
75
+ }
76
+ end
77
+
78
+ def each_resource
79
+ each_answer {|name, ttl, data| yield name, ttl, data}
80
+ each_authority {|name, ttl, data| yield name, ttl, data}
81
+ each_additional {|name, ttl, data| yield name, ttl, data}
82
+ end
83
+
84
+ def encode
85
+ return MessageEncoder.new {|msg|
86
+ msg.put_pack('nnnnnn',
87
+ @id,
88
+ (@qr & 1) << 15 |
89
+ (@opcode & 15) << 11 |
90
+ (@aa & 1) << 10 |
91
+ (@tc & 1) << 9 |
92
+ (@rd & 1) << 8 |
93
+ (@ra & 1) << 7 |
94
+ (@rcode & 15),
95
+ @question.length,
96
+ @answer.length,
97
+ @authority.length,
98
+ @additional.length)
99
+ @question.each {|q|
100
+ name, typeclass = q
101
+ msg.put_name(name)
102
+ msg.put_pack('nn', typeclass::TypeValue, typeclass::ClassValue)
103
+ }
104
+ [@answer, @authority, @additional].each {|rr|
105
+ rr.each {|r|
106
+ name, ttl, data = r
107
+ msg.put_name(name)
108
+ msg.put_pack('nnN', data.class::TypeValue, data.class::ClassValue, ttl)
109
+ msg.put_length16 {data.encode_rdata(msg)}
110
+ }
111
+ }
112
+ }.to_s
113
+ end
114
+
115
+ class MessageEncoder # :nodoc:
116
+ def initialize
117
+ @data = ''.dup
118
+ @names = {}
119
+ yield self
120
+ end
121
+
122
+ def to_s
123
+ return @data
124
+ end
125
+
126
+ def put_bytes(d)
127
+ @data << d
128
+ end
129
+
130
+ def put_pack(template, *d)
131
+ @data << d.pack(template)
132
+ end
133
+
134
+ def put_length16
135
+ length_index = @data.length
136
+ @data << "\0\0"
137
+ data_start = @data.length
138
+ yield
139
+ data_end = @data.length
140
+ @data[length_index, 2] = [data_end - data_start].pack("n")
141
+ end
142
+
143
+ def put_string(d)
144
+ self.put_pack("C", d.length)
145
+ @data << d
146
+ end
147
+
148
+ def put_string_list(ds)
149
+ ds.each {|d|
150
+ self.put_string(d)
151
+ }
152
+ end
153
+
154
+ def put_name(d, compress: true)
155
+ put_labels(d.to_a, compress: compress)
156
+ end
157
+
158
+ def put_labels(d, compress: true)
159
+ d.each_index {|i|
160
+ domain = d[i..-1]
161
+ if compress && idx = @names[domain]
162
+ self.put_pack("n", 0xc000 | idx)
163
+ return
164
+ else
165
+ if @data.length < 0x4000
166
+ @names[domain] = @data.length
167
+ end
168
+ self.put_label(d[i])
169
+ end
170
+ }
171
+ @data << "\0"
172
+ end
173
+
174
+ def put_label(d)
175
+ self.put_string(d.to_s)
176
+ end
177
+ end
178
+
179
+ def Message.decode(m)
180
+ o = Message.new(0)
181
+ MessageDecoder.new(m) {|msg|
182
+ id, flag, qdcount, ancount, nscount, arcount =
183
+ msg.get_unpack('nnnnnn')
184
+ o.id = id
185
+ o.tc = (flag >> 9) & 1
186
+ o.rcode = flag & 15
187
+ return o unless o.tc.zero?
188
+
189
+ o.qr = (flag >> 15) & 1
190
+ o.opcode = (flag >> 11) & 15
191
+ o.aa = (flag >> 10) & 1
192
+ o.rd = (flag >> 8) & 1
193
+ o.ra = (flag >> 7) & 1
194
+ (1..qdcount).each {
195
+ name, typeclass = msg.get_question
196
+ o.add_question(name, typeclass)
197
+ }
198
+ (1..ancount).each {
199
+ name, ttl, data = msg.get_rr
200
+ o.add_answer(name, ttl, data)
201
+ }
202
+ (1..nscount).each {
203
+ name, ttl, data = msg.get_rr
204
+ o.add_authority(name, ttl, data)
205
+ }
206
+ (1..arcount).each {
207
+ name, ttl, data = msg.get_rr
208
+ o.add_additional(name, ttl, data)
209
+ }
210
+ }
211
+ return o
212
+ end
213
+
214
+ class MessageDecoder # :nodoc:
215
+ def initialize(data)
216
+ @data = data
217
+ @index = 0
218
+ @limit = data.bytesize
219
+ yield self
220
+ end
221
+
222
+ def inspect
223
+ "\#<#{self.class}: #{@data.byteslice(0, @index).inspect} #{@data.byteslice(@index..-1).inspect}>"
224
+ end
225
+
226
+ def get_length16
227
+ len, = self.get_unpack('n')
228
+ save_limit = @limit
229
+ @limit = @index + len
230
+ d = yield(len)
231
+ if @index < @limit
232
+ raise DecodeError.new("junk exists")
233
+ elsif @limit < @index
234
+ raise DecodeError.new("limit exceeded")
235
+ end
236
+ @limit = save_limit
237
+ return d
238
+ end
239
+
240
+ def get_bytes(len = @limit - @index)
241
+ raise DecodeError.new("limit exceeded") if @limit < @index + len
242
+ d = @data.byteslice(@index, len)
243
+ @index += len
244
+ return d
245
+ end
246
+
247
+ def get_unpack(template)
248
+ len = 0
249
+ template.each_byte {|byte|
250
+ byte = "%c" % byte
251
+ case byte
252
+ when ?c, ?C
253
+ len += 1
254
+ when ?n
255
+ len += 2
256
+ when ?N
257
+ len += 4
258
+ else
259
+ raise StandardError.new("unsupported template: '#{byte.chr}' in '#{template}'")
260
+ end
261
+ }
262
+ raise DecodeError.new("limit exceeded") if @limit < @index + len
263
+ arr = @data.unpack("@#{@index}#{template}")
264
+ @index += len
265
+ return arr
266
+ end
267
+
268
+ def get_string
269
+ raise DecodeError.new("limit exceeded") if @limit <= @index
270
+ len = @data.getbyte(@index)
271
+ raise DecodeError.new("limit exceeded") if @limit < @index + 1 + len
272
+ d = @data.byteslice(@index + 1, len)
273
+ @index += 1 + len
274
+ return d
275
+ end
276
+
277
+ def get_string_list
278
+ strings = []
279
+ while @index < @limit
280
+ strings << self.get_string
281
+ end
282
+ strings
283
+ end
284
+
285
+ def get_list
286
+ [].tap do |values|
287
+ while @index < @limit
288
+ values << yield
289
+ end
290
+ end
291
+ end
292
+
293
+ def get_name
294
+ return Name.new(self.get_labels)
295
+ end
296
+
297
+ def get_labels
298
+ prev_index = @index
299
+ save_index = nil
300
+ d = []
301
+ while true
302
+ raise DecodeError.new("limit exceeded") if @limit <= @index
303
+ case @data.getbyte(@index)
304
+ when 0
305
+ @index += 1
306
+ if save_index
307
+ @index = save_index
308
+ end
309
+ return d
310
+ when 192..255
311
+ idx = self.get_unpack('n')[0] & 0x3fff
312
+ if prev_index <= idx
313
+ raise DecodeError.new("non-backward name pointer")
314
+ end
315
+ prev_index = idx
316
+ if !save_index
317
+ save_index = @index
318
+ end
319
+ @index = idx
320
+ else
321
+ d << self.get_label
322
+ end
323
+ end
324
+ end
325
+
326
+ def get_label
327
+ return Label::Str.new(self.get_string)
328
+ end
329
+
330
+ def get_question
331
+ name = self.get_name
332
+ type, klass = self.get_unpack("nn")
333
+ return name, Resource.get_class(type, klass)
334
+ end
335
+
336
+ def get_rr
337
+ name = self.get_name
338
+ type, klass, ttl = self.get_unpack('nnN')
339
+ typeclass = Resource.get_class(type, klass)
340
+ res = self.get_length16 do
341
+ begin
342
+ typeclass.decode_rdata self
343
+ rescue => e
344
+ raise DecodeError, e.message, e.backtrace
345
+ end
346
+ end
347
+ res.instance_variable_set :@ttl, ttl
348
+ return name, ttl, res
349
+ end
350
+ end
351
+ end
352
+ end
data/lib/rrs/name.rb ADDED
@@ -0,0 +1,109 @@
1
+ module RRs
2
+ ##
3
+ # A representation of a DNS name.
4
+
5
+ class Name
6
+
7
+ ##
8
+ # Creates a new DNS name from +arg+. +arg+ can be:
9
+ #
10
+ # Name:: returns +arg+.
11
+ # String:: Creates a new Name.
12
+
13
+ def self.create(arg)
14
+ case arg
15
+ when Name
16
+ return arg
17
+ when String
18
+ return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false)
19
+ else
20
+ raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}")
21
+ end
22
+ end
23
+
24
+ def initialize(labels, absolute=true) # :nodoc:
25
+ labels = labels.map {|label|
26
+ case label
27
+ when String then Label::Str.new(label)
28
+ when Label::Str then label
29
+ else
30
+ raise ArgumentError, "unexpected label: #{label.inspect}"
31
+ end
32
+ }
33
+ @labels = labels
34
+ @absolute = absolute
35
+ end
36
+
37
+ def inspect # :nodoc:
38
+ "#<#{self.class}: #{self}#{@absolute ? '.' : ''}>"
39
+ end
40
+
41
+ ##
42
+ # True if this name is absolute.
43
+
44
+ def absolute?
45
+ return @absolute
46
+ end
47
+
48
+ def ==(other) # :nodoc:
49
+ return false unless Name === other
50
+ return false unless @absolute == other.absolute?
51
+ return @labels == other.to_a
52
+ end
53
+
54
+ alias eql? == # :nodoc:
55
+
56
+ ##
57
+ # Returns true if +other+ is a subdomain.
58
+ #
59
+ # Example:
60
+ #
61
+ # domain = Resolv::DNS::Name.create("y.z")
62
+ # p Resolv::DNS::Name.create("w.x.y.z").subdomain_of?(domain) #=> true
63
+ # p Resolv::DNS::Name.create("x.y.z").subdomain_of?(domain) #=> true
64
+ # p Resolv::DNS::Name.create("y.z").subdomain_of?(domain) #=> false
65
+ # p Resolv::DNS::Name.create("z").subdomain_of?(domain) #=> false
66
+ # p Resolv::DNS::Name.create("x.y.z.").subdomain_of?(domain) #=> false
67
+ # p Resolv::DNS::Name.create("w.z").subdomain_of?(domain) #=> false
68
+ #
69
+
70
+ def subdomain_of?(other)
71
+ raise ArgumentError, "not a domain name: #{other.inspect}" unless Name === other
72
+ return false if @absolute != other.absolute?
73
+ other_len = other.length
74
+ return false if @labels.length <= other_len
75
+ return @labels[-other_len, other_len] == other.to_a
76
+ end
77
+
78
+ def hash # :nodoc:
79
+ return @labels.hash ^ @absolute.hash
80
+ end
81
+
82
+ def to_a # :nodoc:
83
+ return @labels
84
+ end
85
+
86
+ def length # :nodoc:
87
+ return @labels.length
88
+ end
89
+
90
+ def [](i) # :nodoc:
91
+ return @labels[i]
92
+ end
93
+
94
+ ##
95
+ # returns the domain name as a string.
96
+ #
97
+ # The domain name doesn't have a trailing dot even if the name object is
98
+ # absolute.
99
+ #
100
+ # Example:
101
+ #
102
+ # p Resolv::DNS::Name.create("x.y.z.").to_s #=> "x.y.z"
103
+ # p Resolv::DNS::Name.create("x.y.z").to_s #=> "x.y.z"
104
+
105
+ def to_s
106
+ return @labels.join('.')
107
+ end
108
+ end
109
+ end
data/lib/rrs/opt.rb ADDED
@@ -0,0 +1,16 @@
1
+ module RRs
2
+ class OPT < Resource
3
+ attr_reader :data
4
+ def initialize(data)
5
+ @data = data
6
+ end
7
+
8
+ def encode_rdata(msg)
9
+ msg.put_bytes(data)
10
+ end
11
+
12
+ def self.decode_rdata(msg)
13
+ return self.new(msg.get_bytes)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,51 @@
1
+ class RRs::Resource
2
+ ##
3
+ # Remaining Time To Live for this Resource.
4
+
5
+ attr_reader :ttl
6
+
7
+ def encode_rdata(msg) # :nodoc:
8
+ raise NotImplementedError.new
9
+ end
10
+
11
+ def self.decode_rdata(msg) # :nodoc:
12
+ raise NotImplementedError.new
13
+ end
14
+
15
+ def ==(other) # :nodoc:
16
+ return false unless self.class == other.class
17
+ s_ivars = self.instance_variables
18
+ s_ivars.sort!
19
+ s_ivars.delete :@ttl
20
+ o_ivars = other.instance_variables
21
+ o_ivars.sort!
22
+ o_ivars.delete :@ttl
23
+ return s_ivars == o_ivars &&
24
+ s_ivars.collect {|name| self.instance_variable_get name} ==
25
+ o_ivars.collect {|name| other.instance_variable_get name}
26
+ end
27
+
28
+ def eql?(other) # :nodoc:
29
+ return self == other
30
+ end
31
+
32
+ def hash # :nodoc:
33
+ h = 0
34
+ vars = self.instance_variables
35
+ vars.delete :@ttl
36
+ vars.each {|name|
37
+ h ^= self.instance_variable_get(name).hash
38
+ }
39
+ return h
40
+ end
41
+
42
+ def self.get_class(type_value, class_value) # :nodoc:
43
+ path = []
44
+ if class_value
45
+ path.push(::RRs::Maps::QCLASS_MAP[class_value.to_i].to_s) if ::RRs::Maps::QCLASS_MAP[class_value.to_i]
46
+ end
47
+ path.push(::RRs::Maps::QTYPE_MAP[type_value.to_i].to_s)
48
+ RRs.const_get(path.join("::"))
49
+ end
50
+
51
+ end
data/lib/rrs.rb CHANGED
@@ -1,15 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
  require "zeitwerk"
3
3
  require_relative "rrs/inflector"
4
+ require_relative "rrs/maps"
4
5
 
5
6
  loader = Zeitwerk::Loader.for_gem
6
7
  loader.inflector = RRs::Inflector.new
8
+ loader.ignore("#{__dir__}/rrs/maps")
7
9
  loader.setup
8
10
 
9
11
  module RRs
10
- VERSION = "0.1.0"
12
+ VERSION = "0.2.0"
11
13
 
12
14
  class Error < StandardError; end
15
+ class DecodeError < StandardError; end
16
+ class EncodeError < StandardError; end
13
17
  end
14
18
 
15
19
  loader.eager_load
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rrs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - reesericci
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-17 00:00:00.000000000 Z
11
+ date: 2024-08-18 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: "RRs is a set of self-contained DNS primitives that do not share state,
14
14
  which makes them suitable for use in Ractors. \n\nThis is a contrast to the Resolv::DNS
@@ -23,10 +23,19 @@ files:
23
23
  - README.md
24
24
  - Rakefile
25
25
  - lib/rrs.rb
26
- - lib/rrs/a.rb
26
+ - lib/rrs/in.rb
27
+ - lib/rrs/in/a.rb
27
28
  - lib/rrs/inflector.rb
28
29
  - lib/rrs/ipv4.rb
29
30
  - lib/rrs/ipv6.rb
31
+ - lib/rrs/label.rb
32
+ - lib/rrs/maps.rb
33
+ - lib/rrs/maps/qclass_map.rb
34
+ - lib/rrs/maps/qtype_map.rb
35
+ - lib/rrs/message.rb
36
+ - lib/rrs/name.rb
37
+ - lib/rrs/opt.rb
38
+ - lib/rrs/resource.rb
30
39
  - sig/rrs.rbs
31
40
  homepage: https://codeberg.org/reesericci/rrs
32
41
  licenses: []