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.
Files changed (59) hide show
  1. data/.document +5 -0
  2. data/.gitignore +22 -0
  3. data/README +0 -0
  4. data/README.markdown +51 -0
  5. data/Rakefile +54 -0
  6. data/VERSION +1 -0
  7. data/lib/ruckus.rb +70 -0
  8. data/lib/ruckus/blob.rb +113 -0
  9. data/lib/ruckus/choice.rb +55 -0
  10. data/lib/ruckus/dfuzz.rb +231 -0
  11. data/lib/ruckus/dictionary.rb +119 -0
  12. data/lib/ruckus/enum.rb +39 -0
  13. data/lib/ruckus/extensions/array.rb +33 -0
  14. data/lib/ruckus/extensions/class.rb +168 -0
  15. data/lib/ruckus/extensions/duplicable.rb +37 -0
  16. data/lib/ruckus/extensions/file.rb +24 -0
  17. data/lib/ruckus/extensions/fixnum.rb +26 -0
  18. data/lib/ruckus/extensions/hash.rb +10 -0
  19. data/lib/ruckus/extensions/integer.rb +26 -0
  20. data/lib/ruckus/extensions/ipaddr.rb +155 -0
  21. data/lib/ruckus/extensions/irb.rb +30 -0
  22. data/lib/ruckus/extensions/math.rb +6 -0
  23. data/lib/ruckus/extensions/module.rb +37 -0
  24. data/lib/ruckus/extensions/numeric.rb +117 -0
  25. data/lib/ruckus/extensions/object.rb +22 -0
  26. data/lib/ruckus/extensions/range.rb +16 -0
  27. data/lib/ruckus/extensions/socket.rb +20 -0
  28. data/lib/ruckus/extensions/string.rb +327 -0
  29. data/lib/ruckus/filter.rb +16 -0
  30. data/lib/ruckus/human_display.rb +79 -0
  31. data/lib/ruckus/ip.rb +38 -0
  32. data/lib/ruckus/mac_addr.rb +31 -0
  33. data/lib/ruckus/mutator.rb +318 -0
  34. data/lib/ruckus/null.rb +24 -0
  35. data/lib/ruckus/number.rb +360 -0
  36. data/lib/ruckus/parsel.rb +363 -0
  37. data/lib/ruckus/selector.rb +92 -0
  38. data/lib/ruckus/str.rb +164 -0
  39. data/lib/ruckus/structure.rb +263 -0
  40. data/lib/ruckus/structure/atcreate.rb +23 -0
  41. data/lib/ruckus/structure/beforebacks.rb +28 -0
  42. data/lib/ruckus/structure/defaults.rb +57 -0
  43. data/lib/ruckus/structure/factory.rb +42 -0
  44. data/lib/ruckus/structure/fixupfields.rb +16 -0
  45. data/lib/ruckus/structure/initializers.rb +14 -0
  46. data/lib/ruckus/structure/relate.rb +34 -0
  47. data/lib/ruckus/structure/replacement.rb +30 -0
  48. data/lib/ruckus/structure/searchmods.rb +33 -0
  49. data/lib/ruckus/structure/structureproxies.rb +28 -0
  50. data/lib/ruckus/structure/validate.rb +12 -0
  51. data/lib/ruckus/structure_shortcuts.rb +109 -0
  52. data/lib/ruckus/time_t.rb +26 -0
  53. data/lib/ruckus/vector.rb +97 -0
  54. data/ruckus.gemspec +104 -0
  55. data/test/test_decides.rb +61 -0
  56. data/test/test_mutator.rb +29 -0
  57. data/test/test_override.rb +23 -0
  58. data/test/test_replace.rb +39 -0
  59. 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