bin_struct 0.2.0 → 0.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a90f75409d497521af8957e89577a4e8b7c14211e5343aa9126a71ef413d759
4
- data.tar.gz: b07e2bf73d2cb1f4b7be4340944cd52df4382fc94f171607357adf10ddc7a914
3
+ metadata.gz: 44d43c8007a5ad33899e5c7ee6a09c306a64a95012bd489ee2d14d69c7e40dcf
4
+ data.tar.gz: db0b44a68cb8a23b1a5110cb12bb587c4cf4a737b42443f72c0ae9241fffa6c9
5
5
  SHA512:
6
- metadata.gz: 6acd7aeb0516bf6692ae5971aecb10c8ae685936f24302d909ff16f055118a68588876409819d0ba966e4f3a7b524b194e58713bfb0ccaef95eb542fca221bb3
7
- data.tar.gz: 790bf0b81cef084c030e0412731e3b0b852815fa1f66f5d28564278579e12b7e3fd3ef3674c6f36dd32bc792e1c248f7f20b3f65f505d3193f370b2d8c6f1d88
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
+ [![Gem Version](https://badge.fury.io/rb/bin_struct.svg)](https://badge.fury.io/rb/bin_struct)
2
+ [![Specs](https://github.com/lemontree55/bin_struct/actions/workflows/main.yml/badge.svg)](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
- $ gem install bin_struct
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
@@ -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] :string String value. Default to +""+
23
+ # @option options [::String] :value String value. Default to +""+
24
24
  def initialize(options = {})
25
- @string = BinStruct::String.new.read(options[:string] || '')
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 = str.to_s
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
@@ -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
@@ -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
- # {.define_bit_attr_on} creates bit attributes on a previously declared integer
85
- # attribute. For example, +frag+ attribute in IP header:
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
- # * {.remove_attribute} to remove an existing attribute,
101
- # * {.uptade_fied} to change options of an attribute (but not its type),
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
- attributes.delete name
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
- attributes.delete name
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
- @attr_defs.delete(name)
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 on given attribute
246
+ # Define a bit attribute
254
247
  # class MyHeader < BinStruct::Struct
255
- # define_attr :flags, BinStruct::Int16
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 6-bit attribute
259
- # define_bit_attributes_on :flags, :flag1, :flag2, :flag3, :type, 3, :stype, 3, :reserved, 7
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 2 methods:
262
- # * +#attr+ which returns a Boolean,
263
- # * +#attr=+ which takes and returns a Boolean.
264
- # A bit attribute of more bits defines 2 methods:
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 and returns an Integer.
267
- # @param [Symbol] attr attribute name (attribute should be a {BinStruct::Int}
268
- # subclass)
269
- # @param [Array] args list of bit attribute names. Name may be followed
270
- # by bit size. If no size is given, 1 bit is assumed.
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
- def define_bit_attrs_on(attr, *args)
274
- check_existence_of(attr)
275
-
276
- type = attr_defs[attr].type
277
- raise TypeError, "#{attr} is not a BinStruct::Int" unless type < Int
278
-
279
- total_size = type.new.nbits
280
- idx = total_size - 1
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
- until args.empty?
283
- arg = args.shift
284
- next unless arg.is_a? Symbol
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
- size = size_from(args)
293
+ move_attr(name, before: other)
294
+ end
287
295
 
288
- unless attr == :_
289
- add_bit_methods(attr, arg, size, total_size, idx)
290
- register_bit_attr_size(attr, arg, size)
291
- end
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
- idx -= size
294
- end
309
+ move_attr(name, after: other)
295
310
  end
296
311
 
297
- # Remove all bit attributes defined on +attr+
298
- # @param [Symbol] attr attribute defining bit attributes
312
+ private
313
+
314
+ # @param [Symbol] name
315
+ # @param [Symbol,nil] before
316
+ # @param [Symbol,nil] after
299
317
  # @return [void]
300
- def remove_bit_attrs_on(attr)
301
- bits = bit_attrs.delete(attr)
302
- return if bits.nil?
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
- bits.each do |bit, size|
305
- undef_method :"#{bit}="
306
- undef_method(size == 1 ? "#{bit}?" : bit)
307
- end
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
- private
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 add_bit_methods(attr, name, size, total_size, idx)
332
- shift = idx - (size - 1)
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 {.define_bit_attrs_on}.
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 |hsh|
417
- hsh.each_key do |bit|
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
- str << (FMT_ATTR % [type, attr, value])
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
@@ -58,7 +58,7 @@ module BinStruct
58
58
  # Format object when inspecting a {Struct} object
59
59
  # @return [::String]
60
60
  def format_inspect
61
- to_human
61
+ to_human.to_s
62
62
  end
63
63
  end
64
64
  end
@@ -8,5 +8,5 @@
8
8
 
9
9
  module BinStruct
10
10
  # BinStruct version
11
- VERSION = '0.2.0'
11
+ VERSION = '0.3.0'
12
12
  end
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.2.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-07-21 00:00:00.000000000 Z
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