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,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
|