bit-struct 0.15.0 → 0.16
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.
- checksums.yaml +4 -4
- data/History.txt +5 -1
- data/LICENSE +56 -0
- data/README.md +2 -2
- data/Rakefile +2 -2
- data/examples/field-ripper.rb +1 -1
- data/examples/ip.rb +6 -6
- data/examples/longlong.rb +2 -2
- data/examples/md.rb +1 -1
- data/examples/modular-def.rb +2 -2
- data/examples/native.rb +3 -3
- data/examples/ping.rb +1 -1
- data/examples/raw.rb +1 -1
- data/examples/switch-endian.rb +2 -2
- data/examples/vector.rb +2 -2
- data/lib/bit-struct.rb +0 -11
- data/lib/bit-struct/bit-struct.rb +54 -144
- data/lib/bit-struct/char-field.rb +3 -3
- data/lib/bit-struct/field.rb +94 -0
- data/lib/bit-struct/fields.rb +13 -13
- data/lib/bit-struct/float-field.rb +4 -4
- data/lib/bit-struct/hex-octet-field.rb +3 -3
- data/lib/bit-struct/nested-field.rb +8 -8
- data/lib/bit-struct/octet-field.rb +4 -4
- data/lib/bit-struct/signed-field.rb +23 -28
- data/lib/bit-struct/text-field.rb +2 -2
- data/lib/bit-struct/unsigned-field.rb +24 -27
- data/lib/bit-struct/vector-field.rb +7 -7
- data/lib/bit-struct/vector.rb +21 -21
- data/lib/bit-struct/yaml.rb +2 -2
- data/test/test-endian.rb +3 -3
- data/test/test-vector.rb +4 -4
- data/test/test.rb +49 -43
- metadata +47 -45
@@ -5,7 +5,7 @@ class BitStruct
|
|
5
5
|
#def self.default
|
6
6
|
# don't define this, since it must specify N nulls and we don't know N
|
7
7
|
#end
|
8
|
-
|
8
|
+
|
9
9
|
# Used in describe.
|
10
10
|
def self.class_name
|
11
11
|
@class_name ||= "char"
|
@@ -17,13 +17,13 @@ class BitStruct
|
|
17
17
|
"Bad offset, #{offset}, for #{self.class} #{name}." +
|
18
18
|
" Must be multiple of 8."
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
unless length % 8 == 0
|
22
22
|
raise ArgumentError,
|
23
23
|
"Bad length, #{length}, for #{self.class} #{name}." +
|
24
24
|
" Must be multiple of 8."
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
offset_byte = offset / 8
|
28
28
|
length_byte = length / 8
|
29
29
|
last_byte = offset_byte + length_byte - 1
|
@@ -0,0 +1,94 @@
|
|
1
|
+
|
2
|
+
class BitStruct
|
3
|
+
class Field
|
4
|
+
# Offset of field in bits.
|
5
|
+
attr_reader :offset
|
6
|
+
|
7
|
+
# Length of field in bits.
|
8
|
+
attr_reader :length
|
9
|
+
alias size length
|
10
|
+
|
11
|
+
# Name of field (used for its accessors).
|
12
|
+
attr_reader :name
|
13
|
+
|
14
|
+
# Options, such as :default (varies for each field subclass).
|
15
|
+
# In general, options can be provided as strings or as symbols.
|
16
|
+
attr_reader :options
|
17
|
+
|
18
|
+
# Display name of field (used for printing).
|
19
|
+
attr_reader :display_name
|
20
|
+
|
21
|
+
# Default value.
|
22
|
+
attr_reader :default
|
23
|
+
|
24
|
+
# Format for printed value of field.
|
25
|
+
attr_reader :format
|
26
|
+
|
27
|
+
# Subclasses can override this to define a default for all fields of this
|
28
|
+
# class, not just the one currently being added to a BitStruct class, a
|
29
|
+
# "default default" if you will. The global default, if #default returns
|
30
|
+
# nil, is to fill the field with zero. Most field classes just let this
|
31
|
+
# default stand. The default can be overridden per-field when a BitStruct
|
32
|
+
# class is defined.
|
33
|
+
def self.default; nil; end
|
34
|
+
|
35
|
+
# Used in describe.
|
36
|
+
def self.class_name
|
37
|
+
@class_name ||= name[/\w+$/]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Used in describe. Can be overridden per-subclass, as in NestedField.
|
41
|
+
def class_name
|
42
|
+
self.class.class_name
|
43
|
+
end
|
44
|
+
|
45
|
+
# Yield the description of this field, as an array of 5 strings: byte
|
46
|
+
# offset, type, name, size, and description. The opts hash may have:
|
47
|
+
#
|
48
|
+
# :expand :: if the value is true, expand complex fields
|
49
|
+
#
|
50
|
+
# (Subclass implementations may yield more than once for complex fields.)
|
51
|
+
#
|
52
|
+
def describe opts
|
53
|
+
bits = size
|
54
|
+
if bits > 32 and bits % 8 == 0
|
55
|
+
len_str = "%dB" % (bits/8)
|
56
|
+
else
|
57
|
+
len_str = "%db" % bits
|
58
|
+
end
|
59
|
+
|
60
|
+
byte_offset = offset / 8 + (opts[:byte_offset] || 0)
|
61
|
+
|
62
|
+
yield ["@%d" % byte_offset, class_name, name, len_str, display_name]
|
63
|
+
end
|
64
|
+
|
65
|
+
# Options are _display_name_, _default_, and _format_ (subclasses of Field
|
66
|
+
# may add other options).
|
67
|
+
def initialize(offset, length, name, opts = {})
|
68
|
+
@offset, @length, @name, @options =
|
69
|
+
offset, length, name, opts
|
70
|
+
|
71
|
+
@display_name = opts[:display_name] || opts["display_name"]
|
72
|
+
@default = opts[:default] || opts["default"] || self.class.default
|
73
|
+
@format = opts[:format] || opts["format"]
|
74
|
+
end
|
75
|
+
|
76
|
+
# Inspect the value of this field in the specified _obj_.
|
77
|
+
def inspect_in_object(obj, opts)
|
78
|
+
val = obj.send(name)
|
79
|
+
str =
|
80
|
+
begin
|
81
|
+
val.inspect_with_options(opts)
|
82
|
+
rescue NoMethodError
|
83
|
+
val.inspect
|
84
|
+
end
|
85
|
+
(f=@format) ? (f % str) : str
|
86
|
+
end
|
87
|
+
|
88
|
+
# Normally, all fields show up in inspect, but some, such as padding,
|
89
|
+
# should not.
|
90
|
+
def inspectable?; true; end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
data/lib/bit-struct/fields.rb
CHANGED
@@ -94,24 +94,24 @@ class BitStruct
|
|
94
94
|
# n.x = 3
|
95
95
|
# a.n = n
|
96
96
|
# p a # ==> #<A n=#<Sub x=3>>
|
97
|
-
#
|
97
|
+
#
|
98
98
|
def nest(name, *rest, &block)
|
99
99
|
nested_class = rest.grep(Class).find {|cl| cl <= BitStruct}
|
100
100
|
rest.delete nested_class
|
101
101
|
opts = parse_options(rest, name, NestedField)
|
102
102
|
nested_class = opts[:nested_class] ||= nested_class
|
103
|
-
|
103
|
+
|
104
104
|
unless (block and not nested_class) or (nested_class and not block)
|
105
105
|
raise ArgumentError,
|
106
106
|
"nested field must have either a nested_class option or a block," +
|
107
107
|
" but not both"
|
108
108
|
end
|
109
|
-
|
109
|
+
|
110
110
|
unless nested_class
|
111
111
|
nested_class = Class.new(BitStruct)
|
112
112
|
nested_class.class_eval(&block)
|
113
113
|
end
|
114
|
-
|
114
|
+
|
115
115
|
opts[:default] ||= nested_class.initial_value.dup
|
116
116
|
opts[:nested_class] = nested_class
|
117
117
|
field = add_field(name, nested_class.bit_length, opts)
|
@@ -250,17 +250,17 @@ class BitStruct
|
|
250
250
|
# entry.y = -456
|
251
251
|
# vec[2] = entry
|
252
252
|
# pkt.v = vec
|
253
|
-
#
|
253
|
+
#
|
254
254
|
def vector(name, *rest, &block)
|
255
255
|
opts = parse_options(rest, name, nil)
|
256
256
|
cl = opts[:field_class]
|
257
257
|
opts[:field_class] = VectorField
|
258
|
-
|
258
|
+
|
259
259
|
unless (block and not cl) or (cl and not block)
|
260
260
|
raise ArgumentError,
|
261
261
|
"vector must have either a class or a block, but not both"
|
262
262
|
end
|
263
|
-
|
263
|
+
|
264
264
|
case
|
265
265
|
when cl == nil
|
266
266
|
vector_class = Class.new(BitStruct::Vector)
|
@@ -272,12 +272,12 @@ class BitStruct
|
|
272
272
|
|
273
273
|
when cl < BitStruct::Vector
|
274
274
|
vector_class = cl
|
275
|
-
|
275
|
+
|
276
276
|
else raise ArgumentError, "Bad vector class: #{cl.inspect}"
|
277
277
|
end
|
278
|
-
|
278
|
+
|
279
279
|
vector_class.default_options default_options
|
280
|
-
|
280
|
+
|
281
281
|
length = opts[:length] || rest.grep(Integer).first
|
282
282
|
## what about :length => :lenfield
|
283
283
|
unless length
|
@@ -287,14 +287,14 @@ class BitStruct
|
|
287
287
|
|
288
288
|
opts[:default] ||= vector_class.new(length) ## nil if variable length
|
289
289
|
opts[:vector_class] = vector_class
|
290
|
-
|
290
|
+
|
291
291
|
bit_length = vector_class.struct_class.round_byte_length * 8 * length
|
292
|
-
|
292
|
+
|
293
293
|
field = add_field(name, bit_length, opts)
|
294
294
|
field
|
295
295
|
end
|
296
296
|
BitStruct.autoload :VectorField, "bit-struct/vector-field"
|
297
297
|
end
|
298
|
-
|
298
|
+
|
299
299
|
autoload :Vector, "bit-struct/vector"
|
300
300
|
end
|
@@ -6,20 +6,20 @@ class BitStruct
|
|
6
6
|
def self.class_name
|
7
7
|
@class_name ||= "float"
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
10
|
def add_accessors_to(cl, attr = name) # :nodoc:
|
11
11
|
unless offset % 8 == 0
|
12
12
|
raise ArgumentError,
|
13
13
|
"Bad offset, #{offset}, for #{self.class} #{name}." +
|
14
14
|
" Must be multiple of 8."
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
unless length == 32 or length == 64
|
18
18
|
raise ArgumentError,
|
19
19
|
"Bad length, #{length}, for #{self.class} #{name}." +
|
20
20
|
" Must be 32 or 64."
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
offset_byte = offset / 8
|
24
24
|
length_byte = length / 8
|
25
25
|
last_byte = offset_byte + length_byte - 1
|
@@ -46,7 +46,7 @@ class BitStruct
|
|
46
46
|
raise ArgumentError,
|
47
47
|
"Unrecognized endian option: #{endian.inspect}"
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
cl.class_eval do
|
51
51
|
define_method attr do ||
|
52
52
|
self[byte_range].unpack(ctl).first
|
@@ -1,18 +1,18 @@
|
|
1
|
-
require 'bit-struct/
|
1
|
+
require 'bit-struct/octet-field'
|
2
2
|
|
3
3
|
class BitStruct
|
4
4
|
# Class for char fields that can be accessed with values like
|
5
5
|
# "xx:xx:xx:xx", where each xx is up to 2 hex digits representing a
|
6
6
|
# single octet. The original string-based accessors are still available with
|
7
7
|
# the <tt>_chars</tt> suffix.
|
8
|
-
#
|
8
|
+
#
|
9
9
|
# Declared with BitStruct.hex_octets.
|
10
10
|
class HexOctetField < BitStruct::OctetField
|
11
11
|
# Used in describe.
|
12
12
|
def self.class_name
|
13
13
|
@class_name ||= "hex_octets"
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
SEPARATOR = ":"
|
17
17
|
FORMAT = "%02x"
|
18
18
|
BASE = 16
|
@@ -7,16 +7,16 @@ class BitStruct
|
|
7
7
|
def initialize(*args)
|
8
8
|
super
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
# Used in describe.
|
12
12
|
def self.class_name
|
13
13
|
@class_name ||= "nest"
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
def class_name
|
17
17
|
@class_name ||= nested_class.name[/\w+$/]
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
def nested_class
|
21
21
|
@nested_class ||= options[:nested_class] || options["nested_class"]
|
22
22
|
end
|
@@ -38,20 +38,20 @@ class BitStruct
|
|
38
38
|
"Bad offset, #{offset}, for nested field #{name}." +
|
39
39
|
" Must be multiple of 8."
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
unless length % 8 == 0
|
43
43
|
raise ArgumentError,
|
44
44
|
"Bad length, #{length}, for nested field #{name}." +
|
45
45
|
" Must be multiple of 8."
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
offset_byte = offset / 8
|
49
49
|
length_byte = length / 8
|
50
50
|
last_byte = offset_byte + length_byte - 1
|
51
51
|
byte_range = offset_byte..last_byte
|
52
52
|
|
53
53
|
nc = nested_class
|
54
|
-
|
54
|
+
|
55
55
|
cl.class_eval do
|
56
56
|
define_method attr do ||
|
57
57
|
nc.new(self[byte_range])
|
@@ -62,12 +62,12 @@ class BitStruct
|
|
62
62
|
raise ArgumentError, "Size mismatch in nested struct assignment " +
|
63
63
|
"to #{attr} with value #{val.inspect}"
|
64
64
|
end
|
65
|
-
|
65
|
+
|
66
66
|
if val.class != nc
|
67
67
|
warn "Type mismatch in nested struct assignment " +
|
68
68
|
"to #{attr} with value #{val.inspect}"
|
69
69
|
end
|
70
|
-
|
70
|
+
|
71
71
|
self[byte_range] = val
|
72
72
|
end
|
73
73
|
end
|
@@ -5,14 +5,14 @@ class BitStruct
|
|
5
5
|
# "xxx.xxx.xxx.xxx", where each xxx is up to 3 decimal digits representing a
|
6
6
|
# single octet. The original string-based accessors are still available with
|
7
7
|
# the <tt>_chars</tt> suffix.
|
8
|
-
#
|
8
|
+
#
|
9
9
|
# Declared with BitStruct.octets.
|
10
10
|
class OctetField < BitStruct::CharField
|
11
11
|
# Used in describe.
|
12
12
|
def self.class_name
|
13
13
|
@class_name ||= "octets"
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
SEPARATOR = "."
|
17
17
|
FORMAT = "%d"
|
18
18
|
BASE = 10
|
@@ -23,7 +23,7 @@ class BitStruct
|
|
23
23
|
sep = self.class::SEPARATOR
|
24
24
|
base = self.class::BASE
|
25
25
|
fmt = self.class::FORMAT
|
26
|
-
|
26
|
+
|
27
27
|
cl.class_eval do
|
28
28
|
define_method attr do ||
|
29
29
|
ary = []
|
@@ -32,7 +32,7 @@ class BitStruct
|
|
32
32
|
end
|
33
33
|
ary.join(sep)
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
old_writer = "#{attr_chars}="
|
37
37
|
|
38
38
|
define_method "#{attr}=" do |val|
|
@@ -6,25 +6,20 @@ class BitStruct
|
|
6
6
|
def self.class_name
|
7
7
|
@class_name ||= "signed"
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
10
|
def add_accessors_to(cl, attr = name) # :nodoc:
|
11
11
|
offset_byte = offset / 8
|
12
12
|
offset_bit = offset % 8
|
13
|
-
|
13
|
+
|
14
14
|
length_bit = offset_bit + length
|
15
15
|
length_byte = (length_bit/8.0).ceil
|
16
16
|
last_byte = offset_byte + length_byte - 1
|
17
|
-
max = 2**length-1
|
18
17
|
mid = 2**(length-1)
|
19
18
|
max_unsigned = 2**length
|
20
19
|
to_signed = proc {|n| (n>=mid) ? n - max_unsigned : n}
|
21
|
-
|
22
|
-
|
20
|
+
|
23
21
|
divisor = options[:fixed] || options["fixed"]
|
24
22
|
divisor_f = divisor && divisor.to_f
|
25
|
-
# if divisor and not divisor.is_a? Fixnum
|
26
|
-
# raise ArgumentError, "fixed-point divisor must be a fixnum"
|
27
|
-
# end
|
28
23
|
|
29
24
|
endian = (options[:endian] || options["endian"]).to_s
|
30
25
|
case endian
|
@@ -42,15 +37,15 @@ class BitStruct
|
|
42
37
|
raise ArgumentError,
|
43
38
|
"Unrecognized endian option: #{endian.inspect}"
|
44
39
|
end
|
45
|
-
|
40
|
+
|
46
41
|
data_is_big_endian =
|
47
42
|
([1234].pack(ctl) == [1234].pack(length_byte <= 2 ? "n" : "N"))
|
48
|
-
|
43
|
+
|
49
44
|
if length_byte == 1
|
50
45
|
rest = 8 - length_bit
|
51
46
|
mask = ["0"*offset_bit + "1"*length + "0"*rest].pack("B8")[0].ord
|
52
47
|
mask2 = ["1"*offset_bit + "0"*length + "1"*rest].pack("B8")[0].ord
|
53
|
-
|
48
|
+
|
54
49
|
cl.class_eval do
|
55
50
|
if divisor
|
56
51
|
define_method attr do ||
|
@@ -74,11 +69,11 @@ class BitStruct
|
|
74
69
|
end
|
75
70
|
end
|
76
71
|
end
|
77
|
-
|
72
|
+
|
78
73
|
elsif offset_bit == 0 and length % 8 == 0
|
79
74
|
field_length = length
|
80
75
|
byte_range = offset_byte..last_byte
|
81
|
-
|
76
|
+
|
82
77
|
cl.class_eval do
|
83
78
|
case field_length
|
84
79
|
when 8
|
@@ -91,7 +86,7 @@ class BitStruct
|
|
91
86
|
val = (val * divisor).round
|
92
87
|
self[offset_byte] = val
|
93
88
|
end
|
94
|
-
|
89
|
+
|
95
90
|
else
|
96
91
|
define_method attr do ||
|
97
92
|
to_signed[self[offset_byte]]
|
@@ -101,7 +96,7 @@ class BitStruct
|
|
101
96
|
self[offset_byte] = val
|
102
97
|
end
|
103
98
|
end
|
104
|
-
|
99
|
+
|
105
100
|
when 16, 32
|
106
101
|
if divisor
|
107
102
|
define_method attr do ||
|
@@ -112,7 +107,7 @@ class BitStruct
|
|
112
107
|
val = (val * divisor).round
|
113
108
|
self[byte_range] = [val].pack(ctl)
|
114
109
|
end
|
115
|
-
|
110
|
+
|
116
111
|
else
|
117
112
|
define_method attr do ||
|
118
113
|
to_signed[self[byte_range].unpack(ctl).first]
|
@@ -122,7 +117,7 @@ class BitStruct
|
|
122
117
|
self[byte_range] = [val].pack(ctl)
|
123
118
|
end
|
124
119
|
end
|
125
|
-
|
120
|
+
|
126
121
|
else
|
127
122
|
reader_helper = proc do |substr|
|
128
123
|
bytes = substr.unpack("C*")
|
@@ -131,7 +126,7 @@ class BitStruct
|
|
131
126
|
(sum << 8) + byte
|
132
127
|
end
|
133
128
|
end
|
134
|
-
|
129
|
+
|
135
130
|
writer_helper = proc do |val|
|
136
131
|
bytes = []
|
137
132
|
val += max_unsigned if val < 0
|
@@ -146,21 +141,21 @@ class BitStruct
|
|
146
141
|
bytes.reverse! if data_is_big_endian
|
147
142
|
bytes.pack("C*")
|
148
143
|
end
|
149
|
-
|
144
|
+
|
150
145
|
if divisor
|
151
146
|
define_method attr do ||
|
152
147
|
to_signed[reader_helper[self[byte_range]] / divisor_f]
|
153
148
|
end
|
154
|
-
|
149
|
+
|
155
150
|
define_method "#{attr}=" do |val|
|
156
151
|
self[byte_range] = writer_helper[(val * divisor).round]
|
157
152
|
end
|
158
|
-
|
153
|
+
|
159
154
|
else
|
160
155
|
define_method attr do ||
|
161
156
|
to_signed[reader_helper[self[byte_range]]]
|
162
157
|
end
|
163
|
-
|
158
|
+
|
164
159
|
define_method "#{attr}=" do |val|
|
165
160
|
self[byte_range] = writer_helper[val]
|
166
161
|
end
|
@@ -171,10 +166,10 @@ class BitStruct
|
|
171
166
|
elsif length_byte == 2 # unaligned field that fits within two whole bytes
|
172
167
|
byte_range = offset_byte..last_byte
|
173
168
|
rest = 16 - length_bit
|
174
|
-
|
169
|
+
|
175
170
|
mask = ["0"*offset_bit + "1"*length + "0"*rest]
|
176
171
|
mask = mask.pack("B16").unpack(ctl).first
|
177
|
-
|
172
|
+
|
178
173
|
mask2 = ["1"*offset_bit + "0"*length + "1"*rest]
|
179
174
|
mask2 = mask2.pack("B16").unpack(ctl).first
|
180
175
|
|
@@ -204,14 +199,14 @@ class BitStruct
|
|
204
199
|
end
|
205
200
|
end
|
206
201
|
end
|
207
|
-
|
202
|
+
|
208
203
|
elsif length_byte == 3 # unaligned field that fits within 3 whole bytes
|
209
204
|
byte_range = offset_byte..last_byte
|
210
205
|
rest = 32 - length_bit
|
211
|
-
|
206
|
+
|
212
207
|
mask = ["0"*offset_bit + "1"*length + "0"*rest]
|
213
208
|
mask = mask.pack("B32").unpack(ctl).first
|
214
|
-
|
209
|
+
|
215
210
|
mask2 = ["1"*offset_bit + "0"*length + "1"*rest]
|
216
211
|
mask2 = mask2.pack("B32").unpack(ctl).first
|
217
212
|
|
@@ -249,7 +244,7 @@ class BitStruct
|
|
249
244
|
end
|
250
245
|
end
|
251
246
|
end
|
252
|
-
|
247
|
+
|
253
248
|
else
|
254
249
|
raise "unsupported: #{inspect}"
|
255
250
|
end
|