ruckus 0.5.4
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/.document +5 -0
- data/.gitignore +22 -0
- data/README +0 -0
- data/README.markdown +51 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/lib/ruckus.rb +70 -0
- data/lib/ruckus/blob.rb +113 -0
- data/lib/ruckus/choice.rb +55 -0
- data/lib/ruckus/dfuzz.rb +231 -0
- data/lib/ruckus/dictionary.rb +119 -0
- data/lib/ruckus/enum.rb +39 -0
- data/lib/ruckus/extensions/array.rb +33 -0
- data/lib/ruckus/extensions/class.rb +168 -0
- data/lib/ruckus/extensions/duplicable.rb +37 -0
- data/lib/ruckus/extensions/file.rb +24 -0
- data/lib/ruckus/extensions/fixnum.rb +26 -0
- data/lib/ruckus/extensions/hash.rb +10 -0
- data/lib/ruckus/extensions/integer.rb +26 -0
- data/lib/ruckus/extensions/ipaddr.rb +155 -0
- data/lib/ruckus/extensions/irb.rb +30 -0
- data/lib/ruckus/extensions/math.rb +6 -0
- data/lib/ruckus/extensions/module.rb +37 -0
- data/lib/ruckus/extensions/numeric.rb +117 -0
- data/lib/ruckus/extensions/object.rb +22 -0
- data/lib/ruckus/extensions/range.rb +16 -0
- data/lib/ruckus/extensions/socket.rb +20 -0
- data/lib/ruckus/extensions/string.rb +327 -0
- data/lib/ruckus/filter.rb +16 -0
- data/lib/ruckus/human_display.rb +79 -0
- data/lib/ruckus/ip.rb +38 -0
- data/lib/ruckus/mac_addr.rb +31 -0
- data/lib/ruckus/mutator.rb +318 -0
- data/lib/ruckus/null.rb +24 -0
- data/lib/ruckus/number.rb +360 -0
- data/lib/ruckus/parsel.rb +363 -0
- data/lib/ruckus/selector.rb +92 -0
- data/lib/ruckus/str.rb +164 -0
- data/lib/ruckus/structure.rb +263 -0
- data/lib/ruckus/structure/atcreate.rb +23 -0
- data/lib/ruckus/structure/beforebacks.rb +28 -0
- data/lib/ruckus/structure/defaults.rb +57 -0
- data/lib/ruckus/structure/factory.rb +42 -0
- data/lib/ruckus/structure/fixupfields.rb +16 -0
- data/lib/ruckus/structure/initializers.rb +14 -0
- data/lib/ruckus/structure/relate.rb +34 -0
- data/lib/ruckus/structure/replacement.rb +30 -0
- data/lib/ruckus/structure/searchmods.rb +33 -0
- data/lib/ruckus/structure/structureproxies.rb +28 -0
- data/lib/ruckus/structure/validate.rb +12 -0
- data/lib/ruckus/structure_shortcuts.rb +109 -0
- data/lib/ruckus/time_t.rb +26 -0
- data/lib/ruckus/vector.rb +97 -0
- data/ruckus.gemspec +104 -0
- data/test/test_decides.rb +61 -0
- data/test/test_mutator.rb +29 -0
- data/test/test_override.rb +23 -0
- data/test/test_replace.rb +39 -0
- metadata +138 -0
@@ -0,0 +1,363 @@
|
|
1
|
+
# === Parsels are tree nodes
|
2
|
+
|
3
|
+
module Ruckus
|
4
|
+
class IncompleteCapture < RuntimeError; end
|
5
|
+
|
6
|
+
# A parsel is an object that supports the following three methods:
|
7
|
+
# * An <tt>initialize</tt> that accepts and passes through an opts hash
|
8
|
+
# * A <tt>to_s<tt> that renders binary, and takes an optional "offset" arg
|
9
|
+
# * A <tt>capture</tt> that parses a binary string and returns whatever
|
10
|
+
# is left of the strin.
|
11
|
+
#
|
12
|
+
# You assemble a tree of Parsel objects to make a packet. The parsel
|
13
|
+
# tree winds up looking a lot like the HTML DOM:
|
14
|
+
# * There's a tree root you can get to from anywhere by calling <tt>root</tt>
|
15
|
+
# * Nodes can have DOM-style "classes" (we call them "names")
|
16
|
+
# * Nodes can have DOM-style "ids" (we call them "tags")
|
17
|
+
#
|
18
|
+
# As the tree is rendered (by calling to_s on every node), the offset
|
19
|
+
# in the final string is stored in @rendered_offset.
|
20
|
+
#
|
21
|
+
# All this is useful for instance with binary formats that required padded
|
22
|
+
# offsets from headers --- tag the header base, look it up from anywhere,
|
23
|
+
# compare rendered offsets, and you know how much padding you need.
|
24
|
+
#
|
25
|
+
# Any attribute of a Parsel can be replaced with:
|
26
|
+
# * A method to call on a sibling node to get the value (for instance,
|
27
|
+
# the size of a string)
|
28
|
+
# * A complex specification of which node to query, what method to use,
|
29
|
+
# and how to modify it
|
30
|
+
# * A block
|
31
|
+
#
|
32
|
+
class Parsel
|
33
|
+
attr_accessor :value
|
34
|
+
attr_accessor :parent
|
35
|
+
attr_accessor :rendered_offset
|
36
|
+
attr_accessor :tag
|
37
|
+
attr_accessor :name
|
38
|
+
attr_accessor :rendering
|
39
|
+
|
40
|
+
# Is this parsel in native byte order?
|
41
|
+
#
|
42
|
+
def native?
|
43
|
+
self.class.native @endian || :little
|
44
|
+
end
|
45
|
+
|
46
|
+
# What's our native endianness? :big or :little
|
47
|
+
#
|
48
|
+
def self.endian?
|
49
|
+
@endianness ||= ([1].pack("I")[0] == 1 ? :little : :big)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Is this endianness native?
|
53
|
+
#
|
54
|
+
def self.native?(endian)
|
55
|
+
endian? == endian
|
56
|
+
end
|
57
|
+
|
58
|
+
# How many bytes, rounded up, does it take to represent this
|
59
|
+
# many bits?
|
60
|
+
#
|
61
|
+
def self.bytes_for_bits(b)
|
62
|
+
(b / 8) + ((b % 8) != 0 ? 1 : 0)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Coerce native types to their equivalent parsels, so you can
|
66
|
+
# assign "1" to a Number field, etc
|
67
|
+
#
|
68
|
+
def self.coerce(val)
|
69
|
+
if val.kind_of? Numeric
|
70
|
+
Number.new :value => val
|
71
|
+
elsif val.kind_of? String
|
72
|
+
Str.new :value => val
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Read Blob first.
|
77
|
+
#
|
78
|
+
# Parsels can have nonscalar values, including blocks and
|
79
|
+
# references to the values of other fields. This is a bit
|
80
|
+
# of a mess. Takes:
|
81
|
+
#
|
82
|
+
# all:: Rebind all instance variables, not just @value
|
83
|
+
# meth:: Method to call on target object to extract value,
|
84
|
+
# but note that you can just pass "val" as a sym
|
85
|
+
# for same effect. (Default: :size)
|
86
|
+
# source:: Source Parsel to extract object, but you'll never
|
87
|
+
# specify this directly.
|
88
|
+
# One exception: :source => :rest means, "apply the
|
89
|
+
# method to all subsequent elements of the blob or
|
90
|
+
# structure"
|
91
|
+
# offset:: (Default: 1) which neighboring Parsel should we
|
92
|
+
# extract from --- can also be <tt>:this</tt>,
|
93
|
+
# <tt>:prev</tt>, and <tt>:next</tt>.
|
94
|
+
# block:: A Proc to call with the object we're extracting.
|
95
|
+
#
|
96
|
+
def resolve(val)
|
97
|
+
return nil if not val
|
98
|
+
return val if [String, Integer, Ruckus::Mutator::Mutator].kind_of_these? val
|
99
|
+
return val if val == true
|
100
|
+
return val if val == false
|
101
|
+
|
102
|
+
o = {}
|
103
|
+
|
104
|
+
if val.kind_of? Symbol
|
105
|
+
o[:meth] = val
|
106
|
+
elsif val.kind_of? Hash
|
107
|
+
o = o.merge(val)
|
108
|
+
end
|
109
|
+
|
110
|
+
if (t = @from_tag) || (t = o[:from_tag])
|
111
|
+
o[:source] = root.find_tag(t)
|
112
|
+
raise "can't find" if not o[:source]
|
113
|
+
end
|
114
|
+
|
115
|
+
if (f = @from_field) || (f = o[:from_field])
|
116
|
+
o[:source] = parent_struct.send f
|
117
|
+
raise "can't find field" if not o[:source]
|
118
|
+
end
|
119
|
+
|
120
|
+
place = parent.place(self)
|
121
|
+
|
122
|
+
if not o[:source]
|
123
|
+
raise "unparented" if not parent
|
124
|
+
|
125
|
+
o[:offset] ||= 1
|
126
|
+
o[:offset] = 0 if o[:offset] == :this
|
127
|
+
o[:offset] = -1 if o[:offset] == :prev
|
128
|
+
o[:offset] = 1 if o[:offset] == :next
|
129
|
+
|
130
|
+
loc = place + o[:offset]
|
131
|
+
o[:source] = parent[loc]
|
132
|
+
|
133
|
+
raise "can't resolve #{ o } for #{ @name }" if not o[:source]
|
134
|
+
end
|
135
|
+
|
136
|
+
if not o[:block]
|
137
|
+
o[:meth] ||= :size
|
138
|
+
|
139
|
+
if o[:source] == :rest
|
140
|
+
r = 0
|
141
|
+
((place+1)..(parent.size)).each do |i|
|
142
|
+
r += parent[i].send(o[:meth]) if parent[i]
|
143
|
+
end
|
144
|
+
else
|
145
|
+
r = o[:source].send o[:meth]
|
146
|
+
end
|
147
|
+
else
|
148
|
+
r = o[:block].call o[:source]
|
149
|
+
end
|
150
|
+
|
151
|
+
# cheat: if resolution returns a symbol --- which happens
|
152
|
+
# with len/string pairs, because they depend on each other ---
|
153
|
+
# return nil. This effectively unbounds the string during to_s.
|
154
|
+
r = nil if r.kind_of? Symbol
|
155
|
+
|
156
|
+
r = @modifier.call(self, r) if @modifier
|
157
|
+
return r
|
158
|
+
end
|
159
|
+
|
160
|
+
# Get to the next node (at this level of the tree --- does
|
161
|
+
# not traverse back through parent)
|
162
|
+
#
|
163
|
+
def next
|
164
|
+
parent[parent.place(self) + 1]
|
165
|
+
end
|
166
|
+
|
167
|
+
# Opposite of Parsel#next
|
168
|
+
#
|
169
|
+
def prev
|
170
|
+
parent[parent.place(self) - 1]
|
171
|
+
end
|
172
|
+
|
173
|
+
# Walk up the parents of this node until we find a containing
|
174
|
+
# structure.
|
175
|
+
#
|
176
|
+
def parent_structure(p = self)
|
177
|
+
while p.parent
|
178
|
+
p = p.parent
|
179
|
+
break if p.kind_of? Ruckus::Structure
|
180
|
+
end
|
181
|
+
return p
|
182
|
+
end
|
183
|
+
alias_method :parent_struct, :parent_structure
|
184
|
+
|
185
|
+
VERBOTEN = [:size, :capture]
|
186
|
+
|
187
|
+
# Note: all opts become instance variables. Never created
|
188
|
+
# directly.
|
189
|
+
#
|
190
|
+
def initialize(opts={})
|
191
|
+
(rec = lambda do |k|
|
192
|
+
begin
|
193
|
+
k.const_get(:OPTIONS).each do |key, v|
|
194
|
+
opts[key] ||= v
|
195
|
+
end
|
196
|
+
rescue
|
197
|
+
ensure
|
198
|
+
rec.call(k.superclass) if k.inherits_from? Parsel
|
199
|
+
end
|
200
|
+
end).call(self.class)
|
201
|
+
|
202
|
+
opts.each do |k, v|
|
203
|
+
next if VERBOTEN.member? k
|
204
|
+
|
205
|
+
instance_variable_set "@#{ k }".intern, v
|
206
|
+
(class << self; self; end).instance_eval {
|
207
|
+
attr_accessor k
|
208
|
+
}
|
209
|
+
end
|
210
|
+
|
211
|
+
capture(opts[:capture]) if opts[:capture]
|
212
|
+
end
|
213
|
+
|
214
|
+
# How big is the rendered output in bytes? By default,
|
215
|
+
# the worst possible impl: render and take size of result.
|
216
|
+
# You can override to make this more reasonable.
|
217
|
+
#
|
218
|
+
def size
|
219
|
+
return nil if not @value
|
220
|
+
return to_s.size
|
221
|
+
end
|
222
|
+
|
223
|
+
# Parsel decorates native types.
|
224
|
+
#
|
225
|
+
def method_missing(meth, *args, &block)
|
226
|
+
@value.send meth, *args, &block
|
227
|
+
end
|
228
|
+
|
229
|
+
# Traverse all the way to the root of the tree
|
230
|
+
#
|
231
|
+
def root(p = self, &block)
|
232
|
+
yield p if block_given?
|
233
|
+
while p.parent
|
234
|
+
p = p.parent
|
235
|
+
yield p if block_given?
|
236
|
+
end
|
237
|
+
return p
|
238
|
+
end
|
239
|
+
|
240
|
+
# Stubbed.
|
241
|
+
#
|
242
|
+
def fixup
|
243
|
+
end
|
244
|
+
|
245
|
+
# Find any node by its tag. This isn't indexed in any way, so
|
246
|
+
# it's slow, but who cares?
|
247
|
+
#
|
248
|
+
def find_tag(t)
|
249
|
+
r = nil
|
250
|
+
if @tag == t
|
251
|
+
r = self
|
252
|
+
elsif
|
253
|
+
begin
|
254
|
+
each do |it|
|
255
|
+
r = it.find_tag(t)
|
256
|
+
break if r
|
257
|
+
end
|
258
|
+
rescue
|
259
|
+
end
|
260
|
+
end
|
261
|
+
return r
|
262
|
+
end
|
263
|
+
|
264
|
+
# Find a node by its tag, but return its enclosing structure, not
|
265
|
+
# the node itself. This is usually what you want; the first field
|
266
|
+
# of a header might be tagged, but you just wanted a reference to
|
267
|
+
# the header entire.
|
268
|
+
#
|
269
|
+
def find_tag_struct(t)
|
270
|
+
p = find_tag(t)
|
271
|
+
if(p)
|
272
|
+
return p.parent_struct
|
273
|
+
else
|
274
|
+
return nil
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# Walk up parents until you find one of type <tt>klass</tt>;
|
279
|
+
# so, if you have a FooHeader containing lots of FooRecords,
|
280
|
+
# and you have a handle on a FooElement inside a FooRecord, you
|
281
|
+
# can call <tt>x.find_containing(FooRecord)</tt> to jump right
|
282
|
+
# back to the header.
|
283
|
+
#
|
284
|
+
def find_containing(klass)
|
285
|
+
p = parent
|
286
|
+
while p and not p.kind_of? klass
|
287
|
+
p = p.parent
|
288
|
+
end
|
289
|
+
return p
|
290
|
+
end
|
291
|
+
|
292
|
+
# Wrap tree-traversal; <tt>&block</tt> is called for each
|
293
|
+
# element of the tree.
|
294
|
+
#
|
295
|
+
def visit(&block)
|
296
|
+
raise "need block" if not block_given?
|
297
|
+
|
298
|
+
block.call(self)
|
299
|
+
|
300
|
+
begin
|
301
|
+
@value.each do |o|
|
302
|
+
o.visit(&block)
|
303
|
+
end
|
304
|
+
rescue
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
# This kind of doesn't belong here. XXX factor out into
|
309
|
+
# module.
|
310
|
+
#
|
311
|
+
# Use Parsel#visit to permute every permutable Parsel in
|
312
|
+
# a message, by calling value.permute (Mutator objects
|
313
|
+
# respond to this message). See Mutate.rb.
|
314
|
+
#
|
315
|
+
def permute(all=nil)
|
316
|
+
visit {|o| o.permute(nil)} and return if all
|
317
|
+
begin
|
318
|
+
@value.permute
|
319
|
+
rescue
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
# See Blob.
|
324
|
+
#
|
325
|
+
# What's our position in the parent? This works for any
|
326
|
+
# enumerable parent.
|
327
|
+
#
|
328
|
+
def where_am_i?
|
329
|
+
return @where_am_i if @where_am_i
|
330
|
+
|
331
|
+
raise "not parented" if not parent
|
332
|
+
|
333
|
+
parent.each_with_index do |o, i|
|
334
|
+
if o == self
|
335
|
+
@where_am_i = i
|
336
|
+
return i
|
337
|
+
end
|
338
|
+
end
|
339
|
+
nil
|
340
|
+
end
|
341
|
+
|
342
|
+
# This got insane following links, so cut down what 'pp' gives you,
|
343
|
+
# mostly by not recursively rendering children.
|
344
|
+
#
|
345
|
+
def inspect
|
346
|
+
if @value.kind_of? Parsel
|
347
|
+
val = "#<#{ @value.class }:#{ @value.object_id }: @name=\"#{ @name }\">"
|
348
|
+
else
|
349
|
+
val = @value.inspect
|
350
|
+
end
|
351
|
+
|
352
|
+
"#<#{ self.class }:#{ self.object_id }: @name=\"#{ @name }\" @value=#{ val }>"
|
353
|
+
end
|
354
|
+
|
355
|
+
# phase in the new names
|
356
|
+
def out(*args); to_s(*args); end
|
357
|
+
def in(*args); capture(*args); end
|
358
|
+
|
359
|
+
def self.factory?; false; end
|
360
|
+
|
361
|
+
def incomplete!; raise IncompleteCapture; end
|
362
|
+
end
|
363
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Ruckus
|
2
|
+
class Selector < String
|
3
|
+
attr_reader :rid
|
4
|
+
attr_reader :rclass
|
5
|
+
attr_reader :rkind
|
6
|
+
|
7
|
+
def initialize(s)
|
8
|
+
if s =~ /(?:([^.#]+))?(?:\.([^.#]+))?(?:#(.+))?/
|
9
|
+
@rkind, @rclass, @rid = [$1, $2, $3]
|
10
|
+
else
|
11
|
+
@rkind, @rclass, @rid = [nil, nil, s]
|
12
|
+
end
|
13
|
+
|
14
|
+
super
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Parsel
|
19
|
+
def index_for_selectors
|
20
|
+
b = lambda {|h, k| h[k] = []}
|
21
|
+
ids, classes, kinds = [Hash.new(&b), Hash.new(&b), Hash.new(&b)]
|
22
|
+
|
23
|
+
visit do |n|
|
24
|
+
k = n.class
|
25
|
+
while k != Ruckus::Parsel and k != Object
|
26
|
+
kinds[k.to_s] << n
|
27
|
+
k = k.superclass
|
28
|
+
end
|
29
|
+
|
30
|
+
(ids[n.tag.to_s] << n) if n.tag
|
31
|
+
(classes[n.name.to_s] << n) if n.name
|
32
|
+
end
|
33
|
+
|
34
|
+
@selector_index = [ids, classes, kinds]
|
35
|
+
end
|
36
|
+
|
37
|
+
def each_matching_selector(sel)
|
38
|
+
index_for_selectors if not @selector_index
|
39
|
+
sels = sel.split.reverse.map {|x| Selector.new(x)}
|
40
|
+
|
41
|
+
first = sels.shift
|
42
|
+
|
43
|
+
ipool = first.rid ? @selector_index[0][first.rid] : nil
|
44
|
+
cpool = first.rclass ? @selector_index[1][first.rclass] : nil
|
45
|
+
kpool = first.rkind ? @selector_index[2][first.rkind] : nil
|
46
|
+
|
47
|
+
pool = ipool || cpool || kpool
|
48
|
+
pool = (pool & ipool) if ipool
|
49
|
+
pool = (pool & cpool) if cpool
|
50
|
+
pool = (pool & kpool) if kpool
|
51
|
+
|
52
|
+
# XXX this loop is fucked:
|
53
|
+
# outer loop needs to be pool
|
54
|
+
# will capture parent nodes in arbitrary order
|
55
|
+
# sels.each do |s|
|
56
|
+
# pool.each do |victim|
|
57
|
+
# found = false
|
58
|
+
# victim.root {|n| found = true if n.matches_selector? s}
|
59
|
+
# pool.delete(victim) if not found
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
|
63
|
+
pool.each do |victim|
|
64
|
+
tmpsels = sels.clone
|
65
|
+
cur = sels.shift
|
66
|
+
|
67
|
+
victim.root do |parent|
|
68
|
+
break if not cur
|
69
|
+
if parent.matches_selector? cur
|
70
|
+
cur = sels.shift
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
pool.delete(victim) if cur
|
75
|
+
end
|
76
|
+
|
77
|
+
pool.each do |n|
|
78
|
+
yield n
|
79
|
+
end
|
80
|
+
|
81
|
+
pool.size
|
82
|
+
end
|
83
|
+
|
84
|
+
def matches_selector?(sel)
|
85
|
+
index_for_selectors if not @selector_index
|
86
|
+
return false if sel.rid and sel.rid != self.tag
|
87
|
+
return false if sel.rclass and sel.rclass != self.name
|
88
|
+
return false if sel.rkind and not @selector_index[2][sel.rkind].include? self
|
89
|
+
return true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|