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
data/lib/ruckus/str.rb
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
# === Pretty much anything that isn't a Number is a Str
|
2
|
+
|
3
|
+
# class Symbol
|
4
|
+
# def clone
|
5
|
+
# self # fuck you ruby, what the fuck is wrong with you
|
6
|
+
# end
|
7
|
+
# end
|
8
|
+
|
9
|
+
module Ruckus
|
10
|
+
# A Ruckus::Str is a bag of bytes, wrapping a Ruby string
|
11
|
+
class Str < Parsel
|
12
|
+
# Options include:
|
13
|
+
# size:: :min = :max = :size
|
14
|
+
# min:: string will be padded to this size
|
15
|
+
# max:: string will be cut off at this size
|
16
|
+
# padding:: (Default: "\x00") --- what to pad with
|
17
|
+
# value:: Normally a string
|
18
|
+
# unicode:: Convert to UTF-16-LE before rendering
|
19
|
+
#
|
20
|
+
def initialize(opts={})
|
21
|
+
opts[:bounded_by] ||= -1 if opts[:bounded]
|
22
|
+
|
23
|
+
if opts[:bounded_by]
|
24
|
+
opts[:size] = { :offset => opts[:bounded_by], :meth => :value }
|
25
|
+
end
|
26
|
+
|
27
|
+
if opts[:size]
|
28
|
+
opts[:min] = opts[:size]
|
29
|
+
opts[:max] = opts[:size]
|
30
|
+
@in_size = opts[:size]
|
31
|
+
end
|
32
|
+
opts[:padding] ||= "\x00"
|
33
|
+
opts[:min] ||= 0
|
34
|
+
|
35
|
+
super(opts)
|
36
|
+
|
37
|
+
@value = @value.clone if @value
|
38
|
+
@value ||= ""
|
39
|
+
end
|
40
|
+
|
41
|
+
# As with Parsel; take a string, return what's left, capture the
|
42
|
+
# value in @value.
|
43
|
+
#
|
44
|
+
def capture(str)
|
45
|
+
if @in_size
|
46
|
+
max = resolve(@in_size)
|
47
|
+
min = resolve(@in_size)
|
48
|
+
end
|
49
|
+
|
50
|
+
max ||= resolve(@max)
|
51
|
+
min ||= resolve(@min)
|
52
|
+
pad = resolve(@padding)
|
53
|
+
nul = resolve(@nul_terminated)
|
54
|
+
uni = resolve(@unicode)
|
55
|
+
del = resolve(@delimiter)
|
56
|
+
|
57
|
+
@value = nil
|
58
|
+
|
59
|
+
incomplete! if not str
|
60
|
+
|
61
|
+
if (s = size)
|
62
|
+
incomplete! if str.size < s
|
63
|
+
cap = str[0...s]
|
64
|
+
elsif nul
|
65
|
+
nterm = str.index(uni ? "\000\000" : "\000")
|
66
|
+
if nterm
|
67
|
+
cap = str[0...nterm]
|
68
|
+
else
|
69
|
+
cap = str
|
70
|
+
end
|
71
|
+
elsif del
|
72
|
+
if((idx = str.index(del)))
|
73
|
+
cap = str[0...idx]
|
74
|
+
else
|
75
|
+
cap = str
|
76
|
+
end
|
77
|
+
else
|
78
|
+
cap = str
|
79
|
+
end
|
80
|
+
|
81
|
+
cap = cap[0...max] if max
|
82
|
+
while cap.size < min
|
83
|
+
cap << pad
|
84
|
+
end
|
85
|
+
|
86
|
+
# must work on a dup of str here or @value may get clobbered by str.slice!
|
87
|
+
@value = uni ? cap.to_ascii : cap.dup
|
88
|
+
|
89
|
+
fin = -1
|
90
|
+
mod = nul ? 1 : 0
|
91
|
+
|
92
|
+
str.slice! 0, (cap.size + mod)
|
93
|
+
return str
|
94
|
+
end
|
95
|
+
|
96
|
+
# As per Parsel, write the string
|
97
|
+
def to_s(off=nil)
|
98
|
+
@rendered_offset = off || 0
|
99
|
+
|
100
|
+
min = resolve(@min)
|
101
|
+
max = resolve(@max)
|
102
|
+
uni = resolve(@unicode)
|
103
|
+
val = (resolve(@value) || "").to_s
|
104
|
+
val = val.clone # gross!
|
105
|
+
pad = resolve(@padding)
|
106
|
+
nul = resolve(@nul_terminated)
|
107
|
+
pto = resolve(@pad_to)
|
108
|
+
|
109
|
+
val << "\x00" if nul and val[-1] != 0
|
110
|
+
|
111
|
+
val = val.to_utf16 if uni
|
112
|
+
|
113
|
+
while min and val.size < min
|
114
|
+
val << pad
|
115
|
+
end
|
116
|
+
|
117
|
+
if pto
|
118
|
+
while ((val.size % pto) != 0) # this is some shameful shit right here
|
119
|
+
val << pad
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
val = val[0...max] if max
|
124
|
+
|
125
|
+
if off
|
126
|
+
return val, off + val.size
|
127
|
+
else
|
128
|
+
return val
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
## ---------------------------------------------------------
|
134
|
+
|
135
|
+
class Asciiz < Str
|
136
|
+
def initialize(opts={})
|
137
|
+
opts[:nul_terminated] ||= true
|
138
|
+
super(opts)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
## ---------------------------------------------------------
|
143
|
+
|
144
|
+
class Unicode < Str
|
145
|
+
def initialize(opts={})
|
146
|
+
opts[:unicode] ||= true
|
147
|
+
super(opts)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
## ---------------------------------------------------------
|
152
|
+
|
153
|
+
class Unicodez < Unicode
|
154
|
+
def initialize(opts={})
|
155
|
+
opts[:nul_terminated] ||= true
|
156
|
+
super(opts)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
Uniz = Unicodez # XXX compat
|
160
|
+
|
161
|
+
## ---------------------------------------------------------
|
162
|
+
|
163
|
+
end
|
164
|
+
|
@@ -0,0 +1,263 @@
|
|
1
|
+
# === Structures are blobs with symbol tables.
|
2
|
+
#
|
3
|
+
|
4
|
+
Dir[File.expand_path("#{File.dirname(__FILE__)}/structure/*.rb")].each do |file|
|
5
|
+
require file
|
6
|
+
end
|
7
|
+
|
8
|
+
module Ruckus
|
9
|
+
# A Ruckus::Structure wraps a Ruckus::Blob, giving each of the fields
|
10
|
+
# a name. Additionally, Structure has classmethod shorthand for DSL-style
|
11
|
+
# descriptions of frame/packet formats; see below.
|
12
|
+
#
|
13
|
+
# Extend Structure by subclassing. Inside the subclass definition,
|
14
|
+
# declare fields, like:
|
15
|
+
#
|
16
|
+
# class Foo < Structure
|
17
|
+
# number :width => 32, :endian => :little
|
18
|
+
# str :size => 10
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# Structure catches classmethod calls and converts them to
|
22
|
+
# requests to add Parsels to the structure definition.
|
23
|
+
#
|
24
|
+
# You can inherit indefinitely (each subclass inherits the parent
|
25
|
+
# class fields), and you can (obvious) nest --- fields are just
|
26
|
+
# parsels.
|
27
|
+
#
|
28
|
+
class Structure < Parsel
|
29
|
+
include StructureInitializers
|
30
|
+
include StructureAllowFieldReplacement
|
31
|
+
include StructureDefaultValues
|
32
|
+
include StructureAtCreate
|
33
|
+
include StructureBeforeCallbacks
|
34
|
+
include StructureProxies
|
35
|
+
include StructureSearchModules
|
36
|
+
|
37
|
+
class_inheritable_array :templates
|
38
|
+
class_inheritable_hash :structure_field_names
|
39
|
+
|
40
|
+
(class << self;self;end).class_eval {
|
41
|
+
include StructureRelateDeclaration
|
42
|
+
include StructureValidateField
|
43
|
+
include StructureDetectFactory
|
44
|
+
include StructureFixupFieldNames
|
45
|
+
|
46
|
+
def class_method_missing_hook(meth, *args); super; end
|
47
|
+
def structure_field_def_hook(*args); super; end
|
48
|
+
|
49
|
+
# Rules for converting classmethod calls to types:
|
50
|
+
# 1. Convert to uppercase
|
51
|
+
# 2. If last arg is an opts hash, pull :from, use as module
|
52
|
+
# 3. Otherwise, use Ruckus as the module
|
53
|
+
# 4. Look up the uppercased name in the module
|
54
|
+
# 5. Call #new on it (when the object is instantiated)
|
55
|
+
#
|
56
|
+
def method_missing(meth, *args)
|
57
|
+
begin
|
58
|
+
raise "method_missing is recursing" if @mm_locked
|
59
|
+
@mm_locked = true
|
60
|
+
return if not class_method_missing_hook(meth, *args)
|
61
|
+
|
62
|
+
if args[-1].kind_of? Hash
|
63
|
+
mod = args[-1][:from]
|
64
|
+
end
|
65
|
+
|
66
|
+
structure_field_def_hook args
|
67
|
+
# XXX no good hook for this
|
68
|
+
mod ||= derive_search_module
|
69
|
+
|
70
|
+
begin
|
71
|
+
klass = mod.const_get(meth.to_s.class_name)
|
72
|
+
rescue
|
73
|
+
raise "can't find \"#{ meth.to_s.class_name }\" in the default module"
|
74
|
+
end
|
75
|
+
|
76
|
+
add(klass, *args)
|
77
|
+
rescue => e
|
78
|
+
raise "you called \"#{ meth.to_s }\" on a #{ self.to_s }, and we don't know how to handle that"
|
79
|
+
ensure
|
80
|
+
@mm_locked = false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
}
|
84
|
+
|
85
|
+
# If you have an actual class reference, you can just pass
|
86
|
+
# it to <tt>add</tt>, as in:
|
87
|
+
#
|
88
|
+
# add(HeaderStructure, :name => :header)
|
89
|
+
#
|
90
|
+
def self.add(*args)
|
91
|
+
raise "no class" if not args[0]
|
92
|
+
|
93
|
+
write_inheritable_array :templates, [[args[0], args[1..-1]]]
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
def template_decoder_ring(t)
|
98
|
+
# Gross. Fields normally take a first argument, a symbol,
|
99
|
+
# specifying the name, and then an opts hash. They can
|
100
|
+
# also just take an options hash, in which case we expect
|
101
|
+
# the name to be in the hash as :name. Extra fun: you
|
102
|
+
# don't have to name every field, and things will still work.
|
103
|
+
#
|
104
|
+
if t[1][0].kind_of? Symbol and (not t[1][1] || t[1][1].kind_of?(Hash))
|
105
|
+
t[1][1] ||= {}
|
106
|
+
t[1][1][:name] = (name = t[1][0])
|
107
|
+
t[1] = [t[1][1]]
|
108
|
+
elsif t[1][0].kind_of? Hash
|
109
|
+
name = t[1][0][:name]
|
110
|
+
end
|
111
|
+
return name
|
112
|
+
end
|
113
|
+
|
114
|
+
def template_entry_added_hook(*args); super *args; end
|
115
|
+
def final_initialization_hook(*args); super *args; end
|
116
|
+
|
117
|
+
public
|
118
|
+
# No special options yet. A structure is just a parsel; pass
|
119
|
+
# options through to the parent class.
|
120
|
+
#f
|
121
|
+
def initialize(opts={})
|
122
|
+
|
123
|
+
# A Structure is really just a Blob with extra goop
|
124
|
+
@value = Blob.new
|
125
|
+
@value.parent = self
|
126
|
+
|
127
|
+
# Most of the goop is for figuring out what fields to
|
128
|
+
# add, with what arguments, given where we are in the
|
129
|
+
# inheritance hierarchy.
|
130
|
+
|
131
|
+
template = self.class.templates
|
132
|
+
|
133
|
+
# If this is the first time we're seeing this definition,
|
134
|
+
# we also need to convert field names into blob offsets.
|
135
|
+
pop = false
|
136
|
+
if not self.class.structure_field_names
|
137
|
+
self.class.write_inheritable_hash :structure_field_names, {}
|
138
|
+
pop = true
|
139
|
+
end
|
140
|
+
|
141
|
+
template.each do |t|
|
142
|
+
# do some rewriting to support an old style of declaring
|
143
|
+
# fields that we supported like 6 months ago.
|
144
|
+
name = template_decoder_ring(t)
|
145
|
+
|
146
|
+
# index the field name if this is the first time we've
|
147
|
+
# ever instantiated this kind of structure, and the field
|
148
|
+
# is valid
|
149
|
+
self.class.structure_field_names[name] = @value.count if (name and pop)
|
150
|
+
|
151
|
+
begin
|
152
|
+
# create the structure field object, parent it
|
153
|
+
klass, args = [t[0], t[1]]
|
154
|
+
obj = klass.new(*args)
|
155
|
+
obj.parent = @value
|
156
|
+
|
157
|
+
template_entry_added_hook(obj) || @value << obj
|
158
|
+
rescue
|
159
|
+
pp t
|
160
|
+
raise
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
opts.each do |k, v|
|
165
|
+
if self.class.structure_field_names.has_key? k
|
166
|
+
raise "attempting to assign field name as option; use with_{field_name} instead"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
super(opts)
|
171
|
+
|
172
|
+
final_initialization_hook
|
173
|
+
end
|
174
|
+
|
175
|
+
# Return a field (the Parsel object) by offset into the
|
176
|
+
# Structure or by name lookup
|
177
|
+
#
|
178
|
+
def [](k)
|
179
|
+
k = k.intern if k.kind_of? String
|
180
|
+
if k.kind_of? Symbol
|
181
|
+
@value[self.class.structure_field_names[k]]
|
182
|
+
else
|
183
|
+
@value[k]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# Assign to a field. You can pass scalar types and they'll
|
188
|
+
# be converted, so struct.name = "foo" works.
|
189
|
+
#
|
190
|
+
def []=(k, v)
|
191
|
+
k = k.intern if k.kind_of? String
|
192
|
+
if k.kind_of? Symbol
|
193
|
+
@value[self.class.structure_field_names[k]] = v
|
194
|
+
else
|
195
|
+
@value[k] = v
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Easy --- delegate to blob
|
200
|
+
#
|
201
|
+
def capture(str)
|
202
|
+
@value.capture(str)
|
203
|
+
end
|
204
|
+
|
205
|
+
def before_render_hook(*args); super(*args); end
|
206
|
+
|
207
|
+
# Easy --- delegate to blob
|
208
|
+
#
|
209
|
+
def to_s(off=nil)
|
210
|
+
before_render_hook
|
211
|
+
@rendered_offset = off || 0
|
212
|
+
@value.to_s(off)
|
213
|
+
end
|
214
|
+
|
215
|
+
def method_missing_hook(meth, *args); super; end
|
216
|
+
|
217
|
+
# A la openstruct/struct --- method calls can be references
|
218
|
+
# to field names.
|
219
|
+
#
|
220
|
+
def method_missing(meth, *args)
|
221
|
+
return if not method_missing_hook(meth, args)
|
222
|
+
|
223
|
+
d = self.class.structure_field_names
|
224
|
+
m = meth.to_s
|
225
|
+
|
226
|
+
setter = (m[-1].chr == "=") ? true : false
|
227
|
+
m = m[0..-2] if setter
|
228
|
+
|
229
|
+
puts "WARNING: assignment to @value as struct field" if setter and m == "value"
|
230
|
+
|
231
|
+
if (i = d[m.intern])
|
232
|
+
if setter
|
233
|
+
self[m.intern] = args[0]
|
234
|
+
else
|
235
|
+
self[m.intern]
|
236
|
+
end
|
237
|
+
else
|
238
|
+
super(meth, *args)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def self.with_args(*args, &block)
|
243
|
+
if args[0].kind_of? Hash
|
244
|
+
name = nil
|
245
|
+
opts = args[0]
|
246
|
+
else
|
247
|
+
name = args[0]
|
248
|
+
opts = args[1]
|
249
|
+
end
|
250
|
+
|
251
|
+
opts ||= {}
|
252
|
+
block.call(name, opts)
|
253
|
+
end
|
254
|
+
|
255
|
+
def each_field
|
256
|
+
@value.each {|f| yield f.name, f}
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# Read me for the current list of field definitions shortcuts.
|
262
|
+
load File.dirname(__FILE__) + '/structure_shortcuts.rb'
|
263
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Ruckus
|
2
|
+
module StructureAtCreate
|
3
|
+
def self.included(klass)
|
4
|
+
klass.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def at_create(arg=nil, &block)
|
9
|
+
if not block_given?
|
10
|
+
raise "need a callback function" if not arg
|
11
|
+
arg = arg.intern if not arg.kind_of? Symbol
|
12
|
+
block = lambda { send(arg) }
|
13
|
+
end
|
14
|
+
|
15
|
+
self.initializers << block
|
16
|
+
end
|
17
|
+
|
18
|
+
def override(field, val)
|
19
|
+
at_create { self[field] = val }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|