ruckus 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|