bin_struct 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/bin_struct.rb CHANGED
@@ -22,10 +22,10 @@ module BinStruct
22
22
  end
23
23
  end
24
24
 
25
- require_relative 'bin_struct/fieldable'
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/fields'
28
+ require_relative 'bin_struct/struct'
29
29
  require_relative 'bin_struct/length_from'
30
30
  require_relative 'bin_struct/abstract_tlv'
31
31
  require_relative 'bin_struct/array'
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.1.0
4
+ version: 0.2.1
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-16 00:00:00.000000000 Z
11
+ date: 2024-11-25 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.
@@ -28,13 +28,13 @@ files:
28
28
  - lib/bin_struct/array.rb
29
29
  - lib/bin_struct/cstring.rb
30
30
  - lib/bin_struct/enum.rb
31
- - lib/bin_struct/fieldable.rb
32
- - lib/bin_struct/fields.rb
33
31
  - lib/bin_struct/int.rb
34
32
  - lib/bin_struct/int_string.rb
35
33
  - lib/bin_struct/length_from.rb
36
34
  - lib/bin_struct/oui.rb
37
35
  - lib/bin_struct/string.rb
36
+ - lib/bin_struct/struct.rb
37
+ - lib/bin_struct/structable.rb
38
38
  - lib/bin_struct/version.rb
39
39
  homepage: https://github.com/lemontree55/bin_struct
40
40
  licenses:
@@ -1,612 +0,0 @@
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) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
6
- # Copyright (C) 2024 LemonTree55 <lenontree@proton.me>
7
- # This program is published under MIT license.
8
-
9
- # rubocop:disable Metrics/ClassLength
10
-
11
- module BinStruct
12
- # @abstract Set of fields
13
- # This class is a base class to define headers or anything else with a binary
14
- # format containing multiple fields.
15
- #
16
- # == Basics
17
- # A {Fields} subclass is generaly composed of multiple binary fields. These fields
18
- # have each a given type. All {Fieldable} types are supported.
19
- #
20
- # To define a new subclass, it has to inherit from {Fields}. And some class
21
- # methods have to be used to declare attributes/fields:
22
- # class MyBinaryStructure < BinStruct::Fields
23
- # # define a first Int8 attribute, with default value: 1
24
- # define_field :attr1, BinStruct::Int8, default: 1
25
- # #define a second attribute, of kind Int32
26
- # define_field :attr2, BinStruct::Int32
27
- # end
28
- #
29
- # These defintions create 4 methods: +#attr1+, +#attr1=+, +#attr2+ and +#attr2=+.
30
- # All these methods take and/or return Integers.
31
- #
32
- # Fields may also be accessed through {#[]} ans {#[]=}. These methods give access
33
- # to type object:
34
- # mybs = MyBinaryStructure.new
35
- # mybs.attr1 # => Integer
36
- # mybs[:attr1] # => BinStruct::Int8
37
- #
38
- # {#initialize} accepts an option hash to populate attributes. Keys are attribute
39
- # name symbols, and values are those expected by writer accessor.
40
- #
41
- # {#read} is able to populate object from a binary string.
42
- #
43
- # {#to_s} returns binary string from object.
44
- #
45
- # == Add Fields
46
- # {.define_field} adds a field to Fields subclass. A lot of field types may be
47
- # defined: integer types, string types (to handle a stream of bytes). More
48
- # complex field types may be defined using others Fields subclasses:
49
- # # define a 16-bit little-endian integer field, named type
50
- # define_field :type, BinStruct::Int16le
51
- # # define a string field
52
- # define_field :body, BinStruct::String
53
- # # define a field using a complex type (Fields subclass)
54
- # define_field :mac_addr, PacketGen::Eth::MacAddr
55
- #
56
- # This example creates six methods on our Fields subclass: +#type+, +#type=+,
57
- # +#body+, +#body=+, +#mac_addr+ and +#mac_addr=+.
58
- #
59
- # {.define_field} has many options (third optional Hash argument):
60
- # * +:default+ gives default field value. It may be a simple value (an Integer
61
- # for an Int field, for example) or a lambda,
62
- # * +:builder+ to give a builder/constructor lambda to create field. The lambda
63
- # takes 2 argument: {Fields} subclass object owning field, and type class as passes
64
- # as second argument to .define_field,
65
- # * +:optional+ to define this field as optional. This option takes a lambda
66
- # parameter used to say if this field is present or not. The lambda takes an argument
67
- # ({Fields} subclass object owning field),
68
- # For example:
69
- # # 32-bit integer field defaulting to 1
70
- # define_field :type, BinStruct::Int32, default: 1
71
- # # 16-bit integer field, created with a random value. Each instance of this
72
- # # object will have a different value.
73
- # define_field :id, BinStruct::Int16, default: ->(obj) { rand(65535) }
74
- # # a size field
75
- # define_field :body_size, BinStruct::Int16
76
- # # String field which length is taken from body_size field
77
- # define_field :body, BinStruct::String, builder: ->(obj, type) { type.new(length_from: obj[:body_size]) }
78
- # # 16-bit enumeration type. As :default not specified, default to first value of enum
79
- # define_field :type_class, BinStruct::Int16Enum, enum: { 'class1' => 1, 'class2' => 2}
80
- # # optional field. Only present if another field has a certain value
81
- # define_field :opt1, BinStruct::Int16, optional: ->(h) { h.type == 42 }
82
- #
83
- # {.define_field_before} and {.define_field_after} are also defined to relatively
84
- # create a field from anoher one (for example, when adding a field in a subclass).
85
- # == Generating bit fields
86
- # {.define_bit_fields_on} creates a bit field on a previuously declared integer
87
- # field. For example, +frag+ field in IP header:
88
- # define_field :frag, Types::Int16, default: 0
89
- # define_bit_fields_on :frag, :flag_rsv, :flag_df, :flag_mf, :fragment_offset, 13
90
- #
91
- # This example generates methods:
92
- # * +#frag+ and +#frag=+ to access +frag+ field as a 16-bit integer,
93
- # * +#flag_rsv?+, +#flag_rsv=+, +#flag_df?+, +#flag_df=+, +#flag_mf?+ and +#flag_mf=+
94
- # to access Boolean RSV, MF and DF flags from +frag+ field,
95
- # * +#fragment_offset+ and +#fragment_offset=+ to access 13-bit integer fragment
96
- # offset subfield from +frag+ field.
97
- #
98
- # == Creating a new field class from another one
99
- # Some methods may help in this case:
100
- # * {.define_field_before} to define a new field before an existing one,
101
- # * {.define_field_after} to define a new field after an existing onr,
102
- # * {.remove_field} to remove an existing field,
103
- # * {.uptade_fied} to change options of a field (but not its type),
104
- # * {.remove_bit_fields_on} to remove a bit fields definition.
105
- #
106
- # @author Sylvain Daubert
107
- class Fields
108
- # @private
109
- FieldDef = Struct.new(:type, :default, :builder, :optional, :options)
110
- # @private field names, ordered as they were declared
111
- @ordered_fields = []
112
- # @private field definitions
113
- @field_defs = {}
114
- # @private bit field definitions
115
- @bit_fields = {}
116
-
117
- class << self
118
- # Get field definitions for this class.
119
- # @return [Hash]
120
- # @since 3.1.0
121
- attr_reader :field_defs
122
- # Get bit fields defintions for this class
123
- # @return [Hash]
124
- # @since 3.1.5
125
- attr_reader :bit_fields
126
-
127
- # On inheritage, create +@field_defs+ class variable
128
- # @param [Class] klass
129
- # @return [void]
130
- def inherited(klass)
131
- super
132
-
133
- field_defs = {}
134
- @field_defs.each do |k, v|
135
- field_defs[k] = v.clone
136
- end
137
- ordered = @ordered_fields.clone
138
- bf = bit_fields.clone
139
-
140
- klass.class_eval do
141
- @ordered_fields = ordered
142
- @field_defs = field_defs
143
- @bit_fields = bf
144
- end
145
- end
146
-
147
- # Get field names
148
- # @return [Array<Symbol>]
149
- def fields
150
- @ordered_fields
151
- end
152
-
153
- # Define a field in class
154
- # class BinaryStruct < BinStruct::Fields
155
- # # 8-bit value
156
- # define_field :value1, Types::Int8
157
- # # 16-bit value
158
- # define_field :value2, Types::Int16
159
- # # specific class, may use a specific constructor
160
- # define_field :value3, MyClass, builder: ->(obj, type) { type.new(obj) }
161
- # end
162
- #
163
- # bs = BinaryStruct.new
164
- # bs[value1] # => Types::Int8
165
- # bs.value1 # => Integer
166
- # @param [Symbol] name field name
167
- # @param [Fieldable] type class or instance
168
- # @param [Hash] options Unrecognized options are passed to object builder if
169
- # +:builder+ option is not set.
170
- # @option options [Object] :default default value. May be a proc. This lambda
171
- # take one argument: the caller object.
172
- # @option options [Lambda] :builder lambda to construct this field.
173
- # Parameters to this lambda is the caller object and the field type class.
174
- # @option options [Lambda] :optional define this field as optional. Given lambda
175
- # is used to known if this field is present or not. Parameter to this lambda is
176
- # the being defined Field object.
177
- # @return [void]
178
- def define_field(name, type, options = {})
179
- fields << name
180
- field_defs[name] = FieldDef.new(type,
181
- options.delete(:default),
182
- options.delete(:builder),
183
- options.delete(:optional),
184
- options)
185
-
186
- add_methods(name, type)
187
- end
188
-
189
- # Define a field, before another one
190
- # @param [Symbol,nil] other field name to create a new one before. If +nil+,
191
- # new field is appended.
192
- # @param [Symbol] name field name to create
193
- # @param [Fieldable] type class or instance
194
- # @param [Hash] options See {.define_field}.
195
- # @return [void]
196
- # @see .define_field
197
- def define_field_before(other, name, type, options = {})
198
- define_field name, type, options
199
- return if other.nil?
200
-
201
- fields.delete name
202
- idx = fields.index(other)
203
- raise ArgumentError, "unknown #{other} field" if idx.nil?
204
-
205
- fields[idx, 0] = name
206
- end
207
-
208
- # Define a field, after another one
209
- # @param [Symbol,nil] other field name to create a new one after. If +nil+,
210
- # new field is appended.
211
- # @param [Symbol] name field name to create
212
- # @param [Fieldable] type class or instance
213
- # @param [Hash] options See {.define_field}.
214
- # @return [void]
215
- # @see .define_field
216
- def define_field_after(other, name, type, options = {})
217
- define_field name, type, options
218
- return if other.nil?
219
-
220
- fields.delete name
221
- idx = fields.index(other)
222
- raise ArgumentError, "unknown #{other} field" if idx.nil?
223
-
224
- fields[idx + 1, 0] = name
225
- end
226
-
227
- # Remove a previously defined field
228
- # @param [Symbol] name
229
- # @return [void]
230
- # @since 2.8.4
231
- def remove_field(name)
232
- fields.delete name
233
- @field_defs.delete name
234
- undef_method name if method_defined?(name)
235
- undef_method :"#{name}=" if method_defined?(:"#{name}=")
236
- end
237
-
238
- # Update a previously defined field
239
- # @param [Symbol] field field name to create
240
- # @param [Hash] options See {.define_field}.
241
- # @return [void]
242
- # @see .define_field
243
- # @raise [ArgumentError] unknown +field+
244
- # @since 2.8.4
245
- def update_field(field, options)
246
- check_existence_of field
247
-
248
- %i[default builder optional].each do |property|
249
- field_defs_property_from(field, property, options)
250
- end
251
-
252
- field_defs[field].options.merge!(options)
253
- end
254
-
255
- # Define a bitfield on given attribute
256
- # class MyHeader < BinStruct::Fields
257
- # define_field :flags, Types::Int16
258
- # # define a bit field on :flag attribute:
259
- # # flag1, flag2 and flag3 are 1-bit fields
260
- # # type and stype are 3-bit fields. reserved is a 6-bit field
261
- # define_bit_fields_on :flags, :flag1, :flag2, :flag3, :type, 3, :stype, 3, :reserved, 7
262
- # end
263
- # A bitfield of size 1 bit defines 2 methods:
264
- # * +#field?+ which returns a Boolean,
265
- # * +#field=+ which takes and returns a Boolean.
266
- # A bitfield of more bits defines 2 methods:
267
- # * +#field+ which returns an Integer,
268
- # * +#field=+ which takes and returns an Integer.
269
- # @param [Symbol] attr attribute name (attribute should be a {Types::Int}
270
- # subclass)
271
- # @param [Array] args list of bitfield names. Name may be followed
272
- # by bitfield size. If no size is given, 1 bit is assumed.
273
- # @raise [ArgumentError] unknown +attr+
274
- # @return [void]
275
- def define_bit_fields_on(attr, *args)
276
- check_existence_of attr
277
-
278
- type = field_defs[attr].type
279
- raise TypeError, "#{attr} is not a BinStruct::Int" unless type < Int
280
-
281
- total_size = type.new.nbits
282
- idx = total_size - 1
283
-
284
- until args.empty?
285
- field = args.shift
286
- next unless field.is_a? Symbol
287
-
288
- size = size_from(args)
289
-
290
- unless field == :_
291
- add_bit_methods(attr, field, size, total_size, idx)
292
- register_bit_field_size(attr, field, size)
293
- end
294
-
295
- idx -= size
296
- end
297
- end
298
-
299
- # Remove all bit fields defined on +attr+
300
- # @param [Symbol] attr attribute defining bit fields
301
- # @return [void]
302
- # @since 2.8.4
303
- def remove_bit_fields_on(attr)
304
- fields = bit_fields.delete(attr)
305
- return if fields.nil?
306
-
307
- fields.each do |field, size|
308
- undef_method :"#{field}="
309
- undef_method(size == 1 ? "#{field}?" : field)
310
- end
311
- end
312
-
313
- private
314
-
315
- def add_methods(name, type)
316
- define = []
317
- if type < Enum
318
- define << "def #{name}; self[:#{name}].to_i; end"
319
- define << "def #{name}=(val) self[:#{name}].value = val; end"
320
- else
321
- define << "def #{name}\n " \
322
- "to_and_from_human?(:#{name}) ? self[:#{name}].to_human : self[:#{name}]\n" \
323
- 'end'
324
- define << "def #{name}=(val)\n " \
325
- "to_and_from_human?(:#{name}) ? self[:#{name}].from_human(val) : self[:#{name}].read(val)\n" \
326
- 'end'
327
- end
328
-
329
- define.delete_at(1) if instance_methods.include?(:"#{name}=")
330
- define.delete_at(0) if instance_methods.include? name
331
- class_eval define.join("\n")
332
- end
333
-
334
- def add_bit_methods(attr, name, size, total_size, idx)
335
- shift = idx - (size - 1)
336
-
337
- if size == 1
338
- add_single_bit_methods(attr, name, size, total_size, shift)
339
- else
340
- add_multibit_methods(attr, name, size, total_size, shift)
341
- end
342
- end
343
-
344
- def compute_field_mask(size, shift)
345
- ((2**size) - 1) << shift
346
- end
347
-
348
- def compute_clear_mask(total_size, field_mask)
349
- ((2**total_size) - 1) & (~field_mask & ((2**total_size) - 1))
350
- end
351
-
352
- def add_single_bit_methods(attr, name, size, total_size, shift)
353
- field_mask = compute_field_mask(size, shift)
354
- clear_mask = compute_clear_mask(total_size, field_mask)
355
-
356
- class_eval <<-METHODS, __FILE__, __LINE__ + 1
357
- def #{name}? # def bit?
358
- val = (self[:#{attr}].to_i & #{field_mask}) >> #{shift} # val = (self[:attr}].to_i & 1}) >> 1
359
- val != 0 # val != 0
360
- end # end
361
- def #{name}=(v) # def bit=(v)
362
- val = v ? 1 : 0 # val = v ? 1 : 0
363
- self[:#{attr}].value = self[:#{attr}].to_i & #{clear_mask} # self[:attr].value = self[:attr].to_i & 0xfffd
364
- self[:#{attr}].value |= val << #{shift} # self[:attr].value |= val << 1
365
- end # end
366
- METHODS
367
- end
368
-
369
- def add_multibit_methods(attr, name, size, total_size, shift)
370
- field_mask = compute_field_mask(size, shift)
371
- clear_mask = compute_clear_mask(total_size, field_mask)
372
-
373
- class_eval <<-METHODS, __FILE__, __LINE__ + 1
374
- def #{name} # def multibit
375
- (self[:#{attr}].to_i & #{field_mask}) >> #{shift} # (self[:attr].to_i & 6) >> 1
376
- end # end
377
- def #{name}=(v) # def multibit=(v)
378
- self[:#{attr}].value = self[:#{attr}].to_i & #{clear_mask} # self[:attr].value = self[:attr].to_i & 0xfff9
379
- self[:#{attr}].value |= (v & #{(2**size) - 1}) << #{shift} # self[:attr].value |= (v & 3) << 1
380
- end # end
381
- METHODS
382
- end
383
-
384
- def register_bit_field_size(attr, field, size)
385
- bit_fields[attr] = {} if bit_fields[attr].nil?
386
- bit_fields[attr][field] = size
387
- end
388
-
389
- def field_defs_property_from(field, property, options)
390
- field_defs[field].send(:"#{property}=", options.delete(property)) if options.key?(property)
391
- end
392
-
393
- def size_from(args)
394
- if args.first.is_a? Integer
395
- args.shift
396
- else
397
- 1
398
- end
399
- end
400
-
401
- def check_existence_of(field)
402
- raise ArgumentError, "unknown #{field} field for #{self}" unless field_defs.key?(field)
403
- end
404
- end
405
-
406
- # Create a new fields object
407
- # @param [Hash] options Keys are symbols. They should have name of object
408
- # attributes, as defined by {.define_field} and by {.define_bit_fields_on}.
409
- def initialize(options = {})
410
- @fields = {}
411
- @optional_fields = {}
412
-
413
- self.class.fields.each do |field|
414
- build_field field
415
- initialize_value field, options[field]
416
- initialize_optional field
417
- end
418
-
419
- self.class.bit_fields.each_value do |hsh|
420
- hsh.each_key do |bit_field|
421
- send(:"#{bit_field}=", options[bit_field]) if options[bit_field]
422
- end
423
- end
424
- end
425
-
426
- # Get field object
427
- # @param [Symbol] field
428
- # @return [Fieldable]
429
- def [](field)
430
- @fields[field]
431
- end
432
-
433
- # Set field object
434
- # @param [Symbol] field
435
- # @param [Object] obj
436
- # @return [Object]
437
- def []=(field, obj)
438
- @fields[field] = obj
439
- end
440
-
441
- # Get all field names
442
- # @return [Array<Symbol>]
443
- def fields
444
- self.class.fields
445
- end
446
-
447
- # Get all optional field name
448
- # @return[Array<Symbol>,nil]
449
- def optional_fields
450
- @optional_fields.keys
451
- end
452
-
453
- # Say if this field is optional
454
- # @return [Boolean]
455
- def optional?(field)
456
- @optional_fields.key? field
457
- end
458
-
459
- # Say if an optional field is present
460
- # @return [Boolean]
461
- def present?(field)
462
- return true unless optional?(field)
463
-
464
- @optional_fields[field].call(self)
465
- end
466
-
467
- # Populate object from a binary string
468
- # @param [String] str
469
- # @return [Fields] self
470
- def read(str)
471
- return self if str.nil?
472
-
473
- force_binary str
474
- start = 0
475
- fields.each do |field|
476
- next unless present?(field)
477
-
478
- obj = self[field].read str[start..]
479
- start += self[field].sz
480
- self[field] = obj unless obj == self[field]
481
- end
482
-
483
- self
484
- end
485
-
486
- # Common inspect method for headers.
487
- #
488
- # A block may be given to differently format some attributes. This
489
- # may be used by subclasses to handle specific fields.
490
- # @yieldparam attr [Symbol] attribute to inspect
491
- # @yieldreturn [String,nil] the string to print for +attr+, or +nil+
492
- # to let +inspect+ generate it
493
- # @return [String]
494
- def inspect
495
- str = Inspect.dashed_line(self.class, 1)
496
- fields.each do |attr|
497
- next if attr == :body
498
- next unless present?(attr)
499
-
500
- result = yield(attr) if block_given?
501
- str << (result || Inspect.inspect_attribute(attr, self[attr], 1))
502
- end
503
- str
504
- end
505
-
506
- # Return object as a binary string
507
- # @return [String]
508
- def to_s
509
- fields.select { |f| present?(f) }
510
- .map! { |f| force_binary @fields[f].to_s }.join
511
- end
512
-
513
- # Size of object as binary string
514
- # @return [nteger]
515
- def sz
516
- to_s.size
517
- end
518
-
519
- # Return object as a hash
520
- # @return [Hash] keys: attributes, values: attribute values
521
- def to_h
522
- fields.to_h { |f| [f, @fields[f].to_human] }
523
- end
524
-
525
- # Get offset of given field in {Fields} structure.
526
- # @param [Symbol] field
527
- # @return [Integer]
528
- # @raise [ArgumentError] unknown field
529
- def offset_of(field)
530
- raise ArgumentError, "#{field} is an unknown field of #{self.class}" unless @fields.include?(field)
531
-
532
- offset = 0
533
- fields.each do |f|
534
- break offset if f == field
535
- next unless present?(f)
536
-
537
- offset += self[f].sz
538
- end
539
- end
540
-
541
- # Get bit fields definition for given field.
542
- # @param [Symbol] field defining bit fields
543
- # @return [Hash,nil] keys: bit fields, values: their size in bits
544
- # @since 2.8.3
545
- def bits_on(field)
546
- self.class.bit_fields[field]
547
- end
548
-
549
- private
550
-
551
- # Deeply duplicate +@fields+
552
- # @return [void]
553
- def initialize_copy(_other)
554
- fields = {}
555
- @fields.each { |k, v| fields[k] = v.dup }
556
- @fields = fields
557
- end
558
-
559
- # Force str to binary encoding
560
- # @param [String] str
561
- # @return [String]
562
- def force_binary(str)
563
- BinStruct.force_binary(str)
564
- end
565
-
566
- # @param [Symbol] attr attribute
567
- # @return [Boolean] +tru+e if #from_human and #to_human are both defined for given attribute
568
- def to_and_from_human?(attr)
569
- self[attr].respond_to?(:to_human) && self[attr].respond_to?(:from_human)
570
- end
571
-
572
- def field_defs
573
- self.class.field_defs
574
- end
575
-
576
- # rubocop:disable Metrics/AbcSize
577
- def build_field(field)
578
- type = field_defs[field].type
579
-
580
- @fields[field] = if field_defs[field].builder
581
- field_defs[field].builder.call(self, type)
582
- elsif !field_defs[field].options.empty?
583
- type.new(field_defs[field].options)
584
- else
585
- type.new
586
- end
587
- end
588
- # rubocop:enable Metrics/AbcSize
589
-
590
- def initialize_value(field, val)
591
- type = field_defs[field].type
592
- default = field_defs[field].default
593
- default = default.to_proc.call(self) if default.is_a?(Proc)
594
-
595
- value = val || default
596
- if value.class <= type
597
- @fields[field] = value
598
- elsif @fields[field].respond_to? :from_human
599
- @fields[field].from_human(value)
600
- else
601
- @fields[field].read(value)
602
- end
603
- end
604
-
605
- def initialize_optional(field)
606
- optional = field_defs[field].optional
607
- @optional_fields[field] = optional if optional
608
- end
609
- end
610
- end
611
-
612
- # rubocop:enable Metrics/ClassLength