Class: Ruckus::Parsel

Inherits:
Object show all
Defined in:
lib/ruckus/parsel.rb,
lib/ruckus/selector.rb

Overview

A parsel is an object that supports the following three methods:

  • An initialize that accepts and passes through an opts hash

  • A <tt>to_s<tt> that renders binary, and takes an optional “offset” arg

  • A capture that parses a binary string and returns whatever is left of the strin.

You assemble a tree of Parsel objects to make a packet. The parsel tree winds up looking a lot like the HTML DOM:

  • There’s a tree root you can get to from anywhere by calling root

  • Nodes can have DOM-style “classes” (we call them “names”)

  • Nodes can have DOM-style “ids” (we call them “tags”)

As the tree is rendered (by calling to_s on every node), the offset in the final string is stored in @rendered_offset.

All this is useful for instance with binary formats that required padded offsets from headers — tag the header base, look it up from anywhere, compare rendered offsets, and you know how much padding you need.

Any attribute of a Parsel can be replaced with:

  • A method to call on a sibling node to get the value (for instance, the size of a string)

  • A complex specification of which node to query, what method to use, and how to modify it

  • A block

Direct Known Subclasses

Blob, Choice, Filter, IP, Null, Number, Str, Structure, Vector

Constant Summary collapse

VERBOTEN =
[:size, :capture]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Parsel

Note: all opts become instance variables. Never created directly.



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/ruckus/parsel.rb', line 190

def initialize(opts={})
    (rec = lambda do |k|
        begin
            k.const_get(:OPTIONS).each do |key, v|
                opts[key] ||= v
            end
        rescue
        ensure
            rec.call(k.superclass) if k.inherits_from? Parsel
        end
    end).call(self.class)

    opts.each do |k, v|
        next if VERBOTEN.member? k

        instance_variable_set "@#{ k }".intern, v
        (class << self; self; end).instance_eval {
            attr_accessor k
        }
    end

    capture(opts[:capture]) if opts[:capture]
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth, *args, &block) ⇒ Object

Parsel decorates native types.



225
226
227
# File 'lib/ruckus/parsel.rb', line 225

def method_missing(meth, *args, &block)
    @value.send meth, *args, &block
end

Instance Attribute Details

#nameObject

Returns the value of attribute name.



37
38
39
# File 'lib/ruckus/parsel.rb', line 37

def name
  @name
end

#parentObject

Returns the value of attribute parent.



34
35
36
# File 'lib/ruckus/parsel.rb', line 34

def parent
  @parent
end

#rendered_offsetObject

Returns the value of attribute rendered_offset.



35
36
37
# File 'lib/ruckus/parsel.rb', line 35

def rendered_offset
  @rendered_offset
end

#renderingObject

Returns the value of attribute rendering.



38
39
40
# File 'lib/ruckus/parsel.rb', line 38

def rendering
  @rendering
end

#tagObject

Returns the value of attribute tag.



36
37
38
# File 'lib/ruckus/parsel.rb', line 36

def tag
  @tag
end

#valueObject

Returns the value of attribute value.



33
34
35
# File 'lib/ruckus/parsel.rb', line 33

def value
  @value
end

Class Method Details

.bytes_for_bits(b) ⇒ Object

How many bytes, rounded up, does it take to represent this many bits?



61
62
63
# File 'lib/ruckus/parsel.rb', line 61

def self.bytes_for_bits(b)
    (b / 8) + ((b % 8) != 0 ? 1 : 0)
end

.coerce(val) ⇒ Object

Coerce native types to their equivalent parsels, so you can assign “1” to a Number field, etc



68
69
70
71
72
73
74
# File 'lib/ruckus/parsel.rb', line 68

def self.coerce(val)
    if val.kind_of? Numeric
        Number.new :value => val
    elsif val.kind_of? String
        Str.new :value => val
    end
end

.endian?Boolean

What’s our native endianness? :big or :little

Returns:

  • (Boolean)


48
49
50
# File 'lib/ruckus/parsel.rb', line 48

def self.endian?
    @endianness ||= ([1].pack("I") == "\x01\x00\x00\x00" ? :little : :big)
end

.factory?Boolean

Returns:

  • (Boolean)


363
# File 'lib/ruckus/parsel.rb', line 363

def self.factory?; false; end

.native?(endian) ⇒ Boolean

Is this endianness native?

Returns:

  • (Boolean)


54
55
56
# File 'lib/ruckus/parsel.rb', line 54

def self.native?(endian)
    endian? == endian
end

Instance Method Details

#each_matching_selector(sel) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/ruckus/selector.rb', line 37

def each_matching_selector(sel)
    index_for_selectors if not @selector_index
    sels = sel.split.reverse.map {|x| Selector.new(x)}
    
    first = sels.shift
    
    ipool = first.rid    ? @selector_index[0][first.rid]    : nil
    cpool = first.rclass ? @selector_index[1][first.rclass] : nil
    kpool = first.rkind  ? @selector_index[2][first.rkind]  : nil
    
    pool = ipool || cpool || kpool
    pool = (pool & ipool) if ipool
    pool = (pool & cpool) if cpool
    pool = (pool & kpool) if kpool
        
    # XXX this loop is fucked:
    # outer loop needs to be pool
    # will capture parent nodes in arbitrary order
#             sels.each do |s|
#                 pool.each do |victim|
#                     found = false
#                     victim.root {|n| found = true if n.matches_selector? s}
#                     pool.delete(victim) if not found
#                 end
#             end

    pool.each do |victim|
        tmpsels = sels.clone
        cur = sels.shift

        victim.root do |parent|
            break if not cur
            if parent.matches_selector? cur
                cur = sels.shift
            end
        end
        
        pool.delete(victim) if cur
    end
    
    pool.each do |n|
        yield n
    end

    pool.size
end

#find_containing(klass) ⇒ Object

Walk up parents until you find one of type klass; so, if you have a FooHeader containing lots of FooRecords, and you have a handle on a FooElement inside a FooRecord, you can call x.find_containing(FooRecord) to jump right back to the header.



288
289
290
291
292
293
294
# File 'lib/ruckus/parsel.rb', line 288

def find_containing(klass)
    p = parent
    while p and not p.kind_of? klass
        p = p.parent
    end
    return p
end

#find_tag(t) ⇒ Object

Find any node by its tag. This isn’t indexed in any way, so it’s slow, but who cares?



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/ruckus/parsel.rb', line 252

def find_tag(t)
    r = nil
    if @tag == t
        r = self
    elsif
        begin
            each do |it|
                r = it.find_tag(t)
                break if r
            end
        rescue
        end
    end
    return r
end

#find_tag_struct(t) ⇒ Object

Find a node by its tag, but return its enclosing structure, not the node itself. This is usually what you want; the first field of a header might be tagged, but you just wanted a reference to the header entire.



273
274
275
276
277
278
279
280
# File 'lib/ruckus/parsel.rb', line 273

def find_tag_struct(t)
    p = find_tag(t)
    if(p)
        return p.parent_struct
    else
        return nil
    end
end

#fixupObject

Stubbed.



246
247
# File 'lib/ruckus/parsel.rb', line 246

def fixup
end

#in(*args) ⇒ Object



361
# File 'lib/ruckus/parsel.rb', line 361

def in(*args); capture(*args); end

#incomplete!Object

Raises:



365
# File 'lib/ruckus/parsel.rb', line 365

def incomplete!; raise IncompleteCapture; end

#index_for_selectorsObject



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/ruckus/selector.rb', line 19

def index_for_selectors
    b = lambda {|h, k| h[k] = []}
    ids, classes, kinds = [Hash.new(&b), Hash.new(&b), Hash.new(&b)]

    visit do |n|
        k = n.class
        while k != Ruckus::Parsel and k != Object
            kinds[k.to_s] << n                   
            k = k.superclass
        end
        
        (ids[n.tag.to_s] << n) if n.tag
        (classes[n.name.to_s] << n) if n.name
    end
                
    @selector_index = [ids, classes, kinds]
end

#inspectObject

This got insane following links, so cut down what ‘pp’ gives you, mostly by not recursively rendering children.



349
350
351
352
353
354
355
356
357
# File 'lib/ruckus/parsel.rb', line 349

def inspect
    if @value.kind_of? Parsel
        val = "#<#{ @value.class }:#{ @value.object_id }: @name=\"#{ @name }\">"
    else
        val = @value.inspect
    end

    "#<#{ self.class }:#{ self.object_id }: @name=\"#{ @name }\" @value=#{ val }>"
end

#matches_selector?(sel) ⇒ Boolean

Returns:

  • (Boolean)


84
85
86
87
88
89
90
# File 'lib/ruckus/selector.rb', line 84

def matches_selector?(sel)
    index_for_selectors if not @selector_index
    return false if sel.rid and sel.rid != self.tag
    return false if sel.rclass and sel.rclass != self.name
    return false if sel.rkind and not @selector_index[2][sel.rkind].include? self
    return true
end

#native?Boolean

Is this parsel in native byte order?

Returns:

  • (Boolean)


42
43
44
# File 'lib/ruckus/parsel.rb', line 42

def native?
    self.class.native @endian || :little
end

#nextObject

Get to the next node (at this level of the tree — does not traverse back through parent)



163
164
165
# File 'lib/ruckus/parsel.rb', line 163

def next
    parent[parent.place(self) + 1]
end

#out(*args) ⇒ Object

phase in the new names



360
# File 'lib/ruckus/parsel.rb', line 360

def out(*args); to_s(*args); end

#parent_structure(p = self) ⇒ Object Also known as: parent_struct

Walk up the parents of this node until we find a containing structure.



176
177
178
179
180
181
182
# File 'lib/ruckus/parsel.rb', line 176

def parent_structure(p = self)
    while p.parent
        p = p.parent
        break if p.kind_of? Ruckus::Structure
    end
    return p
end

#permute(all = nil) ⇒ Object

This kind of doesn’t belong here. XXX factor out into module.

Use Parsel#visit to permute every permutable Parsel in a message, by calling value.permute (Mutator objects respond to this message). See Mutate.rb.



319
320
321
322
323
324
325
# File 'lib/ruckus/parsel.rb', line 319

def permute(all=nil)
    visit {|o| o.permute(nil)} and return if all
    begin
        @value.permute
    rescue
    end
end

#prevObject

Opposite of Parsel#next



169
170
171
# File 'lib/ruckus/parsel.rb', line 169

def prev
    parent[parent.place(self) - 1]
end

#resolve(val) ⇒ Object

Read Blob first.

Parsels can have nonscalar values, including blocks and references to the values of other fields. This is a bit of a mess. Takes:

all

Rebind all instance variables, not just @value

meth

Method to call on target object to extract value, but note that you can just pass “val” as a sym for same effect. (Default: :size)

source

Source Parsel to extract object, but you’ll never specify this directly. One exception: :source => :rest means, “apply the method to all subsequent elements of the blob or structure”

offset

(Default: 1) which neighboring Parsel should we extract from — can also be :this, :prev, and :next.

block

A Proc to call with the object we’re extracting.



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/ruckus/parsel.rb', line 96

def resolve(val)
    return nil if not val
    return val if [String, Integer, Ruckus::Mutator::Mutator].kind_of_these? val
    return val if val == true
    return val if val == false

    o = {}

    if val.kind_of? Symbol
        o[:meth] = val
    elsif val.kind_of? Hash
        o = o.merge(val)
    end

    if (t = @from_tag) || (t = o[:from_tag])
        o[:source] = root.find_tag(t)
        raise "can't find" if not o[:source]
    end

    if (f = @from_field) || (f = o[:from_field])
        o[:source] = parent_struct.send f
        raise "can't find field" if not o[:source]
    end

    place = parent.place(self)

    if not o[:source]
        raise "unparented" if not parent

        o[:offset] ||= 1
        o[:offset] = 0 if o[:offset] == :this
        o[:offset] = -1 if o[:offset] == :prev
        o[:offset] = 1 if o[:offset] == :next

        loc = place + o[:offset]
        o[:source] = parent[loc]

        raise "can't resolve #{ o } for #{ @name }" if not o[:source]
    end

    if not o[:block]
        o[:meth] ||= :size

        if o[:source] == :rest
            r = 0
            ((place+1)..(parent.size)).each do |i|
                r += parent[i].send(o[:meth]) if parent[i]
            end
        else
            r = o[:source].send o[:meth]
        end
    else
        r = o[:block].call o[:source]
    end

    # cheat: if resolution returns a symbol --- which happens
    # with len/string pairs, because they depend on each other ---
    # return nil. This effectively unbounds the string during to_s.
    r = nil if r.kind_of? Symbol

    r = @modifier.call(self, r) if @modifier
    return r
end

#respond_to?(symbol, include_priv = false) ⇒ Boolean

Returns:

  • (Boolean)


229
230
231
# File 'lib/ruckus/parsel.rb', line 229

def respond_to?(symbol, include_priv = false)
  !!(super(symbol, include_priv) || @value.respond_to?(symbol, include_priv))
end

#root(p = self) {|p| ... } ⇒ Object

Traverse all the way to the root of the tree

Yields:

  • (p)


235
236
237
238
239
240
241
242
# File 'lib/ruckus/parsel.rb', line 235

def root(p = self, &block)
    yield p if block_given?
    while p.parent
        p = p.parent
        yield p if block_given?
    end
    return p
end

#sizeObject

How big is the rendered output in bytes? By default, the worst possible impl: render and take size of result. You can override to make this more reasonable.



218
219
220
221
# File 'lib/ruckus/parsel.rb', line 218

def size
    return nil if not @value
    return to_s.size
end

#visit(&block) ⇒ Object

Wrap tree-traversal; &block is called for each element of the tree.



299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/ruckus/parsel.rb', line 299

def visit(&block)
    raise "need block" if not block_given?

    block.call(self)

    begin
        @value.each do |o|
            o.visit(&block)
        end
    rescue
    end
end

#where_am_i?Boolean

See Blob.

What’s our position in the parent? This works for any enumerable parent.

Returns:

  • (Boolean)


332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'lib/ruckus/parsel.rb', line 332

def where_am_i?
    return @where_am_i if @where_am_i

    raise "not parented" if not parent

    parent.each_with_index do |o, i|
        if o == self
            @where_am_i = i
            return i
        end
    end
    nil
end