bin_struct 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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