bin_struct 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,612 @@
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