bin_struct 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +7 -1
- data/lib/bin_struct/bit_attr.rb +186 -0
- data/lib/bin_struct/cstring.rb +2 -1
- data/lib/bin_struct/int_string.rb +4 -4
- data/lib/bin_struct/string.rb +2 -1
- data/lib/bin_struct/struct.rb +113 -134
- data/lib/bin_struct/structable.rb +1 -1
- data/lib/bin_struct/version.rb +1 -1
- data/lib/bin_struct.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44d43c8007a5ad33899e5c7ee6a09c306a64a95012bd489ee2d14d69c7e40dcf
|
4
|
+
data.tar.gz: db0b44a68cb8a23b1a5110cb12bb587c4cf4a737b42443f72c0ae9241fffa6c9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 287f8dbf64a09b27b5fe376f9b6a982055713cdb2453ab7dd2b5ec0489678727441a3545427bb9f1f31d0ccc733bcc10e6af703fdc3b85b7cf539614b306bc21
|
7
|
+
data.tar.gz: cc3ba318afa6ac9706d57676256bbebeeec3b4dc52402728777b8c7a7fb89c9de8d893903b3e753d84a25aa6339b9aa919cfd4a223ff1f1a446a2d169047dee2
|
data/CHANGELOG.md
CHANGED
@@ -3,6 +3,27 @@
|
|
3
3
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
4
4
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
5
5
|
|
6
|
+
## 0.3.0 - 2024-12-02
|
7
|
+
|
8
|
+
### Added
|
9
|
+
|
10
|
+
- `BitAddr` class is added. This class is used as a `Structable` type to handle bitfield attributes.
|
11
|
+
- Add `Struct.define_bit_attr`, `.define_bit_attr_before` and `.define_bit_attr_before` to define bitfield attributes.
|
12
|
+
|
13
|
+
### Changed
|
14
|
+
|
15
|
+
- `Struct.define_bit_attr_on` is removed in favor of `Struct.define_bit_attr`. Bitfield attributes are now first class attributes, and no more an onverlay on `Int`.
|
16
|
+
|
17
|
+
## 0.2.1 - 2024-11-25
|
18
|
+
|
19
|
+
### Added
|
20
|
+
|
21
|
+
- `CString` and `String` initializers now accepts `:value` option to set string initial value.
|
22
|
+
|
23
|
+
### Changed
|
24
|
+
|
25
|
+
- `IntString` initializer option `:string` is renamed into `:value`.
|
26
|
+
|
6
27
|
## 0.2.0 - 2024-07-21
|
7
28
|
|
8
29
|
### Changed
|
data/README.md
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
[](https://badge.fury.io/rb/bin_struct)
|
2
|
+
[](https://github.com/lemontree55/bin_struct/actions/workflows/main.yml)
|
3
|
+
|
1
4
|
# BinStruct
|
2
5
|
|
3
6
|
BinStruct provides a simple way to create and dissect binary data. It is an extraction from [PacketGen](https://github.com/lemontree55/packetgen) 3.x Fields.
|
@@ -6,9 +9,12 @@ BinStruct provides a simple way to create and dissect binary data. It is an extr
|
|
6
9
|
|
7
10
|
Installation using RubyGems is easy:
|
8
11
|
|
9
|
-
|
12
|
+
```shell
|
13
|
+
gem install bin_struct
|
14
|
+
```
|
10
15
|
|
11
16
|
Or add it to a Gemfile:
|
17
|
+
|
12
18
|
```ruby
|
13
19
|
gem 'bin_struct'
|
14
20
|
```
|
@@ -0,0 +1,186 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is part of BinStruct
|
4
|
+
# see https://github.com/lemontree55/bin_struct for more informations
|
5
|
+
# Copyright (C) 2024 LemonTree55 <lenontree@proton.me>
|
6
|
+
# This program is published under MIT license.
|
7
|
+
require 'digest'
|
8
|
+
|
9
|
+
module BinStruct
|
10
|
+
# Define a bitfield attribute to embed in a {Struct}.
|
11
|
+
#
|
12
|
+
# class MyStruct < BinStruct::Struct
|
13
|
+
# # Create a 32-bit bitfield attribute, with fields a (16 bits), b and c (4 bits each) and d (8 bits).
|
14
|
+
# # a is the leftmost field in bitfield, and d the rightmost one.
|
15
|
+
# define_attr :int32, BinStruct::BitAttr.create(width: 32, a: 16, b: 4, c: 4, d:8)
|
16
|
+
# end
|
17
|
+
# @since 0.3.0
|
18
|
+
# @author LemonTree55
|
19
|
+
class BitAttr
|
20
|
+
include Structable
|
21
|
+
|
22
|
+
# @return [Integer] width in bits of bit attribute
|
23
|
+
attr_reader :width
|
24
|
+
# @return [Array[Symbol]]
|
25
|
+
attr_reader :bit_methods
|
26
|
+
|
27
|
+
# @private
|
28
|
+
Parameters = Struct.new(:width, :fields, :int)
|
29
|
+
|
30
|
+
class << self
|
31
|
+
@cache = {}
|
32
|
+
|
33
|
+
# @private
|
34
|
+
# @return [Parameters]
|
35
|
+
attr_reader :parameters
|
36
|
+
|
37
|
+
# Create a new {BitAttr} subclass with specified parameters
|
38
|
+
# @param [Integer] width size of bitfields in bits. Must be a size of an {Int} (8, 16, 24, 32 or 64 bits).
|
39
|
+
# @param [:big,:little,:native] endian endianess of bit attribute as an integer
|
40
|
+
# @param [Hash{Symbol=>Integer}] fields hash associating field names with their size. Total size MUST be equal
|
41
|
+
# to +width+.
|
42
|
+
# @return [Class]
|
43
|
+
# @raise [ArgumentError] raise if:
|
44
|
+
# * width is not a size of one of {Int} subclasses,
|
45
|
+
# * sum of bitfield sizes is not equal to +width+
|
46
|
+
def create(width:, endian: :big, **fields)
|
47
|
+
raise ArgumentError, 'with must be 8, 16, 24, 32 or 64' unless [8, 16, 24, 32, 64].include?(width)
|
48
|
+
|
49
|
+
hsh = compute_hash(width, endian, fields)
|
50
|
+
cached = cache[hsh]
|
51
|
+
return cached if cached
|
52
|
+
|
53
|
+
total_size = fields.reduce(0) { |acc, ary| acc + ary.last }
|
54
|
+
raise ArgumentError, "sum of bitfield sizes is not equal to #{width}" unless total_size == width
|
55
|
+
|
56
|
+
cache[hsh] = Class.new(self) do
|
57
|
+
int_klass = BinStruct.const_get("Int#{width}")
|
58
|
+
@parameters = Parameters.new(width, fields, int_klass.new(endian: endian)).freeze
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# @return [Hash{::String=>Class}]
|
65
|
+
def cache
|
66
|
+
return @cache if defined? @cache
|
67
|
+
|
68
|
+
@cache = {}
|
69
|
+
end
|
70
|
+
|
71
|
+
# @param [::Array] params
|
72
|
+
# @return [::String]
|
73
|
+
def compute_hash(*params)
|
74
|
+
Digest::MD5.digest(Marshal.dump(params))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Initialize bit attribute
|
79
|
+
# @param [Hash{Symbol=>Integer}] opts initialization values for fields, where keys are field names and values are
|
80
|
+
# initialization values
|
81
|
+
# @return [self]
|
82
|
+
def initialize(opts = {})
|
83
|
+
parameters = self.class.parameters
|
84
|
+
raise NotImplementedError, '#initialize may only be called on subclass of {self.class}' if parameters.nil?
|
85
|
+
|
86
|
+
@width = parameters.width
|
87
|
+
@fields = parameters.fields
|
88
|
+
@int = parameters.int.dup
|
89
|
+
@data = {}
|
90
|
+
@bit_methods = []
|
91
|
+
|
92
|
+
parameters.fields.each do |name, size|
|
93
|
+
@data[name] = opts[name] || 0
|
94
|
+
define_methods(name, size)
|
95
|
+
end
|
96
|
+
@bit_methods.freeze
|
97
|
+
end
|
98
|
+
|
99
|
+
# Get type name
|
100
|
+
# @return [::String]
|
101
|
+
def type_name
|
102
|
+
return @type_name if defined? @type_name
|
103
|
+
|
104
|
+
endian_suffix = case @int.endian
|
105
|
+
when :big then ''
|
106
|
+
when :little then 'le'
|
107
|
+
when :native then 'n'
|
108
|
+
end
|
109
|
+
@type_name = "BitAttr#{@width}#{endian_suffix}"
|
110
|
+
end
|
111
|
+
|
112
|
+
# Populate bit attribute from +str+
|
113
|
+
# @param [::String,nil] str
|
114
|
+
# @return [self]
|
115
|
+
def read(str)
|
116
|
+
return self if str.nil?
|
117
|
+
|
118
|
+
@int.read(str)
|
119
|
+
compute_data(@int.to_i)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Give integer associated to this attribute
|
123
|
+
# @return [Integer]
|
124
|
+
def to_i
|
125
|
+
v = 0
|
126
|
+
@fields.each do |name, size|
|
127
|
+
v <<= size
|
128
|
+
v |= @data[name]
|
129
|
+
end
|
130
|
+
|
131
|
+
v
|
132
|
+
end
|
133
|
+
alias to_human to_i
|
134
|
+
|
135
|
+
# Return binary string
|
136
|
+
# @return [::String]
|
137
|
+
def to_s
|
138
|
+
@int.value = to_i
|
139
|
+
@int.to_s
|
140
|
+
end
|
141
|
+
|
142
|
+
# Set fields from associated integer
|
143
|
+
# @param [#to_i] value
|
144
|
+
# @return [self]
|
145
|
+
def from_human(value)
|
146
|
+
compute_data(value.to_i)
|
147
|
+
end
|
148
|
+
|
149
|
+
def format_inspect
|
150
|
+
str = @int.format_inspect << "\n"
|
151
|
+
str << @data.map { |name, value| "#{name}:#{value}" }.join(' ')
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
# @param [Integer] value
|
157
|
+
# @return [self]
|
158
|
+
def compute_data(value)
|
159
|
+
@fields.reverse_each do |name, size|
|
160
|
+
@data[name] = value & ((2**size) - 1)
|
161
|
+
value >>= size
|
162
|
+
end
|
163
|
+
|
164
|
+
self
|
165
|
+
end
|
166
|
+
|
167
|
+
# @param [Symbol] name
|
168
|
+
# @return [void]
|
169
|
+
def define_methods(name, size)
|
170
|
+
instance_eval "def #{name}; @data[#{name.inspect}]; end\n", __FILE__, __LINE__ # def name; data[:name]; end
|
171
|
+
bit_methods << name
|
172
|
+
bit_methods << :"#{name}="
|
173
|
+
|
174
|
+
# rubocop:disable Style/DocumentDynamicEvalDefinition
|
175
|
+
if size == 1
|
176
|
+
instance_eval "def #{name}?; @data[#{name.inspect}] != 0; end\n", __FILE__, __LINE__
|
177
|
+
instance_eval "def #{name}=(val); v = case val when TrueClass; 1 when FalseClass; 0 else val end; " \
|
178
|
+
"@data[#{name.inspect}] = v; end", __FILE__, __LINE__ - 2
|
179
|
+
bit_methods << :"#{name}?"
|
180
|
+
else
|
181
|
+
instance_eval "def #{name}=(val); @data[#{name.inspect}] = val; end", __FILE__, __LINE__
|
182
|
+
end
|
183
|
+
# rubocop:enable Style/DocumentDynamicEvalDefinition
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
data/lib/bin_struct/cstring.rb
CHANGED
@@ -76,8 +76,9 @@ module BinStruct
|
|
76
76
|
|
77
77
|
# @param [Hash] options
|
78
78
|
# @option options [Integer] :static_length set a static length for this string
|
79
|
+
# @option options [::String] :value string value (default to +''+)
|
79
80
|
def initialize(options = {})
|
80
|
-
register_internal_string(+'')
|
81
|
+
register_internal_string(options[:value] || +'')
|
81
82
|
@static_length = options[:static_length]
|
82
83
|
end
|
83
84
|
|
@@ -20,9 +20,9 @@ module BinStruct
|
|
20
20
|
|
21
21
|
# @param [Hash] options
|
22
22
|
# @option options [Class] :length_type should be a {Int} subclass. Default to {Int8}.
|
23
|
-
# @option options [::String] :
|
23
|
+
# @option options [::String] :value String value. Default to +""+
|
24
24
|
def initialize(options = {})
|
25
|
-
@string = BinStruct::String.new.read(options[:
|
25
|
+
@string = BinStruct::String.new.read(options[:value] || +'')
|
26
26
|
@length = (options[:length_type] || Int8).new
|
27
27
|
calc_length
|
28
28
|
end
|
@@ -61,7 +61,7 @@ module BinStruct
|
|
61
61
|
# @return [::String]
|
62
62
|
def string=(str)
|
63
63
|
@length.value = str.to_s.size
|
64
|
-
@string
|
64
|
+
@string.read(str)
|
65
65
|
end
|
66
66
|
|
67
67
|
# Get binary string
|
@@ -82,7 +82,7 @@ module BinStruct
|
|
82
82
|
# Get human readable string
|
83
83
|
# @return [::String]
|
84
84
|
def to_human
|
85
|
-
@string
|
85
|
+
@string.to_s
|
86
86
|
end
|
87
87
|
|
88
88
|
# Set length from internal string length
|
data/lib/bin_struct/string.rb
CHANGED
@@ -32,8 +32,9 @@ module BinStruct
|
|
32
32
|
# @option options [Int,Proc] :length_from object or proc from which
|
33
33
|
# takes length when reading
|
34
34
|
# @option options [Integer] :static_length set a static length for this string
|
35
|
+
# @option options [::String] :value string value (default to +''+)
|
35
36
|
def initialize(options = {})
|
36
|
-
register_internal_string(+'')
|
37
|
+
register_internal_string(options[:value] || +'')
|
37
38
|
initialize_length_from(options)
|
38
39
|
@static_length = options[:static_length]
|
39
40
|
end
|
data/lib/bin_struct/struct.rb
CHANGED
@@ -81,25 +81,23 @@ module BinStruct
|
|
81
81
|
# define_attr :opt1, BinStruct::Int16, optional: ->(h) { h.type == 42 }
|
82
82
|
#
|
83
83
|
# == Generating bit attributes
|
84
|
-
# {.
|
85
|
-
#
|
86
|
-
# define_attr :frag, BinStruct::Int16, default: 0
|
87
|
-
# define_bit_attr_on :frag, :flag_rsv, :flag_df, :flag_mf, :fragment_offset, 13
|
84
|
+
# {.define_bit_attr} creates a bit attribute. For example, +frag+ attribute in IP header:
|
85
|
+
# define_bit_attr :frag, flag_rsv: 1, flag_df: 1, flag_mf: 1, fragment_offset: 13
|
88
86
|
#
|
89
87
|
# This example generates methods:
|
90
88
|
# * +#frag+ and +#frag=+ to access +frag+ attribute as a 16-bit integer,
|
91
89
|
# * +#flag_rsv?+, +#flag_rsv=+, +#flag_df?+, +#flag_df=+, +#flag_mf?+ and +#flag_mf=+
|
92
90
|
# to access Boolean RSV, MF and DF flags from +frag+ attribute,
|
91
|
+
# * +#flag_rsv+, +#flag_df+ and +#flag_mf# to read RSV, MF and DF flags as Integer,
|
93
92
|
# * +#fragment_offset+ and +#fragment_offset=+ to access 13-bit integer fragment
|
94
93
|
# offset subattribute from +frag+ attribute.
|
95
94
|
#
|
96
95
|
# == Creating a new Struct class from another one
|
97
96
|
# Some methods may help in this case:
|
98
|
-
# * {.define_attr_before} to define a new attribute before an existing one,
|
99
|
-
# * {.define_attr_after} to define a new attribute after an existing onr,
|
100
|
-
# * {.
|
101
|
-
# * {.
|
102
|
-
# * {.remove_bit_attrs_on} to remove bit attribute definition.
|
97
|
+
# * {.define_attr_before} and {.define_bit_attr_before} to define a new attribute before an existing one,
|
98
|
+
# * {.define_attr_after} and {.define_bit_attr_after} to define a new attribute after an existing onr,
|
99
|
+
# * {.remove_attr} to remove an existing attribute,
|
100
|
+
# * {.uptade_attr} to change options of an attribute (but not its type),
|
103
101
|
#
|
104
102
|
# @author Sylvain Daubert (2016-2024)
|
105
103
|
# @author LemonTree55
|
@@ -121,7 +119,7 @@ module BinStruct
|
|
121
119
|
# @return [Hash]
|
122
120
|
attr_reader :attr_defs
|
123
121
|
# Get bit attribute defintions for this class
|
124
|
-
# @return [Hash]
|
122
|
+
# @return [Hash{Symbol=>Array[Symbol]}]
|
125
123
|
attr_reader :bit_attrs
|
126
124
|
|
127
125
|
# On inheritage, create +@attr_defs+ class variable
|
@@ -198,11 +196,7 @@ module BinStruct
|
|
198
196
|
define_attr name, type, options
|
199
197
|
return if other.nil?
|
200
198
|
|
201
|
-
|
202
|
-
idx = attributes.index(other)
|
203
|
-
raise ArgumentError, "unknown #{other} attribute" if idx.nil?
|
204
|
-
|
205
|
-
attributes[idx, 0] = name
|
199
|
+
move_attr(name, before: other)
|
206
200
|
end
|
207
201
|
|
208
202
|
# Define an attribute, after another one
|
@@ -217,11 +211,7 @@ module BinStruct
|
|
217
211
|
define_attr name, type, options
|
218
212
|
return if other.nil?
|
219
213
|
|
220
|
-
|
221
|
-
idx = attributes.index(other)
|
222
|
-
raise ArgumentError, "unknown #{other} attribute" if idx.nil?
|
223
|
-
|
224
|
-
attributes[idx + 1, 0] = name
|
214
|
+
move_attr(name, after: other)
|
225
215
|
end
|
226
216
|
|
227
217
|
# Remove a previously defined attribute
|
@@ -229,9 +219,12 @@ module BinStruct
|
|
229
219
|
# @return [void]
|
230
220
|
def remove_attr(name)
|
231
221
|
attributes.delete(name)
|
232
|
-
|
222
|
+
attr_def = attr_defs.delete(name)
|
233
223
|
undef_method name if method_defined?(name)
|
234
224
|
undef_method :"#{name}=" if method_defined?(:"#{name}=")
|
225
|
+
return unless bit_attrs[name]
|
226
|
+
|
227
|
+
attr_def.type.new.bit_methods.each { |meth| undef_method(meth) }
|
235
228
|
end
|
236
229
|
|
237
230
|
# Update a previously defined attribute
|
@@ -250,64 +243,95 @@ module BinStruct
|
|
250
243
|
attr_defs[name].options.merge!(options)
|
251
244
|
end
|
252
245
|
|
253
|
-
# Define a bit attribute
|
246
|
+
# Define a bit attribute
|
254
247
|
# class MyHeader < BinStruct::Struct
|
255
|
-
#
|
256
|
-
# # define a bit attribute on :flag attribute
|
248
|
+
# # define a 16-bit attribute named :flag
|
257
249
|
# # flag1, flag2 and flag3 are 1-bit attributes
|
258
|
-
# # type and stype are 3-bit attributes. reserved is a
|
259
|
-
#
|
250
|
+
# # type and stype are 3-bit attributes. reserved is a 7-bit attribute
|
251
|
+
# define_bit_attr :flags, flag1: 1, flag2: 1, flag3: 1, type: 3, stype: 3, reserved: 7
|
260
252
|
# end
|
261
|
-
# A bit attribute of size 1 bit defines
|
262
|
-
# * +#attr+ which returns
|
263
|
-
# * +#attr
|
264
|
-
#
|
253
|
+
# A bit attribute of size 1 bit defines 3 methods:
|
254
|
+
# * +#attr+ which returns an Integer,
|
255
|
+
# * +#attr?+ which returns a Boolean,
|
256
|
+
# * +#attr=+ which accepts an Integer or a Boolean.
|
257
|
+
# A bit attribute of more bits defines only 2 methods:
|
265
258
|
# * +#attr+ which returns an Integer,
|
266
|
-
# * +#attr=+ which takes
|
267
|
-
# @param [Symbol] attr attribute name
|
268
|
-
#
|
269
|
-
# @param [
|
270
|
-
#
|
271
|
-
# @raise [ArgumentError] unknown +attr+
|
259
|
+
# * +#attr=+ which takes an Integer.
|
260
|
+
# @param [Symbol] attr attribute name
|
261
|
+
# @param [:big,:little,:native] endian endianess of Integer
|
262
|
+
# @param [Integer] default default value for whole attribute
|
263
|
+
# @param [Hash{Symbol=>Integer}] fields Hash defining fields. Keys are field names, values are field sizes.
|
272
264
|
# @return [void]
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
265
|
+
# @since 0.3.0
|
266
|
+
def define_bit_attr(attr, endian: :big, default: 0, **fields)
|
267
|
+
width = fields.reduce(0) { |acc, ary| acc + ary.last }
|
268
|
+
bit_attr_klass = BitAttr.create(width: width, endian: endian, **fields)
|
269
|
+
define_attr(attr, bit_attr_klass, default: default)
|
270
|
+
fields.each_key { |field| register_bit_attr_field(attr, field) }
|
271
|
+
bit_attr_klass.new.bit_methods.each do |meth|
|
272
|
+
if meth.to_s.end_with?('=')
|
273
|
+
define_method(meth) { |value| self[attr].send(meth, value) }
|
274
|
+
else
|
275
|
+
define_method(meth) { self[attr].send(meth) }
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
281
279
|
|
282
|
-
|
283
|
-
|
284
|
-
|
280
|
+
# Define a bit attribute, before another attribute
|
281
|
+
# @param [Symbol,nil] other attribute name to create a new one before.
|
282
|
+
# If +nil+, new attribute is appended.
|
283
|
+
# @param [Symbol] name attribute name to create
|
284
|
+
# @param [:big,:little,:native] endian endianess of Integer
|
285
|
+
# @param [Hash{Symbol=>Integer}] fields Hash defining fields. Keys are field names, values are field sizes.
|
286
|
+
# @return [void]
|
287
|
+
# @since 0.3.0
|
288
|
+
# @see .define_bit_attr
|
289
|
+
def define_bit_attr_before(other, name, endian: :big, **fields)
|
290
|
+
define_bit_attr(name, endian: endian, **fields)
|
291
|
+
return if other.nil?
|
285
292
|
|
286
|
-
|
293
|
+
move_attr(name, before: other)
|
294
|
+
end
|
287
295
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
296
|
+
# Define a bit attribute after another attribute
|
297
|
+
# @param [Symbol,nil] other attribute name to create a new one after.
|
298
|
+
# If +nil+, new attribute is appended.
|
299
|
+
# @param [Symbol] name attribute name to create
|
300
|
+
# @param [:big,:little,:native] endian endianess of Integer
|
301
|
+
# @param [Hash{Symbol=>Integer}] fields Hash defining fields. Keys are field names, values are field sizes.
|
302
|
+
# @return [void]
|
303
|
+
# @since 0.3.0
|
304
|
+
# @see .define_bit_attr
|
305
|
+
def define_bit_attr_after(other, name, endian: :big, **fields)
|
306
|
+
define_bit_attr(name, endian: endian, **fields)
|
307
|
+
return if other.nil?
|
292
308
|
|
293
|
-
|
294
|
-
end
|
309
|
+
move_attr(name, after: other)
|
295
310
|
end
|
296
311
|
|
297
|
-
|
298
|
-
|
312
|
+
private
|
313
|
+
|
314
|
+
# @param [Symbol] name
|
315
|
+
# @param [Symbol,nil] before
|
316
|
+
# @param [Symbol,nil] after
|
299
317
|
# @return [void]
|
300
|
-
|
301
|
-
|
302
|
-
|
318
|
+
# @raise [ArgumentError] Both +before+ and +after+ are nil, or both are set.
|
319
|
+
def move_attr(name, before: nil, after: nil)
|
320
|
+
move_check_destination(before, after)
|
303
321
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
322
|
+
other = before || after
|
323
|
+
attributes.delete(name)
|
324
|
+
idx = attributes.index(other)
|
325
|
+
raise ArgumentError, "unknown #{other} attribute" if idx.nil?
|
326
|
+
|
327
|
+
idx += 1 unless after.nil?
|
328
|
+
attributes[idx, 0] = name
|
308
329
|
end
|
309
330
|
|
310
|
-
|
331
|
+
def move_check_destination(before, after)
|
332
|
+
raise ArgumentError 'one of before: and after: arguments MUST be set' if before.nil? && after.nil?
|
333
|
+
raise ArgumentError 'only one of before and after argument MUST be set' if !before.nil? && !after.nil?
|
334
|
+
end
|
311
335
|
|
312
336
|
def add_methods(name, type)
|
313
337
|
define = []
|
@@ -328,73 +352,15 @@ module BinStruct
|
|
328
352
|
class_eval define.join("\n")
|
329
353
|
end
|
330
354
|
|
331
|
-
def
|
332
|
-
|
333
|
-
|
334
|
-
if size == 1
|
335
|
-
add_single_bit_methods(attr, name, size, total_size, shift)
|
336
|
-
else
|
337
|
-
add_multibit_methods(attr, name, size, total_size, shift)
|
338
|
-
end
|
339
|
-
end
|
340
|
-
|
341
|
-
def compute_mask(size, shift)
|
342
|
-
((2**size) - 1) << shift
|
343
|
-
end
|
344
|
-
|
345
|
-
def compute_clear_mask(total_size, mask)
|
346
|
-
((2**total_size) - 1) & (~mask & ((2**total_size) - 1))
|
347
|
-
end
|
348
|
-
|
349
|
-
def add_single_bit_methods(attr, name, size, total_size, shift)
|
350
|
-
mask = compute_mask(size, shift)
|
351
|
-
clear_mask = compute_clear_mask(total_size, mask)
|
352
|
-
|
353
|
-
class_eval <<-METHODS, __FILE__, __LINE__ + 1
|
354
|
-
def #{name}? # def bit?
|
355
|
-
val = (self[:#{attr}].to_i & #{mask}) >> #{shift} # val = (self[:attr}].to_i & 1}) >> 1
|
356
|
-
val != 0 # val != 0
|
357
|
-
end # end
|
358
|
-
def #{name}=(v) # def bit=(v)
|
359
|
-
val = v ? 1 : 0 # val = v ? 1 : 0
|
360
|
-
self[:#{attr}].value = self[:#{attr}].to_i & #{clear_mask} # self[:attr].value = self[:attr].to_i & 0xfffd
|
361
|
-
self[:#{attr}].value |= val << #{shift} # self[:attr].value |= val << 1
|
362
|
-
end # end
|
363
|
-
METHODS
|
364
|
-
end
|
365
|
-
|
366
|
-
def add_multibit_methods(attr, name, size, total_size, shift)
|
367
|
-
mask = compute_mask(size, shift)
|
368
|
-
clear_mask = compute_clear_mask(total_size, mask)
|
369
|
-
|
370
|
-
class_eval <<-METHODS, __FILE__, __LINE__ + 1
|
371
|
-
def #{name} # def multibit
|
372
|
-
(self[:#{attr}].to_i & #{mask}) >> #{shift} # (self[:attr].to_i & 6) >> 1
|
373
|
-
end # end
|
374
|
-
def #{name}=(v) # def multibit=(v)
|
375
|
-
self[:#{attr}].value = self[:#{attr}].to_i & #{clear_mask} # self[:attr].value = self[:attr].to_i & 0xfff9
|
376
|
-
self[:#{attr}].value |= (v & #{(2**size) - 1}) << #{shift} # self[:attr].value |= (v & 3) << 1
|
377
|
-
end # end
|
378
|
-
METHODS
|
379
|
-
end
|
380
|
-
|
381
|
-
def register_bit_attr_size(attr, name, size)
|
382
|
-
bit_attrs[attr] = {} if bit_attrs[attr].nil?
|
383
|
-
bit_attrs[attr][name] = size
|
355
|
+
def register_bit_attr_field(attr, field)
|
356
|
+
bit_attrs[attr] ||= []
|
357
|
+
bit_attrs[attr] << field
|
384
358
|
end
|
385
359
|
|
386
360
|
def attr_defs_property_from(attr, property, options)
|
387
361
|
attr_defs[attr].send(:"#{property}=", options.delete(property)) if options.key?(property)
|
388
362
|
end
|
389
363
|
|
390
|
-
def size_from(args)
|
391
|
-
if args.first.is_a? Integer
|
392
|
-
args.shift
|
393
|
-
else
|
394
|
-
1
|
395
|
-
end
|
396
|
-
end
|
397
|
-
|
398
364
|
def check_existence_of(attr)
|
399
365
|
raise ArgumentError, "unknown #{attr} attribute for #{self}" unless attr_defs.key?(attr)
|
400
366
|
end
|
@@ -402,7 +368,7 @@ module BinStruct
|
|
402
368
|
|
403
369
|
# Create a new Struct object
|
404
370
|
# @param [Hash] options Keys are symbols. They should have name of object
|
405
|
-
# attributes, as defined by {.define_attr} and by {.
|
371
|
+
# attributes, as defined by {.define_attr} and by {.define_bit_attr}.
|
406
372
|
def initialize(options = {})
|
407
373
|
@attributes = {}
|
408
374
|
@optional_attributes = {}
|
@@ -413,8 +379,8 @@ module BinStruct
|
|
413
379
|
initialize_optional(attr)
|
414
380
|
end
|
415
381
|
|
416
|
-
self.class.bit_attrs.each_value do |
|
417
|
-
|
382
|
+
self.class.bit_attrs.each_value do |bit_fields|
|
383
|
+
bit_fields.each do |bit|
|
418
384
|
send(:"#{bit}=", options[bit]) if options[bit]
|
419
385
|
end
|
420
386
|
end
|
@@ -599,26 +565,39 @@ module BinStruct
|
|
599
565
|
end
|
600
566
|
end
|
601
567
|
|
568
|
+
# @param [Symbol] attr
|
569
|
+
# @return [void]
|
602
570
|
def initialize_optional(attr)
|
603
571
|
optional = attr_defs[attr].optional
|
604
572
|
@optional_attributes[attr] = optional if optional
|
605
573
|
end
|
606
574
|
|
575
|
+
# @return [String]
|
607
576
|
def inspect_titleize
|
608
577
|
title = self.class.to_s
|
609
578
|
+"-- #{title} #{'-' * (66 - title.length)}\n"
|
610
579
|
end
|
611
580
|
|
581
|
+
# @param [:Symbol] attr
|
582
|
+
# @param [Structable] value
|
583
|
+
# @param [Integer] level
|
584
|
+
# @return [::String]
|
612
585
|
def inspect_attribute(attr, value, level = 1)
|
613
|
-
type = value.class.to_s.sub(/.*::/, '')
|
614
|
-
inspect_format(type, attr, value.format_inspect, level)
|
615
|
-
end
|
616
|
-
|
617
|
-
def inspect_format(type, attr, value, level = 1)
|
618
586
|
str = inspect_shift_level(level)
|
619
|
-
|
587
|
+
value_lines = value.format_inspect.split("\n")
|
588
|
+
str << (FMT_ATTR % [value.type_name, attr, value_lines.shift])
|
589
|
+
return str if value_lines.empty?
|
590
|
+
|
591
|
+
shift = (FMT_ATTR % ['', '', 'START']).index('START')
|
592
|
+
value_lines.each do |l|
|
593
|
+
str << inspect_shift_level(level)
|
594
|
+
str << (' ' * shift) << l << "\n"
|
595
|
+
end
|
596
|
+
str
|
620
597
|
end
|
621
598
|
|
599
|
+
# @param [Integer] level
|
600
|
+
# @return [String]
|
622
601
|
def inspect_shift_level(level = 1)
|
623
602
|
' ' * (level + 1)
|
624
603
|
end
|
data/lib/bin_struct/version.rb
CHANGED
data/lib/bin_struct.rb
CHANGED
@@ -25,6 +25,7 @@ end
|
|
25
25
|
require_relative 'bin_struct/structable'
|
26
26
|
require_relative 'bin_struct/int'
|
27
27
|
require_relative 'bin_struct/enum'
|
28
|
+
require_relative 'bin_struct/bit_attr'
|
28
29
|
require_relative 'bin_struct/struct'
|
29
30
|
require_relative 'bin_struct/length_from'
|
30
31
|
require_relative 'bin_struct/abstract_tlv'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bin_struct
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- LemonTree55
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-12-02 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: 'BinStruct is a binary dissector and generator. It eases manipulating
|
14
14
|
complex binary data.
|
@@ -26,6 +26,7 @@ files:
|
|
26
26
|
- lib/bin_struct.rb
|
27
27
|
- lib/bin_struct/abstract_tlv.rb
|
28
28
|
- lib/bin_struct/array.rb
|
29
|
+
- lib/bin_struct/bit_attr.rb
|
29
30
|
- lib/bin_struct/cstring.rb
|
30
31
|
- lib/bin_struct/enum.rb
|
31
32
|
- lib/bin_struct/int.rb
|