Class: Dnsruby::MessageDecoder

Inherits:
Object
  • Object
show all
Defined in:
lib/dnsruby/message/decoder.rb

Overview

This class decodes a binary string containing the raw bytes of the message as in coming over the wire from a nameserver, and parses it into a Dnsruby::Message.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data) {|_self| ... } ⇒ MessageDecoder

Creates an instance of the decoder, optionally with code block to be executed with the instance as its parameter.

Yields:

  • (_self)

Yield Parameters:



16
17
18
19
20
21
# File 'lib/dnsruby/message/decoder.rb', line 16

def initialize(data)
  @data = data
  @index = 0
  @limit = data.length
  yield self if block_given?
end

Instance Attribute Details

#dataObject (readonly)

Keeps a running @index containing the current position (like a cursor) into the binary string. In general ‘get_’ methods will position @index to follow the data they have read.



12
13
14
# File 'lib/dnsruby/message/decoder.rb', line 12

def data
  @data
end

#indexObject (readonly)

Keeps a running @index containing the current position (like a cursor) into the binary string. In general ‘get_’ methods will position @index to follow the data they have read.



12
13
14
# File 'lib/dnsruby/message/decoder.rb', line 12

def index
  @index
end

Instance Method Details

#assert_buffer_position_valid(end_position) ⇒ Object

Asserts that the specified position is a valid position in the buffer. If not, raises a DecodeError. If so, does nothing.



30
31
32
33
34
# File 'lib/dnsruby/message/decoder.rb', line 30

def assert_buffer_position_valid(end_position)
  unless (0..@limit).include?(end_position)
    raise DecodeError.new("requested position of #{end_position} must be between 0 and buffer size (#{@limit}).")
  end
end

#get_byte_at(position) ⇒ Object

Gets the byte value at the specified position



37
38
39
40
41
# File 'lib/dnsruby/message/decoder.rb', line 37

def get_byte_at(position)
  assert_buffer_position_valid(position)
  return nil if @data[position].nil?
  @data[position].getbyte(0)
end

#get_bytes(len = @limit - @index) ⇒ Object

Returns the specified number of bytes from the binary string. Length defaults to the remaining (not yet processed) size of the string.



65
66
67
68
69
# File 'lib/dnsruby/message/decoder.rb', line 65

def get_bytes(len = @limit - @index)
  bytes = @data[@index, len]
  @index += len
  bytes
end

#get_labelObject

Gets a single label.



147
148
149
150
151
152
153
# File 'lib/dnsruby/message/decoder.rb', line 147

def get_label
  begin
    Name::Label.new(get_string)
  rescue ResolvError => e
    raise DecodeError.new(e) # Turn it into something more suitable
  end
end

#get_labels(limit = nil) ⇒ Object

Returns labels starting at @index.



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/dnsruby/message/decoder.rb', line 118

def get_labels(limit = nil)
  limit = @index if limit.nil? || (@index < limit)
  labels = []
  while true
    temp = get_byte_at(@index)
    case temp
      when 0
        @index += 1
        return labels
      when 192..255
        idx = get_unpack('n')[0] & 0x3fff
        if limit <= idx
          raise DecodeError.new('non-backward name pointer')
        end
        save_index = @index
        @index = idx
        labels += self.get_labels(limit)
        @index = save_index
        return labels
      when nil
        return labels
      else
        labels << self.get_label
    end
  end
  labels
end

#get_length16Object

Gets a 16-bit length field from the binary string and yields to the block. This will be the length of the next item to parse in the binary string. Returns the object returned from that block.

When this method returns, @index will point to the byte after the 16-bit length field.



49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/dnsruby/message/decoder.rb', line 49

def get_length16
  len, = self.get_unpack('n')
  save_limit = @limit
  @limit = @index + len
  parsed_data = yield(len)
  if @index < @limit
    message = "Junk exists; limit = #{@limit}, index = #{@index}"
    raise DecodeError.new(message)
  end
  assert_buffer_position_valid(@index)
  @limit = save_limit
  parsed_data
end

#get_nameObject

Gets a Name from the current @index position.



113
114
115
# File 'lib/dnsruby/message/decoder.rb', line 113

def get_name
  Name.new(get_labels)
end

#get_questionObject

Gets a question record.



156
157
158
159
160
161
# File 'lib/dnsruby/message/decoder.rb', line 156

def get_question
  name = self.get_name
  type, klass = self.get_unpack('nn')
  klass = Classes.new(klass)
  Question.new(name, type, klass)
end

#get_rrObject

Gets a resource record.



164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/dnsruby/message/decoder.rb', line 164

def get_rr
  name = get_name
  type, klass, ttl = get_unpack('nnN')
  klass = Classes.new(klass)
  typeclass = RR.get_class(type, klass)
  #  @TODO@ Trap decode errors here, and somehow mark the record as bad.
  #  Need some way to represent raw data only
  record = get_length16 { typeclass.decode_rdata(self) }
  record.name = name
  record.ttl = ttl
  record.type = type
  record.klass = klass
  record
end

#get_stringObject

Gets a string whose 1-byte length is at @index, and the string starting at @index + 1.



97
98
99
100
101
102
103
# File 'lib/dnsruby/message/decoder.rb', line 97

def get_string
  len = get_byte_at(@index) || 0
  assert_buffer_position_valid(@index + 1 + len)
  data_item = @data[@index + 1, len]
  @index += 1 + len
  data_item
end

#get_string_listObject

Gets all strings from @index to the end of the binary string.



106
107
108
109
110
# File 'lib/dnsruby/message/decoder.rb', line 106

def get_string_list
  strings = []
  strings << get_string while has_remaining?
  strings
end

#get_unpack(template) ⇒ Object

Calls String.unpack to get numbers as specified in the template string.



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/dnsruby/message/decoder.rb', line 72

def get_unpack(template)
  len = 0

  template.bytes.each do |byte|
    case byte.chr
      when 'c', 'C', 'h', 'H'
        len += 1
      when 'n'
        len += 2
      when 'N'
        len += 4
      when '*'
        len = @limit - @index
      else
        raise StandardError.new("unsupported template: '#{byte.chr}' in '#{template}'")
    end
  end

  assert_buffer_position_valid(@index + len)
  number_array = @data.unpack("@#{@index}#{template}")
  @index += len
  number_array
end

#has_remaining?Boolean

Has bytes remaining in the binary string to be parsed?

Returns:

  • (Boolean)


24
25
26
# File 'lib/dnsruby/message/decoder.rb', line 24

def has_remaining?
  @limit > @index
end