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,31 @@
|
|
1
|
+
# instead of dealing with .value directly, this class exposes 'set' and
|
2
|
+
# 'display' methods for handling @value as a mac address string.
|
3
|
+
class MacAddr < Ruckus::Str
|
4
|
+
def initialize(opts={})
|
5
|
+
opts[:size] = 6
|
6
|
+
super(opts)
|
7
|
+
@width = 48
|
8
|
+
end
|
9
|
+
|
10
|
+
# mac address may be specified as either
|
11
|
+
# aa-bb-cc-dd-ee-ff
|
12
|
+
# or
|
13
|
+
# aa:bb:cc:dd:ee:ff
|
14
|
+
def set(val)
|
15
|
+
unless m=/^([0-9a-f]{2})([:-]?)([0-9a-f]{2})\2([0-9a-f]{2})\2([0-9a-f]{2})\2([0-9a-f]{2})\2([0-9a-f]{2})$/i.match(val)
|
16
|
+
raise "argument must be a mac_addr string as in aa:bb:cc:dd:ee:ff"
|
17
|
+
end
|
18
|
+
self.value = [$1,$3,$4,$5,$6,$7].join('').dehexify
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def display
|
23
|
+
self.value.split('').map {|x| x.hexify}.join(':')
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_human(indent=nil)
|
27
|
+
indent ||= ""
|
28
|
+
"#{indent}#{name} = #{self.display}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,318 @@
|
|
1
|
+
# === Mutator is a half-assed fuzzing framework intended to snap in to Ruckus.
|
2
|
+
# Mutator provides a decorator-style interface to fuzzed integers and strings,
|
3
|
+
# so that you can chain them to get strings that grow and include metacharacters
|
4
|
+
# with X probability, etc.
|
5
|
+
#
|
6
|
+
# There are two core objects in here:
|
7
|
+
# * <tt>Mutator</tt> is a base type, like a String or an Integer, wrapped
|
8
|
+
# in an object that provides a <tt>permute</tt> method to change the
|
9
|
+
# value, and a stack of <tt>Modifier</tt> objects.
|
10
|
+
# * <tt>Modifier</tt> is an object that takes a value and changes it
|
11
|
+
# to do evil. Modifiers chain.
|
12
|
+
#
|
13
|
+
# This stuff is really kind of an afterthought; the real work in building
|
14
|
+
# fuzzer is getting the encoding right, so you can reach more of the target
|
15
|
+
# code. Ruckus hides the details of how strings and integers are actually
|
16
|
+
# encoded, so most of the "fuzzing" part just comes down to making evil
|
17
|
+
# looking strings. The Parsel framework takes care of encoding those evil
|
18
|
+
# strings properly so that a CIFS stack will read them.
|
19
|
+
#
|
20
|
+
module Ruckus
|
21
|
+
module Mutator
|
22
|
+
|
23
|
+
# Create me with a String, and I wrap the string, forwarding method
|
24
|
+
# invocations to it. Call "permute" and I'll run through my Modifier
|
25
|
+
# chain to derive a new value (for instance, tripling the number of
|
26
|
+
# characters).
|
27
|
+
#
|
28
|
+
class Mutator
|
29
|
+
|
30
|
+
# <tt>base</tt> is usually a String (like "helu") or Fixnum.
|
31
|
+
# Stack is a set of modifier objects (or class names, if you
|
32
|
+
# just want to use the defaults). A Mutator with an empty
|
33
|
+
# Modifier stack doesn't do anything and behaves just like an
|
34
|
+
# ordinary String or Fixnum.
|
35
|
+
#
|
36
|
+
def initialize(base=nil, stack=[])
|
37
|
+
@base = base
|
38
|
+
@cur = @base
|
39
|
+
@stack = stack.map do |o|
|
40
|
+
o = o.new if o.kind_of? Class
|
41
|
+
o
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def method_missing(meth, *args)
|
46
|
+
@cur.send(meth, *args)
|
47
|
+
end
|
48
|
+
|
49
|
+
# A fuzzer clock tick; mess up the enclosed value.
|
50
|
+
#
|
51
|
+
def permute
|
52
|
+
@cur = @stack.inject(@cur) {|cur, mod| mod << cur}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# The guts; each Modifier class implements some way of screwing
|
57
|
+
# with a value to catch bugs. Modifiers are all created with
|
58
|
+
# keyword args; the base class catches:
|
59
|
+
# now:: true/false (def: false) fire immediately, just once,
|
60
|
+
# irrespective of probability, even if probability is
|
61
|
+
# provided.
|
62
|
+
# prob:: (def: 100) pctg chance this modifier will fire on this
|
63
|
+
# tick
|
64
|
+
# max_steps:: number of times to fire this modifier before it stops
|
65
|
+
# and just starts acting like a no-op. -1 equals no max
|
66
|
+
#
|
67
|
+
class Modifier
|
68
|
+
def initialize(opts={})
|
69
|
+
@now = opts[:now] || false
|
70
|
+
@prob = opts[:prob] || 100
|
71
|
+
@max_steps = opts[:max_steps] || 700
|
72
|
+
@cur = 0
|
73
|
+
@opts = opts
|
74
|
+
end
|
75
|
+
|
76
|
+
# Should this fire, based on prob?
|
77
|
+
#
|
78
|
+
def go?
|
79
|
+
if @now
|
80
|
+
@now = false
|
81
|
+
return true
|
82
|
+
end
|
83
|
+
rand(100) < @prob
|
84
|
+
end
|
85
|
+
|
86
|
+
# Base class stub does nothing. Subclasses override this method
|
87
|
+
# to implement logic. Callers don't use this method, though: they
|
88
|
+
# use operator<<.
|
89
|
+
#
|
90
|
+
def mod(x); x; end
|
91
|
+
|
92
|
+
# This is what callers call. Subclasses do not override this
|
93
|
+
# method. This implements probability and max-steps. How it
|
94
|
+
# looks:
|
95
|
+
#
|
96
|
+
# str = modifier << str
|
97
|
+
#
|
98
|
+
def <<(x)
|
99
|
+
return x if (@cur += 1) > @max_steps && @max_steps != -1
|
100
|
+
return x if not go?
|
101
|
+
mod(x)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Geometrically increases size.
|
106
|
+
#
|
107
|
+
# A
|
108
|
+
# AA
|
109
|
+
# AAAA
|
110
|
+
# AAAAAAAA ... etc
|
111
|
+
#
|
112
|
+
class Multiplier < Modifier
|
113
|
+
|
114
|
+
# Takes:
|
115
|
+
# multiplier:: (def: 2) how much to multiply by.
|
116
|
+
#
|
117
|
+
def initialize(opts={})
|
118
|
+
@step = opts[:multiplier] || 2
|
119
|
+
super
|
120
|
+
end
|
121
|
+
|
122
|
+
def mod(x)
|
123
|
+
x * @step
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Adds fixed amounts of data.
|
128
|
+
#
|
129
|
+
# A
|
130
|
+
# AA
|
131
|
+
# AAA
|
132
|
+
# AAAA ... etc
|
133
|
+
#
|
134
|
+
class Adder < Modifier
|
135
|
+
|
136
|
+
# Takes:
|
137
|
+
# base:: (def: "A") what to add
|
138
|
+
# step:: (def: 100) how much of this to add at each step
|
139
|
+
#
|
140
|
+
def initialize(opts={})
|
141
|
+
@base = opts[:base] || "A"
|
142
|
+
@step = opts[:step] || 100
|
143
|
+
super
|
144
|
+
end
|
145
|
+
|
146
|
+
def mod(x)
|
147
|
+
if x.kind_of? String
|
148
|
+
x + (@base * @step)
|
149
|
+
else
|
150
|
+
x + @step
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Path traversal metacharacters and keywords, cycled. Add new
|
156
|
+
# ones to the STRINGS array.
|
157
|
+
#
|
158
|
+
class PathTraversal < Modifier
|
159
|
+
STRINGS = [ "etc/passwd",
|
160
|
+
"etc/passwd\x00",
|
161
|
+
"etc/passwd%00",
|
162
|
+
"boot.ini",
|
163
|
+
"boot.ini\x00",
|
164
|
+
"boot.ini%00" ]
|
165
|
+
def mod(x)
|
166
|
+
x = x + ("../" * (@cur + 1)) + STRINGS[@cur % STRINGS.size]
|
167
|
+
if (@cur % 2) == 0
|
168
|
+
x.gsub!("/", "\\")
|
169
|
+
end
|
170
|
+
return x
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# The format strings, cycled. Add new ones to the STRINGS array.
|
175
|
+
#
|
176
|
+
class FormatStrings < Modifier
|
177
|
+
STRINGS = [ "%n", "%25n", "%s", "%x" ]
|
178
|
+
|
179
|
+
def mod(x)
|
180
|
+
x + STRINGS[@cur % STRINGS.size]
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# The most likely evil metachars, but if you're thorough, you
|
185
|
+
# just try all non-isalnums.
|
186
|
+
#
|
187
|
+
class Metacharacters < Modifier
|
188
|
+
STRINGS = [ ".", "/", "\\", "$", "-", "%", "$", ";",
|
189
|
+
"'", '"', "*", "\x00" ]
|
190
|
+
|
191
|
+
def mod
|
192
|
+
x = x + STRINGS[@cur % STRINGS.size]
|
193
|
+
if opts[:hexify]
|
194
|
+
0.upto(x.size - 1) do |i|
|
195
|
+
x[i] = "%#{ x[i].to_s(16) }"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
return x
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# Things that will break SQL queries.
|
203
|
+
#
|
204
|
+
class SQLStrings < Modifier
|
205
|
+
STRINGS = [ "'sql0", "+sql1", "sql2;", "sql3 ;--", "(sql4)" ]
|
206
|
+
|
207
|
+
def mod
|
208
|
+
x + STRINGS[@cur % STRINGS.size]
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# Trivial XSS tickler.
|
213
|
+
#
|
214
|
+
class XSS < Modifier
|
215
|
+
def mod
|
216
|
+
x + "<script>alert(document.location);</script>"
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# Generate random numbers
|
221
|
+
#
|
222
|
+
class Random < Modifier
|
223
|
+
def initialize(opts={})
|
224
|
+
srand((@seed = opts[:seed])) if opts[:seed]
|
225
|
+
super
|
226
|
+
@max = opts[:max] || 0xFFFFFFFF
|
227
|
+
if opts[:width]
|
228
|
+
@max = Numeric.mask_for(opts[:width])
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def mod(i)
|
233
|
+
rand(@max)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# Randomly sets the top bit of each byte, turning ASCII into
|
238
|
+
# hi-ASCII.
|
239
|
+
#
|
240
|
+
class Hibit < Modifier
|
241
|
+
def initialize(opts={})
|
242
|
+
opts[:prob] ||= 50
|
243
|
+
@width = opts[:width] || 32
|
244
|
+
super
|
245
|
+
end
|
246
|
+
|
247
|
+
def mod(x)
|
248
|
+
if x.kind_of? String
|
249
|
+
0.upto(x.size - 1) do |i|
|
250
|
+
x[i] |= 0x80 if go?
|
251
|
+
end
|
252
|
+
else
|
253
|
+
x |= (0x80 << (@width - 8))
|
254
|
+
end
|
255
|
+
return x
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Cache the starting value (this is meant to the be first modifier
|
260
|
+
# in the chain, if you're using it) and randomly reset the string
|
261
|
+
# back to that starting value.
|
262
|
+
#
|
263
|
+
class Reset < Modifier
|
264
|
+
def initialize(opts={})
|
265
|
+
opts[:prob] ||= 25
|
266
|
+
super(opts.merge(:now => true))
|
267
|
+
end
|
268
|
+
|
269
|
+
def mod(x)
|
270
|
+
(@orig ||= x.clone).clone
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# Randomize a string.
|
275
|
+
#
|
276
|
+
class Randomizer < Modifier
|
277
|
+
def mod(x)
|
278
|
+
0.upto(x.size-1) do |i|
|
279
|
+
x[i] = rand(0xff)
|
280
|
+
end
|
281
|
+
return x
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# Wrap Number with Mutator, make to_i work.
|
286
|
+
#
|
287
|
+
class Number < Mutator
|
288
|
+
def initialize(base=0, stack=[]); super; end
|
289
|
+
def to_i; @cur.to_i; end
|
290
|
+
end
|
291
|
+
|
292
|
+
# Wrap String with Mutator, make to_s work.
|
293
|
+
#
|
294
|
+
class Str < Mutator
|
295
|
+
def initialize(base="", stack=[]); super; end
|
296
|
+
def to_s(off=nil); @cur.to_s; end
|
297
|
+
end
|
298
|
+
|
299
|
+
class << self
|
300
|
+
def random_int(opts={})
|
301
|
+
Number.new(1, [Random.new(opts)])
|
302
|
+
end
|
303
|
+
|
304
|
+
def r8; random_int(:width => 8); end
|
305
|
+
def r16; random_int(:width => 16); end
|
306
|
+
def r32; random_int(:width => 32); end
|
307
|
+
def r64; random_int(:width => 64); end
|
308
|
+
|
309
|
+
def grostring(base="A", opts={})
|
310
|
+
Str.new(base, [Reset, Adder.new(opts)])
|
311
|
+
end
|
312
|
+
|
313
|
+
def randstr(base="A")
|
314
|
+
Str.new(base, [Reset, Randomizer])
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
data/lib/ruckus/null.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Ruckus
|
2
|
+
class Null < Parsel
|
3
|
+
def initialize(opts={})
|
4
|
+
super(opts)
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_s(off=nil)
|
8
|
+
@rendered_offset = off || 0
|
9
|
+
if off
|
10
|
+
return "", off
|
11
|
+
else
|
12
|
+
return ""
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def capture(str)
|
17
|
+
return str
|
18
|
+
end
|
19
|
+
|
20
|
+
def size
|
21
|
+
0
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,360 @@
|
|
1
|
+
# === Most things you want to render are Numbers
|
2
|
+
|
3
|
+
module Ruckus
|
4
|
+
# A Ruckus::Number is a type of Parsel that wraps an Integer.
|
5
|
+
class Number < Parsel
|
6
|
+
attr_accessor :width, :endian, :radix, :pad, :ascii
|
7
|
+
attr_accessor :value
|
8
|
+
|
9
|
+
|
10
|
+
# Options:
|
11
|
+
# width:: (Default: 32) Width in bits --- can be odd!
|
12
|
+
# endian:: (Default: :little) Endianness --- not honored for
|
13
|
+
# odd widths
|
14
|
+
# value:: Usually a fixnum, but can be a method to call on
|
15
|
+
# sibling Parsel, e.g. :size for "Length" fields.
|
16
|
+
#
|
17
|
+
# Note: odd-width fields must be parented (see Blob or Structure)
|
18
|
+
# to render or capture their values
|
19
|
+
#
|
20
|
+
def initialize(opts={})
|
21
|
+
opts[:width] ||= 32
|
22
|
+
opts[:endian] ||= :little
|
23
|
+
# opts[:value] ||= 0xABADCAFE
|
24
|
+
opts[:value] ||= 0
|
25
|
+
opts[:radix] ||= 0
|
26
|
+
|
27
|
+
if opts[:endian] == :native
|
28
|
+
opts[:endian] = Parsel.endian?
|
29
|
+
end
|
30
|
+
super(opts)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def widthcode
|
36
|
+
case @width
|
37
|
+
when 8; code = "C"
|
38
|
+
when 16; code = "S"
|
39
|
+
when 32; code = "I"
|
40
|
+
when 64; code = "Q"
|
41
|
+
else
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
public
|
47
|
+
|
48
|
+
# this is some seriously weak shit down here
|
49
|
+
|
50
|
+
# Is this an odd-width (single bit, nibble, etc) number?
|
51
|
+
#
|
52
|
+
def odd_width? ; not widthcode ; end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def span_start
|
57
|
+
i_am = where_am_i?
|
58
|
+
first = i_am
|
59
|
+
(i_am - 1).downto(0) do |i|
|
60
|
+
begin
|
61
|
+
break if not parent[i].odd_width?
|
62
|
+
rescue
|
63
|
+
break
|
64
|
+
end
|
65
|
+
first = i
|
66
|
+
end
|
67
|
+
return first
|
68
|
+
end
|
69
|
+
|
70
|
+
public
|
71
|
+
|
72
|
+
# For odd-width fields --- find how big the span of neighboring odd-width
|
73
|
+
# fields is, in bits.
|
74
|
+
#
|
75
|
+
def span_bits
|
76
|
+
last = where_am_i?
|
77
|
+
span_start.upto(parent.count - 1) do |i|
|
78
|
+
break if not parent[i].respond_to? :odd_width?
|
79
|
+
break if not parent[i].odd_width?
|
80
|
+
last = i
|
81
|
+
end
|
82
|
+
|
83
|
+
tot = 0
|
84
|
+
span_start.upto(last) { |i| tot += parent[i].width }
|
85
|
+
tot
|
86
|
+
end
|
87
|
+
|
88
|
+
# Where are we in the span of neighboring odd-width fields?
|
89
|
+
#
|
90
|
+
def span_offset
|
91
|
+
tot = 0
|
92
|
+
span_start.upto(where_am_i? - 1) {|i| tot += parent[i].width}
|
93
|
+
tot
|
94
|
+
end
|
95
|
+
|
96
|
+
# Given: a string, return: the remainder of the string after
|
97
|
+
# parsing the number, side-effect: populate @value
|
98
|
+
#
|
99
|
+
def capture(str)
|
100
|
+
return ascii_capture(str) if @ascii
|
101
|
+
return odd_width_capture(str) if not (code = widthcode)
|
102
|
+
|
103
|
+
incomplete! if str.size < size
|
104
|
+
cap = str.shift(size)
|
105
|
+
cap = cap.reverse if not Parsel.native?(@endian)
|
106
|
+
@value = cap.unpack(code).first
|
107
|
+
return str
|
108
|
+
end
|
109
|
+
|
110
|
+
BASERX = {
|
111
|
+
2 => /([01]+)/,
|
112
|
+
8 => /([0-7]+)/,
|
113
|
+
10 => /([0-9]+)/,
|
114
|
+
16 => /([0-9a-fA-F]+)/
|
115
|
+
}
|
116
|
+
|
117
|
+
def ascii_capture(str)
|
118
|
+
incomplete! if str.empty?
|
119
|
+
|
120
|
+
# weak
|
121
|
+
if (rad = resolve(@radix)) == 0
|
122
|
+
if str.starts_with? "0x"
|
123
|
+
rad = 16
|
124
|
+
str.shift 2
|
125
|
+
else
|
126
|
+
rad = 10
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
if(str =~ BASERX[rad])
|
131
|
+
@value = $1.to_i(rad)
|
132
|
+
str.shift($1.size)
|
133
|
+
else
|
134
|
+
@value = 0
|
135
|
+
end
|
136
|
+
|
137
|
+
return str
|
138
|
+
end
|
139
|
+
|
140
|
+
# Quick hack: true is 1, false is 0, nil is 0.
|
141
|
+
#
|
142
|
+
def resolve(x)
|
143
|
+
r = super
|
144
|
+
r = 1 if r == true
|
145
|
+
r = 0 if r == false
|
146
|
+
r = 0 if r == nil
|
147
|
+
return r
|
148
|
+
end
|
149
|
+
|
150
|
+
# Render this number (or, if odd-width, this span of odd-width
|
151
|
+
# values, if we're the first element of the span) as a bit string.
|
152
|
+
#
|
153
|
+
def to_s(off=nil)
|
154
|
+
@rendered_offset = off || 0
|
155
|
+
|
156
|
+
begin
|
157
|
+
if @ascii
|
158
|
+
r = ascii_to_s
|
159
|
+
elsif not (code = widthcode)
|
160
|
+
r = odd_width_to_s
|
161
|
+
else
|
162
|
+
val = resolve(@value).to_i
|
163
|
+
r = [val].pack(code)
|
164
|
+
r = r.reverse if @endian != Parsel.endian?
|
165
|
+
end
|
166
|
+
|
167
|
+
if off
|
168
|
+
return r, off+r.size
|
169
|
+
else
|
170
|
+
return r
|
171
|
+
end
|
172
|
+
rescue
|
173
|
+
raise
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def ascii_to_s
|
178
|
+
if((rad = resolve(@radix)) == 0)
|
179
|
+
rad = 10
|
180
|
+
end
|
181
|
+
|
182
|
+
pad = resolve(@pad) || 0
|
183
|
+
@value.to_s(rad).rjust(pad, "0")
|
184
|
+
end
|
185
|
+
|
186
|
+
# Render a span of odd-width numbers as a byte string
|
187
|
+
#
|
188
|
+
def odd_width_to_s
|
189
|
+
return "" if not odd_width_first?
|
190
|
+
|
191
|
+
acc = 0
|
192
|
+
tot = 0
|
193
|
+
bits = span_bits
|
194
|
+
|
195
|
+
# Cheat horribly: Ruby has bignums, so just treat the whole
|
196
|
+
# span as one giant bignum and math it into place.
|
197
|
+
|
198
|
+
where_am_i?.upto(parent.count - 1) do |i|
|
199
|
+
break if not parent[i].respond_to? :odd_width?
|
200
|
+
break if not parent[i].odd_width?
|
201
|
+
acc <<= parent[i].width
|
202
|
+
tot += parent[i].width
|
203
|
+
acc |= parent[i].resolve(parent[i].value) & Numeric.mask_for(parent[i].width)
|
204
|
+
end
|
205
|
+
acc <<= 8 - (tot % 8) if (tot % 8) != 0
|
206
|
+
|
207
|
+
# Dump the bignum to a binary string
|
208
|
+
|
209
|
+
ret = ""
|
210
|
+
while bits > 0
|
211
|
+
ret << (acc & 0xff).chr
|
212
|
+
acc >>= 8
|
213
|
+
bits -= 8
|
214
|
+
end
|
215
|
+
|
216
|
+
ret.reverse! if @endian == :big
|
217
|
+
return ret
|
218
|
+
end
|
219
|
+
|
220
|
+
# Are we the first odd-width number in the span?
|
221
|
+
#
|
222
|
+
def odd_width_first?
|
223
|
+
return true if odd_width? and where_am_i? == 0
|
224
|
+
begin
|
225
|
+
not parent[where_am_i? - 1].odd_width?
|
226
|
+
rescue
|
227
|
+
true
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# Capture a whole span of odd-width numbers, see capture
|
232
|
+
#
|
233
|
+
def odd_width_capture(str)
|
234
|
+
return str if not odd_width_first?
|
235
|
+
|
236
|
+
incomplete! if str.size < (bytes_for_bits = Parsel.bytes_for_bits(span_bits))
|
237
|
+
|
238
|
+
cap = str.shift(bytes_for_bits)
|
239
|
+
|
240
|
+
acc = 0
|
241
|
+
cap.each_byte do |b|
|
242
|
+
acc <<= 8
|
243
|
+
acc |= b
|
244
|
+
end
|
245
|
+
tbits = cap.size * 8
|
246
|
+
tot = 0
|
247
|
+
where_am_i?.upto(parent.count - 1) do |i|
|
248
|
+
break if not parent[i].respond_to? :odd_width?
|
249
|
+
break if not parent[i].odd_width?
|
250
|
+
tot += parent[i].width
|
251
|
+
parent[i].value = (acc >> (tbits - tot)) & Numeric.mask_for(parent[i].width)
|
252
|
+
end
|
253
|
+
|
254
|
+
return str
|
255
|
+
end
|
256
|
+
|
257
|
+
# How big is the encoded number?
|
258
|
+
#
|
259
|
+
def size
|
260
|
+
if not odd_width?
|
261
|
+
s = Parsel.bytes_for_bits(@width);
|
262
|
+
elsif odd_width_first?
|
263
|
+
s = span_bits * 8
|
264
|
+
else
|
265
|
+
s = 0
|
266
|
+
end
|
267
|
+
s
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
Num = Number.clone
|
272
|
+
|
273
|
+
## ---------------------------------------------------------
|
274
|
+
## XXX DRY, refactoring in process
|
275
|
+
##
|
276
|
+
## Moved this from classmethods to classes so we can encode
|
277
|
+
## more into the type, useful for selectors
|
278
|
+
|
279
|
+
class Le16 < Number
|
280
|
+
def initialize(opts={}); super(opts.merge(:width => 16, :endian => :little))
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# For no good reason, "halfwords" are little endian, but "shorts" are
|
285
|
+
# network byte order. You're welcome.
|
286
|
+
|
287
|
+
H16, Uint16le, Half, Halfword = [ Le16.clone, Le16.clone, Le16.clone, Le16.clone ]
|
288
|
+
|
289
|
+
class Le32 < Number
|
290
|
+
def initialize(opts={}); super(opts.merge(:width => 32, :endian => :little))
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
H32 = Le32.clone
|
295
|
+
Uint32le = Le32.clone
|
296
|
+
|
297
|
+
class Le64 < Number
|
298
|
+
def initialize(opts={}); super(opts.merge(:width => 64, :endian => :little))
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
H64 = Le64.clone
|
303
|
+
Uint64le = Le64.clone
|
304
|
+
|
305
|
+
class Be16 < Number
|
306
|
+
def initialize(opts={}); super(opts.merge(:width => 16, :endian => :big))
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
N16, Uint16be, Short = [Be16.clone, Be16.clone, Be16.clone]
|
311
|
+
|
312
|
+
class Be32 < Number
|
313
|
+
def initialize(opts={}); super(opts.merge(:width => 32, :endian => :big))
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
N32 = Be32.clone
|
318
|
+
Uint32be = Be32.clone
|
319
|
+
|
320
|
+
class Be64 < Number
|
321
|
+
def initialize(opts={}); super(opts.merge(:width => 64, :endian => :big))
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
N64 = Be64.clone
|
326
|
+
Uint64be = Be64.clone
|
327
|
+
|
328
|
+
class Byte < Number
|
329
|
+
def initialize(opts={}); super(opts.merge(:width => 8))
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
Le8, Be8, N8, H8 = [Byte.clone, Byte.clone, Byte.clone, Byte.clone]
|
334
|
+
|
335
|
+
class Bit < Number
|
336
|
+
def initialize(opts={}); super(opts.merge(:width => 1))
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
class Nibble < Number
|
341
|
+
def initialize(opts={}); super(opts.merge(:width => 4))
|
342
|
+
end
|
343
|
+
end
|
344
|
+
Nybble = Nibble.clone
|
345
|
+
|
346
|
+
# A length field (specify the width, if it's not 32 bits
|
347
|
+
# little-endian) --- its value will be :size of the following
|
348
|
+
# field (or provide :offset)
|
349
|
+
|
350
|
+
class Len < Number
|
351
|
+
def initialize(opts={})
|
352
|
+
super(opts.merge(:width => 32, :value => :size))
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
class Decimal < Ruckus::Number
|
357
|
+
OPTIONS = { :ascii => true, :radix => 10 }
|
358
|
+
end
|
359
|
+
|
360
|
+
end
|